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 平臺(tái)可執(zhí)行文件。
readelf 工具由編譯器提供,用來列出關(guān)于可執(zhí)行文件的內(nèi)容的相關(guān)信息。
使用格式如下:
(1)查看可執(zhí)行文件的頭部 信息
(2)查看 section header
一個(gè)可執(zhí)行文件由一系列 section 構(gòu)成,section 稱為段,包括:代碼段 text、只讀數(shù)據(jù)段 rodata、數(shù)據(jù)段 data、bss 段等。
每個(gè) section 用一個(gè)section header描述,包括段名、段的類型、段的起始地址、段的偏移、段的大小等。
將可執(zhí)行文件的所有 section header 集合到一起就是 section header table,使用 readelf 的-S參數(shù)查看的就是該表。
在程序編譯的時(shí)候,對(duì) C 語言代碼中定義的函數(shù)、變量、未初始化的全局變量進(jìn)行編譯分類,放置在不同的段中:
BSS 段比較特殊,未初始化的全局變量和靜態(tài)變量都會(huì)放置到 bss 段中,但因?yàn)檫@些變量的值都是 0,沒有必要再開辟空間存儲(chǔ),所以在可執(zhí)行文件中 bss 段是不占用空間的。
但是 BSS 段的大小、起始地址、各個(gè)變量的地址信息都會(huì)分別保存在 section header table 和符號(hào)表 symtab 中,當(dāng)程序運(yùn)行的時(shí)候,加載器會(huì)根據(jù)這些信息在內(nèi)存中緊挨著數(shù)據(jù)段之后的空間,為 BSS 段開辟一片存儲(chǔ)空間,為各個(gè)變量分配存儲(chǔ)空間。
總而言之:BSS 段在可執(zhí)行文件中不占用空間,在程序運(yùn)行的時(shí)候才分配對(duì)應(yīng)的空間。
如果在編譯時(shí)開啟了調(diào)試選項(xiàng),則可執(zhí)行文件中還會(huì)有 .debug section,用來保存可執(zhí)行文件中每一條二進(jìn)制指令對(duì)應(yīng)的源碼位置信息,根據(jù)這些信息,GDB 調(diào)試器就可以支持源碼級(jí)的單步調(diào)試。
在最后環(huán)節(jié),編譯器還會(huì)在可執(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)
參考資料