Linux下可執行文件格式詳解

Linux下面,目標文件、共享對象文件、可執行文件都是使用ELF文件格式來存儲的。程序經過編譯之後會輸出目標文件,然後經過鏈接可以產生可執行文件或者共享對象文件。Linux下面使用的ELF文件和Windows操作系統使用的PE文件都是從Unix系統的COFF文件格式演化來的。 

我們先來了解一些基本的想法。

首先,最重要的思路是一個程序從人能讀懂的格式轉換爲供操作系統執行的二進制格式之後,代碼和數據是分開存放的,之所以這樣設計有這麼幾個原因:

1、程序執行之後,代碼和數據可以被映射到不同屬性的虛擬內存中。因爲代碼一般是隻讀的,而數據是可讀可寫的;

2、現代CPU有強大的緩存體系。程序和代碼分離可以提高程序的局部性,增加緩存命中的概率;

3、還有最重要的一個原因是當有多個程序副本在運行的時候,只讀部分可以只在內存中保留一份,這樣大大節省了內存。

在ELF的定義中,把他們分開存放的地方稱爲一個 Section ,就是一個段。

一個ELF文件中重要的段包括:

.text 段:存儲 只讀程序

.data 段:存儲 已經初始化的全局變量和靜態變量

.bss 段:存儲 未初始化的全局變量和靜態變量,因爲這些變量的值爲0,所以這個段在文件當中不佔據空間

.rodata 段:存儲 只讀數據,比如字符串常量


我們用一個例子來看一下ELF文件的格式到底是什麼。首先,在Linux下編寫一個C程序:SimpleSection.c

  1. int printf(const char *format, ... );  
  2.   
  3. int global_init_var = 16;  
  4. int global_unint_var;  
  5.   
  6. void func1 (int );  
  7.   
  8. int main()  
  9. {  
  10.     static int static_var = -32;  
  11.     static int static_var_uninit;  
  12.   
  13.     int a = 1;  
  14.     int b;  
  15.   
  16.     func1(static_var + global_init_var + a + b);  
  17.   
  18.     return a;  
  19. }  
  20.   
  21. void func1 (int i)  
  22. {  
  23.     printf("%d\n", i);  
  24. }  

然後,產生目標文件:
  1. [root@xuxingwang-centos Program]# gcc -c SimpleSection.c  
  2. [root@xuxingwang-centos Program]# file SimpleSection.o  
  3. SimpleSection.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped  

file命令的結果也告訴我們,這是一個32位ELF的文件,類型是 relocatable ,就是可重定位。所以目標文件又叫做可重定位文件。

elf文件的最開始是elf文件頭信息,32位有52個字節組成。我們可以使用 readelf 工具來查看一下:

  1. [root@xuxingwang-centos Program]# readelf -h SimpleSection.o  
  2. ELF Header:  
  3.   Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  
  4.   Class:                             ELF32  
  5.   Data:                              2's complement, little endian  
  6.   Version:                           1 (current)  
  7.   OS/ABI:                            UNIX - System V  
  8.   ABI Version:                       0  
  9.   Type:                              REL (Relocatable file)  
  10.   Machine:                           Intel 80386  
  11.   Version:                           0x1  
  12.   Entry point address:               0x0  
  13.   Start of program headers:          0 (bytes into file)  
  14.   Start of section headers:          224 (bytes into file)  
  15.   Flags:                             0x0  
  16.   Size of this header:               52 (bytes)  
  17.   Size of program headers:           0 (bytes)  
  18.   Number of program headers:         0  
  19.   Size of section headers:           40 (bytes)  
  20.   Number of section headers:         11  
  21.   Section header string table index: 8  

Entry point address 指的是程序入口地址,如果是可執行文件,這個字段會有值;

他之前的字段是一些說明字段;

Start of program headers 指的是 程序頭表 的起始位置。程序頭表 是從裝載視圖的角度對elf的各個段進行的分類信息;結構和段表相似;

Start of section headers 指出了elf除文件頭以外的最重要的信息:段表 的起始位置。段表包含了各個段的名稱、屬性、大小、位置等重要信息。操作系統首先找到段表,然後根據段表的信息去找到各個段。段表是一個類似數組的結構,一個段的信息是這個數組的一個元素。

Size of this header 指的是頭文件大小,32位都是 52 個字節,0x34個字節。

Size of program headers 指的是每個 程序頭表 的大小。

Number of program headers 指的是 程序頭表 的數目。

Size of sections headers 指的是每個 段表 的大小;

Number of section headers 指的是 段表的數量;

Section header string table index 指出了段表當中用到的字符串表在段表中的下標。


文件頭之後,緊跟着的是 程序頭,因爲目標文件沒有鏈接,所以沒有裝載信息。我們這裏可以先不理會這個東西,以後專門再說他。

程序頭之後就是各個段的數據,我們用工具查看一下:

  1. [root@xuxingwang-centos Program]# readelf -S SimpleSection.o  
  2. There are 11 section headers, starting at offset 0xe0:  
  3.   
  4. Section Headers:  
  5.   [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al  
  6.   [ 0]                   NULL            00000000 000000 000000 00      0   0  0  
  7.   [ 1] .text             PROGBITS        00000000 000034 000020 00  AX  0   0  4  
  8.   [ 2] .rel.text         REL             00000000 0003f4 000010 08      9   1  4  
  9.   [ 3] .data             PROGBITS        00000000 000054 000008 00  WA  0   0  4  
  10.   [ 4] .bss              NOBITS          00000000 00005c 000004 00  WA  0   0  4  
  11.   [ 5] .rodata           PROGBITS        00000000 00005c 000004 00   A  0   0  1  
  12.   [ 6] .comment          PROGBITS        00000000 000060 00002d 01  MS  0   0  1  
  13.   [ 7] .note.GNU-stack   PROGBITS        00000000 00008d 000000 00      0   0  1  
  14.   [ 8] .shstrtab         STRTAB          00000000 00008d 000051 00      0   0  1  
  15.   [ 9] .symtab           SYMTAB          00000000 000298 0000f0 10     10  10  4  
  16.   [10] .strtab           STRTAB          00000000 000388 00006b 00      0   0  1  
  17. Key to Flags:  
  18.   W (write), A (alloc), X (execute), M (merge), S (strings)  
  19.   I (info), L (link order), G (group), x (unknown)  
  20.   O (extra OS processing required) o (OS specific), p (processor specific)  

各個字段意思依次是:段序號、段名稱、段類型、段虛擬地址、偏移量、大小、ES、標誌、Lk、Inf、對齊。

沒有解釋的列可以先不考慮,我們先關注其他幾個列。

第0個段是爲了讀取的時候下標不用減1。

緊跟着的就是代碼段,偏移量爲0x34,就是說在文件頭結尾之後馬上就是代碼段;

代碼段之後,偏移量 0x54 的地方就是 數據段,佔8個字節,就是程序中已經被賦值的一個全局變量和一個靜態變量;

緊接着是.bss段,這裏只存儲了一個static變量,因爲 未初始化的那個全局變量被一種優化機制存儲到了 .common 段,這裏可以不做理會;

然後是隻讀數據段.rodata,這裏存儲的是 printf 裏面的 %d\n 這三個字符,外加結束符\0,總共4個字節的空間


我們根據Size這一列來算一下這些段總共佔據的空間,(.bss由於不佔空間,不用算進來):

.text 0x20

.data 0x8

.rodata 0x4

.comment 0x2d

.shstrtab 0x51

.rel.text 0x10

.symtab 0xf0

.strtab 0x6b

這裏的每一個段都有一個段表元素來描述,總共11個。從頭文件得知,每個元素的大小爲40字節。也就是說段表總共佔了 0x1b8 個字節的空間。而且段表的開始地址由於內存對齊需要,中間空了2個字節。因爲段表的開始地址是第224個字節;

.rel.text 的開始地址也由於內存對齊的要求,補了一個空字節。

在加上頭文件的 0x34 個字節,總共加起來是   1028 字節。

  1. [root@xuxingwang-centos Program]# ls -al SimpleSection.o  
  2. -rw-r--r-- 1 root root 1028 Aug 21 16:09 SimpleSection.o  

這個目標文件的大小恰好是1028個字節。


轉自:http://blog.csdn.net/topasstem8/article/details/38730971

發佈了34 篇原創文章 · 獲贊 5 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章