The question is specific (but not limited to) to a bare-metal Risc-V code, I am trying to understand what would be required in order to compile a position-independent firmware binary. I tried to compile some simple code with -fpie
passed to gcc
and -static-pie
passed to the linker, and it appears that the produced binary is a self-contained position-independent executable, containing .got
, .got.plt
sections, and seemingly onlu pc
-relative references in the code.
The documentation is quite obscure, and the specific -static-pie
option is rarely mentioned. Most of pie
/pic
documentation is discussing dynamic relocations. Am I correct that with this option the produced binary is a true position-independent executable, which does not require any special relocation logic from the loader (or any loader at all, assuming it is making it's way somehow into the executable memory of course). Or I am missing something?
The GCC manual seems quite clear:
-static-pie
Produce a static position independent executable on targets that support it. A static position independent executable is similar to a static executable, but can be loaded at any address without a dynamic linker. For predictable results, you must also specify the same set of options used for compilation (-fpie, -fPIE, or model suboptions) when you specify this linker option.
To satisfy yourself that your -static-pie
program does not require a dynamic linker/loader you can use the fact that an ELF program that does require one has to have an INTERP
program header that nominates the one required. Thus:
$ cat main.c
#include <stdio.h>
int main(void)
{
puts("Hello World!");
return 0;
}
$ gcc main.c
$ readelf --program-headers --wide a.out | grep -A1 INTERP; echo Done
INTERP 0x000318 0x0000000000000318 0x0000000000000318 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
Done
which is reflected in:
$ ldd a.out
linux-vdso.so.1 (0x00007ffe65fd3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000722244e00000)
/lib64/ld-linux-x86-64.so.2 (0x00007222451f3000)
and at runtime:
$ LD_DEBUG=files ./a.out
55403:
55403: file=libc.so.6 [0]; needed by ./a.out [0]
55403: file=libc.so.6 [0]; generating link map
55403: dynamic: 0x00007d72d8202940 base: 0x00007d72d8000000 size: 0x0000000000211d90
55403: entry: 0x00007d72d802a390 phdr: 0x00007d72d8000040 phnum: 14
55403:
55403:
55403: calling init: /lib64/ld-linux-x86-64.so.2
55403:
55403:
55403: calling init: /lib/x86_64-linux-gnu/libc.so.6
55403:
55403:
55403: initialize program: ./a.out
55403:
55403:
55403: transferring control: ./a.out
55403:
Hello World!
55403:
55403: calling fini: [0]
55403:
55403:
55403: calling fini: /lib/x86_64-linux-gnu/libc.so.6 [0]
55403:
55403:
55403: calling fini: /lib64/ld-linux-x86-64.so.2 [0]
55403:
The dynamic linker starts first and finishes last.
Whereas:
$ gcc -fPIE -static-pie main.c
$ readelf --program-headers --wide a.out | grep -A1 INTERP; echo Done
Done
$ ldd a.out
statically linked
$ LD_DEBUG=files ./a.out
Hello World!
The dynamic linker is not there.