Ubuntu's gcc implies `-fPIC` but openSUSE does not, is it affected by some configuration file or what?

55 Views Asked by At

Lab environments:

  • Ubuntu 18.04, gcc 7.5.0, binutils 2.30 (2019-05-08)
  • openSUSE Leap 15.5, gcc 7.5.0, binutils 2.39 (2022-10-25)

I realize this problem when building a small .so file from foo1.c .

#include <stdio.h>

extern void FooX(int);

void Foo1(int i)
{
        printf("foo1(%d)\n", i);
        i++;
        FooX(i);
}

On openSUSE, compile it using commands:

gcc -g -c foo1.c

gcc -shared foo1.o -o libfoo1.so

The second command fails with error message:

ld: foo1.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC

opensuse linking .so error message.

As suggested by above ld error message. I go back and do gcc -g -c -fPIC foo1.c, then ld succeeds.

But on Ubuntu 18.04, I do not have to explicitly assign -fPIC to gcc.

Ubuntu build .so success

So, on openSUSE, the foo1.o must have different machine code when it lacks vs has -fPIC.

I make some effort to compare them on openSUSE.

gcc -g -c foo1.c
mv foo1.o foo1-noPIC.o

gcc -g -c -fPIC foo1.c
mv foo1.o foo1-PIC.o

objdump -dSlr -M intel foo1-noPIC.o > foo1-noPIC.txt

objdump -dSlr -M intel foo1-PIC.o > foo1-PIC.txt

Below is the difference between foo1-noPIC.txt and foo1-PIC.txt :

Compare -fPIC no vs yes

I also do this on Ubuntu, and foo1-noPIC.txt and foo1-PIC.txt have same content, and they equal openSUSE's foo1-PIC.txt.

Now, I confirm that Ubuntu's gcc implies -fPIC for us.

Then, my question is, is such difference hard codeded into a Linux-distro's gcc binary, or, is it affected by some configuration file on a Linux distro? If latter, which configuration file?

1

There are 1 best solutions below

0
Mike Kinghan On

As you've noticed, some Linux distros build their GCC compilers with -fPIC as a default option, which you must override with -no-fPIC if you don't want it; other distros build with -no-fPIC the default, which you must override with -fPIC1.

Then, my question is, is such difference hard coded into a Linux-distro's gcc binary, or, is it affected by some configuration file on a Linux distro?

Yes, it is hardcoded and No, it is not affected by any configuration file on a linux distro. However, you can always vary the default and non-default options by feeding a suitable configuration file of your own into your gcc commands. I'll demonstrate this by switching my own stock Ubuntu gcc from default PIC to default no-PIC.

Spec strings and spec files

The GCC manual: 3.20 Specifying Subprocesses and the Switches to Pass to Them, para. 1 explains:

gcc is a driver program. It performs its job by invoking a sequence of other programs to do the work of compiling, assembling and linking. GCC interprets its command-line parameters and uses these to deduce which programs it should invoke, and which command-line options it ought to place on their command lines. This behavior is controlled by spec strings. In most cases there is one spec string for each program that GCC can invoke, but a few programs have multiple spec strings to control their behavior. The spec strings built into GCC can be overridden by using the -specs= command-line switch to specify a spec file.

(my emphasis)

Parsing the built-in specs

gcc uses its built-in specs by default:

$ gcc -v
Using built-in specs.
...[cut]...
gcc version 13.2.0 (Ubuntu 13.2.0-4ubuntu3) 

I can extract the built-in specs to a file like this:

$ gcc -dumpspecs > gccspecs.txt

If you do that, you will see that gccspecs.txt is a file of daunting goobledegook, but the documentation already referred to teaches you how to read it (though not easily).

I see that the only spec-string in which fPIC is mentioned is the one for spec-name cc1. This spec-name represents a subset of the arguments that will be passed the (real) C compiler - also called cc1 - that gcc calls to do C-language compilation.

*cc1:
%{!mandroid|tno-android-cc:%(cc1_cpu) %{profile:-p};:%(cc1_cpu) \
%{profile:-p} %{!mglibc:%{!muclibc:%{!mbionic: -mbionic}}} \
%{!fno-pic:%{!fno-PIC:%{!fpic:%{!fPIC: -fPIC}}}}}

(line-continuations \ - and any sort of comments - are not valid spec-string syntax. I pretend otherwise to avoid triggering horizontal scroll.)

Eek. Let's tease this out in pseudo-code, where:

  • input S will mean S is specified in the gcc options
  • output S will mean append S to the expansion of cc1.
  • A reference of the form %(specname) will mean the expansion of spec-name specname.

It says:

If (not input `-mandroid`) or (input `-tno-android-cc`) {
    output `%(cc1_cpu)`
    If input (`-profile`) {
        output `-p`
    }
} else {
    output `%(cc1_cpu)`
    If input (`-profile`) {
        output `-p`
    }
    If (not input `-mglibc`) {
        If (not input `-muclibc`) {
            If (not input `-mbionic`) {
                output `-mbionic`
            }
        }
    }
    If (not input `-fno-pic`) {
        If (not input `-fno-PIC`) {
            If (not input `-fpic`) {
                If (not input `-fPIC` {
                    output `-fPIC`
                }
            }
        }
    }
}

Which is logically equivalent to the more concise:

If (not input `-mandroid`) or (input `-tno-android-cc`) {
    output `%(cc1_cpu)`
    If input (`-profile`) {
        output `-p`
    }
} else {
    output `%(cc1_cpu)`
    If input (`-profile`) {
        output `-p`
    }
    If (not input ( `-mglibc` or `-muclibc` or `-mbionic`)) {
        output `-mbionic`
    }
    If (not input (`-fno-pic` or `-fno-PIC` or `-fpic` or `-fPIC`)) {
        output `-fPIC`
}
    

And if we disregard everything not relevant to the -f[no-](pic|PIC) options2, that can simplify further to:

If (input `-mandroid`) and ( not input `-tno-android-cc`) {
    If (not input (`-fno-pic` or `-fno-PIC` or `-fpic` or `-fPIC`)) {
        output `-fPIC`
}

This reveals that my gcc, by default, will react to the absence of all -f[no-](pic|PIC) options only in case it is targeting android and it has not been told to rule out an android compiler. In that case, it will react to the absence of all -f[no-](pic|PIC) input options by outputting -fPIC to the compiler. If on the other hand:

  • it is not targeting android (no -mandroid), or
  • it has been told to rule out an android compiler (-tno-android-cc), or
  • any of -f[no-](pic|PIC) has been input,

then gcc outputs just the same -f[no-](pic|PIC) options as were input, including none.

Let's confirm that:

$ cat foo.c
#include <stdio.h>
void foo(void)
{
    puts("Hello World");
}

Without input -mandroid, without input -f[no-](pic|PIC), I expect no -f[no-](pic|PIC) output at all:

$ gcc --verbose -c -o foo.o foo.c 2>&1 | egrep -oh cc1.*\(pic\|PIC\); echo Done 
Done

Without input -mandroid, with input -fPIC, I expect -fPIC output:

$ gcc --verbose -fPIC -c -o foo.o foo.c 2>&1 | egrep -oh cc1.*(pic|PIC); echo Done 
cc1 -quiet -v -imultiarch x86_64-linux-gnu foo.c -quiet -dumpbase \
foo.c -dumpbase-ext .c -mtune=generic -march=x86-64 -version -fPIC
Done

With input -mandroid, without input -f[no-](pic|PIC), I expect -fPIC output:

$ gcc --verbose -mandroid -c -o foo.o foo.c 2>&1 | egrep -oh cc1.*(pic|PIC); echo Done  
cc1 -quiet -v -imultiarch x86_64-linux-gnu foo.c -mbionic -fPIC
Done

With input -mandroid, with input fno-PIC, I expect fno-PIC output:

$ gcc --verbose -mandroid -fno-PIC -o foo.o foo.c 2>&1 \
| egrep -oh cc1.*\(pic\|PIC\); echo Done  
cc1 -quiet -v -imultiarch x86_64-linux-gnu foo.c -mbionic -quiet -dumpdir foo.o- \
-dumpbase foo.c -dumpbase-ext .c -mandroid -mtune=generic -march=x86-64 -version -fno-PIC
Done

That all checks out.

Overriding the built-in specs with a specs file

In the absence of any of the relevant options, I can see that my gcc defaults to PIC code if I dump its predefined macros:

$ echo | gcc -dM -E - | egrep -h .*(__pic__|__PIC__)
#define __pic__ 2
#define __PIC__ 2

So I'm assured that I can successfully build a shared library from foo.c with without -fPIC:

$ gcc -c -o foo.o foo.c
$ gcc -shared -o libfoo.so foo.o

and indeed I did:

$ file libfoo.so
libfoo.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, \
BuildID[sha1]=3e5aaafab114a7de260f0c2da16b6f58bb81e5e1, not stripped

And for that to fail I have to perversely try:

$ gcc -c -fno-PIC -o foo.o foo.c 
$ gcc -shared -o libfoo.so foo.o
/usr/bin/ld: foo.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

Suppose however I would like to flip my gcc so that:

  • With -mandroid input , without -f[no-](pic|PIC) input, I get -no-fPIC output, instead of -fPIC
  • Without -mandroid input, without -f[no-](pic|PIC) input, I get -no-fPIC output, instead of nothing.

i.e. With or without -mandroid, without -f[no-](pic|PIC) input, I will get -no-fPIC output. The compiler will default to absolute code, not position independent.

Reworking the *cc1 pseudo-code to that end makes it:

If (not input `-mandroid`) or (input `-tno-android-cc`) {
    output `%(cc1_cpu)`
    If input (`-profile`) {
        output `-p`
    }
} else {
    output `%(cc1_cpu)`
    If input (`-profile`) {
        output `-p`
    }
    If (not input `-mglibc`) {
        If (not input `-muclibc`) {
            If (not input `-mbionic`) {
                output `-mbionic`
            }
        }
    }
}
If (not input `-fno-pic`) {
    If (not input `-fno-PIC`) {
        If (not input `-fpic`) {
            If (not input `-fPIC` {
                output `-no-fPIC`   // instead of `-fPIC`
            }
        }
    }
}

That is just a matter of:

  • replacing the one and only output -fPIC with output -no-fPIC,
  • hoisting the pic/PIC stuff out of the scope of the {...} that enclosed it.

Applying that change to the *cc1 spec string looks like so:

*cc1:
%{!mandroid|tno-android-cc:%(cc1_cpu) %{profile:-p};:%(cc1_cpu) \
%{profile:-p} %{!mglibc:%{!muclibc:%{!mbionic: -mbionic}}}} \
                                                       #  ^ Add `}` here  \
%{!fno-pic:%{!fno-PIC:%{!fpic:%{!fPIC: -fno-PIC}}}} \
                                               #   ^ Delete `}` here \
                                     #  ^ `fPIC` -> `fno-PIC`  

The whole new string goes in a spec file:

$ cat specfile.txt 
*cc1:
%{!mandroid|tno-android-cc:%(cc1_cpu) %{profile:-p};:%(cc1_cpu) %{profile:-p} %{!mglibc:%{!muclibc:%{!mbionic: -mbionic}}}} %{!fno-pic:%{!fno-PIC:%{!fpic:%{!fPIC: -fno-PIC}}}}

Which I can use as follows.

With -mandroid, without -f[no-](pic|PIC) input, I now expect -no-fPIC output:

$ gcc --verbose -mandroid -c -o foo.o foo.c -specs=specfile.txt 2>&1 \
        | egrep -oh cc1.*(pic|PIC); echo Done
cc1 -quiet -v -imultiarch x86_64-linux-gnu foo.c -mbionic -fno-PIC
Done

Without -mandroid, without -f[no-](pic|PIC) input, I now expect -no-fPIC output:

$ gcc --verbose -c -o foo.o foo.c -specs=specfile.txt 2>&1 \
        | egrep -oh cc1.*(pic|PIC); echo Done
cc1 -quiet -v -imultiarch x86_64-linux-gnu foo.c -fno-PIC
Done

Feeding specfile.txt to gcc has the expected effect of undefining the built-in macros __pic__ and __PIC__

$ echo | gcc -specs=specfile.txt -dM -E - | egrep -h .*(__pic__|__PIC__); echo Done
Done

So with my spec file I've flipped gcc from default -fPIC to default -no-fPIC. Which means of course linkage failure when object files compiled without -fPIC are input to a -shared linkage:

$ gcc -c -o foo.o foo.c -specs=specfile.txt
$ gcc -shared -o libfoo.so foo.o
/usr/bin/ld: foo.o: relocation R_X86_64_32 against `.rodata' \
    can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

Can you make gcc search for a spec file?

Yes and No...

No, gcc won't search for spec file unless you specify a spec file with the -specs option. That's the sense in there's no "configuration file" that will do the job if you just install it.

Yes, in the sense that if you do specify -spec=specfile, then gcc will search for a file with precisely the terminal name element specfile (even if it has path components) in the same places that it would search for the toolchain files it requires, such as its programs and libraries, and failing all those it will search the current directory. So if you really wanted to, you could create a subdirectory specs, under, say /usr/lib/gcc/x86_64-linux-gnu/<version>, put a repertoire of spec files in there spec0,...,specN, and have gcc select one of them by passing -specs=specs/specJ. Note that if you specify specs=specfile and gcc can't find it, it gives a fatal error; it doesn't fall back on the built-in specs.

Spec files for portability magic?

Wouldn't it be cool to use a spec file to iron out the default behavioural differences between the gcc that your source package build finds on target system A and the one it finds on target system B? It could obviate the need for some of the thorny conditional logic in your package build files.

I think not. To craft a dependable spec file you would need to know exactly the differences in default behaviour that you had to iron out in all possible cases - the same knowledge you need to craft dependable build files that don't involve spec files. Hard as it is to craft dependable build files, it's even harder to craft a dependable spec file. And more important, the competence to debug a buggy build file is widespread amongst source package consumers and vastly more common than spec file expertise.

Indeed, dependable build logic means not depending on gcc's default behaviour. There's always an option that explicitly mandates or prohibits a behaviour trait F if that is the right thing in any supported build case. You need to know whether to mandate or prohibit F, for each supported build case, and logically arrange that the build specifies the correct option. Best to keep all this in plain sight to the utmost practical extent, in the build files and in their console output. Using spec files would be an obfuscatory approach.


  1. This schism started in 2013, when Debian moved to default PIC.

  2. There is a fine technical distinction between -f[no-]PIC and -f[no-]pic but it's irrelevant here.