[转帖][译] 简明 x86 汇编指南

简明,x86,汇编,指南 · 浏览次数 : 0

小编点评

**器间移动数据,接受两个参数:** * **第一个参数**:目标地址 * **第二个参数**:源地址 **合法寻址的例子:** ``` mov eax, [ebx] ``` **非法寻址的例子:** ``` mov eax, [ebx-ecx] ``` **指令三大类:** * **数据移动**:`mov`、`push`、`pop` * **子过程调用**:`call`、`ret` * **调用约定**:C语言调用约定、汇编函数调用约定 **调用惯例**: * **caller rules**:保存应由调用方保存的寄存器 * **callee rules**:从栈上删除传递的参数

正文

http://arthurchiao.art/blog/x86-asm-guide-zh/

 

译者序

Translated from CS216, University of Virginia.

一份非常好的 x86 汇编教程,国外 CS 课程所用资料,篇幅简短,逻辑清晰,合适作为入 门参考。以原理为主,有两个例子帮助理解。其开始提到使用 MicroSoft MASM 和 Visual Studio,但非必须, 事实上如果你有 Linux 更好。

本文根据原文内容意译,而非逐词逐句翻译,如需了解更多,推荐阅读原文 .


内容:寄存器, 内存和寻址, 指令, 函数调用约定(Calling Convention)

本文介绍 32bit x86 汇编基础,覆盖其中虽小但很有用的一部分。 有多种汇编语言可以生成 x86 机器代码。我们在 CS216 课程中使用的是 MASM( Microsoft Macro Assembler)。MASM 使用标准 Intel 语法。

整套 x86 指令集庞大而复杂(Intel x86 指令集手册超过 2900 页),本文不会全部覆盖。

1. 参考资料

  • Guide to Using Assembly in Visual Studio — a tutorial on building and debugging assembly code in Visual Studio
  • Intel x86 Instruction Set Reference
  • Intel’s Pentium Manuals (the full gory details)

2. 寄存器

Fig 2.1 x86 registers

现代 x86 处理器有 8 个 32 bit 寄存器,如图 1 所示。寄存器名字是早期计算机历史上 流传下来的。例如,EAX 表示 Accumulator,因为它用作算术运算的累加器,ECX 表示 Counter,用来存储循环变量(计数)。大部分寄存器的名字已经失去了原来的意义,但有 两个是例外:栈指针寄存器(Stack Pointer)ESP 和基址寄存器( Base Pointer)EBP。

对于 EAXEBXECXEDX 四个寄存器,可以再将 32bit 划分成多个子寄存器, 每个子寄存器有专门的名字。例如 EAX 的高 16bit 叫 AX(去掉 E, E 大概表示 Extended),低 8bit 叫 AL (Low), 8-16bit 叫 AH (High)。如图 1 所示。

在汇编语言中,这些寄存器的名字是大小写无关的,既可以用 EAX,也可以写 eax

3. 内存和寻址模式

3.1 声明静态数据区

.DATA 声明静态数据区。

数据类型修饰原语:

  • DB: Byte, 1 Byte(DB 的 D 可能表示 Data)
  • DW: Word, 2 Bytes
  • DD: Double Word, 4 Bytes

例子:

.DATA
var     DB 64    ; 声明一个 byte 值, referred to as location var, containing the value 64.
var2    DB ?     ; 声明一个未初始化 byte 值, referred to as location var2.
        DB 10    ; 声明一个没有 label 的 byte 值, containing the value 10. Its location is var2 + 1.
X       DW ?     ; 声明一个 2-byte 未初始化值, referred to as location X.
Y       DD 30000 ; 声明一个 4-byte 值, referred to as location Y, initialized to 30000.

和高级语言不同,在汇编中只有一维数组,只有没有二维和多维数组。一维数组其实就 是内存中的一块连续区域。另外,DUP 和字符串常量也是声明数组的两种方法。

例子:

Z       DD 1, 2, 3      ; 声明 3 个 4-byte values, 初始化为 1, 2, and 3. The value of location Z + 8 will be 3.
bytes   DB 10 DUP(?)    ; 声明 10 个 uninitialized bytes starting at location bytes.
arr     DD 100 DUP(0)   ; 声明 100 个 4-byte words starting at location arr, all initialized to 0
str     DB 'hello',0    ; 声明 6 bytes starting at the address str, 初始化为 hello and the null (0) byte.

3.2 内存寻址 (Addressing Memory)

有多个指令可以用于内存寻址,我们先看使用 MOV 的例子。MOV 将在内存和寄存器之 间移动数据,接受两个参数:第一个参数是目的地,第二个是源。

合法寻址的例子:

mov eax, [ebx]        ; Move the 4 bytes in memory at the address contained in EBX into EAX
mov [var], ebx        ; Move the contents of EBX into the 4 bytes at memory address var. (Note, var is a 32-bit constant).
mov eax, [esi-4]      ; Move 4 bytes at memory address ESI + (-4) into EAX
mov [esi+eax], cl     ; Move the contents of CL into the byte at address ESI+EAX
mov edx, [esi+4*ebx]  ; Move the 4 bytes of data at address ESI+4*EBX into EDX

非法寻址的例子:

mov eax, [ebx-ecx]      ; 只能对寄存器的值相加,不能相减
mov [eax+esi+edi], ebx  ; 最多只能有 2 个寄存器参与地址计算

3.3 数据类型(大小)原语(Size Directives)

修饰指针类型:

  • BYTE PTR - 1 Byte
  • WORD PTR - 2 Bytes
  • DWORD PTR - 4 Bytes
mov BYTE PTR [ebx], 2   ; Move 2 into the single byte at the address stored in EBX.
mov WORD PTR [ebx], 2   ; Move the 16-bit integer representation of 2 into the 2 bytes starting at the address in EBX.
mov DWORD PTR [ebx], 2  ; Move the 32-bit integer representation of 2 into the 4 bytes starting at the address in EBX.

4. 指令

三大类:

  • 数据移动
    1. mov
    2. push
    3. pop
    4. lea - Load Effective Address
  • 算术/逻辑运算
    1. addsub
    2. incdec
    3. imulidiv
    4. andorxor
    5. not
    6. neg
    7. shlshr
  • 控制流
    1. jmp
    2. jejnejzjgjl …
    3. cmp
    4. callret

5. 调用约定

这是最重要的部分。

子过程(函数)调用需要遵守一套共同的调用约定(Calling Convention)。 调用约定是一个协议,规定了如何调用以及如何从过程返回。例如,给定一组 calling convention rules,程序员无需查看子函数的定义就可以确定如何将参数传给它。进一步地 ,给定一组 calling convention rules,高级语言编译器只要遵循这些 rules,就可以使 得汇编函数和高级语言函数互相调用。

Calling conventions 有多种。我们这里介绍使用最广泛的一种:C 语言调用约定(C Language Calling Convention)。遵循这个约定,可以使汇编代码安全地被 C/C++ 调用 ,也可以从汇编代码调用 C 函数库。

C 调用约定:

  • 强烈依赖硬件栈的支持 (hardwared-supported stack)
  • 基于 pushpopcallret 指令
  • 子过程参数通过栈传递: 寄存器保存在栈上,子过程用到的局部变量也放在栈上

在大部分处理器上实现的大部分高级过程式语言,都使用与此相似的调用惯例。

调用惯例分为两部分。第一部分用于 调用方(caller),第二部分用于被调 用方(callee)。需要强调的是,错误地使用这些规则将导致栈被破坏,程序 很快出错;因此在你自己的子过程中实现 calling convention 时需要格外仔细。

Fig 5.1 Stack during Subroutine Call

5.1 调用方规则 (Caller Rules)

在一个子过程调用之前,调用方应该:

  1. 保存应由调用方保存的寄存器(caller-saved registers): EAXECXEDX

    这几个寄存器可能会被被调用方(callee)修改,所以先保存它们,以便调用结 束后恢复栈的状态。

  2. 将需要传给子过程的参数入栈(push onto stack)

    参数按逆序 push 入栈(最后一个参数先入栈)。由于栈是向下生长的,第一个参数 会被存储在最低地址(这个特性使得变长参数列表成为可能)。

  3. 使用 call 指令,调用子过程(函数)

    call 先将返回地址 push 到栈上,然后开始执行子过程代码。子过程代码需要遵 守的 callee rules。

子过程返回后(call 执行结束之后),被调用方会将返回值放到 EAX 寄存器,调用方 可以从中读取。为恢复机器状态,调用方需要做:

  1. 从栈上删除传递的参数

    栈恢复到准备发起调用之前的状态。

  2. 恢复由调用方保存的寄存器(EAXECXEDX)—— 从栈上 pop 出来

    调用方可以认为,除这三个之外,其他寄存器的值没有被修改过。

例子

push [var] ; Push last parameter first
push 216   ; Push the second parameter
push eax   ; Push first parameter last

call _myFunc ; Call the function (assume C naming)

add esp, 12

5.2 被调用方规则 (Callee Rules)

  1. 将寄存器 EBP 的值入栈,然后 copy ESP to EBP

    push ebp
    mov  ebp, esp
    
  2. 在栈上为局部变量分配空间

    栈自顶向下生长,故随着变量的分配,栈顶指针不断减小。

  3. 保存应有被调用方保存(callee-saved)的寄存器 —— 将他们压入栈。包括 EBXEDIESI

以上工作完成,就可以执行子过程的代码了。当子过程返回后,必须做以下工作:

  1. 将返回值保存在 EAX

  2. 恢复应由被调用方保存的寄存器(EDIESI) —— 从栈上 pop 出来

  3. 释放局部变量

  4. 恢复调用方 base pointer EBP —— 从栈上 pop 出来

  5. 最后,执行 ret,返回给调用方 (caller)

例子

.486
.MODEL FLAT
.CODE
PUBLIC _myFunc
_myFunc PROC
  ; Subroutine Prologue
  push ebp     ; Save the old base pointer value.
  mov ebp, esp ; Set the new base pointer value.
  sub esp, 4   ; Make room for one 4-byte local variable.
  push edi     ; Save the values of registers that the function
  push esi     ; will modify. This function uses EDI and ESI.
  ; (no need to save EBX, EBP, or ESP)

  ; Subroutine Body
  mov eax, [ebp+8]   ; Move value of parameter 1 into EAX
  mov esi, [ebp+12]  ; Move value of parameter 2 into ESI
  mov edi, [ebp+16]  ; Move value of parameter 3 into EDI

  mov [ebp-4], edi   ; Move EDI into the local variable
  add [ebp-4], esi   ; Add ESI into the local variable
  add eax, [ebp-4]   ; Add the contents of the local variable
                     ; into EAX (final result)

  ; Subroutine Epilogue 
  pop esi      ; Recover register values
  pop  edi
  mov esp, ebp ; Deallocate local variables
  pop ebp ; Restore the caller's base pointer value
  ret
_myFunc ENDP
END

与[转帖][译] 简明 x86 汇编指南相似的内容:

[转帖][译] 简明 x86 汇编指南

http://arthurchiao.art/blog/x86-asm-guide-zh/ 译者序 Translated from CS216, University of Virginia. 一份非常好的 x86 汇编教程,国外 CS 课程所用资料,篇幅简短,逻辑清晰,合适作为入 门参考。以原理为

[转帖]《AWK程序设计语言》笔记(1)—— AWK入门与简单案例

原文为 《The AWK Programming Language》,GitHub上有中译版,不过有些内容翻译的比较奇怪,建议跟原版对照着看 https://github.com/wuzhouhui/awk 本篇的小案例基本均基于文件 emp.data,三个字段分别为:员工名、每小时工资、工作时长,

[转帖][译] Cilium 未来数据平面:支撑 100Gbit/s k8s 集群(KubeCon, 2022)

http://arthurchiao.art/blog/cilium-tomorrow-networking-data-plane-zh/ 作者写的非常好呢 基础支持的确非常重要呢. Published at 2022-11-12 | Last Update 2022-11-12 译者序 本文翻译自

[转帖][译] Cilium:基于 BPF+EDT+FQ+BBR 实现更好的带宽管理(KubeCon, 2022)

http://arthurchiao.art/blog/better-bandwidth-management-with-ebpf-zh/ Published at 2022-10-30 | Last Update 2022-10-30 译者序 本文翻译自 KubeCon+CloudNativeCo

[转帖][译] Linux Socket Filtering (LSF, aka BPF)(KernelDoc,2021)

http://arthurchiao.art/blog/linux-socket-filtering-aka-bpf-zh/ 译者序 本文翻译自 2021 年 Linux 5.10 内核文档: Linux Socket Filtering aka Berkeley Packet Filter (BP

[转帖][译] 流量控制(TC)五十年:从基于缓冲队列(Queue)到基于时间(EDT)的演进(Google, 2018)

http://arthurchiao.art/blog/traffic-control-from-queue-to-edt-zh/ 译者序 本文组合翻译了 Google 2018 年两篇分享中的技术部分,二者讲的同一件事情,但层次侧重不同: Netdev 2018: Evolving from AF

[转帖][译] Linux 网络栈监控和调优:接收数据(2016)

http://arthurchiao.art/blog/tuning-stack-rx-zh/ 注意:本文内容已经太老,基于 kernel 3.13 和 1Gbps 网卡驱动 igb,建议移步 kernel 5.10 + 25Gbps 驱动版: Linux 网络栈原理、监控与调优:前言 Linux

[转帖][译] Linux 系统调用权威指南(2016)

http://arthurchiao.art/blog/system-call-definitive-guide-zh/ 译者序 本文翻译自 2016 年的一篇英文博客 The Definitive Guide to Linux System Calls 。如果能看懂英文,我建议你阅读原文,或者和本

[转帖][译] RFC 1180:朴素 TCP/IP 教程(1991)

http://arthurchiao.art/blog/rfc1180-a-tcp-ip-tutorial-zh/ 译者序 本文翻译自 1991 年的一份 RFC(1180): A TCP/IP Tutorial。 本文虽距今将近 20 年,但内容并未过时,这不禁让人惊叹于 TCP/IP 协议栈生命

[转帖][译] 大规模微服务利器:eBPF + Kubernetes(KubeCon, 2020)

http://arthurchiao.art/blog/ebpf-and-k8s-zh/ Published at 2020-09-06 | Last Update 2021-02-21 译者序 本文翻译自 2020 年 Daniel Borkmann 在 KubeCon 的一篇分享: eBPF a