GCC全过程详解+剖析生成的.o文件
使用GCC編譯一個.c文件影藏了哪些過程?
?
GCC四步詳解
第一步:預處理(也叫預編譯)
????????gcc -E ?hello.c ?-o hello.i
? ??? ??或者 cpp hello.c > hello.i ? ? 【cpp是預編譯器】
? ??? ??將所有#define刪除,并且展開所有的宏定義
? ??? ??處理所有的條件預編譯指令,如#if #ifdef ?#undef ?#ifndef ?#endif #elif
? ??? ??處理#include,將包含的文件插入到此處,這是一個遞歸的過程
? ??? ??刪除所有注釋 ? // ? /* */
? ??? ??添加行號和文件名標識,以便于編譯時產生的錯誤警告能顯示行號
? ??? ??保留#pragma編譯器指令
第二步:編譯
? ??? ??gcc ?-S ?hello.i ? -o ?hello.s
? ??? ??將預處理完的.i文件進行一系列的詞法分析、語法分析、語義分析及優
? ??? ??化后生成相應的匯編代碼文件,這是整個程序構建的最核心的部分,也是最復雜的部分
第三步:匯編
????????gcc ?-c ?hello.s ?-o ?hello.o或者 as ?hello.s -o ?hello.o
????????匯編是將第二步生成的匯編代碼變成機器可執行的指令,每一個匯編語句幾乎都對應一條機器指令
第四步:鏈接
?????????鏈接動態庫和靜態庫
?
生成的目標文件有什么,什么是目標文件?
目標文件就是源代碼經過編譯后但未進行鏈接的那些中間文件
Linux下的 .o文件就是目標文件,目標文件和可執行文件內容和
格式幾乎都一樣,所以我們可以廣義地將目標文件和可執行文化
看成一類型文件。他們都是按照ELF文件格式存儲的
?
Linux下有哪些ELF類型的文件?
.o文件、可執行文件、核心轉儲文件(core dump)、.so文件(動態鏈
鏈接庫)
?
可執行文件的概貌詳解
File ?Header 、.text section 、.data section 、.bss section
文件頭(File Header)
描述了整個文件的文件屬性,包括目標文件是否可執行、是靜態鏈接還 是動
態鏈接及入口地址、目標硬件、目標操作系統等信息、段表(描述文件中各
個段的偏移位置及屬性等)
代碼段(.text)
存放了程序源代碼編譯后生成的機器指令
數據段(.data)
存放已初始化的全局靜態與非靜態變量和已初始化的局部靜態變量
.bss段
存放未初始化的全局變量(全局靜態和非靜態變量)和局部靜態變量
但是.bss段只是為這些變量預留位置而已,并沒有內容,所以這些變量
在.bss段中也不占據空間
?
深入挖掘 .o文件
使用命令:
?
objdump ?-h ?xxxx.o
????????打印主要段的信息
objdump ?-x ?xxxx.o?
????????????打印更多的詳細信息
objdump ?-s ?xxx.o
????????????將所有段的內容以16進制方式打印出來
objdump ?-d ?xxx.o ?或者-S
????????????將所有包含指令的段反匯編
objdump ? -t ? xxx.o
????????????查看所有的符號以及他們所在段
readelf ?-h ? xxx.o
????????????查看.o文件的文件頭詳細信息
readelf ? -S ? xxx.o
????????????顯示.o文件中的所有段,即查看段表
size xxx.o
????????????查看.o文件中各個段所占大小
nm xxx.o?
????????????查看.o文件中所有的符號
使用命令gcc -c test.c編譯下面這個test.c程序生成test.o文件,然后查看test.o文件結構
?
test.c
?/* this is a test code */
/* test.c */
int printf(const char *format, ...);
int g_var2 = 10;
int g_var2;
void func(int i)
{
printf("%d\n",i);
}
int main(void)
{
static int static_var1 = 20;
static int static_var2;
int var3 = 1;
int var4;
func(static_var1 + static_var2 + var3 + var4);
return var3;
}
然后查看生成的test.o文件的結構
objdump -h test.o
行:
????.text ?:代碼段(存放函數的二進制機器指令)
????.data :數據段(存已初始化的局部/全局靜態變量、未初始化的全局靜態變量)
????.bss ?:bss段(聲明未初始化變量所占大小)
????.rodata :只讀數據段(存放 " " 引住的只讀字符串)
????.comment :注釋信息段
????.node.GUN-stack :堆棧提示段
列:
????Size:段的長度
????File Off :段的所在位置(即距離文件頭的偏移位置)
段的屬性:
????CONTENTS:表示該段在文件中存在
????ALLOC :表示只分配了大小,但沒有存內容
?
?
關于.bss段
我們說.bss段是存放未初始化的全局變量(靜態與非靜態)和局部靜態變量的
所以我們程序中的g_var2和stactic_var2應該都在.bss段中被預留位置,所以
.bss段的size應該是8個字節,但是結果卻是4個字節,怎么回事呢?
這就是不用的編譯器實現不一樣的原因了,有些編譯器會將未初始化的全局非靜態變量放在.bss段,有些則不放,只是預留一個未定義的全局變量符號,等到最終鏈接成可執行文件的時候再在.bss段分配空間。而我的編譯器是沒有將g_var2(全局未初始化的非靜態變量)放在任何段
下面讓我們真正的查看一下g_var2
首先,我們使用 ?readelf -S ?test.o ?查看段表(主要為了查看每個段的段號)
然后我們再使用 readelf -s ?test.o看一下符號表(我們定義的變量名都是符號,包括函數名)
?
符號表里會顯示這個符號所在的位置
我們看到static_var1和g_var1所在段的段號為3(3是.data段),static_var2所在段的段號為4(4是.bss段),而g_var2卻沒有被放入任何一個段,只是用COM標記了一下,那這個COM表示什么意思呢?COM標記的符號被稱為弱符號,一個變量名是弱符號,則這個變量的大小在編譯的時候不能被確定,而在鏈接之后才能確定該變量的大小。test.o文件在鏈接之后,g_var2會被放入.bss段(當然,也只是說明g_var2所需要的空間大小,并不會存放內容),而在程序運行的時候g_var2這樣的變量才會真正去占用內存空間
強制將某變量或者某函數放入某個段
__attribute__((section(".data"))) ?int ? g_var2; ? //強制將g_var2放入.data段中
?
各種變量所在位置總結
????全局已初始化非靜態變量、局部已初始化靜態變量會被放入.data段
????全局未初始化靜態變量會被放入.bss段
????全圖未初始化非靜態變量不會被放入任何一個段,只是用COM標記一下
總結
以上是生活随笔為你收集整理的GCC全过程详解+剖析生成的.o文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cotex-M内核双堆栈指针MSP和PS
- 下一篇: git checkout -b dev