汇编语言过程.md

原始源文件

---
ingested: true
ingestedAt: 2026-05-17
---
标题: 汇编语言 - 过程(子程序)
UP主: runoob
链接: https://www.runoob.com/assembly/assembly-procedure.html
提取方式: WebFetch
内容: 过程(Procedure)是汇编语言中实现函数/子程序复用的机制,它能让代码结构化、可重用。理解过程和栈的关系是汇编编程的重要里程碑。

---

## 什么是过程

过程 是一段可以被多次调用的代码块,类似于高级语言中的函数(Function)或子程序(Subroutine)。

在汇编中,过程通过 `CALL` 指令调用,通过 `RET` 指令返回。`CALL` 会将返回地址压入栈中,`RET` 从栈中弹出返回地址并跳转回去。

## 实例

; 文件路径:simple_proc.asm  
; 最简单的过程调用示例

section .data  
 msg db 'Hello, runoob!', 0xA  
 len equ $ - msg

section .text  
global _start

_start:  
call print_message ; 调用过程(将下条指令地址压栈)  
call print_message ; 再次调用

mov eax, 1  
mov ebx, 0  
int 0x80

; 过程:打印消息  
print_message:  
push eax ; 保存要修改的寄存器  
push ebx  
push ecx  
push edx

mov eax, 4  
mov ebx, 1  
mov ecx, msg  
mov edx, len  
int 0x80

pop edx ; 恢复寄存器(按相反顺序)  
pop ecx  
pop ebx  
pop eax  
ret ; 返回调用处(从栈中弹出返回地址)  

> 在过程内部,应当保存和恢复所有被修改的寄存器(除了 EAX 当返回值时),这是汇编编程的基本礼仪。不这样做会导致调用方的寄存器值被意外修改。

---

## CALL 和 RET 原理

`CALL` 和 `RET` 配对工作,依赖栈来管理返回地址:

| 指令         | 实际执行的操作                        |
| ---------- | ------------------------------ |
| CALL label | push eip(保存下一条指令地址)+ jmp label |
| RET        | pop eip(从栈中弹出地址并跳转)            |

---

## 通过寄存器传递参数

在调用过程前将参数放入约定好的寄存器:

## 实例

; 文件路径:proc_params_reg.asm  
; 通过寄存器传递参数

section .data  
 newline db 0xA

section .text  
global _start

_start:  
; 调用 add_two 过程:计算 10 + 20  
mov eax, 10 ; 第一个参数放在 eax  
mov ebx, 20 ; 第二个参数放在 ebx  
call add_two ; 调用过程  
; 返回值在 eax 中 = 30

mov ebx, eax ; 退出码 = 30  
mov eax, 1  
int 0x80

; 过程:计算 eax + ebx,结果返回在 eax 中  
add_two:  
add eax, ebx ; eax = eax + ebx  
ret  

---

## 通过栈传递参数

栈传递参数更灵活,是 C 语言等高级语言的标准方式:

## 实例

; 文件路径:proc_params_stack.asm  
; 通过栈传递参数(cdecl 风格)

section .data  
 msg db 'Sum is: '  
 msg_len equ $ - msg  
 newline db 0xA

section .bss  
 result_buf resb 4

section .text  
global _start

_start:  
; 调用 sum 过程:计算 100 + 200  
push dword 200 ; 压入第 2 个参数  
push dword 100 ; 压入第 1 个参数  
call sum ; 调用过程  
add esp, 8 ; ★ 调用者清理栈(cdecl 约定)  
; 返回值在 eax 中 = 300

mov ebx, eax  
mov eax, 1  
int 0x80

; 过程:sum(a, b) = a + b  
sum:  
push ebp ; ★ 保存旧的基址指针  
mov ebp, esp ; ★ 设置新的栈帧基址

; 栈布局(从高地址到低地址):  
; [ebp+12] = 参数 b(200)  
; [ebp+8] = 参数 a(100)  
; [ebp+4] = 返回地址  
; [ebp] = 旧的 ebp  
; [ebp-4] = 局部变量空间

mov eax, [ebp + 8] ; 获取第 1 个参数(a)  
add eax, [ebp + 12] ; 加上第 2 个参数(b)

pop ebp ; ★ 恢复旧的基址指针  
ret ; 返回  

栈帧结构图解:

高地址
+------------------+
| 参数2 (200)      |  <-- [ebp + 12]
+------------------+
| 参数1 (100)      |  <-- [ebp + 8]
+------------------+
| 返回地址          |  <-- [ebp + 4]
+------------------+
| 旧的 EBP         |  <-- [ebp]  (当前 EBP 指向这里)
+------------------+
| 局部变量区        |  <-- [ebp - 4], [ebp - 8], ...
+------------------+  <-- ESP 指向这里
低地址

> 函数序言(Prologue)`push ebp; mov ebp, esp` 和尾声(Epilogue)`pop ebp; ret`(或 `leave; ret`)是标准的栈帧管理模板,几乎所有汇编函数都以这个结构开始和结束。

---

## 过程的返回值

返回值通常存放在 EAX 寄存器中(32 位值)或 EDX:EAX(64 位值):

## 实例

; 返回值示例

; 返回 int  
call get_answer  
; eax = 42

; 返回 64 位值(如 long long)  
call get_big_value  
; edx:eax = 64 位结果

get_answer:  
mov eax, 42 ; 返回值放在 eax 中  
ret

get_big_value:  
mov eax, 0x00000001 ; 低 32 位  
mov edx, 0x00000000 ; 高 32 位  
ret  

---

## 局部变量

局部变量使用栈空间,在过程序言中通过减小 ESP 来分配:

## 实例

; 文件路径:local_vars.asm  
; 在过程中使用局部变量

section .text  
global _start

_start:  
push dword 10  
push dword 20  
call multiply_add  
add esp, 8  
; eax = 10 * 20 + 10 + 20 = 230

mov ebx, eax  
mov eax, 1  
int 0x80

; 过程:multiply_add(x, y) = x*y + x + y  
multiply_add:  
push ebp  
mov ebp, esp  
sub esp, 8 ; ★ 在栈上分配 8 字节局部变量空间

; 现在 [ebp-4] 和 [ebp-8] 都是可用的局部变量

mov eax, [ebp + 8] ; x  
mov ebx, [ebp + 12] ; y

; [ebp-4] = x * y  
imul eax, ebx  
mov [ebp - 4], eax ; 局部变量1 = x * y

; [ebp-8] = x + y  
mov eax, [ebp + 8]  
add eax, [ebp + 12]  
mov [ebp - 8], eax ; 局部变量2 = x + y

; 返回值 = [ebp-4] + [ebp-8]  
mov eax, [ebp - 4]  
add eax, [ebp - 8]

; ★ 清理局部变量空间并恢复  
mov esp, ebp ; 等价于 add esp, 8  
pop ebp  
ret  

> 局部变量在栈帧中分配,过程返回后自动释放。使用 `leave` 指令可以替代 `mov esp, ebp; pop ebp`,效果等价。

---

## 过程调用约定对比

| 约定       | 参数传递         | 栈清理者 | 寄存器保存              | 返回值 |
| -------- | ------------ | ---- | ------------------ | --- |
| cdecl    | 栈(右到左压入)     | 调用者  | EAX,ECX,EDX 由调用者保存 | EAX |
| stdcall  | 栈(右到左压入)     | 被调用者 | EAX,ECX,EDX 由调用者保存 | EAX |
| fastcall | ECX, EDX + 栈 | 被调用者 | EAX,ECX,EDX 由调用者保存 | EAX |