PIE 란 Position Independant Executable 의 약자로 위치 독립적인 실행 형식이라는
매우 어려운 한국말로 번역이 되는군요.

조금 설명을 붙이자면 실행위치와 관계없이 실행이 가능하다라는 건데...
공유라이브러리의 경우 어느 프로그램과 링크가 될지 모르기 때문에
PIC ( Position Independant Code) 위치 독립적인 코드형태로 컴파일을 하게 됩니다.

이와 비슷하게 실행이 가능한 코드의 형태로 컴파일 하는 것이 PIE 옵션이 되겠습니다.
예제 파일을 하나두고 컴파일을 해보겠습니다.

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <sys/types.h>
 5 #include <sys/stat.h>
 6 #include <fcntl.h>
 7 #include <dirent.h>
 8 #include <unistd.h>
 9 #include <assert.h>
10 #include <sys/vfs.h>
11 #include <sys/wait.h>
12 
13 
14 void foo(void)
15 {
16     printf("foo  %p\n", foo);
17 }
18 
19 int main(void)
20 {
21     foo();
22 
23     return 0;
24 }

위의 실행파일을 아무런 옵션없이 컴파일 해서 오브젝트를 확인해 보면
foo 함수와 main 함수의 위치는 0x8000 번지대로 나옵니다.
root@boggle70-desktop:pie# arm-generic-linux-gnueabi-gcc pie.c
root@boggle70-desktop:pie# arm-generic-linux-gnueabi-objdump -D a.out

00008380 <foo>:
8380: e1a0c00d mov ip, sp
8384: e92dd800 push {fp, ip, lr, pc}
8388: e24cb004 sub fp, ip, #4 ; 0x4
838c: e59f0008 ldr r0, [pc, #8] ; 839c <foo+0x1c>
8390: e59f1008 ldr r1, [pc, #8] ; 83a0 <foo+0x20>
8394: ebffffca bl 82c4 <_init+0x4c>
8398: e89da800 ldm sp, {fp, sp, pc}
839c: 0000844c .word 0x0000844c
83a0: 00008380 .word 0x00008380

000083a4 <main>:
83a4: e1a0c00d mov ip, sp
83a8: e92dd800 push {fp, ip, lr, pc}
83ac: e24cb004 sub fp, ip, #4 ; 0x4
83b0: ebfffff2 bl 8380 <foo>
83b4: e3a03000 mov r3, #0 ; 0x0
83b8: e1a00003 mov r0, r3
83bc: e89da800 ldm sp, {fp, sp, pc}

00008448 <_IO_stdin_used>:
8448: 00020001 .word 0x00020001
844c: 206f6f66 .word 0x206f6f66
8450: 0a702520 .word 0x0a702520
8454: 00000000 .word 0x00000000

0000828c <.plt>:
82c4: e28fc600 add ip, pc, #0 ; 0x0
printf 가 호출되는 부분은 procedure linkage table 에는 0xe28fc600 입니다.
이번에는 동일한 프로그램을 PIE 옵션을 붙여서 컴파일을 해 보겠습니다.
실행주소의 값은 작은 값을 갖고 있습니다.
그 이유는 공유라이브러리 처럼 실행되는 공간내에서는 어느 곳에서도 실행이 가능하도록
상대주소로 되어 있기 때문입니다.
root@boggle70-desktop:pie# arm-generic-linux-gnueabi-gcc -c -fPIE pie.c
root@boggle70-desktop:pie# arm-generic-linux-gnueabi-gcc -o foo -pie pie.o
root@boggle70-desktop:pie# arm-generic-linux-gnueabi-objdump -D foo

000005c0 <foo>:
5c0: e1a0c00d mov ip, sp
5c4: e92dd800 push {fp, ip, lr, pc}
5c8: e24cb004 sub fp, ip, #4 ; 0x4
5cc: e59f2020 ldr r2, [pc, #32] ; 5f4 <foo+0x34>
5d0: e08f2002 add r2, pc, r2
5d4: e59f301c ldr r3, [pc, #28] ; 5f8 <foo+0x38>
5d8: e0823003 add r3, r2, r3
5dc: e1a00003 mov r0, r3
5e0: e59f3014 ldr r3, [pc, #20] ; 5fc <foo+0x3c>
5e4: e0823003 add r3, r2, r3
5e8: e1a01003 mov r1, r3
5ec: ebffffad bl 4a8 <_init+0x4c>
5f0: e89da800 ldm sp, {fp, sp, pc}
5f4: 000081e4 .word 0x000081e4
5f8: ffff7eec .word 0xffff7eec
5fc: ffff7e04 .word 0xffff7e04

00000600 <main>:
600: e1a0c00d mov ip, sp
604: e92dd800 push {fp, ip, lr, pc}
608: e24cb004 sub fp, ip, #4 ; 0x4
60c: ebffffeb bl 5c0 <foo>
610: e3a03000 mov r3, #0 ; 0x0
614: e1a00003 mov r0, r3
618: e89da800 ldm sp, {fp, sp, pc}

000006a4 <_IO_stdin_used>:
6a4: 00020001 .word 0x00020001
6a8: 206f6f66 .word 0x206f6f66
6ac: 0a702520 .word 0x0a702520
6b0: 00000000 .word 0x00000000

00000470 <.plt>:
4a8: e28fc600 add ip, pc, #0 ; 0x0

동일하게 foo 에서 호출되는 printf 에 해당하는 주소는 0x4a8 위치에 있는데
값은 0xe28fc600 가 됩니다.


마지막으로 실행위치를 실제로 한번 살펴봅니다.
foo 의 함수위치를 print 해서 실행위치를 살펴보겠습니다.
[root@falinux ~]$ /mnt/nfs/a.out 
foo  0x8380
[root@falinux ~]$ /mnt/nfs/foo 
foo  0x2a0005c0
첫번째 실행파일인 a.out 은 절대주소 그래도 실행위치가 보이지만 PIE 옵션으로
컴파일한 파일은 상대위치로 움직이는 것을 알수 있습니다.
마지막으로 재미있는 것은 둘다 실행은 가능한데 file 의 형식을 확인해 보면
root@boggle70-desktop:pie# file a.out foo 
a.out: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), 
                                 for GNU/Linux 2.6.21, not stripped
foo:   ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), 
                                 for GNU/Linux 2.6.21, not stripped

와 같이 a.out 은 실행파일이지만 foo 는 shared object 라는 것입니다. 물론 실행도 가능한 파일입니다.



참조및 출처  : Binary Hacks - O'REILLY