Why Does Gcc Force Pic for X64 Shared Libs

Why does gcc force PIC for x64 shared libs?

Here is the best explanation I've read from a post on comp.unix.programmer:

Shared libs need PIC on x86-64, or more accurately, relocatable code
has to be PIC. This is because a 32-bit immediate address operand
used in the code might need more than 32 bits after relocation. If
this happens, there is nowhere to write the new value.

Will an executable access shared-libraries' global variable via GOT?

Part of the point of a shared library is that one copy gets loaded into memory, and multiple processes can access that one copy. But every program has its own copy of each of the library's variables. If they were accessed relative to the library's GOT then those would instead be shared among the processes using the library, just like the functions are.

There are other possibilities, but it is clean and consistent for each executable to provide for itself all the variables it needs. That requires the library functions to access all of its variables with static storage duration (not just external ones) indirectly, relative to the program. This is ordinary dynamic linking, just going the opposite direction from what you usually think of.

Is -fPIC for shared libraries ONLY?


Should -fPIC never be used during building an executable or a static library?

Never is a strong word, and the statement above is false.

Code built with -fPIC is (slightly) less optimal, so why would you want to put it into anything other than a shared library?

Let's start with a static library, which has an easy answer.

Suppose you want to give your users a static library that can be linked into either an executable, or into a shared library of their own?

In that case you must either give them 3 separate archive libraries (one built with -fPIC for linking into shared libraries, one built with -fPIE for linking into PIE executables, and a "regular" one), or you can give them a single archive library (which must have code built with -fPIC).

Now, it could be argued that you should instead give them a shared library, but that forces your end users to distribute 2 binaries, and they may prefer to not do that.

But suppose you want to build a regular (non-PIE) executable. What could be the reason to link in -fPIC code into such executable?

Well, suppose you are in the development stage, and don't care that much about optimizing the code yet. Suppose further that you want to test your code as both a shared library, and as part of PIE and non-PIE executable.

Under above conditions, you could either compile your code 3 times (with and without -fPIC, and with -fPIE), or you could compile it once (with -fPIC) and link it into all 3 of shared library, PIE and non-PIE executable. Doing this saves a lot of compilation time, and some build system complexity.

TL;DR: putting -fPIC objects into executables and static libraries has its place, and you should understand the reason why you are doing it (if you end up doing it).

Update:

Code in an object file is always relocatable

Correct.

is it position-independent code?

No: not all relocatable code is position-independent.

Position-independent code is a subset of relocatable code. Relocatable code can have relocations that apply to any section. Position-independent code must not have any relocations against .text (and .rodata).

with RIP-addressing, why x86-64 still need relocations?

This article explains it better than I can, but basically global variables in a shared library.

How do I force gcc to call a function directly in PIC code?

If you declare test1() hidden (__attribute__((__visibility__("hidden"))), the jump will be direct.

Now test1() may not be defined in its source translation unit as hidden, but I believe no harm should come from that discrepancy except the C language guarantee that &test1 == &test1 might be broken for you at runtime if one of the pointers was obtained via a hidden reference and one via a public one (the public reference might have been interposed via preloading or a DSO that came before the current one in the lookup scope, while the hidden reference (which results in direct jumps) effective prevents any kind of interposition)

A more proper way to deal with this would be to define two names for test1()—a public name and a private/hidden name.

In gcc and clang, this can be done with some alias magic, which can only be done in the translation unit that defines the symbol.

Macros can make it prettier:

#define PRIVATE __attribute__((__visibility__("hidden")))
#define PUBLIC __attribute__((__visibility__("default")))
#define PRIVATE_ALIAS(Alias,OfWhat) \
extern __typeof(OfWhat) Alias __attribute((__alias__(#OfWhat), \
__visibility__("hidden")))

#if HERE
PUBLIC void test1(void) { }
PRIVATE_ALIAS(test1__,test1);
#else
PUBLIC void test1(void);
PRIVATE void test1__(void);
#endif

void call_test1(void) { test1(); }
void call_test1__(void) { test1__(); }

void call_ext0(void) { void ext0(void); ext0(); }
void call_ext1(void) { PRIVATE void ext1(void); ext1(); }

The above compiles (-O3, x86-64) into:

call_test1:
jmp test1@PLT
call_test1__:
jmp test1__
call_ext0:
jmp ext0@PLT
call_ext1:
jmp ext1

(Defining HERE=1 additionally inlines the test1 call since it's small and local and -O3 is on).

Live example at https://godbolt.org/g/eZvmp7.

Why does GCC create a shared object instead of an executable binary according to file?


What am I doing wrong?

Nothing.

It sounds like your GCC is configured to build -pie binaries by default. These binaries really are shared libraries (of type ET_DYN), except they run just like a normal executable would.

So your should just run your binary, and (if it works) not worry about it.

Or you could link your binary with gcc -no-pie ... and that should produce a non-PIE executable of type ET_EXEC, for which file will say ELF 64-bit LSB executable.

Is this a gcc bug or not?

This issue had been discussed here:

the gcc bug report

I guess that gcc is clear that giving the crt1.o is the right behavior as the final executable is a non-PIE. It seems to choose crt1.o depending on the final executable.

Andrew Pinski's comment 18

According to the comment, gcc does and is designed to give crt1.o. Thus, this is not a gcc bug. Then, this should be either a gold or glibc issue.



Related Topics



Leave a reply



Submit