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'文件中包含有調試信息的
符號表。