Why does gcc force PIC for x64 shared libs?

泄露秘密 提交于 2019-12-03 12:04:32

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.

Just say something additional.

In url provided in the question, it mentions you can pass -mcmodel=large to gcc to tell the compiler to generate 64-bits immediate address operand for your code.

So, gcc -mcmodel=large -shared a.c will generate a non-PIC shared object.

-

Demos:

a.c:

#include <stdio.h>

void foo(void)
{
    printf("%p\n", main);
}

32-bit immediate address operand blocks you from generating non-PIC object.

xiami@gentoo ~ $ cc -shared -o a.so a.c
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/cck3FWeL.o: relocation R_X86_64_32 against `main' can not be used when making a shared object; recompile with -fPIC
/tmp/cck3FWeL.o: error adding symbols: Bad value
collect2: error: ld returned 1 exit status

Use -mcmodel=large to solve it. (The warnings only appear on my system because modification on .text is forbidden by my PaX kernel.)

xiami@gentoo ~ $ cc -mcmodel=large -shared -o a.so a.c
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: /tmp/ccZ3b9Xk.o: warning: relocation in readonly section `.text'.
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.4/../../../../x86_64-pc-linux-gnu/bin/ld: warning: creating a DT_TEXTREL in object.

Now you can see the relocation entry's type is R_X86_64_64 instead of R_X86_64_32, R_X86_64_PLT32, R_X86_64_PLTOFF64.

xiami@gentoo ~ $ objdump -R a.so
a.so:      file format elf64-x86-64
DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
...
0000000000000758 R_X86_64_64       printf
...

And on my system, link this shared object to a normal code and run the program will emit errors like: ./a.out: error while loading shared libraries: ./a.so: cannot make segment writable for relocation: Permission denied

This proves dynamic loader is tring to do relocations on .text which PIC library won't.

The thing is, PIC and non-PIC code is still different.

C source:

extern int x;
void func(void) { x += 1; }

Assembly, not PIC:

addl    $1, x(%rip)

Assembly, with PIC:

movq    x@GOTPCREL(%rip), %rax
addl    $1, (%rax)

So it looks like PIC code has to go through a relocation table to access global variables. It actually has to do the same thing for functions, but it can do functions through stubs created at link-time. This is transparent at the assembly level, while accessing globals is not. (If you need the address of a function, however, then PIC and non-PIC are different, just like globals.) Note that if you change the code as follows:

__attribute__((visibility("hidden"))) extern int x;

In this case, since GCC knows that the symbol must reside in the same object as the code, it emits the same code as the non-PIC version.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!