我有以下基本问题:
我们应该在调试中涉及反汇编
如何解读反汇编,例如下面每个段代表什么
00637CE3 8B 55 08 mov edx,dword ptr [arItem]
00637CE6 52 push edx
00637CE7 6A 00 push 0
00637CE9 8B 45 EC mov eax,dword ptr [result]
00637CEC 50 push eax
00637CED E8 3E E3 FF FF call getRequiredFields (00636030)
00637CF2 83 C4 0C add
语言:C ++
平台:Windows
答案 0 :(得分:4)
估计编译器发出的代码效率非常有用。
例如,如果在循环中使用std::vector::operator[]
而不进行反汇编,则很难猜测每次调用operator[]
实际上需要两次内存访问,但使用迭代器需要一次记忆访问。
在你的例子中:
mov edx,dword ptr [arItem] // value stored at address "arItem" is loaded onto the register
push edx // that register is pushes into stack
push 0 // zero is pushed into stack
mov eax,dword ptr [result] // value stored at "result" address us loaded onto the register
push eax // that register is pushed into stack
call getRequiredFields (00636030) // getRequiredFields function is called
这是调用函数的典型序列 - 参数被推入堆栈,然后控制转移到该函数代码(call
指令)。
在参与有关“编译后如何工作”的参数时,使用反汇编非常有用 - 例如caf中的his answer to this question点。
答案 1 :(得分:3)
1 - 我们应该(我)将调试中的反汇编作为最后的手段。通常,优化编译器生成对人眼无法理解的代码。重新排序指令,删除一些死代码,内联某些特定代码等等。因此,在必要时理解反汇编代码并不是必需的,也不容易。例如,我有时会查看反汇编以查看常量是否是操作码的一部分或存储在const变量中。
2 - 这段代码调用getRequiredFields(result,0,arItem)之类的函数。您必须学习所需处理器的汇编语言。对于x86,请访问www.intel.com并获取IA32的手册。
答案 2 :(得分:2)
当您应该涉及反汇编时:当您确实想知道CPU在执行程序时正在做什么,或者您没有使用任何更高级语言的源代码时,程序就是用来编写的(C ++中的情况)。
如何解释汇编代码:学习汇编语言。您可以在Intel's processor manuals中找到有关英特尔x86 CPU指令的详尽参考。
您发布的代码片段为函数调用准备参数(通过获取并推送堆栈上的某些值并将值放入寄存器eax
),然后调用函数getRequiredFields
答案 3 :(得分:1)
我从1982年开始在CP / M-80和后来的数字研究操作系统上进行PL / M程序的汇编调试。在MS-DOS的早期阶段也是如此,直到微软引入了symdeb,它是一个命令行调试器,同时显示源和汇编。 Symdeb是一个飞跃,但不是那么好,因为早期的调试器迫使我学会识别汇编代码属于哪个源代码行。在CodeView之前,最好的调试器是来自Phoenix Technologies的pfix86。 NuMegas SoftIce是最好的调试器(除了纯硬件ICE)我曾经遇到过它不仅调试了我的应用程序,而且还毫不费力地引导我完成了Windows的内部工作。但我离题了。
1990年末,我正在工作的一个项目的顾问找我,并说他有这个(非常早期的)C ++错误,他已经工作了几天但却无法理解问题是什么。他为我提供了单步执行源代码(在一个窗口的非图形DOS调试器上),而我却不耐烦了。最后,我打断了他并查看了调试器选项,确定有混合的源/汇编模式,包含寄存器和所有内容。这使得很容易意识到应用程序试图释放包含NULL的内部指针(对于局部变量)。对于这个问题,源代码模式完全没有帮助。今天的C ++编译器可能不再包含这样的bug,但会有其他的。
了解汇编级调试可以让您了解源 - 编译器 - 汇编关系,以便能够预测编译器将生成哪些代码。这里有很多关于stackoverflow的人说“profile-profile-profile”,但是这更进了一步,你学习了什么源代码结构(我用C编写)来使用什么时候避免使用哪些。我怀疑这对于C ++来说更为重要,因为C ++可以在没有开发人员怀疑的情况下生成大量代码。例如,有一个处理对象列表的标准类似乎没有缺点 - 只需几行代码和这个奇妙的功能! - 直到你看到它产生的奇怪程序调用的分数。我并不是说使用它们是错误的,我只是说开发人员应该意识到使用它们的利弊。重载运算符可能是很棒的功能(对于像我这样的所见即所得程序员来说有些奇怪)但是执行速度的价格是多少?如果你说“没什么”,我说“证明它。”
调试时使用混合或纯组装模式永远不会出错。通常很容易找到困难的错误,开发人员将学会编写更有效的代码。来自解释阵营(C#和Java)的开发人员会说他们的代码与编译语言一样有效,但如果你知道汇编,你也会知道他们为什么错了,为什么他们错了。你可以微笑并思考“是的,告诉我它!”
在您使用不同的编译器之后,您将遇到具有最惊人的代码生成能力的编译器。一个PowerPC编译器简单地通过对其优化器的高级代码解释将三个嵌套循环压缩到一个循环中。旁边写着我是......好吧,让我们在另一个联盟中说。
直到大约十年前,我写了很多纯粹的程序集,但是使用多阶段管道,多个执行单元和现在多个核心来与C编译器抗衡让我失望。另一方面,我知道编译器可以做什么以及它不应该使用什么:Garbage In仍然等于Garbage Out。对于生成汇编输出的任何编译器都是如此。