linux 内核重定位,Linux 内核学习笔记:预备知识之“目标文件”
8種機械鍵盤軸體對比
本人程序員,要買一個寫代碼的鍵盤,請問紅軸和茶軸怎么選?
本文主要闡述 Linux 的目標文件(有可重定位目標文件、可執行目標文件和共享目標文件三種形式),并把重點放在其格式和案例分析上。
注:一般情況下,我們說的目標文件專指可重定位目標文件,而可執行文件專指可執行目標文件,但在本文中,為了使概念更加清晰,我們會使用這兩種文件的全稱。
按照《程序員的自我修養——鏈接、裝載與庫》一書第 3.1 節的說法,從廣義上看,目標文件與可執行文件的格式幾乎是一模一樣的,所以我們可以廣義地將目標文件與可執行文件看成是一種類型的文件。
即把它們統稱為 ELF(Executable Linkable Format)文件。
實際上,可重定位目標文件跟可執行目標文件還是有區別的。目標文件有三種形式:可重定位目標文件(.o文件),包含二進制代碼和數據,其形式可以在編譯時與其他可重定位目標文件合并起來,創建一個可執行目標文件;
可執行目標文件包含二進制代碼和數據,其形式可以被直接拷貝到存儲器并執行。
[僅為完整性,本文不闡述]共享目標文件是一種特殊類型的可重定位目標文件,可以在加載或者運行時被動態地加載到存儲器并鏈接。
我們先來看一下可重定位目標文件和可執行目標文件生成的過程。
可重定位目標文件和可執行目標文件生成的過程
源文件經過以下幾步生成可重定位目標文件和可執行目標文件:預處理(preprocessor):對 #include、#define、#ifdef/#endif、#ifndef/#endif 等宏進行處理
編譯(compiler):將源碼編譯為匯編代碼
匯編(assembler):將匯編代碼匯編為可重定位目標代碼(文件)
鏈接(linker):將可重定位目標代碼鏈接為可執行目標文件
整個過程可用圖表示如下:
圖片來源
接下來,我們分開對可重定位目標文件與可執行目標文件進行闡述。《深入理解計算機系統》一書第 7 章“鏈接”和《程序員的自我修養——鏈接、裝載與庫》一書第 3 章“目標文件里有什么”將作為主要參考資料。
可重定位目標文件
可重定位目標文件格式
可重定位目標文件的典型格式如下圖所示:
圖片來源:《深入理解計算機系統》
其中,ELF Header: 包含了描述整個文件的基本屬性,如 ELF 文件版本、目標機器型號、程序入口地址等。
.text:已編譯程序機器(二進制)代碼。
.rodata:只讀數據,如 printf 語句中的格式串和開關語句的跳轉表。
.data:已初始化的全局(靜態)變量或靜態變量。但如果我們對全局變量或靜態變量賦值為 0,那它會被放到 .bss 段。 (注意全局變量和全局靜態變量的區別:作用域。)
.bss:未初始化的全局變量或靜態變量。該段不占用可重定位目標文件的實際空間,但當它被加載到內存中時,該段內的變量是要占用內存的。
.symtab:一個符號表,它存放在程序中定義和使用的函數和全局變量的信息,但不包含局部變量的條目。
.rel.text:一個 .text 節中位置的列表,當鏈接器把這個可重定位文件和其他文件結合時,需要修改這些位置。一般而言,任何調用外部函數或者引用全局變量的指令都需要修改。另一方面,調用本地函數的指令不用修改。注意,可執行目標文件中并不需要重定位信息,因此通常省略,除非用戶顯式地指示鏈接器包含這些信息。
.ret.data:被模塊引用或定義的任何全局變量的重定位信息。一般而言,任何已初始化的全局變量,如果它的初始值是一個全局變量地址或者外部定義函數的地址,都需要被修改。
.debug:一個調試符號表,其條目是程序中定義的局部變量和類型定義、程序中定義和引用的全局變量,以及原始的 C/C++ 源文件。只有以 -g 選項調用編譯驅動程序時才會得到這個表。
.line:原始 C/C++ 源程序中的行號和 .text 節中機器指令之間的映射。只有以 -g 選項調用編譯驅動程序時才會得到這個表。
.strtab:一個字符串表,其內容包括 .symtab 和 .debug 節中的符號表,以及節頭部中的節名字。字符串表就是以 null 結尾的字符串序列。
Section Header Table:描述了 ELF 文件包含的所有段的信息,如每個段的段名、段的長度、在文件中的偏移、讀寫權限及段的其他屬性。
案例分析
接下來,我們利用《程序員的自我修養——鏈接、裝載與庫》一書第 3 章提供的一個樣例來分析下這些段。
編譯樣例
樣例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30> File Name: SimpleSection.c
> Compile on Linux:
gcc -c SimpleSection.c
> Compile on Windows:
cl SimpleSection.c /c /Za
*************************************/
int (cont char* format, ...);
int global_init_var = 84;// .data
int global_uninit_var;// .bss
void func(int i)
{
printf("%dn", i);
}
int main()
{
static int static_init_var = 85; // .data
static int static_uninit_var; // .bss
int a = 1;// Stack
int b;// Stack
func(static_init_var + static_uninit_var + a + b);
return 0;
}
編譯該程序可得可重定位目標文件 SimpleSection.o:1$ gcc -c SimpleSection.c
查看 ELF Header
我們可以查看該目標文件的 ELF Header:1$ readelf -h SimpleSection.o
并得到如下結果:
我們可以看到,ELF Header 定義了諸多該目標文件的屬性。關于各個屬性詳細信息請參考《程序員的自我修養——鏈接、裝載與庫》一書第 3.4.1 節“文件頭”。
查看 Section Header Table
我們還可以繼續查看一下該目標文件的 Section Header Table:1$ readelf -S SimpleSection.o
并得到如下結果:
結合該圖和上邊關于 ELF Header 的圖,我們可以將 SimpleSection.o 的 Section Header Table 及所有段的位置和長度畫出來:
正如我們在前邊提到的,Section Header Table 描述了 ELF 文件包含的所有段的信息,如每個段的段名、段的長度、在文件中的偏移、讀寫權限及段的其他屬性。具體地,它是利用了Elf32_Shdr (也稱為段描述符)這樣一個結構體來描述每一段的屬性,該結構體長度為 40 個字節。在 SimpleSection.o 文件中總共有 13 個段,也就需要 13 個Elf32_Shdr來描述它們,總共長度為 13 * 40 = 0x208 個字節。另外,Section Header Table 的第一個元素是無效的Elf32_Shdr,它的類型為 NULL。
整個 SimpleSection.o 文件的長度為 1336 個字節,這跟我們用du命令查看的結果是一致的:
關于 Section Header Table 詳細信息請參考《程序員的自我修養——鏈接、裝載與庫》一書第 3.4.2 節“段表”。
可執行目標文件
可執行目標文件格式
可執行目標文件的典型格式如下圖所示:
圖片來源:《深入理解計算機系統》
案例分析
我們還接著可重定位文件的例子繼續分析。
靜態鏈接
我們先將 SimpleSection.o 文件靜態鏈接成可執行文件 SimpleSection.static:1$ gcc -static SimpleSection.o -o SimpleSection.static
再查看下 SimpleSection.static 文件的 Section Header Table:1$ readelf -S SimpleSection.static
并得到如下結果:
具體分析過程跟“可重定位目標文件”部分相同。
動態鏈接
我們先將 SimpleSection.o 文件動態鏈接成可執行文件 SimpleSection.dynamic:1$ gcc SimpleSection.o -o SimpleSection.dynamic
再查看下 SimpleSection.dynamic 文件的 Section Header Table:1$ readelf -S SimpleSection.dynamic
并得到如下結果:
重定位段信息
值得注意的是上圖(動態鏈接)中的兩個屬性為重定位表的段.rel.dyn和.rel.plt。它們跟.rel.text或.rel.data并不一樣。
我們先用readelf -r命令來查看一下可重定位目標文件和可執行目標文件中的可重定位段的區別:可重定位目標文件1$ readelf -r SimpleSection.o
并得到結果如下:
可執行目標文件1$ readelf -r SimpleSection.dynamic
并得到結果如下:
比較上邊兩圖,我們(從Offset屬性)可以知道:.rel.text屬于普通重定位段,由編譯器編譯產生,存在于可重定位目標文件內;用于最終可執行目標文件或者動態庫的重定位。
.rel.dyn和.rel.plt屬于動態重定位段,由鏈接器產生,存在于可執行目標文件或者動態庫內;借助這兩個段可動態修改對應的.got和.got.plt段,從而實現運行時重定位。.rel.dyn對應.got段;.rel.plt對應.got.plt。
后記
本來的打算是先闡述目標文件,再說明如何將可執行文件加載到進程虛擬地址空間,從而達到敘述虛擬地址空間的目的。很明顯,我失誤了。光是目標文件這么少的知識點我都要寫這么長的博文,再加上加載和虛擬地址空間,可以預計博文將會很長。那時,不但讀者受不了,自己也受不了…所以還是將大的主題繼續拆分。關于加載和虛擬地址空間請見下一博文。
總結
以上是生活随笔為你收集整理的linux 内核重定位,Linux 内核学习笔记:预备知识之“目标文件”的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我的世界java 内存_我的世界如何分
- 下一篇: linux 其他常用命令