C/C++调试---汇编2

汇编2

分析优化后的代码

掌握了汇编的基本知识后,我们继续来解决前面提到的两个问题:
1、如何在函数中找到局部变量和参数
2、如何将指令映射到源代码行。

在栈内存中分配的局部变量和参数很容易找到,因为只要函数没有返回,它们通常就不会被破坏。然而,读取分配在寄存器中的局部变量和参数的正确值会比较有挑战,因为它们可能会在某些地方被临时保存、占用,然后恢复,这在优化的代码中相当常见。当调试器在这种情况下无法提供帮助时,我们需要阅读几行汇编代码来找出变量的实际值。以下是解决这个问题的一些通用步骤:

步骤01 如果寄存器在当前指令(由程序计数器rip指向)之前没有被用于其他目的,那么就可以安全地使用该寄存器来获取关联的局部变量或参数的值。

步骤02 查看ABI规则以确定在函数调用中哪些寄存器需要被调用者或被调用者保留。如果一个寄存器被占用,那么调用者或被调用者必须生成代码将局部变量或参数的副本放在栈上或另一个寄存器中。即使寄存器没有被保存,其原始值也应该来自某个存储位置,这个位置可以推导出来。

步骤03 如果在经过上述尝试之后仍然无法获取参数的值,我们可以将栈帧向上移动到调用函数,检查调用函数是如何设置传出参数的。它必须将参数从调用函数的栈帧或另一个寄存器复制到寄存器中。我们可以一直向上追溯,直到找到这个值。

下面列出一些模式或指南,可以帮助我们快速找到相应的源代码。

1. CALL指令。该指令的操作数为被调用函数的地址。如果生成了调试符号,调试器可以打印出与地址关联的函数名。由于被检查的函数中通常只有几个函数调用(如果有的话),这将帮助我们非常快速地查明源代码行。例如,上例中的main函数有如下CALL指令:

 call  _Z3Sumii10POD_STRUCT15NONE_POD_STRUCTPlfll

注意,该函数名称是经过修饰后的C++函数Sum(int, int, POD_STRUCT, NONE_POD_STRUCT,long*, float, long, long)。源代码显示main函数有一个地方调用函数Sum,由此可以确定此指令与进行此调用的源代码行有关。
2. 条件分支指令通常与C/C++源代码中的if…else语句有关。这些表达式通常包含了变量和常量。根据表达式结果,程序流程可能会转移到不同的地址。如果比较指令的操作数之一是立即数,例如NULL、true/false、枚举类型等,那么可以轻松地在源代码中找到它们。例如,以下指令将变量与立即数0x1a进行比较,如果它们不相等,则会分支到0x2a979d2c5f处的指令。

 cmpl  $0x1a,0xc0(%rsi)
 jne   0x2a979d2c5f <FillXMLStatement+801>

通过查阅源代码,我们会发现0x1a是枚举常量AIICmdCacheAdmin的值。因此,可以相当确定这段汇编代码映射到以下源代码行,因为这是函数中唯一使用该常量进行if语句判断的地方。

 else if(aScheduleInstance.mCommandID == AIICmdCacheAdmin)

3. 考虑以下示例:
movq -24(%rbp), %rax

 movq  (%rax), %rax
 movq  -24(%rbp), %rdi
 movl  $0, %esi
 call  *0x20(%rax)

一个对象被存储在栈帧起始偏移24字节的位置。前两条指令用于获取该对象虚函数表的地址,此表位于对象的首部。接下来的两条指令用于设定函数参数,其中包括被存储在寄存器rdi中的this指针。最后一条指令负责读取虚函数表中偏移32字节(0x20)的条目并跳转至其中的地址,该地址是派生类的虚函数的实现。这个地址在运行时从虚函数表中读取,因此我们无法从指令中直接获取函数名称。但是,我们知道这是一个虚函数,并且它位于对象的虚函数表的32字节偏移处。将这些信息与源代码结合起来进行审查,可能会有助于我们减少代码分析的复杂性。

4. 访问数组元素。要访问数组元素,需要将数组地址加上所需元素的偏移量,该偏移量为元素索引和元素大小的乘积。因此,生成的指令通常使用ModR/M内存寻址模式,该模式接收一个基址、一个索引、一个比例和可选的偏移量。下面的指令是一个典型的例子:

movq -64(%rbp,%rax,8), %rax

它从位于-64(%rbp)的数组中读取元素,每个元素的大小为8字节,元素索引在%rax中。这对应类似的代码arr[i],其中arr的起始地址为-64(%rbp),i存储于%rax中,数组元素大小为8字节,比如long。

5. 访问类对象或结构体的数据成员。通常情况下,数据通过基地址进行引用,即对象的起始地址加上数据成员的偏移量。例如,在上一个示例中,可以使用以下指令访问类NONE_POD_STRUCT的数据成员“d”。对象的地址保存在寄存器%rcx中,而数据成员“d”的偏移量为16字节。编译器在计算对象的内存布局时确定偏移量。

 addsd  16(%rcx), %xmm1

6. 访问栈上的局部变量和参数。正如ABI所讨论的,栈上的局部变量通常通过寄存器%rbp和负偏移量进行访问。如果通过栈传递参数,则通过寄存器%rbp加上正偏移量进行访问。以下指令将一个变量保存在-8(%rbp)的位置:

movq  %rdi, -8(%rbp)

参考:高效C/C++调试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

麦兜c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值