GCC 生成的符号表调试信息剖析
GCC把C語言源文件('.c')編譯成匯編語言文件('.s'),匯編器把匯編語言文件翻譯成目標(biāo)文件('.o'),最后由鏈接器鏈接所有的目標(biāo)文件和有關(guān)的庫生成可執(zhí)行文件('a.out')。
如打開'-g'選項,GCC編譯'.c'文件時,把附加的調(diào)試信息插進'.s'文件,這些調(diào)試信息經(jīng)匯編器和鏈接器稍加轉(zhuǎn)換一直傳到可執(zhí)行文件中。這些調(diào)試信息包括行號、變量的類型和作用域、函數(shù)名字、函數(shù)參數(shù)和函數(shù)的作用域等源文件的特性。
在某些目標(biāo)文件中,調(diào)試信息用'.stab'打頭的一類匯編指導(dǎo)命令表示,這些指導(dǎo)命令穿插在匯編代碼中,這種調(diào)試信息格式叫'Stab',即符號表(Symbol table)。XCOFF和a.out目標(biāo)文件格式采用Stab調(diào)試信息格式。此外,GCC也能在COFF和ECOFF目標(biāo)文件格式中產(chǎn)生Stab。如要生成Stab調(diào)試信息,在GCC編譯源文件時,打開編譯選項'-gstabs+'(此選項將產(chǎn)生GNU調(diào)試器擴展的Stab的調(diào)試信息)或'-gstabs'。
匯編器處理'.stab'打頭指導(dǎo)命令,把Stab中的調(diào)試信息填入'.o'文件的符號表和串表(string table)中,鏈接器合并所有'.o'文件生成只含有一個符號表和一個串表的可執(zhí)行文件。調(diào)試器通過檢索可執(zhí)行文件中的符號表和串表來獲得程序的調(diào)試信息,下面分別介紹Stab的格式,GCC生成Stab和匯編鏈接器對Stab轉(zhuǎn)換。
?
1 Stab的格式
Stab匯編指導(dǎo)命令有3種格式:'.stabs'(string), '.stabn'(number)和'.stabd'(dot)。在MIPS機器上,GCC采用'.stabn'輸出源程序語句行號的Stab調(diào)試信息,而未使用'.stabd',因此,在MIPS機器上,GCC生成的帶有Stab調(diào)試信息的匯編代碼中只含'.stabs'和'.stabn'兩種匯編指導(dǎo)命令,'.stabs'和'.stabn'命令格式如下:
.stabs ″STRING″,TYPE,OTHER,DESC,VALUE
.stabn TYPE,OTHER,DESC,VALUE
下面說明Stab匯編指導(dǎo)命令的各域。
″STRING″的一般格式是:″NAME:SYM-DESC TYPE-INFO″
其中,NAME是由Stab表示的符號的名字,如果Stab表示是一個匿名對象,則NAME可省略,一般以一空格代替。SYM-DESC為一字母,它具體表示Stab所描述的是哪一類符號,例如:
SYM-DESC為'F',表示Stab描述的是全局函數(shù);為'f'時,表示局部函數(shù);為'G'時,表示全局變量。TYPE-INFO則表示數(shù)據(jù)類型信息,它可以是Stab分配給已定義的數(shù)據(jù)類型的序號,表示對已定義的數(shù)據(jù)類型的引用;也可以是一串符號,用來定義一種新的數(shù)據(jù)類型,參見1.3數(shù)據(jù)類型定義。
OTHER沒有使用,其值保持零。
DESC用編譯開關(guān)'-gstabs+'編譯源文件,DESC為源程序的語句行號;用編譯開關(guān)'-gstabs'編譯源文件,DESC為零。
VALUE可為一符號地址,或為自動變量在當(dāng)前棧里相對幀指針的偏移量,或為寄存器變量所分配的寄存器的號碼。
以下各小節(jié)將結(jié)合實例對Stab描述調(diào)試信息的格式作具體的闡述。
1.1 Stab描述程序結(jié)構(gòu) 
 (1)源文件的名字和路徑
在含有調(diào)試信息的匯編代碼中,第一個Stab匯編指導(dǎo)命令指明所編譯的源文件的名字,如果打開GCC編譯開關(guān)'-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( )表示與該文件相對應(yīng)的代碼區(qū)的首地址。
(2)包含文件
描述包含文件的Stab指明隨后出現(xiàn)的變量、函數(shù)等符號所要參考的源文件,調(diào)試器由此查找到定義該符號的源文件。STRING為被包含文件名,TYPE=N-SOL,VALUE為被包含文件代碼區(qū)的首地址,如:
.stabs ″example.c″, 132, 0, 0, $Ltext1 #132 is N-SOL
(3)行號
行號表示匯編程序中的一段代碼所對應(yīng)的C源程序的語句行號。匯編指導(dǎo)命令采用'gstabn',TYPE=N-SLINE,DESC表示源程序的語句行號,VALUE為該語句行所對應(yīng)的一段匯編代碼的起始標(biāo)號,例如:
.stabn 68, 0, 4, $LM6 #68 is N-SLINE
如果一源程序行所產(chǎn)生的匯編代碼不連續(xù),可用多條'.stabn'表示,而DESC為同一值。
(4) 函數(shù) 描述函數(shù)的Stab,其TYPE為N-FUN,VALUE為函數(shù)的符號地址。SYM-DESC=F表示該函數(shù)為全局函數(shù),SYM-DESC=f表示該函數(shù)的為局部函數(shù),TYPE-INFO表示該函數(shù)的返回值的數(shù)據(jù)類型。下列為Stab描述局部函數(shù)func,其函數(shù)返回值為整型。
.stabs ″func: fl ″, 36, 0, 0, func #36 is N-FUN
(5)嵌套函數(shù) 嵌套函數(shù)是GNU C對標(biāo)準(zhǔn)C的擴充,Stab描述嵌套函數(shù)與描述一般函數(shù)的方式大致相同,區(qū)別是在描述嵌套函數(shù)時,在TYPE-INFO之后緊接包含該函數(shù)的最內(nèi)層函數(shù)。
 下面為一嵌套函數(shù)定義的例子,隨后給出了其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之后跟','號,然后被描述的函數(shù)名跟','號,最后是包含該函數(shù)定義的最內(nèi)層函數(shù)的名字。
(6)塊結(jié)構(gòu) 這里塊結(jié)構(gòu)是指C語言函數(shù)定義中表示塊語句開始和結(jié)束的左、右括號。描述左括號的('{')Stab,其TYPE=N-LBRAC,VALUE為以'$LBB'打頭的匯編語句標(biāo)號;描述右括號('}')的Stab,其TYPE=N-RBRAC,VALUE為以'$LBE'打頭的匯編語句標(biāo)號。匯編指導(dǎo)命令為'.stabn'。例如:
.stabn 192, 0, 0, $LBB2 #192 is N-LBRAC
.stabn 224, 0, 0, $LBE2 #224 is N-RBRAC
?
 1.2 Stab描述變量
在C語言里,根據(jù)變量所具有的不同的存儲分配方式,可把變量分為:自動變量、全局變量、寄存器變量和靜態(tài)變量。
(1)自動變量 自動變量存儲在當(dāng)前函數(shù)棧里,因此也叫棧變量。Stab描述自動變量時,TYPE為N-LSYM,Stab描述自動變量在當(dāng)前函數(shù)棧里相對于幀指針的偏移量,SYM-DESC被省缺,如:
.stabs ″x: l″, 128, 0, 0, -12 #128 is N-LSYM
(2)全局變量 全局變量的作用域不局限于定義它的那個文件,可為多個文件使用。Stab描述全局變量時,TYPE為N-GSYM,SYM-DESC為G,VALUE為零,調(diào)試器根據(jù)全局變量的外部符號獲得其地址,如:char gvar='c';
生成的含Stab的匯編代碼為:
stabs ″gvar: G2″, 32, 0, 0, 0 #32 is N-GSYM
.globl gvar
.data
gvar:
byte 99
例中,匯編器根據(jù)'globl gvar'和'gvar: '產(chǎn)生一個外部符號,調(diào)試器由此外部符號獲得全局變量'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)靜態(tài)變量 在函數(shù)內(nèi)定義的靜態(tài)變量具有函數(shù)作用域,在函數(shù)外定義的靜態(tài)變量具有文件作用域。Stab描述靜態(tài)變量時,TYPE為N-STSYM表示該變量已初始化,而TYPE為N-LCSYM表示該變量未初始化,VALUE為變量的符號地址,SYM-DESC為'S'時,該變量的作用域為整個文件,SYM-DESC為'V'時該變量具有函數(shù)作用域。如:
static int var_init=2;
static int var_noinit;
假設(shè)它們的作用域都為文件作用域,生成的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)參數(shù) C語言中,函數(shù)的參數(shù)可通過棧或寄存器傳遞,并且通過寄存器傳遞的參數(shù)也被保留在棧里,描述由棧傳遞的參數(shù),TYPE=N-PSYM;VALUE為該參數(shù)在當(dāng)前函數(shù)棧里相對于幀指針的偏移量,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獲得參數(shù)的值,且根據(jù)第1個Stab知道該變量為參數(shù)。
1.3 數(shù)據(jù)類型定義
Stab采和匯編指導(dǎo)命令'stab'定義數(shù)據(jù)類型,TYPE域為N-LSYM,VALUE域為零,其″STRING″域中包含類型定義信息,下面是它的一般格式:
″NAME: SYM-DESC TYPE-NUM=TYPE-DESC…″
NAME為被定義的數(shù)據(jù)類型的名字;SYM-DESC=T表示聯(lián)合、結(jié)構(gòu)和枚舉這3種數(shù)據(jù)類型,SYM-DESC=t表示其它數(shù)據(jù)類型;TYPE-NUM為一序號,如'1'表示整型,'2'表示字符型等;TYPE-DESC為類型描述器,更精確地對數(shù)據(jù)類型加以定義,如:TYPE-DESC= * ,表示指向其它類型的指針,TYPE-DESC=r,表示子界類型。這里介紹內(nèi)部數(shù)據(jù)類型和部分其它數(shù)據(jù)類型的定義。
(1)內(nèi)部數(shù)據(jù)類型 C語言的內(nèi)部數(shù)據(jù)類型包括整型、字符型、浮點類型和'void'類型等,整型和字符型定義成其自身的子界,如對字符型(char)的定義:
.stabs ″char: t2 = r2; 0; 127; ″, 128, 0, 0, 0 #128 is N-LSYM
在″STRING″中,'r2'是子界類型定義,表示為'2'號類型(字符型)的子界類型,字符型的下界為0,上界為127,浮點類型被定義為整型的子界類型,與整型定義所不同的是,其上界為零,而下界為一正整數(shù),表示該類型的大小(以字節(jié)為單位),如:
.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)數(shù)組類型 定義數(shù)組類型時,在類型描述器(TYPE-DESC)'a'之后跟其下標(biāo)和其元素的類型信息,例如:int vector[3];
生成的匯編代碼為:
.stabs ″vector: G20= arl; 0; 2; 1 ″, 32, 0, 0, vector
.comm vector, 12
(3)結(jié)構(gòu) 聯(lián)合和枚舉類型 這3種數(shù)據(jù)類型都用T作為SYM-DESC。當(dāng)TYPE-DESC=S時表示結(jié)構(gòu)類型,TYPE-DESC=u時表示聯(lián)合類型,TYPE-DESC=e表示枚舉類型,另外枚舉類型和其它兩種數(shù)據(jù)類型在描述上還有差別,描述枚舉類型時,在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
描述結(jié)構(gòu)和聯(lián)合這兩種數(shù)據(jù)類型時,TYPE-DESC之后為類型的大小,然后是對其元素的描述,其元素描述采用這樣的格式:″名字:類型,相對于結(jié)構(gòu)或聯(lián)合始地的按位的偏移量,元素所占的存儲位″。如:
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調(diào)試信息,以'.stab'打頭的匯編指導(dǎo)命令穿插在匯編代碼中間,下面介紹Stab在匯編代碼中出現(xiàn)的形式以及GCC編譯軟件中與Stab生成相關(guān)的幾個主要函數(shù)。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語言內(nèi)部定義的數(shù)據(jù)類型,然后是C源文件中定義的初始化全局變量和初始化的具有文件作用域的靜態(tài)變量。 第二部分,對應(yīng)文件(包括被編譯的主文件和包含文件)中定義的每個函數(shù),分別產(chǎn)生這么一串Stab并插入函數(shù)的匯編代碼中,包括該函數(shù)由哪個文件定義,匯編語句與C源程序的語句行的對應(yīng)關(guān)系,最后是被定義的函數(shù)名字、函數(shù)的參數(shù)、自動變量、本函數(shù)中定義的靜態(tài)變量以及塊語句結(jié)構(gòu)。
第三部分,在所有函數(shù)的匯編代碼之后出現(xiàn)的Stab,包括未初始化全局變量和未初始化的具有文件作用域的靜態(tài)變量。
GCC編譯軟件中用于輸出Stab調(diào)試信息的函數(shù)定義在dbxout.c中,下面列出一些主要函數(shù)的名字和功能。
dbxout_init輸出被編譯的主源文件和C語言內(nèi)部定義的數(shù)據(jù)類型的Stab。
dbxout_source_file輸出包含文件和Stab。
dbxout_function輸出函數(shù)名字、函數(shù)的參數(shù)、自動變量、函數(shù)中定義的靜態(tài)變量以及塊結(jié)構(gòu)的Stab。
dbxout_source_line輸出源程序語句行號的Stab。在MIPS機器上由定義在文件mips.c中的函數(shù)mips_output_source_line輸出源程序語句行號的Stab。
?
 3 Stab的轉(zhuǎn)換 
 下面描述可執(zhí)行文件('a.out')中符號表入口(symbol table entries)的格式,以及其與匯編指導(dǎo)命令的映射關(guān)系,并簡要介紹匯編器和鏈接器怎樣對符號表里的數(shù)據(jù)進行轉(zhuǎn)換。
每當(dāng)匯編器遇到符號表匯編指導(dǎo)命令('.stab'),就把其各域填到其輸出文件('.o')的符號表入口的相應(yīng)的各域中,如果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為該串在串表里以字節(jié)為單位的偏移量,串以空字符(″ /0″)標(biāo)記結(jié)尾,如果 stab不含串(如:.stabn),則n_strx的值為零。
符號表里n_type域的值在于0xlf(十進制值:31)的入口或項(entry)是由編譯器生成的符號表調(diào)試信息轉(zhuǎn)換來的,而其它入口是由匯編器和鏈接器加進去的用戶在源程序中定義的符號。
鏈接器合并所有目標(biāo)文件,整理好外部定義符號,生成一個符號表和一個串表。在UNIX系統(tǒng)下用命令'nmap'可分別觀察經(jīng)匯編和鏈接之后的'.o'與'a.out'文件中包含有調(diào)試信息的
 符號表。
總結(jié)
以上是生活随笔為你收集整理的GCC 生成的符号表调试信息剖析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Android Studio-Andro
- 下一篇: GCC-3.4.6源代码学习笔记
