elf各個段介紹(堆棧基礎知識)
段基礎
段表(Section Header Table)是一個以 Elf32_Shdr 結構體為元素的數組,每個結構體對應一個段,它描述了各個段的信息。ELF 文件頭的 e_shoff 成員給出了段表在 ELF 中的偏移,e_shnum 成員給出了段描述符的數量,e_shentsize 給出了每個段描述符的大小。
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
使用 readelf 命令查看目標文件中完整的段:
$ readelf -S elfDemo.o
There are 15 section headers, starting at offset 0x41c:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .group GROUP 00000000 000034 000008 04 12 16 4
[ 2] .text PROGBITS 00000000 00003c 000078 00 AX 0 0 1
[ 3] .rel.text REL 00000000 000338 000048 08 I 12 2 4
[ 4] .data PROGBITS 00000000 0000b4 000008 00 WA 0 0 4
[ 5] .bss NOBITS 00000000 0000bc 000004 00 WA 0 0 4
[ 6] .rodata PROGBITS 00000000 0000bc 000004 00 A 0 0 1
[ 7] .text.__x86.get_p PROGBITS 00000000 0000c0 000004 00 AXG 0 0 1
[ 8] .comment PROGBITS 00000000 0000c4 000012 01 MS 0 0 1
[ 9] .note.GNU-stack PROGBITS 00000000 0000d6 000000 00 0 0 1
[10] .eh_frame PROGBITS 00000000 0000d8 00007c 00 A 0 0 4
[11] .rel.eh_frame REL 00000000 000380 000018 08 I 12 10 4
[12] .symtab SYMTAB 00000000 000154 000140 10 13 13 4
[13] .strtab STRTAB 00000000 000294 0000a2 00 0 0 1
[14] .shstrtab STRTAB 00000000 000398 000082 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
注意,ELF 段表的第一個元素是被保留的,類型為 NULL。
下面將介紹三個非常重要的段

一般情況,一個程序本質上都是由 bss段、data段、text段三個段組成——這是計算機程序設計中重要的基本概念。
text段:就是放程序代碼的,編譯時確定,只讀;
data段:存放在編譯階段(而非運行時)就能確定的數據,可讀可寫。也就是通常所說的靜態存儲區,賦了初值的全局變量和賦初值的靜態變量存放在這個區域,常量也存放在這個區域;
bss段:定義而沒有賦初值的全局變量和靜態變量,放在這個區域
.bss段
在采用段式內存管理的架構中(比如intel的80x86系統),bss段(Block Started by Symbol segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域,一般在初始化時bss 段部分將會清零(bss段屬于靜態內存分配,即程序一開始就將其清零了)。比如,在C語言程序編譯完成之后,已初始化的全局變量保存在.data 段中,未初始化的全局變量保存在.bss 段中。
.text段
用于存放程序代碼的區域, 編譯時確定, 只讀。更進一步講是存放處理器的機器指令,當各個源文件單獨編譯之后生成目標文件,經連接器鏈接各個目標文件并解決各個源文件之間函數的引用,與此同時,還得將所有目標文件中的.text段合在一起,但不是簡單的將它們“堆”在一起就完事,還需要處理各個段之間的函數引用問題。
.data段
用于存放在編譯階段(而非運行時)就能確定的數據,可讀可寫。也是通常所說的靜態存儲區,賦了初值的全局變量、常量和靜態變量都存放在這個域。
關于.bss段和.data段更詳細的區別我們用下面兩個程序來介紹
程序一
int array[65537];
void main()
{
......
}
程序二
int array[65537] = {1, 2, 3, 4, 5, 6 };
void main()
{
......
}
發現程序2編譯之后所得的可執行文件比程序1大得多。
區別很明顯,程序1位于bss段,程序2位于data段,兩者的區別在于:
- 全局的未初始化變量存在于bss段中,具體體現為一個占位符,全局的已初始化變量存于data段中,而函數內的自動變量都在棧上分配空間。
- bss不占用可執行文件空間,其內容由操作系統初始化(清零),裸機程序需要自行手動清零。
- 而data段則需要占用可執行文件空間,其內容由程序初始化,因此造成了上述情況。
堆棧基礎知識
申請方式
- stack: 由系統自動分配。 int a; 系統自動在棧中為a開辟空間
- heap: 需要程序員自己申請,并指明大小(例如malloc)。
申請后系統的響應
- 棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
- 堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。
申請大小的限制
- 棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
- 堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
申請效率的比較:
- 棧:由系統自動分配,速度較快。但程序員是無法控制的。
- 堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活
堆和棧中的存儲內容
- 棧:在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。 - 堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
參考