What is the -fPIE option for standalone executables in gcc and ld?

How will this change code, such as function calls?

+57
gcc linker
Mar 17
source share
2 answers

The PIE must support address space allocation (ASLR) randomization in executables.

Before the PIE mode was created, the executable file of the program could not be placed at a random address in memory, dynamic libraries with independent position (PIC) could be transferred to a random offset. It is very similar to what PIC does for dynamic libraries, the difference is that no procedure binding table (PLT) has been created; instead, PC-relative wrapping is used.

After enabling PIE support in gcc / linkers, the body of the program is compiled and linked as position-independent code. The dynamic linker performs full motion processing in the software module, just like dynamic libraries. Any use of global data is converted to access through the global offset table (GOT) and GOT redirects are added.

PIE is well described in this OpenBSD PIE presentation .

Changes in functions are displayed in this slide (PIE vs PIC).

x86 pic vs pie

Local global variables and functions optimized in pie

External global variables and functions are the same as pic

and into this slide (PIE vs old-style linking)

x86 pie vs no-flags (fixed)

Local global variables and functions are similar to fixed

External global variables and functions are the same as pic

Note that PIE may not be compatible with -static

+69
Feb 17 '11 at 2:53 on
source share

Minimal executable example: GDB executable twice

For those who want to see some kind of action, let's see how ASLR works on the PIE executable and changes the address every time it starts:

main.c

 #include <stdio.h> int main(void) { puts("hello"); } 

main.sh

 #!/usr/bin/env bash echo 2 | sudo tee /proc/sys/kernel/randomize_va_space for pie in no-pie pie; do exe="${pie}.out" gcc -O0 -std=c99 "-${pie}" "-f${pie}" -ggdb3 -o "$exe" main.c gdb -batch -nh \ -ex 'set disable-randomization off' \ -ex 'break main' \ -ex 'run' \ -ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \ -ex 'run' \ -ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \ "./$exe" \ ; echo echo done 

For one with -no-pie everything is boring:

 Breakpoint 1 at 0x401126: file main.c, line 4. Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x401126 Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x401126 

Before starting to execute, break main sets the 0x401126 break to 0x401126 .

Then, during both executions, run stops at 0x401126 .

However, with -pie much more interesting:

 Breakpoint 1 at 0x1139: file main.c, line 4. Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x5630df2d6139 Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x55763ab2e139 

Before starting the execution, GDB simply takes the "dummy" address that is present in the executable: 0x1139 .

However, after starting GDB, it is reasonable to notice that the dynamic loader placed the program in another place, and the first gap stopped at 0x5630df2d6139 .

Then the second run also reasonably noticed that the executable moved again, and eventually 0x55763ab2e139 to 0x55763ab2e139 .

echo 2 | sudo tee/proc/sys/kernel/randomize_va_space echo 2 | sudo tee/proc/sys/kernel/randomize_va_space echo 2 | sudo tee/proc/sys/kernel/randomize_va_space echo 2 | sudo tee/proc/sys/kernel/randomize_va_space ensures that ASLR is enabled (by default in Ubuntu 17.10): how can I temporarily disable ASLR (randomization of address space location)? | Ask Ubuntu .

set disable-randomization off , otherwise GDB, as the name implies, disables ASLR for the process by default to give fixed addresses for each run to improve the debugging process: the difference between GDB addresses and "real" addresses? | Stack overflow .

readelf analysis

In addition, we can also observe that:

 readelf -s ./no-pie.out | grep main 

gives the actual load address at runtime (pc indicates the following instruction after 4 bytes):

 64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 main 

while:

 readelf -s ./pie.out | grep main 

gives only offset:

 65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 main 

Turning off ASLR (either using randomize_va_space or set disable-randomization off ), GDB always gives the main address: 0x5555555547a9 , so we conclude that the -pie address consists of:

 0x555555554000 + random offset + symbol offset (79a) 

TODO where 0x555555554000 is hard-coded in the Linux kernel / glibc loader / anywhere? How is the text section address of a Linux PIE executable determined?

Minimal assembly example

Another cool thing we can do is play around with some assembler code to more specifically understand what PIE means.

We can do this using the standalone Linux x86_64 build:

main.S

 .text .global _start _start: asm_main_after_prologue: /* write */ mov $1, %rax /* syscall number */ mov $1, %rdi /* stdout */ mov $msg, %rsi /* buffer */ mov $len, %rdx /* len */ syscall /* exit */ mov $60, %rax /* syscall number */ mov $0, %rdi /* exit status */ syscall msg: .ascii "hello\n" len = . - msg 

Github upstream

and it builds and works fine with:

 as -o main.o main.S ld -o main.out main.o ./main.out 

However, if we try to connect it like a cake with:

 ld --no-dynamic-linker -pie -o main.out main.o 

then the link will fail with:

 ld: main.o: relocation R_X86_64_32S against '.text' can not be used when making a PIE object; recompile with -fPIC ld: final link failed: nonrepresentable section on output 

Because the line:

 mov $msg, %rsi /* buffer */ 

hardcodes the message address in the operand mov and therefore does not depend on the position.

--no-dynamic-linker is required as described in the section: How to create a statically linked position-independent ELF executable on Linux?

If we instead write it in an independent way:

 lea msg(%rip), %rsi 

then the PIE link works fine, and GDB shows us that the executable is loaded every time in a different place in memory.

The difference here is that lea encodes the msg address relative to the current PC address due to rip syntax, see Also: How to use relative RIP addressing in a 64-bit build program?

We can also figure this out by parsing both versions with:

 objdump -S main.o 

which give respectively:

 e: 48 c7 c6 00 00 00 00 mov $0x0,%rsi e: 48 8d 35 19 00 00 00 lea 0x19(%rip),%rsi # 2e <msg> 000000000000002e <msg>: 2e: 68 65 6c 6c 6f pushq $0x6f6c6c65 

Thus, we clearly see that lea already has the full correct msg address encoded as the current address + 0x19.

However, the mov version set the address 00 00 00 00 , which means the move will be done there: what are the linkers doing? The cryptic R_X86_64_32S in the ld error message is the actual type of move that was needed and that could not happen in the PIE executables.

Another fun thing we can do is put msg in the data section instead of .text with:

 .data msg: .ascii "hello\n" len = . - msg 

Now .o is going to:

 e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15> 

therefore, the RIP offset is now 0 , and we assume that the assembler requested a move. We confirm this:

 readelf -r main.o 

which gives:

 Relocation section '.rela.text' at offset 0x160 contains 1 entry: Offset Info Type Sym. Value Sym. Name + Addend 000000000011 000200000002 R_X86_64_PC32 0000000000000000 .data - 4 

it is so clear that R_X86_64_PC32 is the relative movement of the PC that ld can handle for PIE executables.

This experiment taught us that the linker himself checks that the program can be a PIE, and marks it as such.

Then, when compiling with GCC, -pie tells GCC to generate a position-independent assembly.

But if we write the assembly ourselves, we must manually verify that we have achieved independence of position.

In ARMv8 aarch64, a hi-independent world can be achieved using the ADR instruction .

How to determine if an ELF is position independent?

Besides just starting through GDB, some static methods are mentioned in:

Tested on Ubuntu 18.10.

+18
Jul 12 '18 at 14:19
source share



All Articles