3.2.3 关于格式的注解

3.2.3 关于格式的注解

GCC 产生的汇编代码对我们来说有点儿难读。一方面,它包含一些我们不需要关心的信息,另一方面,它不提供任何程序的描述或它是如何工作的描述。例如,假设我们用如下命令生成文件 mstore.s

linux> gcc -Og -S mstore.c

mstore.s 的完整内容如下:

    .file   "010-mstore.c"
    .text
    .globl  multstore
    .type   multstore, @function
multstore:
    pushq   %rbx
    movq    %rdx, %rbx
    call    mult2
    movq    %rax, (%rbx)
    popq    %rbx
    ret
    .size   multstore, .-multstore
    .ident  "GCC: (Ubuntu 4.8.1-2ubuntu1~12.04) 4.8.1"
    .section    .note.GNU-stack,"",@progbits

所有以 . 开头的行都是指导汇编器和链接器工作的伪指令。我们通常可以忽略这些行。另一方面,也没有关于指令的用途以及它们与源代码之间关系的解释说明。

为了更清楚地说明汇编代码,我们用这样一种格式来表示汇编代码,它省略了大部分伪指令,但包括行号和解释性说明。对于我们的示例,带解释的汇编代码如下:

void multstore(long x, long y, long *dest)
x in %rdi, y in %rsi, dest in %rdx

1  multstore:
2      pushq   %rbx            Save %rbx
3      movq    %rdx, %rbx      Copy dest to %rbx
4      call    mult2           Call mult2(x, y)
5      movq    %rax, (%rbx)    Store result at *dest
6      popq    %rbx            Restore %rbx
7      ret                     Return

通常我们只会给出与讨论内容相关的代码行。每一行的左边都有编号供引用,右边是注释,简单地描述指令的效果以及它与原始 C 语言代码中的计算操作的关系。这是一种汇编语言程序员写代码的风格。

我们还提供网络旁注,为专门的机器语言爱好者提供一些资料。一个网络旁注描述的是 IA32 机器代码。有了 x86-64 的背景,学习 IA32 会相当简单。另外一个网络旁注简要描述了在 C 语言中插入汇编代码的方法。对于一些应用程序,程序员必须用汇编代码来访问机器的低级特性。一种方法是用汇编代码编写整个函数,在链接阶段把它们和 C 函数组合起来。另一种方法是利用 GCC 的支持,直接在 C 程序中嵌入汇编代码。

旁注:ATT 与 Intel 汇编代码格式

我们的表述是 ATT(根据 “AT&T” 命名的,AT&T 是运营贝尔实验室多年的公司)格式的汇编代码,这是 GCC、OBJDUMP 和其他一些我们使用的工具的默认格式。其他一些编程工具,包括 Microsoft 的工具,以及来自 Intel 的文档,其汇编代码都是 Intel 格式的。这两种格式在许多方面有所不同。例如,使用下述命令行,GCC 可以产生 multstore 函数的 Intel 格式的代码:

linux> gcc -Og -S -masm=intel mstore.c

这个命令得到下列汇编代码:

multstore:
    push    rbx
    mov     rbx, rdx
    call    mult2
    mov     QWORD PTR [rbx], rax
    pop     rbx
    ret

我们看到 Intel 和 ATT 格式在如下方面有所不同:

  • Intel 代码省略了指示大小的后缀。我们看到指令 pushmov,而不是 pushqmovq
  • Intel 代码省略了寄存器名字前面的 % 符号,用的是 rbx,而不是 %rbx
  • Intel 代码用不同的方式来描述内存中的位置,例如是 QWORD PTR [rbx] 而不是 (%rbx)
  • 在带有多个操作数的指令情况下,列出操作数的顺序相反。当在两种格式之间进行转换的时候,这一点非常令人困惑。

虽然在我们的表述中不使用 Intel 格式,但是在来自 Intel 和 Microsoft 的文档中,你会遇到它。

网络旁注 ASM:EASM:把 C 程序和汇编代码结合起来

虽然 C 编译器在把程序中表达的计算转换到机器代码方面表现出色,但是仍然有一些机器特性是 C 程序访问不到的。例如,每次 x86-64 处理器执行算术或逻辑运算时,如果得到的运算结果的低 8 位中有偶数个 1,那么就会把一个名为 PF 的 1 位条件码(condition code)标志设置为 1,否则就设置为 0。这里的 PF 表示 “parity flag(奇偶标志)”。在 C 语言中计算这个信息需要至少 7 次移位、掩码和异或运算(参见习题 2.65)。即使作为每次算术或逻辑运算的一部分,硬件都完成了这项计算,而 C 程序却无法知道 PF 条件码标志的值。在程序中插入几条汇编代码指令就能很容易地完成这项任务。

在 C 程序中插入汇编代码有两种方法。第一种是,我们可以编写完整的函数,放进一个独立的汇编代码文件中,让汇编器和链接器把它和用 C 语言书写的代码合并起来。第二种方法是,我们可以使用 GCC 的内联汇编(inline assembly)特性,用 asm 伪指令可以在 C 程序中包含简短的汇编代码。这种方法的好处是减少了与机器相关的代码量。

当然,在 C 程序中包含汇编代码使得这些代码与某类特殊的机器相关(例如 x86-64),所以只应该在想要的特性只能以此种方式才能访问到时才使用它。