Search code examples
gccrelocationposition-independent-code

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


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.


Solution

  • 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.