开发一个 Linux 调试器(九):处理变量
变量是偷偷摸摸的。有时,它们会很高兴地呆在寄存器中,但是一转头就会跑到堆栈中。为了优化,编译器可能会完全将它们从窗口中抛出。无论变量在内存中的如何移动,我们都需要一些方法在调试器中跟踪和操作它们。这篇文章将会教你如何处理调试器中的变量,并使用 libelfin
演示一个简单的实现。
系列文章索引
在开始之前,请确保你使用的 libelfin
版本是我分支上的 fbreg
。这包含了一些 hack 来支持获取当前堆栈帧的基址并评估位置列表,这些都不是由原生的 libelfin
提供的。你可能需要给 GCC 传递 -gdwarf-2
参数使其生成兼容的 DWARF 信息。但是在实现之前,我将详细说明 DWARF 5 最新规范中的位置编码方式。如果你想要了解更多信息,那么你可以从这里获取该标准。
DWARF 位置
某一给定时刻的内存中变量的位置使用 DW_AT_location
属性编码在 DWARF 信息中。位置描述可以是单个位置描述、复合位置描述或位置列表。
- 简单位置描述:描述了对象的一个连续的部分(通常是所有部分)的位置。简单位置描述可以描述可寻址存储器或寄存器中的位置,或缺少位置(具有或不具有已知值)。比如,
DW_OP_fbreg -32
: 一个整个存储的变量 - 从堆栈帧基址开始的32个字节。 - 复合位置描述:根据片段描述对象,每个对象可以包含在寄存器的一部分中或存储在与其他片段无关的存储器位置中。比如,
DW_OP_reg3 DW_OP_piece 4 DW_OP_reg10 DW_OP_piece 2
:前四个字节位于寄存器 3 中,后两个字节位于寄存器 10 中的一个变量。 - 位置列表:描述了具有有限生存期或在生存期内更改位置的对象。比如:
[ 0]DW_OP_reg0
[ 1]DW_OP_reg3
[ 2]DW_OP_reg2
- 根据程序计数器的当前值,位置在寄存器之间移动的变量。