其他 DWARF 数据行号表DWARF行号表包含了存有可执行代码的内存地址和对应这些地址的源代码行号的映射关系表。简单的说,它可以看成是一列包含内存地址,另一列包含对应地址源代码三元信息(文件,行号,列号)的这样一个矩阵。如果你想在某行源代码设定一个断点,关系表就可以给你提供存储这个断点指令的内存地址。相反地,如果你的程序在某个内存位置发生故障(比如使用了一个错误的指针)你也可以将错误定位在最靠近该内存位置的相应源代码处。 DWARF已经扩展了另外的列来表达一个程序的更多信息。比如一个编译器优化了这个程序,它将某个指令移走或者删除了。某些源代码语句也可能并没有直接生成对应的指令序列,而是分散掺杂于附近的语句生成的指令序列中。识别一个函数的开始和结束也是有用的,那样调试器就可以在所有参数加载之后或者在函数返回之前暂停程序执行。有一些处理器硬件可以执行不只一套指令集,也可以用专门的一列用来表示使用哪一套指定的指令集。 你可能会想到,如果这个表用为每一个机器指令使用一行来存储,那他将会非常巨大。DWARF使用一种叫做(line number program)的方法把这些数据编码并压缩了。这些指令被一个简单的有穷状态机解释,并重新构造完整的行号表。这个有穷状态机使用一些默认值来初始化,行号表中的每行靠使用一个或几个操作数来执行(line number program)。操作数一般来讲很简单:举个例子,给一个地址或者行号项加入一个值,设定列号,或者设定一个表示某源代码语句开始的标志,或者一个函数结束的标志,或者一个函数开始的标志。某些特殊的操作数,将常用的一些一般操作合成了一个操作数(前移内存地址和前移或后移源代码行号)。 最后,如果行号表中的一项与前一项有着相同的源代码三元数据,则不会再为此项生成指令。 调用时的信息 每种处理器都有一种功能调用和相应参数传递的方法。常被定义在ABI中。最简单的情况,对每个功能调用采用一样的方式,并且调试器明确地知道如何找到参数值和返回地址。对于一些处理器而言,功能函数的编写方式可能会导致不同的调用顺序。例如,一个功能函数有一个参数或多个参数将会导致不同。也会因为操作系统而导致调用数序的不同。编译器将会试图优化这些调用顺序,而使代码即简洁又高效。一个常见的优化:有一个没有调用其他任何函数的功能函数,使用他的调用者的栈空间来代替为他自己创建栈空间。再举一个优化的例子:清除一个指向当前调用空间的寄存器的值。一些寄存器的值可能会在调用之间被保留,另一些则不会被保留。当调试器努力地找到所有调用顺序的排列可能和如何被优化时,这项工作即枯燥乏味又容易出错。在优化时的一个小小的变化就可以使调试器不用再在栈里寻找调用信息。 DWARF调用信息(CFI)为调试器提供了足够的关于一个功能调用是如何被调用的信息,因此就可以定位函数调用的每个参数值的位置。这些信息被调试器用来解析栈里的信息,甚至定位更前面的函数调用和被传递的参数值。 就像行号表一样,CFI也编码成可以被解释生成一个表的指令序列。在表里每个包含代码的地址有其相对应的一行。第一列包含了机器地址后面的列包含了当指令在那个地址被执行时的机器寄存器值。就像行号表一样,这个表也可能会很巨大。幸运的是,机器指令之间的不同不会导致表里数据的巨大变化,所以CFI编码十分紧凑。
阅读(4681) | 评论(0) | 转发(0) |