1. 程式人生 > >GCC 生成的符號表除錯資訊剖析

GCC 生成的符號表除錯資訊剖析

    原文地址:http://blog.csdn.net/KataDoc360/article/details/3898016


    GCC把C語言原始檔('.c')編譯成組合語言檔案('.s');

    彙編器把組合語言檔案翻譯成目標檔案('.o');

    最後由連結器連結所有的目標檔案和有關的庫生成可執行檔案('a.out')。


    如開啟'-g'選項,GCC編譯'.c'檔案時,把附加的除錯資訊插進'.s'檔案,這些除錯資訊經彙編器和連結器稍加轉換一直傳到可執行檔案中。這些除錯資訊包括行號、變數的型別和作用域、函式名字、函式引數和函式的作用域等原始檔的特性。

    

    在某些目標檔案中,除錯資訊用'.stab'打頭的一類彙編指導命令表示,這些指導命令穿插在彙編程式碼中,這種除錯資訊格式叫'Stab',即符號表(Symbol table)。XCOFF和a.out目標檔案格式採用Stab除錯資訊格式。此外,GCC也能在COFF和ECOFF目標檔案格式中產生Stab。如要生成Stab除錯資訊,在GCC編譯原始檔時,開啟編譯選項'-gstabs+'(此選項將產生GNU偵錯程式擴充套件的Stab的除錯資訊)或'-gstabs'。

    

    彙編器處理'.stab'打頭指導命令,把Stab中的除錯資訊填入'.o'檔案的符號表和串表(string table)中,連結器合併所有'.o'檔案生成只含有一個符號表和一個串表的可執行檔案。偵錯程式通過檢索可執行檔案中的符號表和串表來獲得程式的除錯資訊,下面分別介紹Stab的格式,GCC生成Stab和彙編連結器對Stab轉換。


    1、Stab的格式

    Stab彙編指導命令有3種格式:'.stabs'(string), '.stabn'(number)和'.stabd'(dot)。

    在MIPS機器上,GCC採用'.stabn'輸出源程式語句行號的Stab除錯資訊,而未使用'.stabd',因此,在MIPS機器上,GCC生成的帶有Stab除錯資訊的彙編程式碼中只含'.stabs'和'.stabn'兩種彙編指導命令。

    '.stabs'和'.stabn'命令格式如下:

.stabs ″STRING″,TYPE,OTHER,DESC,VALUE
.stabn TYPE,OTHER,DESC,VALUE

 

    下面說明Stab彙編指導命令的各域。

    ″STRING″的一般格式是:″NAME:SYM-DESC TYPE-INFO″

    其中,NAME是由Stab表示的符號的名字,如果Stab表示是一個匿名物件,則NAME可省略,一般以一空格代替。SYM-DESC為一字母,它具體表示Stab所描述的是哪一類符號,例如:

    SYM-DESC為'F',表示Stab描述的是全域性函式;為'f'時,表示區域性函式;為'G'時,表示全域性變數。TYPE-INFO則表示資料型別資訊,它可以是Stab分配給已定義的資料型別的序號,表示對已定義的資料型別的引用;也可以是一串符號,用來定義一種新的資料型別,參見1.3資料型別定義。

    OTHER沒有使用,其值保持零。

    DESC用編譯開關'-gstabs+'編譯原始檔,DESC為源程式的語句行號;用編譯開關'-gstabs'編譯原始檔,DESC為零。

    VALUE可為一符號地址,或為自動變數在當前棧裡相對幀指標的偏移量,或為暫存器變數所分配的暫存器的號碼。

    

    以下各小節將結合例項對Stab描述除錯資訊的格式作具體的闡述。

    1.1 Stab描述程式結構 
    (1)原始檔的名字和路徑

    在含有除錯資訊的彙編程式碼中,第一個Stab彙編指導命令指明所編譯的原始檔的名字,如果開啟GCC編譯開關'-gstabs+',還會指明該源文明所在的目錄。

    例如:

.stabs ″usr/people/ycq/work / ″, 100, 0, 0, $Ltext( ) #100 is N-SO
.stabs ″example.c″, 100, 0, 0, $Ltext( )

    其中TYPE為N-SO,表示該Stab描述的是原始檔的名字或路徑,$Ltext( )表示與該檔案相對應的程式碼區的首地址。


    (2)包含檔案

    描述包含檔案的Stab指明隨後出現的變數、函式等符號所要參考的原始檔,偵錯程式由此查詢到定義該符號的原始檔。

    STRING為被包含檔名,TYPE=N-SOL,VALUE為被包含檔案程式碼區的首地址。

    如:

.stabs ″example.c″, 132, 0, 0, $Ltext1 #132 is N-SOL


    (3)行號

    行號表示彙編程式中的一段程式碼所對應的C源程式的語句行號。

    彙編指導命令採用'gstabn',TYPE=N-SLINE,DESC表示源程式的語句行號,VALUE為該語句行所對應的一段彙編程式碼的起始標號。

    例如:

.stabn 68, 0, 4, $LM6 #68 is N-SLINE

    如果一源程式行所產生的彙編程式碼不連續,可用多條'.stabn'表示,而DESC為同一值。


    (4)函式

    描述函式的Stab,其TYPE為N-FUN,VALUE為函式的符號地址。

    SYM-DESC=F表示該函式為全域性函式,SYM-DESC=f表示該函式的為區域性函式,TYPE-INFO表示該函式的返回值的資料型別。

    下例為Stab描述區域性函式func,其函式返回值為整型。

.stabs ″func: fl ″, 36, 0, 0, func #36 is N-FUN

    (5)巢狀函式

    巢狀函式是GNU C對標準C的擴充,Stab描述巢狀函式與描述一般函式的方式大致相同,區別是在描述巢狀函式時,在TYPE-INFO之後緊接包含該函式的最內層函式。

    下面為一巢狀函式定義的例子,隨後給出了其Stab描述。

int funx (int x)
{
    int funy (int y)
    {
    int funz (int z){return x+y+z; }
    return funz (x+y);
   }
   return funy (x);
}

生成的Stab為:

.stabs ″funz: fl, funy″, 36, 0, 0, funz.5
.stabs ″funy: fl, funx″, 36, 0, 0, funy.2
.stabs ″funx: Fl″, 36, 0, 0, funx
    作用域的描述格式是:TYPE-INFO之後跟','號,然後被描述的函式名跟','號,最後是包含該函式定義的最內層函式的名字。


    (6)塊結構 

    這裡塊結構是指C語言函式定義中表示塊語句開始和結束的左、右括號。

    描述左括號的('{')Stab,其TYPE=N-LBRAC,VALUE為以'$LBB'打頭的彙編語句標號;

    描述右括號('}')的Stab,其TYPE=N-RBRAC,VALUE為以'$LBE'打頭的彙編語句標號。彙編指導命令為'.stabn'。

    例如:

.stabn 192, 0, 0, $LBB2 #192 is N-LBRAC
.stabn 224, 0, 0, $LBE2 #224 is N-RBRAC

    1.2 Stab描述變數

    在C語言裡,根據變數所具有的不同的儲存分配方式,可把變數分為:自動變數、全域性變數、暫存器變數和靜態變數。

    (1)自動變數

    自動變數儲存在當前函式棧裡,因此也叫棧變數。

    Stab描述自動變數時,TYPE為N-LSYM,Stab描述自動變數在當前函式棧裡相對於幀指標的偏移量,SYM-DESC被省缺。

    例如:

.stabs ″x: l″, 128, 0, 0, -12 #128 is N-LSYM

    

    (2)全域性變數

    全域性變數的作用域不侷限於定義它的那個檔案,可為多個檔案使用。

    Stab描述全域性變數時,TYPE為N-GSYM,SYM-DESC為G,VALUE為零,偵錯程式根據全域性變數的外部符號獲得其地址,如:char gvar='c';

    生成的含Stab的彙編程式碼為:

stabs ″gvar: G2″, 32, 0, 0, 0 #32 is N-GSYM
.globl gvar
.data
gvar:
byte 99

    例中,彙編器根據'globl gvar'和'gvar: '產生一個外部符號,偵錯程式由此外部符號獲得全域性變數'gvar'的地址。


    (3)暫存器變數

    暫存器變數的值儲存在暫存器裡。

    Stab描述暫存器變數時,TYPE為N-RSYM,VALUE為暫存器號,SYM-DESC為'r'。

    如:

register int rvar asm ( ″ $30 ″ );

    生成的Stab為:

.stabs ″ rvar: rl″, 64, 0, 0, 30 #64 is N-RSYM


    (4)靜態變數

    在函式內定義的靜態變數具有函式作用域,在函式外定義的靜態變數具有檔案作用域。

    Stab描述靜態變數時,TYPE為N-STSYM表示該變數已初始化,而TYPE為N-LCSYM表示該變數未初始化,VALUE為變數的符號地址,SYM-DESC為'S'時,該變數的作用域為整個檔案,SYM-DESC為'V'時該變數具有函式作用域。

    如:

static int var_init=2;
static int var_noinit;

    假設它們的作用域都為檔案作用域,生成的Stab為:

.tabs ″var_init: Sl″ , 38, 0, 0, var_init #38 is N-STSYM
.stabs ″var_noinit: Sl″ , 40, 0, 0, var_noinit #40 is N-LCSYM

    (5)引數

    C語言中,函式的引數可通過棧或暫存器傳遞,並且通過暫存器傳遞的引數也被保留在棧裡,描述由棧傳遞的引數,TYPE=N-PSYM;VALUE為該引數在當前函式棧裡相對於幀指標的偏移量,SYM-DESC為'p'。

    如:main (int argc, char * *argv) 生成的Stab為:

.stabs ″main: Fl″ , 36, 0, 0, main #36 is N-FUN
.stabs ″argc: pl″ , 160, 0, 0, 68 #160 is N-PSYM
.stabs ″argv: p20= *21= *2″ , 160, 0, 0,72
    暫存器由第2個Stab獲得引數的值,且根據第1個Stab知道該變數為引數。


    1.3 資料型別定義

    Stab採和彙編指導命令'stab'定義資料型別,TYPE域為N-LSYM,VALUE域為零,其″STRING″域中包含型別定義資訊,下面是它的一般格式:

    ″NAME: SYM-DESC TYPE-NUM=TYPE-DESC…″

    NAME為被定義的資料型別的名字;SYM-DESC=T表示聯合、結構和列舉這3種資料型別,SYM-DESC=t表示其它資料型別;TYPE-NUM為一序號,如'1'表示整型,'2'表示字元型等;TYPE-DESC為型別描述器,更精確地對資料型別加以定義,如:TYPE-DESC= * ,表示指向其它型別的指標,TYPE-DESC=r,表示子界型別。這裡介紹內部資料型別和部分其它資料型別的定義。


    (1)內部資料型別

    C語言的內部資料型別包括整型、字元型、浮點型別和'void'型別等,整型和字元型定義成其自身的子界。

    如對字元型(char)的定義:

.stabs ″char: t2 = r2; 0; 127; ″, 128, 0, 0, 0 #128 is N-LSYM

    

    在″STRING″中,'r2'是子界型別定義,表示為'2'號型別(字元型)的子界型別,字元型的下界為0,上界為127,浮點型別被定義為整型的子界型別,與整型定義所不同的是,其上界為零,而下界為一正整數,表示該型別的大小(以位元組為單位)。

    如:

.stabs ″float: t12= rl; 4; 0; ″,
.stabs ″double: t13= rl; 8; 0; ″, 128, 0, 0, 0
    void型別被定義為其自身,即:.stabs ″void: t15 = 15″ , 128, 0, 0, 0


    (2)陣列型別

    定義陣列型別時,在型別描述器(TYPE-DESC)'a'之後跟其下標和其元素的型別資訊,例如:int vector[3];

    生成的彙編程式碼為:

.stabs ″vector: G20= arl; 0; 2; 1 ″, 32, 0, 0, vector
.comm vector, 12

    (3)結構、聯合和列舉型別

    這3種資料型別都用T作為SYM-DESC。當TYPE-DESC=S時表示結構型別,TYPE-DESC=u時表示聯合型別,TYPE-DESC=e表示列舉型別,另外列舉型別和其它兩種資料型別在描述上還有差別,描述列舉型別時,在TYPE-DESC之後跟其元素的名字和值對(NAME:VALUE)。

    如:

enum e_places{first, second=3,last};

    生成的Stab為:

.stabs ″e_places: T22=first: 0, second: 3, last: 4; ″,128, 0, 0, 0

    

    描述結構和聯合這兩種資料型別時,TYPE-DESC之後為型別的大小,然後是對其元素的描述,其元素描述採用這樣的格式:″名字:型別,相對於結構或聯合始地的按位的偏移量,元素所佔的儲存位″。

    如:

struct s_tag
{
int s_int;
float s_float;
};

    生成的Stab為:

.stabs ″s_tag: t17=s_int: l, 0, 32: s_float: 12, 32, 32; ; ″, 128, 0, 0, 0


    2、Stab的生成 
    GCC編譯C語言原始檔時,如果開啟編譯選項'-gstabs'或'-gstabs+',則其生成的彙編程式碼中就包含有Stab除錯資訊,以'.stab'打頭的彙編指導命令穿插在彙編程式碼中間,下面介紹Stab在彙編程式碼中出現的形式以及GCC編譯軟體中與Stab生成相關的幾個主要函式。

    Stab在彙編程式碼中按其生成的順序可分為三部分,如下所示。


    1) Stab for the source file 

.. Stab for 'source files' 
.. Stab for 'Defining types' 
.. Stab for 'Initialized global & file-scope static variables'


    2) Stab for each function defined in main source file or include file 

.. Stab for 'Include files' 
.. … 
.. Stab for 'Line numbers' 
.. … 
.. Stab for 'function'or'procedure' 
.. Stab for 'Parameters' 
.. Stab for 'Automatic & Function-scope static variables'Stab for 'Block structures'

    3) Stab for 'uninutialized global & file-scope static variables'


    第一部分,在檔案開始處的Stab,包括被編譯的主檔案的名字,C語言內部定義的資料型別,然後是C原始檔中定義的初始化全域性變數和初始化的具有檔案作用域的靜態變數。 

    第二部分,對應檔案(包括被編譯的主檔案和包含檔案)中定義的每個函式,分別產生這麼一串Stab並插入函式的彙編程式碼中,包括該函式由哪個檔案定義,彙編語句與C源程式的語句行的對應關係,最後是被定義的函式名字、函式的引數、自動變數、本函式中定義的靜態變數以及塊語句結構。

    第三部分,在所有函式的彙編程式碼之後出現的Stab,包括未初始化全域性變數和未初始化的具有檔案作用域的靜態變數。


    GCC編譯軟體中用於輸出Stab除錯資訊的函式定義在dbxout.c中,下面列出一些主要函式的名字和功能。

    dbxout_init輸出被編譯的主原始檔和C語言內部定義的資料型別的Stab。

    dbxout_source_file輸出包含檔案和Stab。

    dbxout_function輸出函式名字、函式的引數、自動變數、函式中定義的靜態變數以及塊結構的Stab。

    dbxout_source_line輸出源程式語句行號的Stab。在MIPS機器上由定義在檔案mips.c中的函式mips_output_source_line輸出源程式語句行號的Stab。


    3、Stab的轉換 
    下面描述可執行檔案('a.out')中符號表入口(symbol table entries)的格式,以及其與彙編指導命令的對映關係,並簡要介紹彙編器和連結器怎樣對符號表裡的資料進行轉換。

    每當彙編器遇到符號表彙編指導命令('.stab'),就把其各域填到其輸出檔案('.o')的符號表入口的相應的各域中,如果stab含有串域('string'),在符號表裡用一指標指向該串在串表的起始位置。

    在'a.out'檔案中符號表入口的格式如下:

struct internal_nlist
{
unsigned long n_strx; /* index into string table of name */
unsigned char n_type; /* type of symbol */
unsigned char n_other; /* misc info (usually empty) */
unsigned short n_desc; /* description field */
bfd_vma n_value; /* value of symbol */
};

    如果stab含有串(如: .stabs),域n_strx為該串在串表裡以位元組為單位的偏移量,串以空字元(″ /0″)標記結尾,如果 stab不含串(如:.stabn),則n_strx的值為零。

    符號表裡n_type域的值在於0xlf(十進位制值:31)的入口或項(entry)是由編譯器生成的符號表除錯資訊轉換來的,而其它入口是由彙編器和連結器加進去的使用者在源程式中定義的符號。

    連結器合併所有目標檔案,整理好外部定義符號,生成一個符號表和一個串表。在UNIX系統下用命令'nmap'可分別觀察經彙編和連結之後的'.o'與'a.out'檔案中包含有除錯資訊的符號表。