Why can I assemble and link get_got.asm
as position-independent code when it contains a reference to the absolute address of its GOT?
get_got.asm
extern _GLOBAL_OFFSET_TABLE_
section .text
global get_got
get_got:
mov rax, _GLOBAL_OFFSET_TABLE_
ret
main.c
#include <stdio.h>
void* get_got(void);
int main(int argc, char* argv[]) {
printf("%p\n", get_got());
}
assembling, compiling, linking, running:
nasm -felf64 -o get_got.o get_got.asm
gcc -fPIC -shared -o get_got.so get_got.o
gcc -Wl,-rpath=\$ORIGIN -o main main.c get_got.so
./main
0x148ba1cba000
What's going on here? It looks to me like get_got.so somehow has a baked-in absolute address for a GOT that won't have a known address until runtime. Disassembling get_got.so shows that the mov does in fact contain an immediate (0x201000). Obviously I have a major misunderstanding of something. I expected this to cause nasm to generate a relocation that the linker would choke on.
I built your code and a modified version using lea rax, [rel _GLOBAL_OFFSET_TABLE_]
.
I diffed the readelf -a
output. There's a lot of noise from different addresses, though.
readelf -a get_got.so | diff -u - <(readelf -a get_got_rel.so) | less
The most interesting difference is:
--- readelf -a get_got.so
+++ readelf -a get_got_rel.so
....
-Dynamic section at offset 0xe40 contains 22 entries:
+Dynamic section at offset 0xe50 contains 21 entries:
...
- 0x0000000000000016 (TEXTREL) 0x0
0x000000006ffffffe (VERNEED) 0x3b0
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x398
- 0x000000006ffffff9 (RELACOUNT) 4
+ 0x000000006ffffff9 (RELACOUNT) 3
So the absolute version has a text relocation. I didn't know Linux / ELF dynamic linking could apply fixups after mapping shared libraries. But apparently it can. (It's better not to, because it dirties the memory page so it's no longer just backed by the file on disk.)
But I checked with GDB, and that's what's going on: set a breakpoint in get_got
and run it:
(gdb) disas
Dump of assembler code for function get_got:
=> 0x00007f9e77b235b0 <+0>: movabs rax,0x7f9e77d24000
0x00007f9e77b235ba <+10>: ret
objdump -dRC -Mintel get_got.so
: (note line wrapping without -w
):
00000000000005b0 <get_got>:
5b0: 48 b8 00 10 20 00 00 movabs rax,0x201000
5b7: 00 00 00
5b2: R_X86_64_RELATIVE *ABS*+0x201000
5ba: c3 ret
Thanks @Jester for the -R
tip; I normally use objdump -dr ...
, not -R
, and lower-case r doesn't print any relocations for the .so
.
On get_got.o
, -r
shows movabs rax,0x0 2: R_X86_64_64 _GLOBAL_OFFSET_TABLE_
.
gcc -nostdlib -pie
will link 64-bit absolute relocations into PIE executables as well. (A PIE executable is an ELF shared object).
What isn't allowed in PIC / PIE are 32-bit absolute relocations: 32-bit absolute addresses no longer allowed in x86-64 Linux?. You get a linker error. Addressing modes like array[rcx*4]
aren't usable in PIC/PIE code, you need a separate instruction to get the address into a register.
lea rdi, [rel array]
is a much better choice than a 64-bit immediate absolute, because it's smaller and more friendly to the uop cache, and doesn't require a fixup when loading.