基础

基础 属于 x86-64 Assembly

1 个练习

关于 基础

注释

Exercism 的 x86-64 轨道使用 NASM(The Netwide Assembler)作为汇编器。 在 NASM 中,注释以分号(;)开头。

注释可以放在程序中的任何位置,汇编器会忽略;之后的所有内容。

常量

在 NASM 中,可以用 equ 定义汇编期常量。 例如,下面这段代码定义了一个名为UNIVERSE的常量,值为42:

UNIVERSE equ 42

常量在定义时求值一次,之后不能重新定义。

通用寄存器(GPRs)

在汇编中,我们通常不使用变量,而是用寄存器来存储值。 CPU 寄存器是计算机处理器内部的一块快速内存。 大多数计算都在 CPU 的寄存器中进行。

有些寄存器用于各种不同的计算,被称为通用寄存器(GPRs)。 另一些寄存器则有特殊或专门的用途。

在 x86-64 中,有 16 个 64 位通用寄存器(GPRs),它们也可以按 32 位、16 位或 8 位进行访问。 下面介绍了这些通用寄存器,其中rn里的n取值范围是 8 到 15:r8、r9、r10、r11、r12、r13、r14和r15。

64-bit 32-bit 16-bit 8-bit
rax eax ax ah/al
rbx ebx bx bh/bl
rcx ecx cx ch/cl
rdx edx dx dh/dl
rsi esi si sil
rdi edi di dil
rbp ebp bp bpl
rsp esp sp spl
rn rnd rnw rnb

当使用少于 64 位时,访问的通常是寄存器中较低的那部分位。 这条规则的例外是ah、bh、ch和dh,它们访问的是寄存器 16 位部分中的高 8 位。

下面以rax寄存器为例,说明这些位是如何被访问的:

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

其中一些寄存器在函数调用之间必须保持不变:rbp、rsp、rbx、r12、r13、r14和r15。 如果没有保持它们,可能会导致错误或未定义行为。

其余的寄存器不需要保持,可以自由使用:rax、rcx、rdx、rdi、rsi、r8、r9、r10和r11。

Note

通常使用push指令把寄存器保存到栈上。 然后在函数结束前使用pop指令把它们恢复。

指令

指令是 CPU 能够执行的计算操作。 它们通常具有以下形式:

name destination, source

也就是说,指令名放在最前面,然后至少一个空白字符,接着是目标操作数、一个逗号(,),最后是源操作数。 指令通常不会修改源操作数,只修改目标操作数。

除非另有说明,两个操作数的大小必须相同。 例如,如果源操作数是 16 位,那么目标操作数也必须是 16 位。

例如,要把一个值存入寄存器,可以使用 mov 指令:

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

上面的代码片段把源操作数rdx全部 64 位的内容复制到rax的全部 64 位中,rax是该指令的目标操作数。

在大多数指令中,可以把整数常量用作源操作数(但不能用作目标操作数):

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

注意,向 32 位寄存器写入也会清除高位,因此mov eax, 42与mov rax, 42等价。 对 16 位和 8 位寄存器来说并非如此。

对于加法、减法和乘法这些算术运算,可以使用 add、sub 和 imul 指令:

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

函数

指令被组织成函数。 所有函数都放在源文件的section .text中。

一个函数声明由以下部分组成:

  1. 一个带有函数名的标签,后面跟一个:。
  2. 定义该函数的指令。
  3. 返回指令 ret。

实参如何传入函数、如何从函数返回,都有特定的约定。 两种主要的调用约定是 System V AMD64 ABI 和 Microsoft x64。

本轨道使用 System V AMD64 ABI 调用约定,前六个整数实参通过寄存器传给函数。 它们按以下顺序传递:rdi、rsi、rdx、rcx、r8和r9。

整数值通过rax寄存器从函数返回。

要调用函数,我们使用 call 指令。 函数可以在同一个源文件中的任何位置调用。 为了让函数对其他源文件可见,必须使用global指示符。

例如,下面声明了一个函数sum:

section .text ; functions are placed here

global sum ; sum is visible to other source files

sum:
    ; first argument is passed in rdi
    ; second argument is passed in rsi
    ; return value is placed in rax

    mov rax, rdi ; rax is now equal to rdi
    add rax, rsi ; rax = rax + rsi

    ret ; function return

而下面这段代码用实参 3 和 5 调用了我们的sum函数:

mov rdi, 3  ; First argument in rdi
mov rsi, 5  ; Second argument in rsi
call sum

; The rax register now contains the value 8 (3 + 5), after sum returns
通过 GitHub 编辑 该链接会在新窗口或标签页中打开

学习 基础