In Xcode, select your target in the "Targets" section, then click the "Build Settings" tab to view its settings.
For iOS apps, set iOS Deployment Target to iOS 4.3 or later. For Mac apps, set OS X Deployment Target to OS X 10.7 or later.
Verify that Generate Position-Dependent Code is set at to NO.
Verify that Don't Create Position Independent Executables is set to NO.
複製代碼
PIE(position-independent executable)是一種生成地址無關可執行程序的技術。若是編譯器在生成可執行程序的過程當中使用了PIE,那麼當可執行程序被加載到內存中時其加載地址存在不可預知性。html
PIE還有個孿生兄弟PIC(position-independent code)。其做用和PIE相同,都是使被編譯後的程序可以隨機的加載到某個內存地址。區別在於PIC是在生成動態連接庫時使用(Linux中的so),PIE是在生成可執行文件時使用。安全
PIE能夠提升緩衝區溢出攻擊的門檻。它屬於ASLR(Address space layout randomization)的一部分。ASLR要求執行程序被加載到內存時,它其中的任意部分都是隨機的。包括 Stack, Heap ,Libs and mmap, Executable, Linker, VDSO。經過PIE咱們可以實現Executable 內存隨機化bash
除了安全性,地址無關代碼還有一個重要的做用是提升內存使用效率。app
一個共享庫能夠同時被多個進程裝載,若是不是地址無關代碼(代碼段中存在絕對地址引用),每一個進程必須結合其自生的內存地址調用動態連接庫。致使不得不將共享庫總體拷貝到進程中。若是系統中有100個進程調用這個庫,就會有100份該庫的拷貝在內存中,這會照成極大的空間浪費。dom
相反若是被加載的共享庫是地址無關代碼,100個進程調用該庫,則該庫只須要在內存中加載一次。這是由於PIE將共享庫中代碼段需要變換的內容分離到數據段。使得代碼段加載到內存時能作到地址無關。多個進程調用共享庫時只須要在本身的進程中加載共享庫的數據段,而代碼段則能夠共享。 函數
咱們先從實際的例子出發,觀察PIE和NO-PIE在可執行程序表現形式上的區別。管中窺豹探索地址無關代碼的實現原理。ui
定義以下C代碼:spa
#include <stdio.h>
int global;
void main()
{
printf("global address = %x\n", &global);
}
複製代碼
程序中定義了一個全局變量global並打印其地址。咱們先用普通的方式編譯程序。.net
gcc -o sample1 sample1.c
code
運行程序能夠觀察到global加載到內存的地址每次都同樣。
$./sample1
global address = 6008a8
$./sample1
global address = 6008a8
$./sample1
global address = 6008a8
複製代碼
接着用PIE方式編譯 sample1.c
gcc -o sample1_pie sample1.c -fpie -pie
運行程序觀察global的輸出結果:
./sample1_pie
global address = 1ce72b38
./sample1_pie
global address = 4c0b38
./sample1_pie
global address = 766dcb38
複製代碼
每次運行地址都會發生變換,說明PIE使執行程序每次加載到內存的地址都是隨機的。
在代碼中聲明一個外部變量global。但這個變量的定義並未包含進編譯文件中。
#include <stdio.h>
extern int global;
void main()
{
printf("extern global address = %x\n", &global);
}
複製代碼
首先使用普通方式編譯 extern_var.c。在編譯選項中故意不包含有global定義的源文件。
gcc -o extern_var extern_var.c
發現不能編譯經過, gcc提示:
/tmp/ccJYN5Ql.o: In function `main': extern_var.c:(.text+0xa): undefined reference to `global'
collect2: ld returned 1 exit status
複製代碼
編譯器在連接階段有一步重要的動做叫符號解析與重定位。連接器會將全部中間文件的數據,代碼,符號分別合併到一塊兒,並計算出連接後的虛擬基地址。好比 「.text」段從 0x1000開始,」.data」段從0x2000開始。接着連接器會根據基址計算各個符號(global)的相對虛擬地址。
當編譯器發如今符號表中找不到global的地址時就會報出 undefined reference to global
.說明在靜態連接的過程當中編譯器必須在編譯連接階段完成對全部符號的連接。
若是使用PIE方式將extern_var.c編譯成一個share library會出現什麼狀況呢?
gcc -o extern_var.so extern_var.c -shared -fPIC
程序可以順利編譯經過生成extern_var.so。但運行時會報錯,由於裝載時找不到global符號目標地址。這說明-fPIC選項生成了地址無關代碼。將靜態連接時沒有找到的global符號的連接工做推遲到裝載階段。
那麼在編譯連接階段,連接器是如何將這個缺失的目標地址在代碼段中進行地址引用的呢?
連接器巧妙的用一張中間表GOT(Global Offset Table)來解決被引用符號缺失目標地址的問題。若是在連接階段(jing tai)發現一個不能肯定目標地址的符號。連接器會將該符號加到GOT表中,並將全部引用該符號的地方用該符號在GOT表中的地址替換。到裝載階段動態連接器會將GOT表中每一個符號對應的實際目標地址填上。
當程序執行到符號對應的代碼時,程序會先查GOT表中對應符號的位置,而後根據位置找到符號的實際的目標地址。
地址無關代碼的生成方式 所謂地址無關代碼要求程序被加載到內存中的任意地址都可以正常執行。因此程序中對變量或函數的引用必須是相對的,不能包含絕對地址。
好比以下僞彙編代碼:
PIE方式:代碼能夠運行在地址100或1000的地方
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP
複製代碼
Non-PIE: 代碼只能運行在地址100的地方
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP
複製代碼
由於可執行程序的代碼段只有讀和執行屬性沒有寫屬性,而數據段具備讀寫屬性。要實現地址無關代碼,就要將代碼段中需要改變的絕對值分離到數據段中。在程序加載時能夠保持代碼段不變,經過改變數據段中的內容,實現地址無關代碼。
在Non-PIE時程序每次加載到內存中的位置都是同樣的。
執行程序會在固定的地址開始加載。系統的動態連接器庫ld.so會首先加載,接着ld.so會經過.dynamic段中類型爲DT_NEED的字段查找其餘須要加載的共享庫。並依次將它們加載到內存中。注意:由於是Non-PIE模式,這些動態連接庫每次加載的順序和位置都同樣。
而對於經過PIE方式生成的執行程序,由於沒有絕對地址引用因此每次加載的地址也不盡相同。
不只動態連接庫的加載地址不固定,就連執行程序每次加載的地址也不同。這就要求ld.so首先被加載後它不只要負責重定位其餘的共享庫,同時還要對可執行文件重定位。