Dwarf2结构在gcc中的应用及调试器实现分析

一、查看方法。

通过gcc -S -g 生成的汇编代码中包含了一些使用树脂表示的调试信息,但是这些信息本身如果我们一个一个看文档的话还是比较麻烦的,所以我们只有通过其它的方法来实现。还要readelf提供了-w功能来显示整个结构中调试信息的格式。我们就可以结合生成的汇编文件和对应的readelf的输出再结合dwarf的文档说明理解一下调试信息的格式。

二、类型信息

1、debug_abbrev节,这个节一般在一个文件的最开始,它可以说是调试信息中一个数据结构,并且为这个类型定义一个标号,之后的结构就可以通过这个标号来表示自己的数据使用的类型。为什么要这么做呢?这就和我们写程序一样,通常先声明一个数据结构,然后通过这个结构就可以方便的定义大量的实例,这样就达到了共享的目的。这里就有一个比较重要的划分性结构,比如说,一个结构的定义了很多的类型,那么这些类型是不是有子类型的确定,这个是在一个类型的最开始声明的一个类型说明,因为这个比较重要,所以放在开始

#define DW_CHILDREN_no       0x00
#define DW_CHILDREN_yes       0x01

如果为0表示这个结构是一个叶子结构,不再有子结构;另一方面,一个结构声明的结束标志是通过两个连续的00字节来表示的。这样也相当于是一种自适应的算法。可以避免复杂的结构定义。

2、debug_info

既然这里已经定义了类型,在这个debug_info中就可以引用这些类型了,并且每个类型实例结束也需要定义自己的结束标志,那就是通过一个0字节来表示这个结构已经结束。另外,当一个实例是一个有子结构的实例的时候,这个为零的标志位也就起了比较重要的作用。因为如果一个结构有子结构,那么这个子结构将会紧邻着这个父结构,并且子结构的层数可以通过为零的字节的数目来确定,也就是遇到一个0就表示一个有子结构的结构已经结束

通过这两个结构,我们就知道了系统中最为重要的类型信息,其实还有符号信息,而这些符号信息则是“符号调试器”的精髓所在。

三、堆栈信息

当进入函数的时候,我们通常需要看函数中局部变量,局部变量的位置是不确定的,只有在运行时才知道这个值。所以就隐身出了CFA Call Frame Address的值,我们只需要记录下局部变量相对于CFA的位置,然后再给出CFA的计算方法、最终结合前面提供的结构类型信息,可以算出局部变量的内容

堆栈信息的存放位于debug_frame节中,而frame节的一个重要任务就是要能够实时的(精确到每一个指令行)计算出CFA的地址。对于powerPC的初始位置,可以看到里面一些常用的结构。

在其中的Debug Frame Entry DFE中,有一个栈帧的结构信息,一般在这个结构的开始有一个cfa_def_cfa r1,也就是通过强制定义的方法更新栈帧的地址为r1,此时一般已经通过序言 stwu指令还没有执行,r1就是上个栈帧的位置;当执行了stwu只有,此时r1寄存器的值已经更新,但是寄存器还是r1,此时需要定义偏移量为stwu的值对应。这样虽然r1的值变换了,加上这个值之后还是相同的,然后通过mr r31,r1之后,由于r1和r31的值相同,所以就可以重新定义寄存器基地址为r31。这样当我们计算一个CFA的地址的时候就可以通过寄存器r31+offset来得到栈帧的起始地址,然后加上前面在debug_info中定义的局部变量属性中的偏移量就可以运行时计算出变量的起始地址,再加上成员就可以知道各个成员的位置和值。

四、行号信息

源代码级调试另一个基本功能。这个需要将汇编指令的若干行对应到某个文件的某一行,从而可以实现从源代码到可执行文件以及可执行文件中的某一行到可执行文件之间的关系转换。

posted on 2019-03-06 20:12  tsecer  阅读(685)  评论(0编辑  收藏  举报

导航