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)