You are on page 1of 113

系統程式 – 理論與實務

第 5 章、連結與載入

作者:陳鍾誠

旗標出版社
第 5 章、連結與載入
5.1 簡介
5.2 函式庫
5.3 目的檔
5.4 連結器
5.5 載入器
5.6 動態連結
5.7 實務案例
5.7.1 GNU 連結工具
5.7.2 目的檔格式 – a.out
5.7.3 目的檔格式 – ELF
5.1 簡介
目的檔 v.s 可執行檔

連結器

絕對載入器
5.1 目的檔 v.s. 可執行檔
 當組譯器組譯完成之後,所輸出的檔案,通常稱為目的
檔。

 假如目的檔中沒有未連結的外部變數,而且已經指定了
起始位址,那麼,這種目的檔通常被稱為執行檔。

 此時,載入器可以將執行檔載入到記憶體當中執行。

 但是,如果有外部變數尚未被連結,那麼,目的檔就必
須被連結成執行檔後,才能被放入到記憶體中執行。
連結器
連結器必須計算出區段與變數的記憶體位址,並利
用修正記錄進行修正後,才能消除這些外部變數,
以便將目的檔轉換成執行檔。

Windows
目的檔: *.obj
執行檔: *.exe

Linux
目的檔: *.o
執行檔:預設 a.out
絕對載入器
最簡單的載入器,是採用絕對定位方式的載入器,
也就是在目的檔當中直接指定所要載入的記憶體位
址,然後,載入器就將程式載入到對應的記憶體位
址執行之。
載入器與目的檔
載入器的設計與目的檔格式有關
不同的目的檔格式之載入器將會不同

在本節中
A 格式 Loader_A
 只有一筆 T 記錄
 直接將 T 記錄載入到指定位址即可

B 格式 Loader_B
 包含 T 記錄與 B 記錄
 必須用迴圈讀取 T, B 記錄,放到指定位址
圖 5.1 格式 A 的執行檔
T{
001F0010 001F0020 31000020 013F0008
00000001 00000007 00000003 00000006
00000000 00000004 12400000 12500000
00600024 10520000 0D000010 13445000
13556000 0EFFFFFF0 32000000 00000000
00000000 00000001
}
圖 5.2 格式 A 的載入器的演算

Algorithm Loader_A
Input objFile
fileHeader = objFile.readHeader();
memory = allocateMemory(objFile. memorySize);
section = objFile.readSection(T)
put section.objCode into
memory(memory.startAddress)
PC = memory.startAddress
End
圖 5.3 < 圖 5.1> 的目的檔載入記憶
體後的情況
記憶體 記憶體內容
位址
1200 001F0010 001F0020 31000020 013F0008
1210 00000001 00000007 00000003 00000006
1220 00000000 00000004 12400000 12500000
1230 00600024 10520000 0D000010 13445000
1240 13556000 0EFFFFF0 32000000 00000000
1250 00000000 00000001 XXXXXXXX XXXXXXXX
1260 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
圖 5.4 格式 B 的執行檔
T(0000) {
001F0010 001F0020 31000020 013F0008
00000001 00000007 00000003 00000006
}
B(0020,0004)
T(0024) {
00000004 12400000 12500000 00600024
10520000 0D000010 13445000 13556000
0EFFFFF0 32000000
}
B(004C,0004)
T(0050) {
00000000 00000001
}
圖 5.5 格式 B 的載入器演算法
演算法 說明
Algorithm Loader_B B 格式載入器
Input objFile 輸入為目的檔
objFile.open(); 開啟目的檔
memory = allocateMemory(objFile. 分配記憶空間
memorySize); 讀取目的檔
while not objFile.isEnd() 讀取下一筆區段
section = objFile.readSection(); 如果是 T 區段
if section.type() is T
put section.objCode into
memory(section.startAddress)
else if sectin.type() is B
do nothing // 或者 fill memory(section) with
0
end if
end foreach
PC = memory.startAddress
圖 5.6 < 圖 5.4> 的 B 格式目的檔
載入記憶體的情況
記憶體 記憶體內容
位址
1200 001F0010 001F0020 31000020 013F0008
1210 00000001 00000007 00000003 00000006
1220 XXXXXXXX 00000004 12400000 12500000
1230 00600024 10520000 0D000010 13445000
1240 13556000 0EFFFFF0 32000000 XXXXXXXX
1250 00000000 00000001 XXXXXXXX XXXXXXXX
1260 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
5.2 函式庫
函式庫會先被組譯成目的檔。

主程式才被組譯為目的檔。

最後再經由連結器連結成完整的執行檔。
外部引用
組譯器允許外部函數的存在

在連結的時候,才去解決這些外部引用的情況。
範例 5.1 具有交互引用的 C 語言程
式 ( 實作堆疊功能 )
StackType.c StackFunc.c StackMain.c
int stack[128]; extern int extern void
int top = 0; stack[]; push(int x);
extern int top; extern int pop();
void push(int x) int main() {
{ int x;
stack[top++] = push(3);
x; x= pop();
} return 0;
int pop() { }
return stack[--
top];
}
範例 5.2 具有交互引用的組合語言
程式 ( 實作堆疊功能 ) ( 頁 1)
檔名: StackType.s 檔名: StackMain.s
.bss .text
.global stack .extern push
stack RESW 512 .extern pop
.data .global main
.global top main:
top WORD 0 LDI R1, #3
PUSH R1
JSUB push
JSUB pop
ST R1, x
LDI R1, #0
RET
x RESW 1
範例 5.2 具有交互引用的組合語言
程式 ( 實作堆疊功能 ) ( 頁 2)
檔名: StackFunc.s 續…
.extern stack .global pop
.extern top pop:
.text LD R2, top // R2=top
.global push LD R3, stack // R3=stack
push : LDI R4, #4 // R4=4
POP R1 // R1=x LDI R5, #1 // R5 = 1
LD R2, top MUL R5, R2, R4 // R5 =
LD R3, stack top*4
LDI R4, #4 LDR R1,
LDI R5, #1 [R3+R5]//R1=stack[top]
MUL R5, R2, R4 SUB R2, R2, R5 // R2=R2-1
STR R1, [R3+R5] // ST R2, top // top = R2
stack[top]=x RET
ADD R2, R2, R5
ST R2, top
RET
範例 5.3 堆疊型態 StackType.s
及其目的碼
位址 組合語言檔: 目的碼 說明
StackType.s
.bss BSS 段開始
.global stack stack 是全域變數
B 0000 stack RESW B(0200),S(B,0000,stac 保留 512 個 bytes
512 k) 給 BSS 段
DATA 段開始
.data top 是全域變數
D 0000 .global top D(00000000),S(D,000 top 的位址是
top WORD 0 0,top) DATA 段的 0000
範例 5.4 堆疊函數 StackFunc.s
及其目的碼 ( 第 1 頁 )
組合語言檔: 目的碼 說明
StackFunc.s
.data 資料段開始
.extern stack S(U,,stack) stack 為外部變數
.extern top S(U,,top) top 為外部變數
.text 程式段開始
.global push push 為全域變數
push : S(T,0000, push)
T 0000 POP R1 // R1=x T(31100000)
T 0004 LD R2, top T(002F0000), 使用 PC 相對定址
T 0008 LD R3, stack M(T,0004,top,pc) 使用 PC 相對定址
T 000C LDI R4, #4 T(003F0000),
T 0010 LDI R5, #1 M(T,0008,stack,pc)
T 0014 MUL R5, R2, R4 T(08400004)
T 0018 STR R1, T(08500001)
T 001C [R3+R5] T(15524000)
T 0020 ADD R2, R2, R5 T(05135000)
T 0024 ST R2, top T(13225000)
RET T(01200000),
M(T,0020,top,pc)
T(2C000000)
範例 5.4 堆疊函數 StackFunc.s
及其目的碼 ( 第 2 頁 )
組合語言檔: 目的碼 說明
StackFunc.s
… … … …
T 0028 .global pop S(T,0028, pop) pop 為全域變數
T 002C pop: pop 函數開始
T 0030 LD R2, top T(002F0000), 使用 PC 相對定址
T 0034 LD R3, stack M(T,0028,top,pc) 使用 PC 相對定址
T 0038 LDI R4, #4 T(003F0000),
T 003C LDI R5, #1 M(T,002C,stack,pc)
T 0040 MUL R5, R2, R4 T(08400004)
T 0044 LDR R1, T(08500001)
T 0048 [R3+R5] T(15524000)
SUB R2, R2, R5 T(04135000) 使用 PC 相對定址
ST R2, top T(14225000)
RET T(012F0000),M(T,0044,top,pc
)
T(2C000000)
範例 5.5 堆疊主程式 StackMain.s
及其目的碼
組合語言檔 目的碼

StackMain.
.extern
s S(U,,push) push 為外部變數
T push S(U,,push) pop 為外部變數
0000 .extern S(T,0000,main) 程式 (Text) 段
T pop T(08100003) 開始
0004 .text T(30100000) main 為全域變
T .global T(2BF00000), 數
0008 main M(T,0008,push,pc) main 程式開始
T main: T(2BF00000),
000C LDI R1, M(T,000C,pop,pc) 使用 PC 相對定
T #3 T(01100008) 址
0010 PUSH R1 T(08100000)
T JSUB T(2C000000) 使用 PC 相對定
0014 push T(00000000) 址
T JSUB pop
5.3 目的檔
程式段 (.text, 以 T 開頭 )

資料段 (.data ,以 D 開頭 )

BSS 段 (.bss, 以 B 開頭 )

.global :符號表記錄 (S 記錄 )

.extern :修改記錄 (M 記錄 )
圖 5.7 目的檔中的記錄與範例

記錄 程式範例 目的碼 說明
T JSUB push T(2BF00000) 程式段的目的碼
D top WORD 0 D(00000000) 資料段的目的碼
B stack RESW B(400) 保留 BSS 段的空間
M 400 push
JSUB M(T,0008,push, 修改記錄 ( 或稱重定位記
pc) 錄 ) :採用相對於 PC 的位
址計算方式
S .global pop S(T,0028, pop) 符號記錄:記錄全域變數的
位址
S .extern stack S(U,,stack) 符號記錄:記錄外部變數
圖 5.8 重定位記錄 (M 記錄 ) 的表
示法

#define TYPE_ABSOLUTE 0
#define TYPE_PC_RELATIVE 1

typedef struct{
int section; // 段代號。
int offset; // 待修改位址。
int symbol; // 符號代碼 ( 用以取得修改值 ) 。
int type; // 修改記錄類型 (0. 絕對 1. 相對於 PC 、
2. …)
} RelocationRecord; // 修改記錄 (M 記錄 )
圖 5.9 符號記錄 (S 記錄 ) 的表
示法

#define SYM_TYPE_DATA 0
#define SYM_TYPE_FUNC 1
#define SYM_TYPE_SECT 2
#define SYM_TYPE_FILE 3

typedef struct {
int name; // 符號在字串表中的位址。
int section; // 定義該符號的分段
int value; // 符號的值 ( 通常為一個位址 ) 。
} SymbolRecord; // 符號記錄 (S 記錄 )
圖 5.10 字串表的結構

字串: ".text\0.data\0.bss\0stack\0top\0push\0pop\0main\0"
位址 0 1 2 3 4 5 6 7 9 9 A B C D E F
0 . T E X T \0 . D A T A \0 . B S S
000
0 \0 S T A C K \0 T O P \0 P U S H \0
010
0 P O P \0 M A I N \0
020
圖 5.11 目的檔 a.out 的格式與
範例
檔頭 (H 記錄 ) H(StackFunc.s, 各段的長度 )

程式段 (T 記錄 ) T{31100000 00200000 00300000 08400004 … }

資料段 (D 記錄 )
程式重定位資訊 M { (0004,top,pc) (0008,stack,pc) (0020,top,pc) …}
(M 記錄 )
資料重定位資訊
(M 記錄 )
符號表 S { (T,0000,push) (T,0028,pop) (U,,stack) (U,,top) }
(S 記錄 )
字串表 .text\0.data\0.bss\0top\0stack\0push\0pop\0
String Table

(a) a.out 檔案的格式 (b) a.out 的檔案範例 (StackFunc.o)


圖 5.12 本書使用的目的檔表示法 – 以
StackFunc.o, StackType.o, StackMain.o 為
範例
目的碼 說明
B { 0200 } BSS 段
D { 00000000 } 資料段
S { (B,0000,stack), (D,0000, top) } 符號表
T{ 程式段
31100000 00200000 00300000 08400004
08500001 15524000 05135000 13225000
01200000 2C000000 00200000 00300000
08400004 08500001 15524000 04135000
14225000 01200000 2C000000
}
M{ 重定位表
(T,0004,top,pc) (T,0008,stack,pc) (T,0020,top,pc)
(T,0028,top,pc) (T,002C,stack,pc) (T,0044,top,pc)
}
S { (T,0000,push) (T,0028,pop) (U,,stack) (U,,top) } 符號表
T{ 程式段
08100003 30100000 2B000000 2B000000
01100008 08100000 2C000000 00000000
}
M { (T,0008,push,pc), (T,000C,pop,pc) } 重定位表
S { (U,,push), (U,,pop), (T,0000, main) } 符號表
5.4 連結器
功能
連結器可以處理目的檔中的外部引用與外部變數
使得許多個目的檔得以整合再一起
成為一個完整的可執行檔。

輸出入
輸入是一群目的檔
輸出是一個執行檔
區段合併
除了解決外部引用的問題之外,連結器通常還必須
對程式進行區段合併的動作。

常見區段
程式段 (text section)
資料段 (data section)
BSS 段 (Block Started by Symbol)
 一種特殊的資料段,用來存放那些未設定初值的變數。
圖 5.13 連結器的輸入與輸出
函式庫 目的檔 目的檔
lib obj obj

連結器
linker

函式庫 執行檔 動態函式庫


lib exe dll
圖 5.14 連結器的功能 – 區段合併與
解決外部引用
.text
.data
.bss
.text

.text
.data .data

.bss
.bss

.text
.data
.bss
圖 5.15 連結器輸出的執行檔,以
stack.o 為例
T{
// 來源: StackMain.o 的程式段 (T 記錄 )
08100003 30100000 2B000014
2B000028
01100008 08100000 2C000000
00000000
// 來源: StackFunc.o 的程式段 (T 記錄 )
31100000 00200000 00300000
08400004
08500001 15524000 05135000
13225000
01200000 2C000000 00200000
00300000
08400004 08500001 15524000
04135000
14225000 01200000 2C000000
}
B { 0200 }
D指令: ld -o Stack.o
{ 00000000 } StackFunc.o StackType.o StackMain.o
說明:連結 StackFunc.o StackType.o StackMain.o 三個檔案以形成執行檔
Stack.o
目的檔: StackMain.o 圖 5.16 連結過程
T,0000
程式段: T {
08100003 30100000 2B000000 2B000000 圖
01100008 08100000 2C000000 00000000
T,001F }
T,000 執行檔: Stack.o
M { (T,0008,push,pc), (T,000C,pop,pc) } 0
S { (U,,push), (U,,pop), (T,0000, main) } T,001
程式段: T{
0
08100003 30100000 2B000014 2B000028
T,002
01100008 08100000 2C000000 00000000
目的檔 : 0
31100000 00200000 00300000 08400004
StackFunc.o T,003
08500001 15524000 05135000 13225000
程式段: T{ 0
T,0000 01200000 2C000000 00200000 00300000
31100000 00200000 00300000 08400004 T,004
08400004 08500001 15524000 04135000
08500001 15524000 05135000 13225000 0
14225000 01200000 2C000000
01200000 2C000000 00200000 00300000 T,005
}
08400004 08500001 15524000 04135000 0
14225000 01200000 2C000000 T,006
D,0000
} 0 資料段: D { 00000000 }
T,004C D,0004
S { (T,0000,push)(T,0028,pop)(U,,stack)(U,,top) } B,0000
M { (T,0004,top,pc)(T,0008,stack,pc)(T,0020,top,pc) BSS 段: B { 0200}
B,0200

(T,0028,top,pc)(T,002C,stack,pc)(T,0044,top,pc)
}

目的檔: StackType.o
D,0000
資料段: D { 00000000 }
D,0004
B,0000
BSS 段: B { 0200}
B,0200
S { (B, 0000, stack) (D,0000,top) }
5.5 載入器
將目的檔載入到記憶體,然後開始執行該目的檔

必須處理分段資訊與修改記錄等結構。

載入器必須讀取執行檔,組合其中的分段,放入到
記憶體中,然後根據修改記錄修正記憶體中的指令
區與資料區,最後才將程式計數器 PC 設定為起
始指令的位址,開始執行該程式。
圖 5.17 執行中程式的記憶體配置情
況 - Stack.o 載入後的情況
0000 1200 08100003 30100000 2B000014 2B000028
01100008 08100000 2C000000 00000000
31100000 00200044 00300044 08400004
程式段 (.text) 08500001 15524000 05135000 13225000
01200028 2C000000 00200020 00300020
08400004 08500001 15524000 04135000
14225000 01200004 2C000000

126C
資料段 (.data) 00000000

1270
BSS 段 (.bss) XXXXXXXX …

1470
堆積段 (.heap)

堆疊段 (.stack)
size 2000
(a) 載入到記憶體後的分段情況 (b) 執行檔 Stack.o 被載入記憶體後的情況
圖 5.18 目的檔 Stack.o 載入記憶體
後的情況
位址 記憶體內容
1200 08100003 30100000 2B000014 2B000028
1210 01100008 08100000 2C000000 00000000
1220 31100000 00200044 00300044 08400004
1230 08500001 15524000 05135000 13225000
1240 01200028 2C000000 00200020 00300020 Top:12
6C
1250 08400004 08500001 15524000 04135000
1260 14225000 01200004 2C000000 00000000
1270 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
符號表 : S { (T,0000,main) (T,0020,push) (T,0048,pop)(B,0000, stack) (D,0000, top)
stack:127 }
0 修正記錄 : M { (T,0024,top,pc) (T,0028,stack,pc) (T,0040,top,pc) (T,0048,top,pc)
(T,004C,stack,pc) (T,0064,top,pc) }
圖 5.19 載入器的演算法 ( 頁 1)
載入器的演算法 說明
1 Algorithm Loader 載入器
2 Input objFile 輸入檔:執行檔
3 symbolTable = new SymbolTable() 建立符號表
4 sectionTable = new SectionTable(); 建立分段表
5 fileHeader = objFile.readHeader(); 讀取檔頭
6 memory = allocateMemory(objFile. 分配執行空間
7 memorySize); 取得開頭位址
8 address = memory.address 對於每個段頭
9 foreach sectionHeader in fileHeader 建立分段物件
10 section = new Section(sectionHeader); 放入分段表
11 sectionTable[section.name] = section 如果是 {T,D,B} 記錄
12 if section.type() in {T,D,B} 設定分段位址
13 section.startAddress = address 設定下一個分段位址
14 address = address +
15 section.memorySize;
… end if
end foreach
圖 5.19 載入器的演算法 ( 頁 2)
載入器的演算法 說明
… … …
16 foreach section se in sectionTable 對於每個分段 se
17 if section.type() in {T, D} 如果是 {T,D} 類型分段
18 put se.objCode into memory(se.startAddress) 將目的碼放入記憶體
19 else if section.type() in {S} 如果是 {S} 類型分段
20 foreach symbolRecord smb in section 取出其中的符號 smb
21 smb.address += se.startAddress 設定符號位址
22 end foreach
23 end if
24 end foreach
25 foreach section in sectionTable 對於每一個分段
26 if section.type() in {M} 如果是 {M} 類型分段
27 foreach modifyRecord m in section 對於每個修改記錄 m
28 modifyMemoryAt(m.address, 根據 m 修改記憶體
29 symbol[m.symbolID], m.type)
30 end for
31 end if
32 end foreach 將程式計數器 PC 設定為起
33 PC = memory.startAddress 始位址,開始執行。
34 End algorithm
5.6 動態連結
靜態連結器
如果再程式開始執行之前,連結器就給定了外部參考
的記憶體位址,則該連結器就被稱為靜態連結器

動態連結器
如果連結器在執行期間才決定外部參考的記憶體位址
,則被稱為動態連結器。
靜態連結 v.s 動態連結
靜態連結
傳統的連結器會將所有程式連結成單一的執行檔,在
執行時只需要該執行檔就能順利執行。

動態連結
使用動態連結機制時,函式庫可以先不需要被連結進
來,而是在需要的時候才透過動態連結器 (Dynamic
Linker) 尋找並連結函式庫,這種方式可以不用載入
全部的程式,因此可以節省記憶體。當很多執行中的
程式共用到函式庫時,動態連結所節省的記憶體數量
就相當可觀。
圖 5.20 動態連結機制的實作方

主程式 動態連結符號區
LD R1, var3@GOT
PUSH R1
CALL f2@PLT

PLT : 動態連結函數區
(Stub)
f1: f2_in_meory
LD PC, Ptr_f1@GOT
DL_f1: CALL DLinker
f2:
LD PC, Ptr_f2@GOT
DL_f2: CALL Dlinker

f2_in_memory:
….
動態連結器 ….
DLinker :
尋找 f1, f2, f3 … 對應的函數,
然後將函數位址填入 Ptr_f1,
Ptr_f2, Ptr_f3, …
動態連結函式庫的特性
動態連結函式庫通常是與位置無關的程式碼
(Position Independent Code) ,使用相對定址的方
式。否則,如果在動態連結時利用修改記錄修正函
式庫的記憶體內容,會造成每個程式都在修正函式
庫,就可能造成不一致的窘境。
動態連結函式庫的優缺點
動態函式庫的優點是可任意抽換,不需刻意更新舊
程式,系統隨時保持最新狀態。但是,這也可能造
成『動態連結地獄』 (DLL hell) 的困境,因為,
如果新的函式庫有錯,或者與舊的主程式不相容,
那麼,原本執行正常的程式會突然出現錯誤,甚至
無法使用。
Windows 與 Linux 中的動態連

在 Windows 中
稱為 DLLs (Dynamic Linking Libraries)
其附檔名通常為 .dll

在 UNIX/Linux 中
被稱為 Share Objects
其附檔名通常是 .so 。
動態載入技術
動態連結
編譯時就已經知道動態函數的名稱與參數類型,因此
,編譯器可以事先檢查函數型態的相容性。

動態載入
允許程式設計人員在程式執行的過程中,動態決定要
載入哪個函式庫,要執行哪個函數,這種技術比動態
連結更具有彈性,靈活度也更高。其方法是讓程式可
以呼叫載入器,以便動態的載入程式,因此才被稱為
動態載入技術。
動態載入技術
簡化版
Linux 中的 execve

強化版
Linux 中的 dl 函式庫
範例 5.6 使用 execve 呼叫載入器的範

#include <unistd.h>
int main()
{
char *argv[]={"ls","-al","/etc/passwd",(char
*)0};
char *envp[]={"PATH=/bin",0};
execve("/bin/ls",argv,envp);
}
表格 5.1 Linux 與 MS. Windows
中動態載入函式庫的對照比較表
UNIX/Linux Windows
#include
引入檔 #include <dlfcn.h>
<windows.h>
函式庫檔 libdl.so Kernel32.dll
LoadLibrary
載入功能 dlopen
LoadLibraryEx
取得函數 dlsym GetProcAddress
關閉功能 dlclose FreeLibrary
範例 5.7 Linux 動態載入函式庫的
範例
程式: dlcall.c 說明
編譯指令: gcc -o dl_call dl_call.c –ldl
#include <dlfcn.h> 引用 dlfcn.h 動態函式庫
int main(void) {
void *handle = dlopen ("libm.so", 開啟 shared library 'libm'
RTLD_LAZY); 宣告 cos() 函數的變數
double (*cosine)(double); 找出 cos() 函數的記憶體位址
cosine = dlsym(handle, "cos"); 呼叫 cos() 函數
printf ("%f\n", (*cosine)(2.0)); 關閉函式庫
dlclose(handle);
return 0;
}
5.7 實務案例
5.7.1 GNU 連結工具

5.7.2 目的檔格式 – a.out

5.7.3 目的檔格式 – ELF


5.7.1 GNU 連結工具
GNU 的主要連結工具是 ld

可以直接用 gcc 進行連結動作


因為 gcc 會幫忙傳送給 ld 去執行
表格 C.5 連結指令 ld 的常用參數

參數 範例 說明
-o <file> ld -o ab a.o b.o 連結 a.o, b.o 為執行檔 ab
-L <path> ld -o ab a.o b.o -L 指定函式庫搜尋路徑為
/home/lib /home/lib
-l<name> ld -o ab a.o b.o –lm 連結函式庫 lib<name>.a ,
本範例連結的是 libm.a
-e <offset> ld -e 0x10000 -o hello 設定連結啟始位址為
crt0.o hello.o 0x10000
-s ld -s -o ab a.o b.o 移除所有符號
-S ld -S -o ab a.o b.o 移除除錯符號
-r ld -r -o romfs.o 輸出可重定位 (relocatable) 的
romfs.img 檔案
-Map ld -o ab a.o b.o -Map 產生連結後的符號表
-T ld -o ab a.o b.o -T ab.ld
ab.map 指定 link script 為 ab.ld
<linkscript
-T< 段 >< 位 ld -o ab a.o b.o –Ttext 指定 text 段位址
> >
址 0x0
ld -o ab a.o b.o –Ttext 指定 data 段位址
-T 0x1000
ld -o ab a.o b.o -Ttext 指定 text 段位址為
0x0 -Tdata 0x1000 0x0 、 data 段位址為
-Tbss 0x3000 0x1000 、 bss 段位址為
0x3000
使用 ld 進行連結
範例: ch05/stack
Stack.h
StackMain.c
StackFunc.c
StackType.c
組譯
StackMain.o
StackFunc.o
StackType.o
連結
stack.exe
範例 5.8 具有交互引用的 C 語言程
式 ( 實作堆疊功能 )
檔案 ch05/stack/Stack.h 檔案 ch05/stack/StackType.c
#ifndef _StackType_H_ #include <Stack.h>
#define _StackType_H_ int stack[STACK_SIZE];
#define STACK_SIZE 100 int top = 0;
extern int stack[];
extern int top;
extern void push(int x);
extern int pop();
#endif
檔案 ch05/stack/StackFunc.c 檔案 ch05/stack/StackMain.c
#include <assert.h> #include <stdio.h>
#include <Stack.h> #include <Stack.h>
void push(int x) { int main() {
assert(top < STACK_SIZE); int x;
stack[top++] = x; push(3);
} x= pop();
int pop() { printf("x=%d\n",x);
assert(top > 0); return 0;
return stack[--top]; }
}
範例 5.9 < 範例 5.1> 的編譯、連結執

Cygwin 中的指令與執行結果 說明

$ gcc -I . -c StackType.c -o StackType.o 編譯 StackType.c 為目


的檔
$ gcc -I . -c StackFunc.c -o StackFunc.o
編譯 StackFunc.c 為目
$ gcc -I . -c StackMain.c -o StackMain.o 的檔

$ gcc StackMain.o StackFunc.o 編譯 StackMain.c 為目


StackType.o -o stack 的檔

$ ./stack 連結三者成為執行檔
x=3
執行 stack 檔
結果輸出 3 。
範例 5.10 將 < 範例 5.1> 的 C 語
言程式編譯為 IA32 組合語言
Cygwin 中的指令與執行結果 說明
$ gcc -S StackType.c -o 編譯 StackType.c ,輸出組合
StackType.s -I . 語言檔 StackType.s
編譯 StackFunc.c ,輸出組合
$ gcc -S StackFunc.c -o 語言檔 StackFunc.s
StackFunc.s -I . 編譯 StackMain.c ,輸出組合
語言檔 StackFunc.s
$ gcc -S StackMain.c -o
StackMain.s -I .
檔案: StackType.s

.file "StackType.c"
.globl _top
.bss
.align 4
_top:
.space 4
.comm _stack, 400 #
400
檔案: StackFunc.s
. .section .rdata,"dr" .section .rdata,"dr"
LC0: .ascii "top < STACK_SIZE\0" LC2: .ascii "top > 0\0"
LC1: .ascii "StackFunc.c\0" .text
.text .globl _pop
.globl _push .def _pop;.scl 2;.type
.def _push;.scl 2;.type 32;.endef
32;.endef _pop: pushl %ebp
_push: movl %esp, %ebp
pushl %ebp subl $24, %esp
movl %esp, %ebp cmpl $0, _top
subl $24, %esp jg L6
cmpl $99, _top movl $LC2, 8(%esp)
jle L3 movl $10, 4(%esp)
movl $LC0, 8(%esp) movl $LC1, (%esp)
movl $5, 4(%esp) call ___assert
movl $LC1, (%esp) L6: decl _top
call ___assert movl _top, %eax
L3: movl _top, %eax movl _stack(,%eax,4),
movl %eax, %edx %eax
movl 8(%ebp), %eax leave
movl %eax, ret
_stack(,%edx,4) .def ___assert;.scl 3;.type
incl _top 32;.endef
leave
檔案: StackMain.s
.file "StackMain.c" movl %eax, -8(%ebp)
.def ___main;.scl 2;.type 32;.endef movl -8(%ebp), %eax
.section .rdata,"dr" call __alloca
LC0: call ___main
.ascii "x=%d\12\0" movl $3, (%esp)
.text call _push
.globl _main call _pop
.def _main; .scl 2; movl %eax, -4(%ebp)
.type 32; .endef movl -4(%ebp), %eax
_main: movl %eax, 4(%esp)
pushl %ebp movl $LC0, (%esp)
movl %esp, %ebp call _printf
subl $24, %esp movl $0, %eax
andl $-16, %esp leave
movl $0, %eax ret
addl $15, %eax .def _printf;.scl 3;.type 32;.endef
addl $15, %eax .def _pop;.scl 3;.type 32;.endef
shrl $4, %eax .def _push;.scl 3;.type 32;.endef
sall $4, %eax
範例 5.12 使用 nm 指令觀看目的檔
的連結地圖
Cygwin 當中的操作與結果 說明
$ nm StackType.o 顯示 StackType.o 的連結地圖
00000000 b .bss (map)
00000000 d .data bss 段 (b: 未初始化變數 )
00000000 t .text data 段 (d: 已初始化資料 )
00000190 C _stack text 段 (t: 程式段 )
00000000 B _top int stack[] 的定義
$ nm StackFunc.o (C:Common)
00000000 b .bss int top=0 的定義 (B:bss)
00000000 d .data 顯示 StackType.o 的連結地圖
00000000 r .rdata (map)
00000000 t .text bss 段 (b: 未初始化變數 )
U ___assert data 段 (d: 已初始化資料 )
00000044 T _pop rdata 段 (r: 唯讀資料段 )
00000000 T _push text 段 (t: 程式段 )
U _stack 未定義 (U:___assert)
U _top int stack[] 的定義
(C:Common)
int top=0 的定義 (B:bss)
範例 5.13 用 strings 指令檢視目的
檔中的字串表
Cygwin 的操作
$ strings StackType.o
.text
0`.data
.bss
.file
StackType.c
.text
.data
.bss
_top
_stack
範例 5.14 用 size 指令檢視目的檔
中分段大小
Cygwin 的操作
$ size stack.exe
text data bss dec hex filename
1140 416 468 2024 7e8 stack.exe
製作靜態函式庫
GNU 工具的 ar 指令
建立函式庫
新增函式
取出函式

範例:
建立函式庫
 ar -r lib/libstack.a StackFunc.o StackType.o
連結函式庫
 gcc -o stack.o StackMain.c -lstack -L lib -I inc
表格 C.6 函式庫指令 ar 的參數表

參數 範例 說明
-r ar -r libx.a a.o 將 a.o 與 b.o 包裝為函式庫
-tv b.o-tv libx.a
ar libx.alibx.a 函式庫的內容
查看
-x ar -x libx.a a.o 取出 libx.a 中的目的檔 a.o
範例 5.15 使用 ar 指令建立函式

Cygwin 當中的操作與結果 說明
$ ar -r lib/libstack.a StackFunc.o 建立 lib/libstack.a 函式
StackType.o 庫
ar: creating lib/libstack.a
$ gcc -o stack.o StackMain.c -lstack 編譯 StackMain.c 並連結
-L lib -I inc libstack.a 函式庫,輸
出執
行檔 stack.o
$ ./stack.o 執行 stack.o
x=3 結果輸出 3 。
$ ar -tv lib/libstack.a 顯示函式庫 libstack.a
rw-r--r-- … 略… 30 12:59 2009 包含 StackFunc.o
StackFunc.o 包含 StackType.o
rw-r--r-- … 略… 30 12:59 2009 從 lib/libstack.a 中取出
StackType.o StackFunc.o
$ ar -x lib/libstack.a StackFunc.o
製作動 態函式庫
建立動態函式庫
gcc -shared a.o b.o c.o -o libabc.so

連結動態函式庫
 gcc -labc test.o -o test
GNU 目的檔工具
GNU 的目的檔工具可分為
觀察工具 :
 objdump 、 nm 、 strings
修改工具 :
 objcopy 、 strip

主要用法
用 objdump 觀察目的檔
用 objcopy 轉換目的檔
範例 5.16 使用 objdump 反組譯目的

指令: objdump -d StackFunc.o
StackFunc.o: file format pe-i386
Disassembly of section .text:
00000000 <_push>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 18 sub $0x18,%esp
6: 83 3d 00 00 00 00 63 cmpl $0x63,0x0
d: 7e 1c jle 2b <_push+0x2b>
f: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
16: 00
17: c7 44 24 04 05 00 00 movl $0x5,0x4(%esp)
1e: 00
1f: c7 04 24 11 00 00 00 movl $0x11,(%esp)
26: e8 00 00 00 00 call 2b <_push+0x2b>
2b: a1 00 00 00 00 mov 0x0,%eax

範例 5.17 使用 objdump 觀察目的
檔 ( 頁 1)
指令: objdump -x StackFunc.o
StackFunc.o: file format pe-i386
StackFunc.o
architecture: i386, flags 0x00000039:
HAS_RELOC, HAS_DEBUG, HAS_SYMS, HAS_LOCALS
start address 0x00000000

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000084 00000000 00000000 000000b4 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000000 2**2
ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000000 2**2
ALLOC
3 .rdata 00000028 00000000 00000000 00000138 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
SYMBOL TABLE:
[ 0](sec -2)(fl 0x00)(ty 0)(scl 103) (nx 1) 0x00000000 StackFunc.c
File
[ 2](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x00000000 _push
AUX tagndx 0 ttlsiz 0x0 lnnos 0 next 0
範例 5.17 使用 objdump 觀察目的
檔 ( 頁 2)

RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000008 dir32 _top
00000013 dir32 .rdata
00000022 dir32 .rdata
00000027 DISP32 ___assert
0000002c dir32 _top
00000038 dir32 _stack
0000003e dir32 _top
0000004c dir32 _top
00000057 dir32 .rdata
00000066 dir32 .rdata
0000006b DISP32 ___assert
00000071 dir32 _top
00000076 dir32 _top
0000007d dir32 _stack
目的檔修改工具 - objcopy
GNU 的主要目的檔複製工具 – Objcopy
該工具不只可以進行複製
還可以對目的檔進行修改與格式轉換等處理

語法: objcopy [ 參數 ] infile [outfile]


參數 說明
-I 指定輸入檔案格式 (--input-target)
-O 指定輸出檔案格式 (--output-target)
-B 指定機器架構 (--binary architecture)
-S 去除全部符號資訊 (--strip-all)
-g 去除全部除錯資訊 (--strip-debug)
-j <section name> 只抽取指定區段 (--only-section)
-R <section name> 去除特定區段 (--remove-section)
目的檔修改工具 - objcopy
 使用範例
objcopy -O binary -S -R .comment -R .note code.o
code.bin
 把目的檔 code.o 中的目的碼從 ELF 中抽取出來,並且去除註
解欄位後存入 code.bin 中。

objcopy -O binary -j .text code.o code.bin


 把 code.o 中的取程式段 (.text) 抽取出來,存入 code.bin 中

objcopy -I binary -O elf32-i386 -B i386 image.jpg


image.o

 把一個 JPEG 圖片檔 image.jpg ,轉換成 image.o 的目的檔


範例 5.18 使用 objcopy 把影像檔
轉換為目的檔
objcopy -I binary -O elf32-i386 -B i386 image.jpg image.o

Cygwin 使用過程
$ objcopy --readonly-text -I binary -O elf32-i386 -B i386 ccc.jpg ccc.o
$ ls -all
total 8
drwxrwxrwx+ 2 ccc None 0 May 15 13:11 .
drwxrwxrwx+ 8 ccc None 0 May 15 13:07 ..
-rwxrwxrwx 1 ccc None 3183 Jul 3 2008 ccc.jpg
-rw-r--r-- 1 ccc None 3612 May 15 13:11 ccc.o
$ objdump -x ccc.o | grep ccc
ccc.o: file format elf32-i386
ccc.o
00000000 g .data 00000000 _binary_ccc_jpg_start
00000c6f g .data 00000000 _binary_ccc_jpg_end
00000c6f g *ABS* 00000000 _binary_ccc_jpg_size
範例 5.19 使用外部變數引用目的檔
中的影像區塊
extern char *_binary_ccc_jpg_start;
extern char *_binary_ccc_jpg_end;
extern char *_binary_ccc_jpg_size;

 範例 5.19 的作法,在嵌入式系統中相當常見,因為嵌
入式系統當中很可能沒有檔案系統,因此無法使用像
fopen() 這種檔案函數。此時,如果要顯示影像,就可
以直接將影像放入記憶體,然後取得其記憶體區塊,直
接在程式中使用。
範例 5.18 使用 objcopy 把影像檔
轉換為目的檔
Cygwin 使用過程
$ objcopy --readonly-text -I binary -O elf32-i386 -B i386 ccc.jpg ccc.o
$ ls -all
total 8
drwxrwxrwx+ 2 ccc None 0 May 15 13:11 .
drwxrwxrwx+ 8 ccc None 0 May 15 13:07 ..
-rwxrwxrwx 1 ccc None 3183 Jul 3 2008 ccc.jpg
-rw-r--r-- 1 ccc None 3612 May 15 13:11 ccc.o
$ objdump -x ccc.o | grep ccc
ccc.o: file format elf32-i386
ccc.o
00000000 g .data 00000000 _binary_ccc_jpg_start
00000c6f g .data 00000000 _binary_ccc_jpg_end
00000c6f g *ABS* 00000000 _binary_ccc_jpg_size
專案建置工具 - make
當程式越來越多時,編譯、連結與測試的動作會變
得相當繁瑣,此時就必須使用專案建置工具

GNU 的 make 是專案編譯上相當著名的典型工



著名的 Linux 作業系統就是以 make 工具所建構的
範例 5.21 專案編譯的 Makefile 檔

CC = gcc
AR = ar
OBJS = StackType.o StackFunc.o
BIN = stack
RM = rm -f
INCS = -I .
LIBS = -L lib
CFLAGS = $(INCS) $(LIBS)
all: $(BIN)
clean:
${RM} *.o *.exe lib/*.a
$(BIN): $(AR)
$(CC) StackMain.c -lstack -o $(BIN) $(CFLAGS)
$(AR) : $(OBJS)
$(AR) -r lib/libstack.a $(OBJS)
StackFunc.o : StackFunc.c
$(CC) -c StackFunc.c -o StackFunc.o $(CFLAGS)
StackType.o : StackType.c
$(CC) -c StackType.c -o StackType.o $(CFLAGS)
範例 5.22 在 Cygwin 中使用
make 工具的過程
Cygwin 的命令執行過程 說明
$ make clean 清除上一次產生的檔案
rm -f *.o *.exe lib/*.a 使用 rm 清除 *.o,*.exe,*.a 檔

$ make 進行專案編譯
gcc -c StackType.c -o StackType.o -I inc -L 編譯 StackType 中…
lib 編譯 StackFunc 中…
gcc -c StackFunc.c -o StackFunc.o -I inc -L 建立函式庫 lib/libstack.a 中…
lib 函式庫建立完成
ar -r lib/libstack.a StackType.o StackFunc.o 編譯主程式,並連結函式庫
ar: creating lib/libstack.a 輸出執行檔 stack
gcc StackMain.c -lstack -o stack -I inc -L lib
啟動執行檔 stack
輸出結果 x=3
$ ./stack
x=3
5.7.2 目的檔格式 – a.out
真正的目的檔,為了效能的緣故,通常不會儲存為
文字格式,而是儲存為二進位格式。像是 DOS
當中的 .com 檔案, Windows 當中的 .exe 檔案
,與 Linux 早期的 a.out 格式,還有近期的 ELF
格式,都是常見的目的檔格式。

為了讓讀者更清楚目的檔的格式,在本節中,我們
將以 Linux 早期使用的 a.out 這個格式,作為範
例,以便詳細說明目的檔的格式。
圖 5.22 兩種目的檔的格式 – a.out 與
ELF
檔頭 檔頭
a.out header ELF header
程式表頭
程式段
Program Header
Text Section
Table
資料段 第 1 段
Data Section Section 1
程式重定位資訊 第 2 段
Text Relocation Section 2
資料重定位資訊

Data Relocation
符號表 第 n 段
Symbol Table Section n
字串表 分段表頭
String Table Section Header Table

(a) a.out 檔案的格式 (b) ELF 檔案的格式


圖 5.23 目的檔 a.out 的資料結構 ( 第
1頁)

檔頭結構
struct exec { // a.out 的檔頭結構
unsigned long a_magic; // 執行檔魔數
// OMAGIC:0407, NMAGIC:0410,ZMAGIC:0413
unsigned a_text; // 程式段長度
unsigned a_data; // 資料段長度
unsigned a_bss; // 檔案中的未初始化資料區長度
unsigned a_syms; // 檔案中的符號表長度
unsigned a_entry; // 執行起始位址
unsigned a_trsize; // 程式重定位資訊長度
unsigned a_drsize; // 資料重定位資訊長度
};
圖 5.23 目的檔 a.out 的資料結構 ( 第
2頁)

重定位結構
struct relocation_info { // a.out 的重定位結構
int r_address; // 段內需要重定位的位址
unsigned int r_symbolnum:24; // r_extern=1 時 : 符號的序號值 ,
// r_extern=0 時 : 段內需要重定位的位址
unsigned int r_pcrel:1; // PC 相關旗標
unsigned int r_length:2; // 被重定位的欄位長度 (2 的次方 )
/ 2^0=1,2^1=2,2^2=4,2^3=8 bytes) 。
unsigned int r_extern:1; // 外部引用旗標。 1- 以外部符號重定位
// 0- 以段的地址重定位。
unsigned int r_pad:4; // 最後補滿的 4 個位元 ( 沒有使用到的部分 ) 。
};
圖 5.23 目的檔 a.out 的資料結構 ( 第
3頁)

符號表結構
struct nlist { // a.out 的符號表結構
union { //
char *n_name; // 字串指標,
struct nlist *n_next; // 或者是指向另一個符號項結構的指標,
long n_strx; // 或者是符號名稱在字串表中的位元組偏移值。
} n_un; //
unsigned char n_type; // 符號類型 N_ABS/N_TEXT/N_DATA/N_BSS 等。
char n_other; // 通常不用
short n_desc; // 保留給除錯程式用
unsigned long n_value; // 含有符號的值,對於代碼、資料和 BSS 符號,
// 通常是一個位址。
};
圖 5.24 目的檔a.out 各區段所對應
的資料結構
檔頭
struct exec {…}
header
程式段
0101…..
Text Section
資料段
資料結構 0101…..
Data Section
程式重定位資訊 struct relocation_info {…} ( 很多
Text Relocation 個)
資料重定位資訊 struct relocation_info {…} ( 很多
Data Relocation 個)
符號表
struct nlist {…} ( 很多個 )
Symbol Table
字串表 \0.bss\0.comment\0.data\0.text\
String Table 0stack\0ListA\0ListB\0 ….

(a) a.out 檔案的格式 (b) a.out 各區塊對應的資料結構


圖 5.25 目的檔 a.out 的載入過

指定長度
檔頭 程式
header text
程式段 資料
Text Section data
資料段 未初始化資料
Data Section 搬動 bss
程式重定位資訊 堆積
Text Relocation heap
資料重定位資訊
Data Relocation
符號表
Symbol Table
字串表 堆疊
String Table stack
5.7.3 目的檔格式 – ELF
 ELF 格式 (Executable and Linking Format)

是 UNIX/Linux 系統中較先進的目的檔格式。

由 AT&T 公司於第五代 UNIX (UNIX System V) 所

發展出來的。
主要文件在『 System V Application Binary

Interface 』的第四章的 Object Files 中


ELF 規格書
 該文件詳細的介紹了 UNIX System V 中二進位檔案

格式的存放方式。
 並且在第五章的 Program Loading and Dynamic

Linking 當中,說明了動態連結與載入的設計方法。
 讀者可於下列網址下載到 System V 的二進位應用介

面規格書『 System V Application Binary Interface 』 ,


http://www.caldera.com/developers/devspecs/gabi41.pdf


 各家 CPU 廠商會自行補充與處理器有關的 ELF 規格書
ELF 的功用
ELF 可用來記錄
目的檔 (object file)
執行檔 (executable file)
動態連結檔 (share object)
核心傾印 (core dump) 檔

ELF 支援
動態連結與載入功能
ELF 的兩種不同時期之觀點
 連結時期觀點 (Linking View)

是以分段 (Section) 為主的結構

 執行時期觀點 (Execution View) 。

以分區 (Segment) 為主的結構

一個區通常是數個分段的組合體
 像是與程式有關的段落,包含程式段、程式重定位等,在執

行時期會被組合為一個分區。
圖 5.26 目的檔 ELF 的兩種不同觀點
檔頭 檔頭
ELF header ELF header
p_offset
程式表頭 ( 非必要 ) 程式表頭
Program Header Table Program Header Table
第 1 段 (Section 1)
第 2 段 (Section 2) 第 1 區 (Segment 1)

第 k 段 (Section k)
… 第 2 區 (Segment 2)

… …

sh_offset 分段表頭 分段表頭 ( 非必要 )


Section Header Table Section Header Table

(a) 連結時期觀點 (Linking View) (b) 執行時期觀點 (Execution View)


圖 5.27 目的檔 ELF 的檔頭結構
(Elf32_Ehdr)
ELF 的檔頭結構 說明
typedef struct {
unsigned char ELF 辨識代號區
e_ident[EI_NIDENT]; 檔案類型代號
Elf32_Half e_type; 機器平台代號
Elf32_Half e_machine; 版本資訊
Elf32_Word e_version; 程式的起始位址
Elf32_Addr e_entry; 程式表頭的位址
Elf32_Off e_phoff; 分段表頭的位址
Elf32_Off e_shoff; 與處理器有關的旗標值
Elf32_Word e_flags; ELF 檔頭的長度
Elf32_Half e_ehsize; 程式表頭的記錄長度
Elf32_Half e_phentsize; 程式表頭的記錄個數
Elf32_Half e_phnum; 分段表頭的記錄長度
Elf32_Half e_shentsize; 分段表頭的記錄個數
Elf32_Half e_shnum; 分段字串表 .shstrtab 的分段代號
Elf32_Half e_shstrndx;
} Elf32_Ehdr;

分段名稱 說明
.text 程式段
.data, .data1 資料段
.bss 未設初值的全域變數
.rodata, 唯讀資料段
.rodata1
.dynamic 動態連結資訊
.dynstr 動態連結用字串表
.dynsym 動態連結用符號表
.got 動態連結用的全域位移表 (Global Offset Table)
.plt 動態連結用的程序連結表 (Porcedure Linkage Table)
.interp 記錄程式解譯器的路徑 (program interpreter file)
.ctors 物件導向中的建構函數 (constructor) (C++ 可用 )
.dtors 物件導向中的解構函數 (destructor) (C++ 可用 )
.hash 雜湊表
.init 在主程式執行前會執行此段落
.fini 在主程式執行後會執行此段落
.rel<name>, 重定位資訊,例如: rel.text 是程式段的重定位資訊, rel.data
.rela<name> 則是資料段的重定位資訊。
.shstrtab 儲存分段 (Section) 名稱
.strtab 字串表
.symtab 符號表
.debug 除錯資訊 ( 保留給未來用 )
.line 除錯時的行號資訊
.comment 版本控制訊息
.note 附註資訊
圖 5.28 目的檔 ELF 的資料結構
檔頭
typedef struct {…} Elf32_Ehdr
ELF header
程式表頭
typedef struct {…} Elf32_Phdr
Program Header Table
第 1 段 可能為程式段 (.text) 、資料段 (.data) 、
Section 1 資料結構 bss 段 (.bss) 、字串表 (.strtab,
第 2 段 .shstrtab) 、符號表 (.symtab) 、重定位表
Section 2 ( 、動態連結表 、或是其他類型的段落…

… 符號表 : typedef struct {…} Elf32_Sym


重定位表: typedef struct {…} Elf32_Rel,
第 n 段 typedef struct {…} Elf32_Rela
Section n 動態連結: typedef struct {…} Elf32_Dyn
分段表頭
typedef struct {…} Elf32_Shdr
Section Header Table

(a) ELF 的檔案結構 (b) ELF 各區塊對應的資料結構


圖 5.29 目的檔 ELF 的資料結構
ELF 的分段表頭記錄 說明
typedef struct {
Elf32_Word sh_name; 分段名稱代號
Elf32_Word sh_type; 分段類型
Elf32_Word sh_flags; 分段旗標
Elf32_Addr sh_addr; 分段位址 ( 在記憶體中的位址 )
Elf32_Off sh_offset; 分段位移 ( 在目的檔中的位址 )
Elf32_Word sh_size; 分段大小
Elf32_Word sh_link; 連結指標 ( 依據分段類型而定 )
Elf32_Word sh_info; 分段資訊
Elf32_Word sh_addralign; 對齊資訊
Elf32_Word sh_entsize; 分段中的結構大小 ( 分段包含子結構時使用 )
} Elf32_Shdr;
圖 5.30 目的檔 ELF 的分段表頭
檔頭 ELF header
(ELF32_Ehdr)
程式表頭 Program Header Table
(ELF32_Phdr[0..m-1])
Elf32_Shdr[i].sh_offset
第 1 段
Section 1 第 1 段的表頭
第 2 段 Elf32_Shdr[0]
Section 2 第 2 段的表頭
Elf32_Shdr[1]


第 n 段
Section n 第 n 段的表頭
分段表頭 Section Header Table Elf32_Shdr[n-1]
(ELF32_Shdr[0..m-1])
圖 5.31 目的檔 ELF 的資料結構

ELF 的程式表頭記錄 說明
typedef struct {
Elf32_Word p_type; 分區類型
Elf32_Off p_offset; 分區位址 ( 在目的檔中 )
Elf32_Addr p_vaddr; 分區的虛擬記憶體位址
Elf32_Addr p_paddr; 分區的實體記憶體位址
Elf32_Word p_filesz; 分區在檔案中的大小
Elf32_Word p_memsz; 分區在記憶體中的大小
Elf32_Word p_flags; 分區旗標
Elf32_Word p_align; 分區的對齊資訊
} Elf32_Phdr;
圖 5.32 目的檔 ELF 的程式表頭
檔頭
ELF header
程式表頭 Program Header Table 區塊 1 的表頭
(ELF32_Phdr[0..m-1]) Elf32_Phdr[0]
區塊 2 的表頭
區塊 1 (Segment 1)
Elf32_Phdr[1]

區塊 2 (Segment 2) …

區塊 n 的表頭

Elf32_Phdr[m-1]

區塊 m (Segment m) Elf32_Phdr[i].p_offset

分段表頭 Section Header Table


(ELF32_Shdr[0..n-1])
圖 5.33 目的檔 ELF 的符號記錄
ELF 的符號結構 說明
typedef struct{
Elf32_Word st_name; 符號名稱的代號
Elf32_Addr st_value; 符號的值,通常是位址
Elf32_Word st_size; 符號的大小,以 byte 為單位
unsigned char st_info; 細分為 bind 與 type 兩欄位
unsigned char st_other; 目前為 0 ,保留未來使用
Elf32_Half st_shndx; 符號所在的分段 (Section) 代號
} Elf32_Sym; 取出 st_info 中的 bind 欄位
#define ELF32_ST_BIND(i) ((i) >> 4) 取出 st_info 中的 type 欄位
#define ELF32_ST_TYPE(i) ((i)&0xf) 將 bind 與 type 組成 info
#define ELF32_ST_INFO(b,t)
(((b)<<4)+((t)&0xf))
圖 5.34 目的檔 ELF 的重定位記

ELF 的重定位結構 說明
typedef struct{
Elf32_Addr r_offset; 符號的位址
Elf32_Word r_info; r_info 可分為 sym 與 type 兩欄
} Elf32_Rel;
typedef struct{ 符號的位址
Elf32_Addr r_offset; r_info 可分為 sym 與 type 兩欄
Elf32_Word r_info; 外加的數值
Elf32_Sword r_addend;
} Elf32_Rela;
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char) (i))
#define ELF32_R_INFO(s,t) (((s)<<8) +
(unsigned char) (t))
圖 5.35 目的檔ELF 中的重定位表、
符號表與字串表的關連性
檔頭 r_offset, r_info, r_addend
(sym, type)
程式表頭 Elf32_Rela[0]
Elf32_Rela [1]
RelocationTable

(rela.text)
Elf32_Rela[k]

Symbol Table (.symtab)
name value size info
… shndx
Elf32_Sym[0] *LISTA
String Table (.strtab) Elf32_Sym[1] *LISTB

… Elf32_Sym[k] *Var1

分段表頭
Section Header Table \0.bss\0.comment\0.data\0.text\0.got
\0stack\0LISTA\0LISTB\0Var1\0….
表格 5.3 目的檔ELF 的常見區塊列表

Segment Sections
說明
區塊型態 分段

Program Header 表頭段,用來計算基底


PT_PHDR
位址 (base address)

PT_INTERP .interp 動態載入區段。

.interp .note .hash .dynsym


PT_LOAD .dynstr .rel.dyn .rel.plt .init 載入器將此區塊載入程式段。
.plt .text .fini .rodata …

.data .dynamic .ctors .dtors


PT_LOAD 載入器將此區塊載入資料段。
.jcr .got .bss

PT_DYNAMIC .dynamic 由動態載入器處理


ELF 與動態連結功能
 為了支援動態連結與載入的技術, ELF 當中多了許多相關的分段,包含

解譯段 (.interp) 、動態連結段 (.dynamic) 、全域位移表 (Global Offset


Table : .got) 、程序連結表 (Porcedure Linkage Table : .plt) 等,另外還
有動態連結專用的字串表 (.dynstr) 、符號表 (.dynsym) 、映射表
(.hash) 、全域修改記錄 (rel.got) 等作為輔助。

 參考文獻

 有關 Linux 的動態連結技術可以參考 Ulrich Drepper , “How To Write Shared

Libraries, ”, Red Hat, Inc., August 20, 2006, 其網址為


http://people.redhat.com/drepper/dsohowto.pdf。

 Jollen’s Blog, “Program Loading 觀念介紹 ,last update 2007/03/13, “

http://www.jollen.org/EmbeddedLinux/Program_Loading.html
ELF 動態連結的作法
 執行 ELF 載入動作時,使用的是以區塊為主的執行時

期觀點,常見的區塊包含程式表頭 (PHDR) 、解譯區


塊 (INTERP) 、載入區塊 (LOAD) 、動態區塊
(DYNAMIC) 、註解區塊 (NOTE) 、共用函式庫區塊
(SHLIB) 等。其中,載入區塊通常有兩個以上,如此
才能容納程式區塊 (TEXT) 與資料區塊 (DATA) 等不
同屬性的區域。

 Linux 的載入動作是由核心 (Kernel) 所負責的,載入

器是作業系統的一部分。
Linux 中的動態載入功能
1. Kernel 將 ELF 檔案中的所有 PT_LOAD 型態的區塊載入到記憶體,
這些區塊包含程式區塊與資料區塊。

2. Kernel 將載入的區塊映射到該行程的虛擬位址空間中 ( 例如使用


linux 的 mmap 系統呼叫 ) 。

3. Kernel 找到 PT_INTERP 型態的區塊,並根據區塊內的資訊找到動


態連結器 (Dynamic Linker) 的 ELF 檔。

4. Kernel 將動態連結器載入到記憶體,並將其映射到該行程的虛擬位址
空間中,然後啟動『動態連結器』。

5. 目的程式開始執行,在呼叫動態函數時,『動態連結器』根據需要,
決定出正確的連結順序,然後對該程式與動態函數進行重定位的動作
,再將控制權轉移到動態函數中。
Linux 與動態連結
 動態連結器的英文名稱為 Dynamic Linker ,但在
ELF 文件中被稱為 Program Interpreter 。

 動態連結器 (Dynamic Linker ) 的 ELF 檔,在


Linux 中通常被儲存的檔名為 ld.so 。

 ELF 檔案的載入過程,會因 CPU 的結構不同


而有差異,因此,在 ELF 文件中這些與 CPU
有關的主題都被分離出來,由各家 CPU 廠商自
行撰寫。舉例而言,動態函數的呼叫就是一個與
CPU 有關的主題,不同的 CPU 實作方法會有
所不同。
範例 5.22 IA32 處理器中的靜態函
數呼叫與動態函數呼叫方式

C 語言程式 靜態函數呼叫 動態函數呼叫


extern int var; pushl var movl var@GOT(%ebx)
extern int func(int); call func pushl (%eax)
int call_func(void) { call func@PLT
return func(var);
}
範例 5.23 IA32 處理器中的動態連
結函數區 (Stub) 的程式

.PLT0: pushl 4(%ebx)


Jmp *8(%ebx)
nop
nop
.PLT1: jmp
*name1@GOT(%ebx)
pushl $offset1
jmp .PLT0@PC
.PLT2 jmp *name2@GOT(%ebx)
pushl $offset2
jmp .PLT0@PC
圖 5.36 目的檔 ELF 的動態連結
記錄

ELF 的重定位結構 說明
typedef struct {
Elf32_Sword d_tag; 標記
union {
Elf32_Word d_val; 值 ( 用途很多樣 )
Elf32_Addr d_ptr; 指標 ( 程式的虛擬位址 )
} d_un;
} Elf32_Dyn;
extern Elf32_Dyn _DYNAMIC[]; 動態連結陣列
第 5 章、連結與載入 ( 本章摘要
)
5.1 簡介
目的檔、連結器、絕對載入器

5.2 函式庫
外部引用

5.3 目的檔
記錄: T,D,B,M,S ( 程式、資料、 BSS 、修改、符
號)
第 5 章、連結與載入 ( 本章摘要
)
5.4 連結器
將區段合併後,消除外部引用。

5.5 載入器
將執行檔載入記憶體,並利用修改記錄 M 進行修改

5.6 動態連結
在執行期間才決定外部參考的記憶體位址,則被稱為
動態連結器。
使用動態跳轉機制與 Stub 程式區。
第 5 章、連結與載入 ( 本章摘要
)
5.7 實務案例
5.7.1 GNU 連結工具
 gcc, ld ( 連結器 ) 、 objdump ( 目的檔列印 ) 、 objcopy( 目的
檔複製 ) 、 nm ( 符號表列印 ) 、 ar ( 函式庫建立 ) 、 make
( 專案建置 )

5.7.2 目的檔格式 – a.out


 簡單且直接的目的檔格式
 包含 .text, .data, .bss 等三種段落

5.7.3 目的檔格式 – ELF


 目前的 UNIX/Linux 主流格式
 支援動態連結載入功能

You might also like