ELF Structure
ELF Header(檔案標頭)
位於檔案最開頭,記錄這個檔案的基本資訊:
- 檔案類型:是目標檔(.o)、可執行檔、還是動態函式庫(.so)
- 處理器架構:x86、ARM、RISC-V 等
- 位元組順序:big-endian 或 little-endian
- 程式起始執行位址
- 其他兩個重要表格(Program Header Table 和 Section Header Table)的位置
Program Header Table(程式標頭表)
只有可執行檔需要這個表格。 它指導作業系統如何將檔案內容載入到記憶體。
每一項記錄一個 segment(記憶體區段),包含:
- 這個 segment 在檔案中的位置
- 要載入到記憶體的哪個位址
- 大小
- 存取權限:可讀、可寫、可執行
例如:程式碼通常放在可讀可執行的 segment,資料則放在可讀可寫的 segment。
.text Section(程式碼區段)
存放編譯後的機器指令,也就是程式實際執行的內容。
- 你寫的 C 程式碼 → 編譯器轉成組合語言 → 組譯器轉成機器碼 → 存放在這裡
- 執行時的權限:可讀、可執行,但不可寫(防止程式意外修改自己的指令)
- 例如:
int add(int a, int b) { return a + b; }編譯後的指令就在這裡
區域變數定義也存放在這裡,因為區域變數是執行到此 instruction 才會被分配到 stack frame 上
.data Section(已初始化資料區段)
存放有給初始值的全域變數和靜態變數。
- 例如:
int count = 10;或static char message[] = "Hello"; - 程式載入時,會將這些變數連同初始值一起複製到記憶體
- 執行時的權限:可讀、可寫
.bss Section(未初始化資料區段)
存放沒有初始值或初始值為 0 的全域變數和靜態變數。
- 例如:
int array[1000];或static int counter; - 特點:在檔案中不佔實際空間,只記錄需要多少記憶體
- 程式載入時,作業系統才分配記憶體並全部設為 0
- 這樣設計可以大幅減少執行檔大小
.symtab Section(符號表)
記錄程式中所有符號(函數名稱、變數名稱)的資訊。
每個符號包含:
- 類型:是函數還是變數
- 所在位置:屬於哪個 section
- 記憶體位址和大小
- 作用範圍:local、global 或 weak
用途:
- 鏈接階段:找到函數和變數的實際位址(例如找到
printf的位址) - 除錯時:讓除錯器顯示有意義的名稱,而不只是記憶體位址
.rel.text Section(程式碼重定位資訊)
記錄 .text 中哪些位址需要在鏈接或載入時修正。
- 編譯時無法確定最終的記憶體位址,所以先用暫時位址
- 例如:呼叫其他檔案定義的函數時,需要等鏈接後才能填入正確位址
- 這個 section 就記錄「哪些地方需要修正」
- 例如:
extern
.rel.data Section(資料重定位資訊)
與 .rel.text 類似,但針對 .data 中的指標資料。
- 例如:
int *ptr = &global_var; - 編譯時無法確定
global_var的最終位址 - 記錄這個指標的位置,等鏈接時再填入正確位址
- 在動態函式庫中特別重要(因為載入位址每次可能不同)
.debug Section(除錯資訊)
使用 gcc -g 編譯時會產生的額外資訊。
包含:
- 原始碼檔名
- 程式碼行號與機器碼的對應
- 變數的類型資訊
- 函數的參數資訊
用途:讓除錯器(如 gdb)能夠顯示原始碼、設定中斷點、檢查變數值
Section Header Table(區段標頭表)
列出所有 sections 的詳細資訊。
每一項包含:
- Section 名稱和類型
- 在檔案中的位置
- 應該載入到記憶體的哪個位址
- 大小
- 對齊要求
用途:
- 對於目標檔(.o):鏈接器需要它來組合多個檔案
- 對於可執行檔:主要供分析工具使用(如 objdump、readelf)