【问题标题】:In which scenario it is useful to use Disassembly language while debugging在哪种情况下,调试时使用反汇编语言很有用
【发布时间】:2009-10-14 10:53:24
【问题描述】:

我有以下基本问题:

  • 什么时候我们应该在调试中涉及反汇编

  • 如何解释反汇编,例如下面每个段代表什么

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

【问题讨论】:

  • 对于它的价值,我以前从未听说过它被称为“反汇编”语言,但我知道你的意思。

标签: c++ debugging disassembly


【解决方案1】:

估计编译器发出的代码的效率非常有用。

例如,如果您在没有反汇编的情况下在循环中使用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

【讨论】:

  • 为什么循环中的vector<T>::operator[] 需要两次内存访问?将基 T* 加载到寄存器中,并使用单个索引内存访问。
  • 如果您在循环中调用 operator[],编译器可能很难看到缓冲区开始没有改变,并且不会在每次迭代时重新加载基本 T*。
【解决方案2】:

1 - 我们应该 (I) 在调试中将反汇编作为最后的手段。通常,优化编译器生成的代码对于人眼来说并不容易理解。指令被重新排序,一些死代码被消除,一些特定代码被内联等等等等。所以当需要理解反汇编代码时没有必要也不容易。例如,我有时会查看反汇编以查看常量是操作码的一部分还是存储在 const 变量中。

2 - 这段代码调用 getRequiredFields(result, 0, arItem) 之类的函数。你必须为你想要的处理器学习汇编语言。对于 x86,请访问 www.intel.com 并获取 IA32 的手册。

【讨论】:

  • 当调试优化代码时正好当你想使用反汇编时。由于所有的内联和重新排序,源代码的执行将没有任何意义。唯一有意义的代码是反汇编。
  • @Zan Lynx:再想想,你是对的。我的意思是,反汇编列表与“看到的”C++ 源代码/代码流有很大不同,所以它宁愿让他感到困惑,也不愿提供帮助。
【解决方案3】:

当您应该涉及反汇编时:当您确切地想知道 CPU 在执行程序时在做什么时,或者当您没有使用任何更高级别语言编写的程序的源代码时(C++ 在您的案例)。

如何解释汇编代码:学习汇编语言。您可以在Intel's processor manuals 中找到有关 Intel x86 CPU 指令的详尽参考。

您发布的这段代码为函数调用准备参数(通过在堆栈上获取和推送一些值并将值放入寄存器eax),然后调用函数getRequiredFields

【讨论】:

    【解决方案4】:

    我于 1982 年开始在 CP/M-80 和后来的数字研究操作系统上对 PL/M 程序进行汇编调试。在 MS-DOS 的早期也是如此,直到 Microsoft 引入了 symdeb,它是一个命令行调试器,可以同时显示源代码和程序集。 Symdeb 是一个飞跃,但不是那么好,因为早期的调试器迫使我学会识别哪些汇编代码属于哪个源代码行。在 CodeView 之前,最好的调试器是 Phoenix Technologies 的 pfix86。 NuMegas SoftIce 是我遇到过的最好的调试器(除了纯硬件 ICE),它不仅调试了我的应用程序,而且还毫不费力地引导我完成了 Windows 的内部工作。但我离题了。

    1990 年末,我正在从事的一个项目中的一位顾问找到我,说他有这个(很早的)C++ 错误,他已经研究了好几天,但不明白问题出在哪里。当我不耐烦时,他为我单步执行了源代码(在一个窗口化的非图形 DOS 调试器上)。最后我打断了他,查看了调试器选项,果然有寄存器和所有东西的混合源/汇编模式。这使得很容易意识到应用程序正在尝试释放包含 NULL 的内部指针(用于局部变量)。对于这个问题,源码模式根本没有帮助。今天的 C++ 编译器可能不再包含这样的错误,但还会有其他错误。

    了解汇编级调试可以让您了解源-编译器-汇编的关系,从而能够预测编译器将生成什么代码。 stackoverflow 上的许多人都说“profile-profile-profile”,但这更进一步,因为您了解何时使用哪些源代码构造(我用 C 编写)以及避免使用哪些。我怀疑这对于 C++ 更为重要,它可以生成大量代码,而开发人员不会怀疑任何事情。例如,有一个用于处理对象列表的标准类,它似乎没有缺点——只需几行代码和这个奇妙的功能! - 直到您查看它生成的奇怪程序调用的分数。我并不是说使用它们是错误的,我只是说开发人员应该意识到使用它们的利弊。重载运算符可能是很棒的功能(对于像我这样的所见即所得程序员来说有点奇怪),但执行速度的代价是什么?如果你说“没什么”,我会说“证明”。

    调试时使用混合或纯汇编模式永远不会出错。困难的错误通常更容易找到,开发人员将学习编写更有效的代码。来自解释阵营(C# 和 Java)的开发人员会说他们的代码与编译语言一样高效,但如果你了解汇编,你也会知道它们为什么错,为什么它们是大错特错。你可以微笑着想“是的,告诉我吧!”

    在您使用过不同的编译器后,您会遇到一种具有最惊人代码生成能力的编译器。一个 PowerPC 编译器只需通过其优化器的高级代码解释将三个嵌套循环压缩为一个循环。在写我是……好吧,让我们说在不同的联盟。

    直到大约十年前,我编写了相当多的纯汇编程序,但使用多级管道、多个执行单元以及现在与 C 编译器抗衡的多个内核使我毫不犹豫地击败了我。另一方面,我知道编译器可以很好地处理什么以及不应该处理什么:Garbage In 仍然等于 Garbage Out。对于任何产生汇编输出的编译器都是如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-05
      • 2012-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多