C程序是如何跑起來的01 —— 可執(zhí)行文件的構(gòu)成
ubuntu 20.04 使用 arm-linux-gnueabihf-gcc 7.5.0。
main.c:
calc.h:
calc.c:
編譯:
交叉編譯生成 a.out 可執(zhí)行文件,文件類型是 32 位 ARM 平臺可執(zhí)行文件。
readelf 工具由編譯器提供,用來列出關(guān)于可執(zhí)行文件的內(nèi)容的相關(guān)信息。
使用格式如下:
(1)查看可執(zhí)行文件的頭部 信息
(2)查看 section header
一個可執(zhí)行文件由一系列 section 構(gòu)成,section 稱為段,包括:代碼段 text、只讀數(shù)據(jù)段 rodata、數(shù)據(jù)段 data、bss 段等。
每個 section 用一個section header描述,包括段名、段的類型、段的起始地址、段的偏移、段的大小等。
將可執(zhí)行文件的所有 section header 集合到一起就是 section header table,使用 readelf 的-S參數(shù)查看的就是該表。
在程序編譯的時候,對 C 語言代碼中定義的函數(shù)、變量、未初始化的全局變量進(jìn)行編譯分類,放置在不同的段中:
BSS 段比較特殊,未初始化的全局變量和靜態(tài)變量都會放置到 bss 段中,但因為這些變量的值都是 0,沒有必要再開辟空間存儲,所以在可執(zhí)行文件中 bss 段是不占用空間的。
但是 BSS 段的大小、起始地址、各個變量的地址信息都會分別保存在 section header table 和符號表 symtab 中,當(dāng)程序運(yùn)行的時候,加載器會根據(jù)這些信息在內(nèi)存中緊挨著數(shù)據(jù)段之后的空間,為 BSS 段開辟一片存儲空間,為各個變量分配存儲空間。
總而言之:BSS 段在可執(zhí)行文件中不占用空間,在程序運(yùn)行的時候才分配對應(yīng)的空間。
如果在編譯時開啟了調(diào)試選項,則可執(zhí)行文件中還會有 .debug section,用來保存可執(zhí)行文件中每一條二進(jìn)制指令對應(yīng)的源碼位置信息,根據(jù)這些信息,GDB 調(diào)試器就可以支持源碼級的單步調(diào)試。
在最后環(huán)節(jié),編譯器還會在可執(zhí)行文件中添加一些其它的 section,比如 .init section,這些代碼來自 C 語言運(yùn)行庫的一些匯編代碼,用來初始化 C 程序所依賴的環(huán)境。
學(xué)習(xí)目的
一、編譯環(huán)境搭建
二、程序源碼
#include
#ifndef _CALC_H_ #define _CALC_H_ int add(int a, int b); int sub(int a, int b); #endif
#include "calc.h" int add(int a, int b) { return a + b;
} int sub(int a, int b) { return a - b;
}
arm-linux-gnueabihf-gcc main.c calc.c
三、readelf 工具
Usage: readelf
四、可執(zhí)行文件的組成結(jié)構(gòu)
參考資料