为什么汇编代码会因我使用的反汇编程序而异?

Why does assembly code differ depending on the disassembler I use?

我正在自学调试汇编语言;我是组装新手。我有一个非常简单的 C++ 程序,我使用不同的反汇编器将它反汇编了 3 次:GDB、otool 和 godbolt.org。 GDB 和 godbolt.org 产生了大致相同数量的代码(文字处理器中的一页),尽管很多行不同。 otool -tv 命令产生了大约 14 页代码,因此在 GDB 和 godbolt.org 输出方面存在许多差异。汇编代码太长post。我期望汇编代码输出彼此相同。为什么它们不同,哪个反汇编器最好?

这是我的 C++ 程序:

#include <iostream>

int main () {

int a = 1;
int b = 2;
int c = 3;

a += b;
a = a + c;

std::cout << "Value of A is " << a << std::endl;

return 0;

}

汇编差异示例:

GDB:

0x0000000100000f44 <+4>:    sub    [=11=]x30,%rsp
0x0000000100000f48 <+8>:    mov    0x10c1(%rip),%rdi        # 0x100002010
0x0000000100000f4f <+15>:   lea    0xfb6(%rip),%rsi

Godbolt.org:

sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 2

Otool -tv 给出的代码比其他代码多 13 页,因此两者之间存在明显差异。

因为源代码和程序集之间不是一对一的关系。编译器可能会为以下语句生成相同的程序集:

x = x + 1

x++;

两者都会被编译成类似

的东西
add dword ptr [rdi], 1

那么,我们在拆解那个的时候,应该拆到哪一个呢? x = x+1 还是 x++?这实际上适用于程序的每一条语句——如果源语言中有不止一种表达方式,并且效果相同,编译器可能会选择将它们翻译成相同的输出。之后你就不知道用的是哪一个了

您遇到的差异不在反汇编程序中,而是在用于表示机器指令的语法中。

汇编是一种非常低级的语言,机器指令助记符之间存在一对一的映射关系.前者是位序列,可能具有可变长度——就像 x86 体系结构的情况一样。这种表示直接由CPU解释来执行与语义指令相关的工作。汇编语言是此类序列的“人类可读”表示。

基本上,您可以找到任何方式来表示相同的机器指令。这是汇编语法

众所周知,对于 x86 架构,存在两种不同的语法:AT&TIntel。您从 GBD 获得的输出是根据 AT&T 语法生成的,而您从 Godbolt.org 获得的输出是 Intel 的。

Intel 和 AT&T 的语法在外观上非常不同,可能这就是为什么您一直认为结果不一样的原因。实际上,这只是表示 非常相同的 指令的不同方式。

这两种“方言”针对同一架构的组件是出于不同的目标而诞生的。 AT&T 语法是在 AT&T 实验室开发的,以支持为许多不同的 CPU 生成程序(参见本书:Jeff Duntermann, Assembly Language Step-by-Step)。当时,AT&T 在计算机历史上扮演着重要角色。 AT&T(贝尔实验室)一直是 Unix 的源头——它的范式目前(尽管部分)由 Linux——C 编程语言,以及我们今天继续使用的许多其他基本工具承诺。

另一方面,Intel 语法已经开发出来,好吧... 由 Intel 为他们自己的 CPU 开发。许多 Intel 语法的采用者说在 Intel CPUs 上编程时它更整洁。这很可能就是这种情况,因为语法是为 CPU 支持的内容精心设计的。

虽然目前不再使用 AT&T 语法(至少,据我所知)为 x86 以外的 CPU 编写程序,但一些 "culprits"语法是从它生成的 "general".

那么,学哪一个呢?我的选择将取决于您的工作环境。整个 Unix 生态系统(包括 Linux 和 Mac Os)都有一个直接使用该语法的工具链(例如 gas)。在 Linux 内核(和其他低级软件)中,您肯定会找到采用 AT&T 语法的内联汇编代码来与硬件交互。另一方面,Windows 系统具有使用 Intel 语法的工具链(例如 nasm)。虽然编译时标志可以要求这些工具切换到其他语法(例如 objdump-M 标志),但习惯是采用 "native" 语法。

关于问题中给出的具体例子,它们是"incompatible",从某种意义上说,它们指的是反汇编代码的不同部分,因此两者之间存在较大程度的差异。 事实上,关于这个 GDB 输出:

sub    [=10=]x30, %rsp
mov    0x10c1(%rip), %rdi
lea    0xfb6(%rip), %rsi

对应的英特尔反汇编为:

sub    rsp, 0x30
mov    rdi, QWORD PTR [rip+0x10c1]
lea    rsi, [rip+0xfb6]

另一方面,关于 Godbolt.org 输出:

sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 2

相应的 AT&T 反汇编为:

sub    [=13=]x10,%rsp
movl   [=13=]x1,-0x4(%rbp)
movl   [=13=]x2,-0x8(%rbp)

如您所见,最大的差异(可能会引起很多麻烦)与以下事实有关:AT&T 语法将源放在首位,然后是目标,而 Intel 语法则相反。

不同语法的汇编序列并不等同,它们只是不同,可能是由于使用了不同的编译器。

第一对:

sub [=10=]x30,%rsp             ;rsp -= 0x30
sub rsp,16                 ;rsp -= 0x10

下一对:

mov 0x10c1(%rip),%rdi      ;rdi = [rip+0x10c1]  (loads a value)
mov DWORD PTR [rbp-4],1    ;[rbp+4] = 1  (stores an immediate value)

下一对:

lea    0xfb6(%rip),%rsi    ;rsi = rip+0xfb6   (loads an offset)
mov DWORD PTR [rbp-8],2    ;[rbp+8] = 2 (stores an immediate value)

两个序列都不完整,但我认为这无关紧要,因为显示的序列已经显示出差异。