《程序員的自我修養(yǎng)》讀書筆記——目標(biāo)文件

上一篇介紹了編譯連接的過程,提到了目標(biāo)文件是通過匯編過程生成的,最終鏈接生成可執(zhí)行文件,這篇介紹一下目標(biāo)文件里面到底有什么。

本文導(dǎo)圖


格式概述

目標(biāo)文件相對于最終的可執(zhí)行文件而言,結(jié)構(gòu)上已經(jīng)和可執(zhí)行文件的結(jié)構(gòu)基本一樣了。只是還沒有經(jīng)過鏈接過程,某些符號、地址還沒被重定位,大部分內(nèi)容都已經(jīng)具備了。

可執(zhí)行文件的格式在Windows(PE-Portable Executable)和Linux(ELF- Executable Linkable Format)、Mac(Mach-O)不同,前兩者都是基于COFF(Common file format)格式的,除此之外還有其他不常見的,如Intel/Microsoft的OMF、Unix a.out、MS-Doc .Com格式等。

廣義上將,目標(biāo)文件與可執(zhí)行文件格式幾乎一樣,可以看成同一種類型的文件。

動態(tài)鏈接庫(window .dll、Linux .so)及靜態(tài)鏈接庫(window lib、Linux .a)、Mac(dylb,tbd)都是按照可執(zhí)行文件的格式存儲。靜態(tài)庫稍微不同,因為他是把很多目標(biāo)文件集合在一起,需要在頭部加上一些索引。也就是包含了很多目標(biāo)文件的一個目標(biāo)文件包。

ELF格式文件更可以做如下歸納(目標(biāo)文件.o 靜態(tài)庫、可執(zhí)行文件、動態(tài)庫):


查看格式

file命令查看相應(yīng)的文件格式。

Mac下


Linux下


下面以ELF結(jié)構(gòu)作為分析

淺析目標(biāo)文件內(nèi)部結(jié)構(gòu)

目標(biāo)文件至少包含編譯后的機(jī)器指令代碼、數(shù)據(jù)、和鏈接所需要的信息比如、符號表、調(diào)試信息、字符串等,下面的內(nèi)容圍繞著這幾個部分進(jìn)行總結(jié)。

按照信息的不同屬性以節(jié)(Section)也叫段(Segment)的形式存儲,表示一個一定長度 的區(qū)域。這些區(qū)域里面分別存儲了上訴編譯后的信息。

  • 代碼段:編譯之后的機(jī)器指令放在代碼段(Code Section),一般是叫.code.text。
  • 數(shù)據(jù)段:全局變量和靜態(tài)變量數(shù)據(jù)放在數(shù)據(jù)段(Data Section),一般叫.data
  • BBS段:未初始化的全局變量、未初始化靜態(tài)變量放在.bbs段。

具體來講,下面代碼與目標(biāo)文件對應(yīng)關(guān)系:

  • 文件頭:ELF文件有個文件頭,描述整個文件的文件屬性,如是否可執(zhí)行、靜態(tài)庫還是動態(tài)庫、及入口地址(可執(zhí)行文件)、目標(biāo)硬件、操作系統(tǒng)等。其中還包含一個段表,段表示描述各個段的一個數(shù)組,包含各個段在文件的偏移位置、段的屬性。文件頭后面就是各個段的內(nèi)容,比如代碼段保存指令,數(shù)據(jù)段保存數(shù)據(jù)?!渲卸伪矸浅V匾?/li>

未初始化的全局變量、局部靜態(tài)變量雖然默認(rèn)值都是0,但是沒必要在.data段為它們分配空間(浪費),存放0是沒必要的。所以放在.bbs段,也就給他們預(yù)留個位置而已,沒有內(nèi)容,所以在文件中不占空間。

總體來說,程序代碼被編譯后分成兩種段,程序指令(代碼段)和程序數(shù)據(jù)(數(shù)據(jù)端及.BSS

指令和數(shù)據(jù)分開的好處:

  • 指令和數(shù)據(jù)分別映射到兩個虛擬區(qū)域,數(shù)據(jù)可讀可寫,而指令是只讀的,可以設(shè)值區(qū)域的權(quán)限,防止惡意修改程序指令。——權(quán)限,安全
  • 分開有利于程序的局部性(在一段時間內(nèi),整個程序的執(zhí)行僅限于程序中的某一部分。相應(yīng)地,執(zhí)行所訪問的存儲空間也局限于某個內(nèi)存區(qū)域。)CPU有幾大的緩存體系?!植啃栽?/li>
  • 復(fù)用,如果系統(tǒng)有多個程序的副本,那么指令都是一樣的。這個也是動態(tài)庫非常大的優(yōu)勢,節(jié)省內(nèi)存開銷——復(fù)用、復(fù)用?。。?/li>

實驗?zāi)繕?biāo)文件內(nèi)容

實驗代碼:



使用objdump -h 查看目標(biāo)文件內(nèi)部結(jié)構(gòu)。

在Mac上實驗結(jié)果

objdump -h  hello.o

hello.o:    file format Mach-O 64-bit x86-64

Sections:
Idx Name          Size      Address          Type
  0 __text        00000067 0000000000000000 TEXT
  1 __data        00000008 0000000000000068 DATA
  2 __cstring     00000004 0000000000000070 DATA
  3 __bss         00000004 0000000000000120 BSS
  4 __compact_unwind 00000040 0000000000000078 DATA
  5 __eh_frame    00000068 00000000000000b8 DATA

在Linux實驗結(jié)果


除了之前講到的三個段,這里多了只讀數(shù)據(jù)端,注釋端,堆棧提示段。其中包含了一些屬性關(guān)鍵詞,如長度(size)、端的位置(File off)、該段是否在文件中存在(Contents)、段的各種屬性(Alloc)。可以看到.bbs段沒有contents所以在文件中沒有內(nèi)容。而堆棧提示段size為0

將上面的內(nèi)容整理一下:

可以通過size命令查看可執(zhí)行文件的各個端大?。ㄗ⒁鈊ec所有段的十進(jìn)制和,hex是16進(jìn)制的和)

Mac上實驗(文件格式是Mach-o)


Linux上實驗(文件格式是ELF)


深入目標(biāo)文件內(nèi)部結(jié)構(gòu)

下面將深入了解目標(biāo)文件中的各個段,他們的作用及含義。

核心段

上面提到過核心段有代碼段、數(shù)據(jù)端、BBS段。下面分別介紹!

代碼段

代碼段可以使用objdump的-s參數(shù)打印出來,-d可以將包含指令的段反匯編信息。

  • 最左邊是偏移地址,緊跟著的是16進(jìn)制信息(每兩個數(shù)字為一個字節(jié),一般都是以字節(jié)位單位查看。

Mac上實驗(文件格式是Mach-o):最前面的部分是反匯編內(nèi)容,接下來是各個段的內(nèi)容。

$ objdump -s -d hello.o

hello.o:    file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
_fun1:
       0:   55  pushq   %rbp
       1:   48 89 e5    movq    %rsp, %rbp
       4:   48 83 ec 10     subq    $16, %rsp
       8:   48 8d 05 61 00 00 00    leaq    97(%rip), %rax
       f:   89 7d fc    movl    %edi, -4(%rbp)
      12:   8b 75 fc    movl    -4(%rbp), %esi
      15:   48 89 c7    movq    %rax, %rdi
      18:   b0 00   movb    $0, %al
      1a:   e8 00 00 00 00  callq   0 <_fun1+0x1F>
      1f:   89 45 f8    movl    %eax, -8(%rbp)
      22:   48 83 c4 10     addq    $16, %rsp
      26:   5d  popq    %rbp
      27:   c3  retq
      28:   0f 1f 84 00 00 00 00 00     nopl    (%rax,%rax)

_main:
      30:   55  pushq   %rbp
      31:   48 89 e5    movq    %rsp, %rbp
      34:   48 83 ec 10     subq    $16, %rsp
      38:   c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
      3f:   c7 45 f8 6f 00 00 00    movl    $111, -8(%rbp)
      46:   8b 05 00 00 00 00   movl    (%rip), %eax
      4c:   03 05 00 00 00 00   addl    (%rip), %eax
      52:   03 45 f8    addl    -8(%rbp), %eax
      55:   03 45 f4    addl    -12(%rbp), %eax
      58:   89 c7   movl    %eax, %edi
      5a:   e8 00 00 00 00  callq   0 <_main+0x2F>
      5f:   31 c0   xorl    %eax, %eax
      61:   48 83 c4 10     addq    $16, %rsp
      65:   5d  popq    %rbp
      66:   c3  retq
Contents of section __text:
 0000 554889e5 4883ec10 488d0561 00000089  UH..H...H..a....
 0010 7dfc8b75 fc4889c7 b000e800 00000089  }..u.H..........
 0020 45f84883 c4105dc3 0f1f8400 00000000  E.H...].........
 0030 554889e5 4883ec10 c745fc00 000000c7  UH..H....E......
 0040 45f86f00 00008b05 00000000 03050000  E.o.............
 0050 00000345 f80345f4 89c7e800 00000031  ...E..E........1
 0060 c04883c4 105dc3                      .H...].
Contents of section __data:
 0068 54000000 55000000                    T...U...
Contents of section __cstring:
 0070 25640a00                             %d..
Contents of section __bss:
<skipping contents of bss section at [0120, 0124)>
Contents of section __compact_unwind:
 0078 00000000 00000000 28000000 00000001  ........(.......
 0088 00000000 00000000 00000000 00000000  ................
 0098 30000000 00000000 37000000 00000001  0.......7.......
 00a8 00000000 00000000 00000000 00000000  ................
Contents of section __eh_frame:
 00b8 14000000 00000000 017a5200 01781001  .........zR..x..
 00c8 100c0708 90010000 24000000 1c000000  ........$.......
 00d8 28ffffff ffffffff 28000000 00000000  (.......(.......
 00e8 00410e10 8602430d 06000000 00000000  .A....C.........
 00f8 24000000 44000000 30ffffff ffffffff  $...D...0.......
 0108 37000000 00000000 00410e10 8602430d  7........A....C.
 0118 06000000 00000000                    ........

# instanza @ InstanzadeMacBook-Pro in ~/Desktop/Temp [12:12:42]
$

Linux實驗(文件格式是ELF)


  • contents of section .text就是數(shù)據(jù)已十六進(jìn)制打印出來的內(nèi)容。中間4列是16進(jìn)制內(nèi)容,最右邊是.text段的ASCII碼形式。
  • .text段包含是.c文件兩個函數(shù)的指令,比如第一個字節(jié)是0x55,就是func1()函數(shù)第一條push %ebp指令,最后一個0xc3代表main函數(shù)最后一個指令ret

數(shù)據(jù)段

.data段保存了初始化的全局靜態(tài)變量和局部靜態(tài)變量。上面代碼中有兩個這樣的變量,每個變量4個字節(jié)一共8個字節(jié)。所以.data這個段大小為8個字節(jié)?!?strong>這是個非常準(zhǔn)確的計算,不會存在多一個、少一個字節(jié)的情況

注意printf的時候,有一個字符串常量“%d\n”。在linux中放到了.rodata段,分別有四個字符%、d、\n(換行符)、空字符。一個字符一個字節(jié)(8位ASCII碼),所以一共四個字節(jié)。在Mac上放到了字符串常量區(qū)。

以Mac下例子為例:

Contents of section __cstring:
 0070 25640a00                             %d..

通過查ASCII表,得到對應(yīng)情況:%——25,d——64,\n(換行符)——0a,空字符(NULL)——00。剛好和字符串常量區(qū)對應(yīng)

在來看一起.data段(Mac下):

 0060 c04883c4 105dc3                      .H...].
Contents of section __data:
 0068 54000000 55000000                    T...U...

根據(jù)偏移范圍可以知道.data端一共8個字節(jié)。和前面用size看到的不一樣,size看到的是12個字節(jié)。

Linux下

可以看到Linux也是8個字節(jié)。字節(jié)從低到高分別是0x54、0x00、0x00、0x00。剛好是十進(jìn)制的84,特別注意這里的順序,字節(jié)從低到高還是從高到底排列涉及到CPU的字節(jié)序問題,也就是大端小端。后面四個字節(jié)也剛好是85

BBS段

Mac中的bss段如下:

Contents of section __bss:
<skipping contents of bss section at [0120, 0124)>

可以知道具體的值放到了0120到0124之間,一共四個字節(jié)。

Linux下


同樣也是四個字節(jié)。和global_uninit_var、static_var2合起來大小8個字節(jié)不符。

BBS段最終是通過符號表決定的。在Linux下只有static_var2存放到了bbs段。而global_uninit_var沒有存放到任何段。只是一個未定義的common符號,這和語言及編譯器實現(xiàn)有關(guān)。有一點可以明確——編譯單元內(nèi)部可見的靜態(tài)變量是存在bbs段的。

其他段

ELF中除了.text、.data、.bbs三個最常用的段還有其他段。具體如下:


.開頭的都是系統(tǒng)保留的。如果想要自定義端,則不能以.開頭。

GCC提供了擴(kuò)展機(jī)制,可以將變量或者代碼放到你指定的段中去。用以下方式實現(xiàn):


以上只是ELF文件的輪廓,下面用一張圖總結(jié):



幾點說明一下:

  • 文件頭:描述怎個文件的基本屬性,文件版本,目標(biāo)機(jī)器型號,程序入口地址。
  • 段表:描述了ELF文件包含所有段的信息,比如每個段的段名,長度,文件中的偏移,讀寫權(quán)限,和其他屬性。

下面開始從文件頭開始

文件頭

Linux用readelf來查看文件頭,Mac用otool查看(otool -h hello.o

在Linux中


系統(tǒng)定義文件頭數(shù)據(jù)結(jié)構(gòu):

#define EI_NIDENT 16
typedef struct {
    unsigned char 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;
    Elf32_Half e_ehsize;
    Elf32_Half e_phentsize;
    Elf32_Half e_phnum;
    Elf32_Half e_shentsize;
    Elf32_Half e_shnum;
    Elf32_Half e_shstrndx;
} Elf32_Ehdr;

各個字段代表什么意思根據(jù)名稱就知道了。關(guān)鍵要知道在哪里定義這些常數(shù)的。ELF文件頭定義在user/include/elf。mach.o類型的目標(biāo)文件也有對應(yīng)的頭文件。

下圖是對readelf和ELF頭文件定義的各個成員的含義解釋。這里先大致了解下,后面會詳解!

ELF魔數(shù)(e_ident前四個字節(jié))

通過readelf之后看到最前面的16個字節(jié)剛好是e_ident,這16個ELF標(biāo)準(zhǔn)用來標(biāo)識ELF文件平臺屬性,比如ELF字長(32位、64位),字節(jié)序、文件版本等。

下表是對e_ident數(shù)組各個成員的說明:


  • 前四個字節(jié)是所有ELF文件必須相同的標(biāo)識碼:0x7F、0x45、0x4c、0x46。第一個字節(jié)賭贏DEL控制符,后三個對應(yīng)ELF。所有的可執(zhí)行文件最開始的幾個字節(jié)都是魔數(shù),用來確認(rèn)文件類型,操作系統(tǒng)在加載的時候會確認(rèn)魔數(shù)是否正確,不正確就不會加載。
  • 下一個字節(jié)標(biāo)識文件類型;0x01標(biāo)識32,0x02標(biāo)識64
  • 第六個字節(jié)是字節(jié)序
  • 第七個字節(jié):規(guī)定ELF文本版本
  • 后面9個字節(jié):標(biāo)準(zhǔn)還沒有定義,一般填0

文件類型

e_type表示文件類型,之前提到過3種ELF文件類型。如下表


機(jī)器類型

e_machine表示平臺屬性

段表

ELF最終段結(jié)構(gòu)由段表決定,編譯器、鏈接器和 裝載器都是依賴段表來定位和訪問各個端的屬性。段表的位置有ELF文件頭的e_shoff決定,如上面的例子段表的偏移為0x118。

直接來查看所有的段信息

這里的數(shù)據(jù)其實在代碼層面來講都是由對應(yīng)的數(shù)據(jù)結(jié)構(gòu)的。無論是mach.o還是ELF。比如這里的Elf32_Shdr結(jié)構(gòu)體是對一個段的封裝,段表也就是由Elf32_Shdr組成的數(shù)組。對上圖而言一個有11個這樣的元素。

段表元素數(shù)據(jù)結(jié)構(gòu)


 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_

各個字段的含義


1

現(xiàn)在把整個.o文件所有段的位置及長度都分析清楚了。

  • 段表長度為0x1b8,一共440個字節(jié),包含11個段描述符,每個段描述符為40個字節(jié),這個長短剛好是結(jié)構(gòu)Elf32_Shdr的長度。
  • 文件最后一段.re.text結(jié)束后,長度為0x450也就是1104個字節(jié),剛好是.o文件的大小。

中間有段表和.rel.text都因為對齊的原因,與前面的短之間有一個字節(jié)和兩個字節(jié)的間隔。注意這里是為了對齊。

段類型(sh_type、sh_flags)

段名(sh_name)只有在編譯、鏈接過程有意義,但不能真正表示段的類型,段名(sh_name)其實只是一個索引指向字符串表(SHT_STRTAB)的某個位置,在編譯器和鏈接器中起作用的是段類型字段和段標(biāo)志位字段。

段類型:


段標(biāo)志位


系統(tǒng)保留段

段的鏈接信息(sh_link、sh_info)

段的類型與鏈接相關(guān)的話都需要sh_link、sh_info,無論是動態(tài)庫還是靜態(tài)庫。比如重定位表,符號表。對于其他段這兩個成員沒意義


重定位表(段類型SHT_REL)

在剛才的段中,有一個.rel.text的短,類型(sh_type)為SHT_REL,也就是這個段包含的是重定位表。

鏈接器在處理目標(biāo)文件的時候,需要對目標(biāo)文件某些部分重定位,雖然在代碼段和數(shù)據(jù)端中用的是絕對地址的引用位置。重定位信息都記錄在重定位表里面,每一個需要重定位的代碼段或數(shù)據(jù)段都會有一個相應(yīng)的重定位表,比如上面的.rel.text就是對.text的重定位表。因為.text段有一個絕對地址的引用,就是printf函數(shù)的調(diào)用。這里的決定地址引用也就是寫死的地址。而.data段沒有絕對地址引用。

字符串表(段類型SHT_STRTAB)

ELF用到很多字符串 ,如段名、變量名、因為字符串長度不確定,所以固定的解構(gòu)表示比較困難。一種很常見做法就是把字符串集中放到一個表中,使用字符串的時候在表中的偏移來引用字符串。如下圖

字符串表一般有兩種一種用來保存普通的字符串,比如符號的名字;另一種段表字符串用來來保存段表中用到的字符串,比如段表名。字符串表中包含有若干個以’null’結(jié)尾的字符序列。

只要分析ELF頭文件,就可以得到段表和段表字符串表的位置,從而解析整個ELF文件。

符號表(symbol table)

符號

假如B文件要用到A文件的函數(shù)foo,那么在目標(biāo)文件A定義了函數(shù)foo,目標(biāo)文件B引用了A中的foo函數(shù)。每個函數(shù)、變量都有自己的名字,將函數(shù)名、變量名統(tǒng)稱為符號名。

每一個目標(biāo)文件都會一個相應(yīng)的符號表,記錄了目標(biāo)文件所要用到的符號,每個定義的符號有一個對應(yīng)的值,叫做符號值,對于變量和函數(shù)來說,符號值就是他們的地址。出了變量和函數(shù)外還有其他幾種符號,可以將符號表中的符號進(jìn)行分類。

符號表包含的信息用于定位和重定位程序中的符號定義和引用。目標(biāo)文件的其它部分通過一個符號在這個表中的索引值來使用 該符號。索引值從 0 開始計數(shù),但值為 0 的表項(即第一項)并沒有實際的意義, 它表示未定義的符號。這里用常量 STN_UNDEF 來表示未定義的符號。

符號一般有如下類型:

  • 定義在目標(biāo)文件的全局符號:可以被其他目標(biāo)文件引用。如上面的func1,main,global_init_var
  • 引用其他目標(biāo)文件中的全局符號:一般叫做外部符號,如 printf
  • 段名:由編譯器產(chǎn)生,它的值就是該段的其實起始地址。如.text、.data
  • 局部符號:只在編譯單元內(nèi)部可見,如static_var、static_var2。調(diào)試器可以是用哪個這些符號來分析程序。對于鏈接沒什么作用,鏈接器也會忽略他們。
  • 行號符號:目標(biāo)文件指令與源代碼中代碼行的對應(yīng)關(guān)系。

可以使用nm查看符號表的內(nèi)容

Mac上


Linux上


符號表結(jié)構(gòu)(SHT_SYMTAB)

符號表示文件中的一個段,段名.symtab。符號表數(shù)據(jù)結(jié)構(gòu)比較簡單Elf32_sym(符號表項),每個結(jié)構(gòu)體對應(yīng)一個符號。

其結(jié)構(gòu)體如下:

 typedef struct {
  Elf32_Word st_name;
  Elf32_Addr st_value;
  Elf32_Word st_size;
  unsigned char st_info;
  unsigned char st_other;
  Elf32_Half st_shndx;

各個字段的含義


符號類型和屬性(st_info)

由低4位表示符號類型,高28位表示符號屬性信息。

符號所在段(st_shndx)

如果符號定義在本目標(biāo)文件,則這個成員表示符號所載的段在段表中的下標(biāo)。因為符號是為段而定義,在段中被引用。本數(shù)據(jù)成員即指明了相關(guān)聯(lián)的段。本數(shù)據(jù)成員是一個索引值,它指向相關(guān)聯(lián)的段在段表中的索引。在重定位過程中,段的位置會改變,本數(shù)據(jù)成員的值也隨之改變,繼續(xù)指向段的新位置。

如果沒有定義在本目標(biāo)文件,則sh_shndx有些特殊。

符號值(st_value)

每個符號都有一個對應(yīng)的值。如果是一個變量或者函數(shù),符號值就是其地址。總的來說有以下幾種情況:

  • 如果是符號定義且不是Common塊類型(st_shndx不為SHN_COMMON),則st_value表示該符號在段中的偏移,即符號所對應(yīng)的函數(shù)、變量位于st_shndx指定段。這種是最常見的,比如func1、main、global_init_var。
  • 如果是COMMON塊的類型,則st_value表示該符號的對齊屬性,如global_uninit_var
  • 在可執(zhí)行文件中,st_value表示符號的虛擬地址。

符號表內(nèi)容解析

內(nèi)容如下:


  • 第一列表示符號表數(shù)組下標(biāo),從0開始共15個符號
  • 第二列就是符號值——st_value
  • 第三列Size為符號大小——st_size
  • 第四列、第五列分別為符號類型和綁定信息,對應(yīng)st_info的低四位和高28位
  • 第六列不知道
  • 第七列表示符號所屬的段——st_shndx
  • 第八列表示符號名稱

根據(jù)前面的內(nèi)容做出如下解析:

  • func1、main是函數(shù)所有在代碼段,代碼段Ndx為1,所以.text為1.并且類型是STT_Func,并且是全局可見,所以是STB——GLOBAL。
  • global_uninit_var是一個SHN_COMMON類型的符號,本身并沒有在BSS段。
  • ....
  • 依次類推可以把各個符號都解析出來

需要說明的:static_var和static_var2變成了static_var.1533和static_var.1534,是因為進(jìn)行符號修正。其次綁定的屬性是STB_LOCAL,表示只在編譯單元可見。類型是STT_SECTION類型的符號,表示下標(biāo)為Ndx段的短名。但是符號沒有顯示。比如2號符號Ndx為1那么就是.text段。那么符號名字就是.text??梢允褂?code>objdump -t查看段名符號。

特殊符號

當(dāng)使用鏈接器鏈接的時候,鏈接器會定義很多特殊符號,這些特殊符號沒有在程序定義,但是可以直接聲明并應(yīng)用它。鏈接器將這些特殊符號放在了鏈接腳本中,鏈接器會在程序最終鏈接為可執(zhí)行文件的時候,將其解析為正確的值。

常見特殊符號


符號修飾、函數(shù)簽名(防止沖突)

最開始編譯器產(chǎn)生目標(biāo)文件的時候,符號名和相應(yīng)的變量函數(shù)名一樣。但是如果已經(jīng)定義這些符號就會產(chǎn)生目標(biāo)文件沖突。為了解決目標(biāo)文件沖突,就在對應(yīng)的符號名簽名加一些字符以示區(qū)分。如在符號名前、后加上_。

如果模塊較多,命名規(guī)范不嚴(yán)格,同樣可能導(dǎo)致沖突。于是就增加了命名空間的方法來解決。

所以看到上面的static_var和static_var2變成了static_var.1533和static_var.1534。也就是進(jìn)行了一次符號修飾。

C++符號修飾

C++強(qiáng)大而復(fù)雜,為了支持C++特性,發(fā)明了符號修飾和符號改編機(jī)制。

對于不同類,同名函數(shù),引入了一個函數(shù)簽名的概念。函數(shù)簽名包含一個函數(shù)的信息,函數(shù)名,參數(shù)類型及所在的命名空間、其他細(xì)心,用于識別不同的函數(shù)。

編譯器在鏈接處理符號的時候,會使用名稱修飾的方法,使得每個函數(shù)簽名對應(yīng)一個修飾后的名稱。如下圖:


弱符號、強(qiáng)符號()

經(jīng)常在編程中將一個符號重復(fù)定義,如果出現(xiàn)定義錯誤則說明這種事強(qiáng)符號。有些符號可以定義為弱符號,比如未初始化的全局變量,也可以使用__attribut__((weak))定義一個強(qiáng)符號為弱符號。

它們的規(guī)則如下:

  • 不允許強(qiáng)符號被多次定義,也就是不同目標(biāo)文件不能有相當(dāng)?shù)膹?qiáng)符號(iOS中經(jīng)常出現(xiàn)的符號沖突就是這個意思)
  • 如果符號在某個目標(biāo)文件是強(qiáng)符號,其他文件是弱符號,那么選擇強(qiáng)符號
  • 如果在所有文件中都是弱符號則選擇其中占空間最大的一個

弱引用、強(qiáng)引用

注意不是iOS中強(qiáng)弱引用

如果沒有找到該符號的定義,鏈接器就會報未定義錯誤這種成為強(qiáng)引用。與之相對的是弱引用,如果是弱引用,鏈接器不認(rèn)為是個錯誤,一般對未定義的弱引用,鏈接器默認(rèn)為0,或者其他值,以便程序識別。在動態(tài)庫中使用到,和COMMON塊概念聯(lián)系很緊密

可以使用__attribute__((weakref))擴(kuò)展自聲明對一個外部函數(shù)為弱引用。如果把它編譯為可執(zhí)行文件,并不會報鏈接錯誤。但是當(dāng)運行的時候,就會發(fā)生非法地址訪問。

可以用if加以判斷,防止這種情況。

這種弱符號和弱引用對于動態(tài)庫來講非常非常有用,比如動態(tài)庫中定義的弱符號可以被用戶定義的強(qiáng)符號覆蓋,使得程序可以使用自定義版的庫函數(shù)。

調(diào)試信息

目標(biāo)文件還可能保存調(diào)試信息,比如在函數(shù)里面設(shè)置斷點,可以監(jiān)視變量變化,可以單步行進(jìn)等,前提都是編譯器必須提前將源代碼與目標(biāo)代碼之間的關(guān)系(如目標(biāo)代碼中的地址對應(yīng)源代碼中的哪一行、函數(shù)和變量的類你先給,結(jié)構(gòu)體的定義,字符串保存到目標(biāo)文件里面)保存到目標(biāo)文件。

如果在gcc 中加入-g參數(shù)就可以在目標(biāo)文件里面加上調(diào)試信息。gcc -c -g hello.c

比如:

objdump -h hello.o

hello.o:    file format Mach-O 64-bit x86-64

Sections:
Idx Name          Size      Address          Type
  0 __text        00000067 0000000000000000 TEXT
  1 __data        00000008 0000000000000068 DATA
  2 __cstring     00000004 0000000000000070 DATA
  3 __bss         00000004 00000000000004e4 BSS
  4 __debug_str   0000009e 0000000000000074 DATA
  5 __debug_loc   00000000 0000000000000112 DATA
  6 __debug_abbrev 00000087 0000000000000112 DATA
  7 __debug_info  000000e0 0000000000000199 DATA
  8 __debug_ranges 00000000 0000000000000279 DATA
  9 __debug_macinfo 00000001 0000000000000279 DATA
 10 __apple_names 000000c8 000000000000027a DATA
 11 __apple_objc  00000024 0000000000000342 DATA
 12 __apple_namespac 00000024 0000000000000366 DATA
 13 __apple_types 00000047 000000000000038a DATA
 14 __compact_unwind 00000040 00000000000003d8 DATA
 15 __eh_frame    00000068 0000000000000418 DATA
 16 __debug_line  00000062 0000000000000480 DATA

ELF采用一個DWARF的標(biāo)準(zhǔn)的調(diào)試信息格式。在Xcode中也有DWARF的身影。

調(diào)試信息在目標(biāo)文件和可執(zhí)行文件中占用很大的空間,往往比程序的代碼和數(shù)據(jù)大很多倍(這一點在iOS開發(fā)中用Instrument調(diào)試的時候最終就是用得這個文件實現(xiàn)的。

小結(jié)

這一部分內(nèi)容比較多,主要是解析目標(biāo)文件格式。如文件頭、段、段表、重定位表、符號表、字符串表各自的數(shù)據(jù)結(jié)構(gòu)是如何的。其實對ELF文件解析的內(nèi)容內(nèi)容遠(yuǎn)遠(yuǎn)不止這些,也有專門的官方文檔對ELF格式記性詳細(xì)的說明。

最重要的是明白各個部分的關(guān)系

擴(kuò)展閱讀

可執(zhí)行文件格式
理解ELF文件格式
ELF文件格式分析
Comparison of executable file formats
MachOView

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容