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文件中所有的符號


編譯下面這個test.c程序生成.o文件,然後查看.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;
}

 


gcc -c test.c
然後查看生成的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標記一下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章