跳转至

.symtab: Symbol Table

概述

每個目標文件都會有一個符號表,熟悉編譯原理的就會知道,在編譯程序時,必須有相應的結構來管理程序中的符號以便於對函數和變量進行重定位。

此外,鏈接本質就是把多個不同的目標文件相互“粘”在一起。實際上,目標文件相互粘合是目標文件之間對地址的引用,即函數和變量的地址的相互引用。而在粘合的過程中,符號就是其中的粘合劑。

目標文件中的符號表包含了一些通用的符號,這部分信息在進行了 strip 操作後就會消失。這些符號信息可能包括變量名、函數名。

符號表可以被視爲一個數組,數組中的每一個元素都是一個結構體,具體如下

typedef struct
{
    Elf32_Word st_name;     /* Symbol name (string tbl index) */
    Elf32_Addr st_value;    /* Symbol value */
    Elf32_Word st_size;     /* Symbol size */
    unsigned char st_info;  /* Symbol type and binding */
    unsigned char st_other; /* Symbol visibility */
    Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;

每個字段的含義如下

字段 說明
st_name 符號在字符串表中對應的索引。如果該值非 0,則它表示了給出符號名的字符串表索引,否則符號表項沒有名稱。 注:外部 C 符號在 C 語言和目標文件的符號表中具有相同的名稱。
st_value 給出與符號相關聯的數值,具體取值依賴於上下文,可能是一個正常的數值、一個地址等等。
st_size 給出對應符號所佔用的大小。如果符號沒有大小或者大小未知,則此成員爲0。
st_info 給出符號的類型和綁定屬性。之後會給出若干取值和含義的綁定關係。
st_other 目前爲 0,其含義沒有被定義。
st_shndx 如果符號定義在該文件中,那麼該成員爲符號所在節在節區頭部表中的下標;如果符號不在本目標文件中,或者對於某些特殊的符號,該成員具有一些特殊含義。

其中,符號表中下標 0 存儲了符號表的一個元素,同時這個元素也相對比較特殊,作爲所有未定義符號的索引,具體如下

名稱 取值 說明
st_name 0 無名稱
st_value 0 0 值
st_size 0 無大小
st_info 0 無類型,局部綁定
st_other 0 無附加信息
st_shndx 0 無節區

st_value

在 Linux 的 ELF 文件中,具體說明如下

  1. 該符號對應着一個變量,那麼表明該變量在內存中的偏移。我們可由這個值獲取其文件偏移
    1. 獲取該符號對應的 st_shndx,進而獲取到相關的節區。
    2. 根據節區頭元素可以獲取節區的虛擬基地址和文件基地址。
    3. value-內存基虛擬地址=文件偏移-文件基地址
  2. 該符號對應着一個函數,那麼表明該函數在文件中的起始地址。

st_info

st_info 中包含符號類型和綁定信息,這裏給出了控制它的值的方式具體信息如下

#define ELF32_ST_TYPE(i)    ((i)&0xf)
#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))

Symbol Type

可以看出 st_info 的低 4 位表示符號的類型,具體定義如下

名稱 取值 說明
STT_NOTYPE 0 符號的類型沒有定義。
STT_OBJECT 1 符號與某個數據對象相關,比如一個變量、數組等等。
STT_FUNC 2 符號與某個函數或者其他可執行代碼相關。
STT_SECTION 3 符號與某個節區相關。這種類型的符號表項主要用於重定位,通常具有 STB_LOCAL 綁定。
STT_FILE 4 一般情況下,符號的名稱給出了生成該目標文件相關的源文件的名稱。如果存在的話,該符號具有 STB_LOCAL 綁定,其節區索引是 SHN_ABS 且優先級比其他STB_LOCAL符號高。
STT_LOPROCSTT_HIPROC 13~15 保留用於特定處理器

共享目標文件中的函數符號有比較特殊,當另一個目標文件從共享目標文件中引用一個函數時,鏈接器自動爲被引用符號創建過程鏈接表項。共享目標中除了STT_FUNC , 其它符號將不會通過過程鏈接表自動被引用。

如果一個符號的值指向節內的特定位置,則它的節索引號 st_shndx,包含了它在節頭表中的索引。當一個節在重定位過程中移動時,該符號值也做相應改變,對該符號的引用繼續指向程序中的相同位置。有些特定節索引值具有其他語義。

Symbol Binding

根據 #define ELF32_ST_BIND(i) ((i)>>4) 可以看出 st_info 的高 4 位表示符號綁定的信息。而這部分信息確定了符號的鏈接可見性以及其行爲,具體的取值如下

名稱 取值 說明
STB_LOCAL 0 表明該符號爲局部符號,在包含該符號定義的目標文件以外不可見。相同名稱的局部符號可以存在於多個文件中,互不影響。
STB_GLOBAL 1 表明該符號爲全局符號,對所有將被組合在一起的目標文件都是可見的。一個文件中對某個全局符號的定義將滿足另一個文件對相同全局符號的未定義引用。我們稱初始化非零變量的全局符號爲強符號,只能定義一次。
STB_WEAK 2 弱符號與全局符號類似,不過它們的定義優先級比較低。
STB_LOPROC ~STB_HIPROC 13 這個範圍的取值是保留給處理器專用語義的。

在每個符號表中,所有具有 STB_LOCAL 綁定的符號都優先於弱符號和全局符號。符號表節區中的 sh_info 項所對應的頭部的成員包含第一個非局部符號的符號表索引。

此外,全局符號與弱符號的主要區別如下:

  • 當鏈接器在鏈接多個可重定位目標文件時,不允許定義多個相同名字的 STB_GLOBAL 符號。另一方面,如果存在一個已定義全局符號,則同名的弱符號的存在不會引起錯誤。鏈接器會優先選擇全局定義,忽略弱符號定義。類似的,如果一個公共符號存在(st_shndx域爲SHN_COMMON的符號),則同名的弱符號的存在不會引起錯誤。鏈接器會選擇公共定義,忽略弱符號定義。
  • 當鏈接器尋找文件庫時,它會提取包含未定義全局符號的成員,可能是一個全局符號或者弱符號。鏈接器不會爲了解決未定義的弱符號問題而提取文件,未定義的弱符號的值爲0。

符號取值

不同的目標文件類型對符號表項中 st_value 成員的解釋不同:

  • 在可重定位文件中,st_value 保存了節區索引爲 SHN_COMMON 的符號的對齊約束。
  • 在可重定位文件中,st_value 保存了已定義符號的節區偏移。也就是說,st_value保留了st_shndx 所標識的節區的頭部到符號位置的偏移。
  • 在可執行和共享目標文件中,st_value 包含一個虛地址。爲了使得這些文件的符號對動態鏈接器更有用,節區偏移(針對文件的解釋)給出了與節區號無關的虛擬地址(針對內存的解釋)。

符號表取值在不同的目標文件中具有相似的含義,可以有適當的程序可以採取高效的方法來訪問數據。

st_shndx

特殊的索引及其意義如下

  • SHN_ABS: 符號的取值具有絕對性,不會因爲重定位而發生變化。
  • SHN_COMMON: 符號標記了一個尚未分配的公共塊。符號的取值給出了對齊約束,與節區的 sh_addralign 成員類似。就是說,鏈接編輯器將在地址位於 st_value 的倍數處爲符號分配空間。符號的大小給出了所需要的字節數。
  • SHN_UNDEF: 此索引值表示符號沒有定義。當鏈接編輯器將此目標文件與其他定義了該符號的目標文件進行組合時,此文件中對該符號的引用將被鏈接到實際定義的位置。

如何定位

那麼對於一個符號來說如何定位其對應字符串的地址呢?具體步驟如下

  1. 根據 Section Header Table 中符號節頭中的 sh_link 獲取該符號節中對應符號字符串節在 Section Header Table 中的下標。進而我們就可以獲取對應符號節的地址。
  2. 根據該符號的定義中的 st_name 獲取該符號的偏移,即在對應符號節中的偏移。
  3. 根據上述兩者就可以定位一個符號對應的字符串的地址了。