靜態(tài)鏈接

靜態(tài)鏈接

當(dāng)有兩個(gè)目標(biāo)文件時(shí),如何將它們連接起來形成一個(gè)可執(zhí)行文件敢会?其中發(fā)生了什么曾沈?

使用兩個(gè)源代碼文件作為研究例子:

a.c


extern int shared;

int main()
{
    int a = 100;

    swap(&a,&shared);

}

b.c


int shared = 1;

void swap(int *a,int *b)
{
    *a^=*b^=*a^=*b;
}

使用gcc將兩個(gè)文件編譯成目標(biāo)文件a.o和b.o


gcc -c a.c b.c

圖示:

圖1.png

b.c里面定義了兩個(gè)全局符號,

  • shared
  • 函數(shù)swap

a.c里面定義了一個(gè)全局符號

  • main

a.c引用到了b.c里面的swao與shared鸥昏,接下來就是把這兩個(gè)目標(biāo)文件鏈接在一起最終形成一個(gè)可執(zhí)行文件

空間與地址分配

可執(zhí)行文件中代碼段和數(shù)據(jù)段都是輸入的目標(biāo)文件合并而來的晦譬,那么鏈接過程產(chǎn)生的第一個(gè)問題:對于多個(gè)輸入目標(biāo)文件,鏈接器如何將它們的各個(gè)段合并到輸出文件互广?

按序疊加

一個(gè)方案就是按照輸入次序疊加起來:

圖2.png

這種做法很簡單敛腌,但是有一個(gè)問題:

在很多輸入文件的時(shí)候,輸出文件將會(huì)有很多零散的段惫皱,這種做法很浪費(fèi)空間像樊,因?yàn)槊總€(gè)段都需要有一定的地址和空間對齊要求,而且還會(huì)造成碎片問題旅敷,所以這不是一個(gè)好的方案

相似段合并

一個(gè)更實(shí)際的方法是將相同性質(zhì)的段合并到一起生棍,比如將所有輸入文件的.text合并到輸出文件的.text,接著是.data,.bss

圖示:

圖3.png

.bss段在目標(biāo)文件和可執(zhí)行文件并不占用文件空間媳谁,但是在裝載時(shí)占用地址空間涂滴,所以鏈接器在合并各個(gè)段的時(shí)候,需要將.bss也合并晴音,并且分配虛擬空間

鏈接器為目標(biāo)文件分配地址和空間中柔纵,地址和空間有兩個(gè)含義:

  • 一個(gè)是在輸出的可執(zhí)行文件中的空間

  • 一個(gè)是在裝載后的虛擬地址中的虛擬地址空間

對于有實(shí)際數(shù)據(jù)的段,如.text和.data,它們在文件中和虛擬地址中都有分配空間锤躁,因?yàn)樗鼈冊谶@兩者中都存在搁料,而對于.bss這樣的段來說,分配空間的意義只局限于虛擬地址空間,因?yàn)樗谖募胁]有內(nèi)容

現(xiàn)在鏈接器空間分配策略采用第二種方法郭计,使用這種方法的鏈接器一般采用兩步鏈接的方法:

  • 空間和地址分配

掃描所有的輸入目標(biāo)文件霸琴,并且獲得它們的各個(gè)段的長度,屬性昭伸,位置梧乘,并且將輸入目標(biāo)文件中的符號表中所有的符號定義和符號引用收集起來,統(tǒng)一放在一個(gè)全局符號表庐杨。這一步宋下,鏈接器能夠獲得所有輸入目標(biāo)文件的段長度,并且將它們合并辑莫,計(jì)算出輸出文件中各個(gè)段合并后的長度和位置学歧,并建立映射關(guān)系。

  • 符合解析與重定位

使用上面的第一步中收集到的所有信息各吨,讀取輸入文件中段的數(shù)據(jù)枝笨,重定位信息,并且進(jìn)行符合解析與重定位揭蜒,調(diào)整代碼中的地址等横浑。

用鏈接器將a.o和b.o鏈接起來:


ld a.o b.o -e main -o ab

接下來使用objdump來查看鏈接前后地址的分配情況:

objdump -h a.o

輸出:



a.o:     文件格式 elf64-x86-64

節(jié):
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000051  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000091  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000091  2**0
                  ALLOC
  3 .comment      0000002a  0000000000000000  0000000000000000  00000091  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000bb  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  0000000000000000  0000000000000000  000000c0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA


objdump -h b.o

輸出



b.o:     文件格式 elf64-x86-64

節(jié):
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000004b  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000004  0000000000000000  0000000000000000  0000008c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000090  2**0
                  ALLOC
  3 .comment      0000002a  0000000000000000  0000000000000000  00000090  2**0
                  CONTENTS, READONLY
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000ba  2**0
                  CONTENTS, READONLY
  5 .eh_frame     00000038  0000000000000000  0000000000000000  000000c0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA



objdump -h ab

輸出:


ab:     文件格式 elf64-x86-64

節(jié):
Idx Name          Size      VMA               LMA               File off  Algn
 13 .text         00000222  0000000000000560  0000000000000560  00000560  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .fini         00000009  0000000000000784  0000000000000784  00000784  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .rodata       00000004  0000000000000790  0000000000000790  00000790  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame_hdr 00000044  0000000000000794  0000000000000794  00000794  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .eh_frame     00000128  00000000000007d8  00000000000007d8  000007d8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .init_array   00000008  0000000000200db8  0000000000200db8  00000db8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 19 .fini_array   00000008  0000000000200dc0  0000000000200dc0  00000dc0  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000001f0  0000000000200dc8  0000000000200dc8  00000dc8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000048  0000000000200fb8  0000000000200fb8  00000fb8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .data         00000014  0000000000201000  0000000000201000  00001000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .bss          00000004  0000000000201014  0000000000201014  00001014  2**0
                  ALLOC
 24 .comment      00000029  0000000000000000  0000000000000000  00001014  2**0
                  CONTENTS, READONLY


//省略了剩余內(nèi)容

上述輸出中:VMA:虛擬地址,LMA:加載地址

鏈接前后的程序中所使用的的地址已經(jīng)是程序在進(jìn)程中的虛擬地址屉更,即各個(gè)段里面的VMA和Size徙融,可以看到,在鏈接之前瑰谜,目標(biāo)文件中所有段的VMA都是0欺冀,因?yàn)樘摂M地址空間還沒分配,所以默認(rèn)為0萨脑,鏈接之后隐轩,可執(zhí)行文件ab中各個(gè)段都被分配到了相應(yīng)的虛擬地址,

符號地址的確定

在第一步的掃描和空間分配階段渤早,鏈接器按照上面的空間分配方法進(jìn)行分配职车,這時(shí)候輸入文件中各個(gè)段在鏈接后的虛擬地址就已經(jīng)確定了。

當(dāng)前面這步完成后鹊杖,鏈接器開始計(jì)算各個(gè)符號的虛擬地址悴灵,因?yàn)楦鱾€(gè)符號在段內(nèi)的相對位置是固定的,只不過鏈接器要給每個(gè)符號加上一個(gè)偏移量骂蓖,使得它們能夠調(diào)整到正確的虛擬地址积瞒。

符號解析與重定位

重定位

完成空間與地址分配后,鏈接器就進(jìn)入了符號解析與重定位的步驟

在分析符號解析與重定位之前涯竟,先看看a.o里面是怎樣使用這兩個(gè)外部符號的赡鲜,也就是源代碼里面的shared變量以及swap函數(shù)

編譯器將a.c編譯成指令后空厌,如何訪問shared和調(diào)用swap庐船?

使用objdump的-d參數(shù)可以查看反匯編結(jié)果


objdump -d a.o




a.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
   f:   00 00 
  11:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  15:   31 c0                   xor    %eax,%eax
  17:   c7 45 f4 64 00 00 00    movl   $0x64,-0xc(%rbp)
  1e:   48 8d 45 f4             lea    -0xc(%rbp),%rax
  22:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 29 <main+0x29>
  29:   48 89 c7                mov    %rax,%rdi
  2c:   b8 00 00 00 00          mov    $0x0,%eax
  31:   e8 00 00 00 00          callq  36 <main+0x36>
  36:   b8 00 00 00 00          mov    $0x0,%eax
  3b:   48 8b 55 f8             mov    -0x8(%rbp),%rdx
  3f:   64 48 33 14 25 28 00    xor    %fs:0x28,%rdx
  46:   00 00 
  48:   74 05                   je     4f <main+0x4f>
  4a:   e8 00 00 00 00          callq  4f <main+0x4f>
  4f:   c9                      leaveq 
  50:   c3                      retq   

程序代碼里面使用的都是虛擬地址银酬,可以發(fā)現(xiàn)main的起始地址為 0x00000000,這是因?yàn)樵谖催M(jìn)行空間分配之前筐钟,目標(biāo)文件代碼段中的起始地址以 0x00000000開始揩瞪,等到空間分配完成以后,各個(gè)函數(shù)才會(huì)確定自己在虛擬地址中的位置

上邊反匯編輸出中:

  • 最左邊那列是每條指令的偏移量篓冲。一行代表一條指令

接著反匯編輸出ab的代碼段李破,可以發(fā)現(xiàn)main函數(shù)的兩個(gè)重定位入口被修正到了正確的位置:


objdump -d ab



000000000000066a <main>:
 66a:   55                      push   %rbp
 66b:   48 89 e5                mov    %rsp,%rbp
 66e:   48 83 ec 10             sub    $0x10,%rsp
 672:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
 679:   00 00 
 67b:   48 89 45 f8             mov    %rax,-0x8(%rbp)
 67f:   31 c0                   xor    %eax,%eax
 681:   c7 45 f4 64 00 00 00    movl   $0x64,-0xc(%rbp)
 688:   48 8d 45 f4             lea    -0xc(%rbp),%rax
 68c:   48 8d 35 7d 09 20 00    lea    0x20097d(%rip),%rsi        # 201010 <shared>
 693:   48 89 c7                mov    %rax,%rdi
 696:   b8 00 00 00 00          mov    $0x0,%eax
 69b:   e8 1b 00 00 00          callq  6bb <swap>
 6a0:   b8 00 00 00 00          mov    $0x0,%eax
 6a5:   48 8b 55 f8             mov    -0x8(%rbp),%rdx
 6a9:   64 48 33 14 25 28 00    xor    %fs:0x28,%rdx
 6b0:   00 00 
 6b2:   74 05                   je     6b9 <main+0x4f>
 6b4:   e8 87 fe ff ff          callq  540 <__stack_chk_fail@plt>
 6b9:   c9                      leaveq 
 6ba:   c3                      retq   

00000000000006bb <swap>:

重定位表

鏈接器如何知道哪些指令需要被調(diào)整?這些指令的哪些部分要被調(diào)整壹将?怎么調(diào)整嗤攻?

在ELF文件中,有一個(gè)叫做重定位表的結(jié)構(gòu)專門用來保存這些與重定位相關(guān)的信息

對于可重定位的ELF文件來說诽俯,它必須具有重定位表妇菱,用來描述如何修改相應(yīng)的段里的內(nèi)容,而對于每個(gè)要被重定位的ELF段都有一個(gè)對應(yīng)的重定位表暴区,這一個(gè)重定位表往往就是ELF文件中的一個(gè)段闯团,比如代碼段.text如有要被重定位地方,那么會(huì)有一個(gè)相對于叫做.rel.text的段保存了代碼段的重定位表

使用objdump查看:


objdump -r a.o


a.o:     文件格式 elf64-x86-64

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE 
0000000000000025 R_X86_64_PC32     shared-0x0000000000000004
0000000000000032 R_X86_64_PLT32    swap-0x0000000000000004
000000000000004b R_X86_64_PLT32    __stack_chk_fail-0x0000000000000004


RELOCATION RECORDS FOR [.eh_frame]:
OFFSET           TYPE              VALUE 
0000000000000020 R_X86_64_PC32     .text



每個(gè)要被重定位的地方叫一個(gè)重定位入口仙粱,重定位入口的偏移OFFSET 表示該入口在要被重定位的段中的位置房交,“RELOCATION RECORDS FOR [.text]:”表示這個(gè)重定位表是代碼段的重定位表

符號解析

之所以鏈接時(shí)因?yàn)槟繕?biāo)文件中用到的符號被定義在其他目標(biāo)文件,所以要將它們鏈接起來伐割,比如直接用ld來鏈接a.o候味,而不將b.o作為輸入,就會(huì)報(bào)錯(cuò)


ld a.o


ld: 警告: 無法找到項(xiàng)目符號 _start; 缺省為 00000000004000b0
a.o:在函數(shù)‘main’中:
a.c:(.text+0x25):對‘shared’未定義的引用
a.c:(.text+0x32):對‘swap’未定義的引用
a.c:(.text+0x4b):對‘__stack_chk_fail’未定義的引用


重定位的過程也伴隨著符號的解析過程隔心,每個(gè)目標(biāo)文件中都可能定義一些符號负溪,也可能引用到定義在其它目標(biāo)文件的符號,重定位的過程济炎,每個(gè)重定位的入口都是對一個(gè)符號的引用川抡,那么當(dāng)鏈接器要對某個(gè)符號的引用進(jìn)行重定位時(shí),它就要確定這個(gè)符號的目標(biāo)地址须尚,這是鏈接器就去查找所有輸入目標(biāo)文件的符號表組成的全局符號,找到后進(jìn)行重定位

比如查看a.o的符號表:


readelf -s a.o


Symbol table '.symtab' contains 13 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     8: 0000000000000000    81 FUNC    GLOBAL DEFAULT    1 main
     9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND shared
    10: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __stack_chk_fail

可以發(fā)現(xiàn)"shared"和"swap"都是UND耐床,即“undefined”未定義類型

COMMON塊

弱符號機(jī)制允許同一個(gè)符號的定義存在于多個(gè)文件中。目前的鏈接器本身并不支持符號的類型撩轰,即變量類型對于鏈接器來說是透明的昧廷,它只知道一個(gè)符號的名字偎箫,并不知道類型是否一致。

多個(gè)符號(同名)定義類型不一致的幾種情況

  • 兩個(gè)或兩個(gè)以上強(qiáng)符號類型不一致
  • 有一個(gè)強(qiáng)符號淹办,其他都是弱符號眉枕,出現(xiàn)類型不一致

  • 兩個(gè)或兩個(gè)以上弱符號類型不一致

針對第一種,無需額外處理怜森,因?yàn)槎鄠€(gè)強(qiáng)符號定義本身就是非法的

現(xiàn)在的編譯器和鏈接器都支持一種叫COMMON塊的機(jī)制速挑,來源于Fortran,早起Fortran沒有動(dòng)態(tài)分配空間的機(jī)制副硅,程序員必須事先聲明它所需的臨時(shí)使用空間的大小

COMMON類型的鏈接規(guī)則:原則上講最終鏈接后的輸出文件中姥宝,弱符號的大小以輸入文件中最大(所占空間)的那個(gè)為準(zhǔn)

如果有 一個(gè)符號為強(qiáng)符號,那么最終輸出結(jié)果所占空間大小與強(qiáng)符號相同恐疲±奥【如果鏈接過程中,有弱符號大于強(qiáng)符號流纹,編譯器會(huì)報(bào)警告】

未初始化的全局變量最終還是被放在BSS段的
一旦有一個(gè)未初始化的全局變量不是以COMMON塊的形式存在糜烹,那么它就相當(dāng)于一個(gè)強(qiáng)符號。

靜態(tài)庫鏈接

程序之所以有用漱凝,因?yàn)樗鼤?huì)有輸入輸出疮蹦,這些輸入輸出對象可以是數(shù)據(jù),也可以是人茸炒,也可以是另外一個(gè)程序愕乎,還可以是另外一臺(tái)計(jì)算機(jī)。但是一個(gè)程序如何做到輸入輸出呢壁公?最簡單的辦法是使用操作系統(tǒng)提供的應(yīng)用程序編程接口(API)感论。當(dāng)然,操作系統(tǒng)也是一個(gè)程序紊册。

程序如何使用操作系統(tǒng)提供的 API ? 一般情況下比肄,一種語言的開發(fā)環(huán)境往往會(huì)附帶語言庫,這些庫就是對操作系統(tǒng) API 的包裝囊陡。庫里面還有一些很常用的函數(shù)芳绩,這部分函數(shù)不調(diào)用操作系統(tǒng) API。

靜態(tài)庫可以簡單的看成一組目標(biāo)文件的集合撞反,即很多目標(biāo)文件打包后形成的一個(gè)文件

  • Linux中最常用的C語言靜態(tài)庫libc位于/usr/lib/libc.a妥色,屬于glibc項(xiàng)目的一部分

  • Windows上,最常用的C語言庫是IDE附帶的運(yùn)行庫

鏈接過程控制

絕大部分情況下遏片,我們使用鏈接器提供的默認(rèn)規(guī)則對目標(biāo)文件進(jìn)行鏈接撮竿。一般情況下沒有問題幢踏,但對于一些特殊程序惑折,比如操作系統(tǒng)內(nèi)核,BIOS或在一些沒有操作系統(tǒng)的情況下運(yùn)行的程序(如引導(dǎo)程序 Boot Loader或者嵌入式系統(tǒng)的程序)敛助,以及另外的一些需要特殊的鏈接過程的程序纳击,如一些內(nèi)核驅(qū)動(dòng)程序焕数,它們往往受限于一些特殊的條件堡赔,如需要指定輸出文件的各個(gè)段虛擬地址善已,段的名稱离例,段的存放順序等宫蛆,因?yàn)檫@些特殊的環(huán)境耀盗,特別是某些硬件的限制,往往對程序的各個(gè)段的地址有特殊的要求磷醋。

由于整個(gè)鏈接過程有很多內(nèi)容需要確定:

  • 使用哪些目標(biāo)文件

  • 使用哪些庫文件

  • 是否在最終可執(zhí)行文件中保存調(diào)試信息

  • 輸出文件格式(可執(zhí)行文件還是動(dòng)態(tài)鏈接庫)

  • 還要考慮是否要導(dǎo)出某些符號以供調(diào)試器或程序本身或其他程序使用等

鏈接控制腳本

鏈接器一般都提供多種控制整個(gè)鏈接過程的方法邓线,以用來產(chǎn)生用戶所需要的文件。一般鏈接器有如下3種方法:

  • 1.使用命令行來給鏈接器指定參數(shù)震庭,如 ld 使用的 -o, -e
  • 2.將鏈接器指令存放在目標(biāo)文件里面器联,編譯器進(jìn)程會(huì)通過這種方法向鏈接器傳遞指令拨拓。
  • 3.使用鏈接控制腳本渣磷,也是最為靈活醋界,最為強(qiáng)大的鏈接控制方法

最小的程序

這個(gè)最小的程序形纺,功能就是在終端上輸出:"Hello world!",但這個(gè)經(jīng)典的hello wrold程序有點(diǎn)不同:

  • 之前的程序使用了printf函數(shù)逐样,該函數(shù)是C語言庫的一部分官研,這次希望這個(gè)小程序能夠擺脫C語言運(yùn)行庫闯睹,使得它成為一個(gè)獨(dú)立于任何庫的純正的程序

  • 經(jīng)典的版本程序使用了庫始花,所以必須由main函數(shù)酷宵,所以此次不適用main這個(gè)函數(shù)名了用nomain作為整個(gè)程序的入口

  • 經(jīng)典的程序會(huì)產(chǎn)生許多段躬窜,這次將所有段都合并到一個(gè)叫做tinytext的段荣挨,這個(gè)段的名字是任意起的

TinyHelloWorld.c



char* str = "Hello World!\n";

void print(){
    asm( "movl $14, %%edx \n\t"     
        "movl  %0, %%ecx \n\t"      
        "movl $0, %%ebx \n\t"       
        "movl $4, %%eax \n\t"       
        "int $0x80     \n\t"
        ::"r"(str):"edx","ecx","ebx");  
}

void exit() {
    asm( "movl $42,%ebx \n\t"
        "movl $1,%eax \n\t"
        "int $0x80    \n\t");
}

void nomain() {
    print();
    exit();
}


程序入口函數(shù)為nomain函數(shù),調(diào)用了print函數(shù)甚纲,打印“hello world”介杆,接著調(diào)用exit函數(shù)春哨,結(jié)束進(jìn)程悲靴,上面使用了GCC內(nèi)嵌匯編莫其,

現(xiàn)在先使用普通命令行的方式來編譯和鏈接TinyHelloWorld.c:


(1)gcc -c -fno-builtin -m32 TinyHelloWorld.c


(2)ld -static -m elf_i386  -e nomain -o TinyHelloWorld TinyHelloWorld.o

第一步用GCC將TinyHelloWorld.c編譯成TinyHelloWorld.o,接著用ld將TinyHelloWorld.o鏈接成可執(zhí)行文件TinyHelloWorld

其中一些參數(shù)的含義:

  • -fno-builtin,GCC提供了很多內(nèi)置函數(shù)(Built-in Function),它會(huì)把一些常用的C庫函數(shù)替換成編譯器的內(nèi)置函數(shù)乱陡,以達(dá)到優(yōu)化的功能
  • -static憨颠,表示ld將使用靜態(tài)鏈接的方式來鏈接程序爽彤,而不是使用默認(rèn)的動(dòng)態(tài)鏈接

  • -e nomain适篙,表示該程序的入口函數(shù)為nomain

  • -o TinyHelloWorld嚷节,指定輸出可執(zhí)行文件名為TinyHelloWorld

ld鏈接腳本

如果把整個(gè)鏈接過程比作一臺(tái)計(jì)算機(jī)硫痰,那么ld鏈接器就是計(jì)算機(jī)的CPU效斑,所有的目標(biāo)文件缓屠,庫文件就是輸入,鏈接結(jié)果輸出的可執(zhí)行文件就是輸出敬矩,而鏈接控制腳本正是這臺(tái)計(jì)算機(jī)的“程序”,

不論是輸出文件還是輸入文件业踏,它們的主要的數(shù)據(jù)就是文件中的各個(gè)段勤家,我們把輸入文件中的段热幔,叫輸入段绎巨,輸出文件中的段稱為輸出段场勤。簡單的說歼跟,控制鏈接過程無非就是控制輸入段如何變成輸出段留瞳,比如哪些輸入段要合并成一個(gè)輸出段叹卷,哪些輸入段要丟棄帝牡;指定輸出段的名字蒙揣,裝載地址罩息,屬性等葱色。

TinyHelloWorld的鏈接腳本TinyHelloWorld.lds:


ENTRY(nomain) 
SECTIONS  
{
  . = 0x0804800 + SIZEOF_HEADERS;
  tinytext : { *(.text) *(.data) *(.rodata) }
  /DISCARD/ : { *(.comment) }
}

ENTRY :指定了程序的入口為 nomain() 函數(shù)
SECTIONS : 鏈接腳本的主體苍狰,這個(gè)命令制定了各個(gè)輸入端到輸出端的變換淋昭,SECTIONS 后面緊跟著一對大括號里面包含了 SECTIONS 變換規(guī)則翔忽,其中有3條語句,每條語句一行贬丛。第一行是賦值語句,后面2條是段轉(zhuǎn)換規(guī)則够庙,它們的含義如下:

    1. . = 0x0804800 + SIZEOF_HEADERS; 第一條賦值語句的意思是將當(dāng)期虛擬地址設(shè)置為 0x0804800 + SIZEOF_HEADERS, SIZEOF_HEADERS 為輸出文件的文件大小耘眨。
      "." 表示當(dāng)前的虛擬地址,因?yàn)檫@條語句后面緊跟著輸出端 "tinytext", 所以 "tinytext" 段的起始虛擬地址為 0x0804800 + SIZEOF_HEADERS偶宫。它將當(dāng)期虛擬地址
      設(shè)置成一個(gè)比較巧妙的值,以便于裝載時(shí)頁面映射更為方便。
    1. tinytext : { *(.text) *(.data) *(.rodata) } 第二條是段轉(zhuǎn)換規(guī)則吵冒,它的意思即所有的輸入文件中的名字為 ".text", ".data" 或者 ".rodata" 的段依次合并
      到輸出文件的 "tinytext"
    1. /DISCARD/ : { *(.comment) } 第三天規(guī)則為:將所有輸入文件中的名字為 ".comment" 的段丟棄纯命,不保存到輸出文件中。

通過命令行來編譯TinyHelloWorld痹栖,并啟用該鏈接控制腳本:


ld -static -m elf_i386 -T TinyHelloWorld.lds -e nomain -o TinyHelloWorld TinyHelloWorld.o


使用objdum查看TinyHelloWorld的段亿汞,可以發(fā)現(xiàn)整個(gè)程序只有一個(gè)段“tinytext”

圖4.png

參考資料

<<程序員的自我修養(yǎng)—鏈接、裝載與庫>>

https://blog.csdn.net/neuq_jtxw007/article/details/78112672

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揪阿,一起剝皮案震驚了整個(gè)濱河市留夜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌图甜,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枕面,死亡現(xiàn)場離奇詭異易结,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門涣旨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵烘跺,是天一觀的道長脖咐。 經(jīng)常有香客問我秤朗,道長常挚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任羊赵,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己聚霜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著簿训,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹦哼,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音愤惰,去河邊找鬼施流。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜀漆,可吹牛的內(nèi)容都是我干的鲜侥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼胰伍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了斑司?” 一聲冷哼從身側(cè)響起翠胰,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤衬以,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后互妓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冯勉,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灼狰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年交胚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝴簇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匆帚,死狀恐怖熬词,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吸重,我是刑警寧澤荡澎,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布均践,位于F島的核電站,受9級特大地震影響摩幔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鞭铆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望车遂。 院中可真熱鬧,春花似錦坡疼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剪况。三九已至教沾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間授翻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工杨蛋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曙寡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓镀琉,卻偏偏與公主長得像替梨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子挽鞠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容