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'文件中包含有调试信息的符号表。

猜你喜欢

转载自blog.csdn.net/doniexun/article/details/45044015
gcc