Fix relocations for global variables in position-independent executables with GCC

1.2k Views Asked by At

I'm looking for a gcc command-line flag or other settings to produce GOTOFF relocations rather than GOT relocations for my statically linked, position-independent i386 executable. More details on what I was trying below.

My source file g1.s looks like this:

extern int answer;
int get_answer1() { return answer; }

My other source file g2.s looks like this:

extern int answer;
int get_answer2() { return answer; }

I compile them with gcc -m32 -fPIE -Os -static -S -ffreestanding -fomit-frame-pointer -fno-unwind-tables -fno-asynchronous-unwind-tables g1.c for i386.

I get the following assembly output:

    .file   "g1.c"
    .text
    .globl  get_answer1
    .type   get_answer1, @function
get_answer1:
    call    __x86.get_pc_thunk.cx
    addl    $_GLOBAL_OFFSET_TABLE_, %ecx
    movl    answer@GOT(%ecx), %eax
    movl    (%eax), %eax
    ret
    .size   get_answer1, .-get_answer1
    .section        .text.__x86.get_pc_thunk.cx,"axG",@progbits,__x86.get_pc_thunk.cx,comdat
    .globl  __x86.get_pc_thunk.cx
    .hidden __x86.get_pc_thunk.cx
    .type   __x86.get_pc_thunk.cx, @function
__x86.get_pc_thunk.cx:
    movl    (%esp), %ecx
    ret
    .ident  "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
    .section        .note.GNU-stack,"",@progbits

Here is how to reproduce this behavior online with GCC 7.2: https://godbolt.org/g/XXkxJh

Instead of GOT above, I'd like to get GOTOFF, and the movl %(eax), %eax should disappear, so the assembly code for the function should look like this:

get_answer1:
    call    __x86.get_pc_thunk.cx
    addl    $_GLOBAL_OFFSET_TABLE_, %ecx
    movl    answer@GOTOFF(%ecx), %eax
    ret

I have verified that this GOTOFF assembly version is what works, and the GOT version doesn't work (because it has an extra pointer indirection).

How can I convince gcc to generate the GOTOFF version? I've tried various combinations of -fPIC, -fpic, -fPIE, -fpie, -pie, -fno-plt. None of them worked, all of them made gcc produce the GOT version.

I couldn't find any i386-specific flag on https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html or any generic flag here: https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

In fact, I'm getting GOTOFF relocations for "..." string literals, and I also want to get them for extern variables.

The final output is a statically linked executable in a custom binary format (for which I've written a GNU ld linker script). There is no dynamic linking and no shared libraries. The address randomization is performed by a custom loader, which is free to load the executable to any address. So I do need position-independent code. There is no per-segment memory mapping: the entire executable is loaded as is, contiguously.

All the documentation I've been able to find online talk about position-independent executables which are dynamically linked, and I wasn't able to find anything useful there.

2

There are 2 best solutions below

1
On BEST ANSWER

You have to inform the compiler that global variables will end up in the same loadable module after linking. This is done by specifying their visibility as "hidden", either via the attribute:

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

int get_answer1() { return answer; }

or using the pragma:

#pragma GCC visibility push(hidden)
extern int answer;
// In your case there is no need to restore visibility
// #pragma GCC visibility pop
int get_answer1() { return answer; }

(but note that -fvisibility=hidden only affects definitions, not declarations, so it's not useful for this purpose)

Since you are linking everything together in the end, you can attach hidden visibility to everything. You can put

#pragma GCC visibility push(hidden)

in a separate file (say vis.h) and include it everywhere using -include vis.h. Or even say gcc -include <(echo '#pragma GCC visibility push(hidden)') if your build system allows that.

Under -flto -fpie -pie GCC will deduce hidden visibility automatically.

0
On

I wasn't able to solve this with gcc -fPIE, so I solved it manually, by processing the output file.

I use gcc -Wl,-q, with an output ELF executable file containing the relocations. I post-process this ELF executable file, and I add the following assembly instructions to the beginning:

call next
next:
pop ebx
add [ebx + R0 + (after_add - next)], ebx
add [ebx + R1 + (after_add - next)], ebx
add [ebx + R2 + (after_add - next)], ebx
...
after_add:

, where R0, R1, R2 ... are the addresses of R_386_32 relocations in the ELF executable. The In use objdump -O binary prog.elf prog.bin, and now prog.bin contains position-independent code, because it starts with the add [ebx + ...], ebx instructions, which do the necessary relocations to the code when the code starts running.

Depending on the execution environment, the gcc flag -Wl,-N is needed, to make the .text section writable (the add [ebx + ...], ebx instructions need that).