1. 程式人生 > >讀書筆記--《程式設計師的自我修養》第3章:目標檔案裡有什麼(1)

讀書筆記--《程式設計師的自我修養》第3章:目標檔案裡有什麼(1)

3.1、目標檔案的格式

1、目標檔案從結構上講,它是已經編譯後的可執行檔案格式,只是還沒有經過連結的過程,其中可能有些符號或有些地址還沒有調整。其實它本身就是按照可執行檔案格式儲存的。

2、現在PC平臺流行的可執行檔案格式主要是windows下的PE和Linux下的ELF,他們都是COFF格式的變種。Windows的.obj和Linux的.o檔案都是目標檔案。其他不太常見的可執行檔案格式有Intel/Microsoft的OMF、Unix的a.out格式和MS-DOS .COM格式等。

3、可執行檔案、動態連結庫檔案(Windows的.dll和Linux的.so)和靜態連結庫檔案(Windows的.lib和Linux的.a)都是按照可執行檔案儲存。其中,靜態連結庫稍有不同,它是把很多目標檔案捆綁在一期形成一個檔案,再加上一些索引,可以簡單地理解為一個包含很多目標檔案的檔案包。
ELF檔案標準裡面把系統中採用的ELF格式的檔案分為4類。
(1)可重定位檔案:包含程式碼和資料,可以被用來連結成可執行檔案或共享目標檔案,靜態連結庫也可歸為這一類。如Linux的.o和Windows的.obj
(2)可執行檔案:可直接執行的程式。如/bin/bash檔案、Windows的.exe
(3)共享目標檔案:包含程式碼和資料,使用情況一:連結器可以使用這種檔案和其他的可重定位檔案和共享目標檔案連結;使用情況二:動態連結器可以將幾個共享目標檔案與可執行檔案結合,作為程序映像的一部分來執行。如Linux的.so、Windows的DLL
(4)核心轉儲檔案:當程序意外終止時,系統可以將程序的地址空間的內容及終止時的一些其他資訊轉儲到核心轉儲檔案。如Linux的core dump

3.2 目標檔案是什麼樣的

一般目標檔案按照不同的屬性,以節的形式儲存資訊,也叫段。一般情況下,它們都表示一個一定長度的區域,基本上不加以區別。
程式原始碼編譯後的機器指令經常被放在.text段;初始化的全域性變數和區域性靜態變數儲存在.data;未初始化的全域性變數和區域性靜態變數一般放在.bss段裡。.bss段只是為未初始化的全域性變數和區域性靜態變數預留位置而已,並沒有內容,在檔案中也不佔空間。

3.3 分析目標檔案

以下面程式為例說明:
這裡寫圖片描述
用GCC編譯這個檔案:

示只编译不链接)利用objdump查看目标文件的结构:" role="presentation" style="position: relative;"> g c c c S i m p l e S e c t i o n . c ( c ) o b j d u m p objdump -h SimpleSection.o(引數-h表示只把ELF檔案的各個段的資訊打印出來)
這裡寫圖片描述
其中,第2行的CONTENTS、ALLOC等表示段的屬性。CONTENTS表示該段在檔案中存在,可以看到.bss段沒有這個屬性,因為它實際上在ELF檔案中不存在。
使用size命令可以直接檢視ELF檔案的各個段的長度:$size SimpleSection.o
這裡寫圖片描述
其中都是十進位制表示,只有hex是十六進位制,dec表示3個段的和。

3.3.1 程式碼段
輸入命令:$objdump -s -d SimpleSection.o(引數-s可以將段的內容用十六進位制打印出來,引數-d可以將所有包含指令的段反彙編)
這裡寫圖片描述

3.3.2 資料段和只讀資料段
.data段儲存的是初始化了的全域性靜態變數和區域性靜態變數,即global_init_varabal和static_var。這兩個變數一共8個位元組,因此,.data這個段的大小為8個位元組。
程式呼叫printf時,用到了一個字串常量”%d\n”,它是一種只讀資料,所以它被放到了.rodata段。且這個字串常量以\0結尾。
注意到.data的前4個位元組是0x54、0x00、0x00、0x00。這個值剛好是golbao_init_varabal,即十進位制的84,後一個位元組是static_init_var,是85.
這裡寫圖片描述
這裡寫圖片描述

3.3.3 BSS段
.bss段存放的是未初始化的全域性變數和區域性靜態變數。即上述程式碼中的global_uninit_var和static_var2就是被存放在.bss段。但我們看到的是4個位元組而不是8個位元組。
這裡寫圖片描述
這是因為static_var2被存放在了.bss段,而global_uninit_var卻沒有被存放在任何段,只是一個未定義的”COMMON符號”。這其實跟不同的語言和不同的編譯器實現有關,有些編譯器會將全域性靜態未初始化變數存放在.bss段,有些則不放,只是預留一個未定義的全域性變數符號,等到最終連結成可執行檔案的時候再在.bss段分配空間。如果將global_uninit_var加上staic就會被放到.bss段。

3.3.4 其他段
除了.text、.data、.bss這3個最常用的段之外,ELF檔案也有可能包含其他的段,用來儲存與程式相關的其他資訊。
這裡寫圖片描述

3.3.5 自定義段
正常情況下,GCC編譯出來的目標檔案中,程式碼會被放到.text段,全域性變數和靜態變數會被放到.data和.bss段,但有時候你可能希望變數或部分程式碼放到指定的段中,以實現某些特定的功能。
GCC提供了一個擴充套件基址,使得程式設計師可以指定變數所處的段:
attribute((section(“FOO”))) int global = 42;
attribute((section(“BAR”))) void foo(){}
我們在全域性變數或函式之前加上“attribute((section(“name”)))”屬性就可以把相應的變數或函式放到以”name”作為段名的段中。