I think I found a GCC bug. Here is how to reproduce the issue: ``` $ cat foo.c #include <stdio.h> extern char _GLOBAL_OFFSET_TABLE_[]; int main() { printf("%lx", (unsigned long)_GLOBAL_OFFSET_TABLE_); } $ gcc-12 -c -fPIC foo.c $ gcc -o foo foo.o $ ./foo Illegal instruction (core dumped) ``` The resulting executable crashes with an illegal instruction because the linker overwrites instructions with an immediate. Take a look at the following objdump output: ``` $ objdump -dr foo.o foo.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 48 8b 05 00 00 00 00 mov 0x0(%rip),%rax # f <main+0xf> b: R_X86_64_GOTOFF64 _GLOBAL_OFFSET_TABLE_-0x4 f: 48 89 c6 mov %rax,%rsi 12: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 19 <main+0x19> 15: R_X86_64_PC32 .rodata-0x4 19: 48 89 c7 mov %rax,%rdi 1c: b8 00 00 00 00 mov $0x0,%eax 21: e8 00 00 00 00 call 26 <main+0x26> 22: R_X86_64_PLT32 printf-0x4 26: b8 00 00 00 00 mov $0x0,%eax 2b: 5d pop %rbp 2c: c3 ret ``` At offset 0xb, there's a relocation of type R_X86_64_GOTOFF64. GOTOFF64 makes the linker to write a 8-bytes offset to a given symbol. However, the instruction for that relocation is just `mov` and not `movabs`, so the subsequent 4-bytes are accidentally overwrote byt eh linker.
It was originally reported to the mold linker. For the record, here is the link to the original issue: https://github.com/rui314/mold/issues/693
Can you show how gcc -S output looks for you on this testcase? For me the problematic instruction is just movl $_GLOBAL_OFFSET_TABLE_, %eax or leaq _GLOBAL_OFFSET_TABLE_(%rip), %rax with -fpie, so it's the assembler who chooses the relocation type (which would make that a Binutils bug).
Here is my gcc -S output: $ gcc-12 -S -o- foo.c .file "foo.c" .text .section .rodata .LC0: .string "%lx" .text .globl main .type main, @function main: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq _GLOBAL_OFFSET_TABLE_(%rip), %rax movq %rax, %rsi leaq .LC0(%rip), %rax movq %rax, %rdi movl $0, %eax call printf@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc
Probably a Binutils bug then, with binutils-2.37 I get the correct 4: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # b <main+0xb> 7: R_X86_64_GOTPC32 _GLOBAL_OFFSET_TABLE_-0x4 Can you please report it against binutils at https://sourceware.org/bugzilla/ and mention the link here?
(In reply to Alexander Monakov from comment #4) > Probably a Binutils bug then, with binutils-2.37 I get the correct Do you mean gas or ld? > > 4: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # b <main+0xb> > 7: R_X86_64_GOTPC32 _GLOBAL_OFFSET_TABLE_-0x4 How did you get this output, please (from foo.o or final executable)?
(In reply to Martin Liška from comment #5) > Do you mean gas or ld? gas > How did you get this output, please (from foo.o or final executable)? From foo.o like in comment #0.
Note I get 7: R_X86_64_GOTOFF64 _GLOBAL_OFFSET_TABLE_-0x4 when I use both as 2.37 and 2.39 with the output provided by gcc-12 -fPIC: $ cat foo.s .file "pr106834.c" .text .section .rodata .LC0: .string "%lx" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movq _GLOBAL_OFFSET_TABLE_@GOTPCREL(%rip), %rax movq %rax, %rsi leaq .LC0(%rip), %rax movq %rax, %rdi movl $0, %eax call printf@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (SUSE Linux) 12.2.1 20220830 [revision e927d1cf141f221c5a32574bde0913307e140984]" .section .note.GNU-stack,"",@progbits while using the assembly from Comment 3 I get: b: R_X86_64_GOTPC32 _GLOBAL_OFFSET_TABLE_-0x4 for both as versions.
Right, sorry, due to presence of 'main' I overlooked -fPIC in comment #0, and then after my prompt it got dropped in comment #3. If you modify the testcase as follows and compile it with -fPIC, it's evident that GCC is treating both external symbols the same, but gas does not. Similar to PR 106835, it seems Binutils is special-casing by symbol name. But here the situation is worse, because GCC output is mentioning the intended relocation kind: movq _GLOBAL_OFFSET_TABLE_@GOTPCREL(%rip), %rax so silently using R_X86_64_GOTOFF64 instead doesn't look right. #include <stdio.h> extern char _GLOBAL_OFFSET_TABLE_[]; extern char xGLOBAL_OFFSET_TABLE_[]; int main() { printf("%lx", (unsigned long)_GLOBAL_OFFSET_TABLE_); printf("%lx", (unsigned long)xGLOBAL_OFFSET_TABLE_); }
_GLOBAL_OFFSET_TABLE_ is a special symbol and can't be accessed like regular symbols. To workaround it: [hjl@gnu-tgl-3 tmp]$ cat x.c #include <stdio.h> extern char GLOBAL_OFFSET_TABLE[]; int main() { printf("%lx\n", (unsigned long)GLOBAL_OFFSET_TABLE); } [hjl@gnu-tgl-3 tmp]$ gcc -fPIC x.c /usr/local/bin/ld: /tmp/cc4Lekj4.o: in function `main': x.c:(.text+0x7): undefined reference to `GLOBAL_OFFSET_TABLE' collect2: error: ld returned 1 exit status [hjl@gnu-tgl-3 tmp]$ gcc -fPIC x.c -Wl,--defsym,GLOBAL_OFFSET_TABLE=_GLOBAL_OFFSET_TABLE_ [hjl@gnu-tgl-3 tmp]$ ./a.out 403fe8 [hjl@gnu-tgl-3 tmp]$
Okay, so this should have been reported against Binutils, but since we are having the conversation here: the current behavior is not good, gas is silently selecting a different relocation kind for no clear reason. Why is it not a warning or an error? Note that if you assemble such GOT reference via NASM: extern _GLOBAL_OFFSET_TABLE_ default rel f: mov rax, [_GLOBAL_OFFSET_TABLE_ wrt ..gotpc] ret then t.o has 0000000000000000 <f>: 0: 48 8b 05 00 00 00 00 mov 0x0(%rip),%rax # 7 <f+0x7> 3: R_X86_64_GOTPCREL _GLOBAL_OFFSET_TABLE_-0x4 7: c3 ret and ld -shared --no-relax -o t.so t.o does not reject it and t.so has 0000000000001000 <f>: 1000: 48 8b 05 f1 1f 00 00 mov 0x1ff1(%rip),%rax # 2ff8 <_DYNAMIC+0xe0> 1007: c3 ret and without --no-relax: 0000000000001000 <f>: 1000: 48 8d 05 f9 1f 00 00 lea 0x1ff9(%rip),%rax # 3000 <_GLOBAL_OFFSET_TABLE_> 1007: c3 ret So I don't see the reason why it's special-cased in gas.
I opened: https://sourceware.org/bugzilla/show_bug.cgi?id=29551
We can't use movq _GLOBAL_OFFSET_TABLE_@GOTPCREL(%rip), %rax to get the address of _GLOBAL_OFFSET_TABLE_ since there is no entry for _GLOBAL_OFFSET_TABLE_ in GOT. We can't use movl $_GLOBAL_OFFSET_TABLE_, %eax either since it generates R_X86_64_GOTPC32 relocation. The reliable way to get the address of _GLOBAL_OFFSET_TABLE_ is to use leaq _GLOBAL_OFFSET_TABLE_(%rip), %rsi
.