汇编语言内存管理.md

原始源文件

---
ingested: true
ingestedAt: 2026-05-17
---
标题: 汇编语言 - 内存管理
链接: https://www.runoob.com/assembly/assembly-memory-mgmt.html
内容:

## 汇编语言 - 内存管理

内存管理是系统编程的重要部分。在汇编语言中,你可以通过系统调用动态分配和释放内存,直接操作内存地址,实现高效的内存管理。

---

## 程序的内存布局

一个运行中的程序在内存中的分布如下:

![程序内存布局图](https://www.runoob.com/wp-content/uploads/2026/04/memory-layout.svg)

一个 Linux 进程在内存中的布局如下:

高地址 (0xFFFFFFFF)
+----------------------+
|    内核空间           |  用户程序不可访问
+----------------------+  (0xC0000000)
|    栈 (Stack)        |  <-- ESP 指向栈顶
|    向下增长           |
+----------------------+
|                      |
|    内存映射区域        |  mmap 分配的区域
|                      |
+----------------------+
|    堆 (Heap)         |  <-- brk/sbrk 管理
|    向上增长           |
+----------------------+
|    BSS 段 (.bss)     |  未初始化全局变量
+----------------------+
|    数据段 (.data)     |  已初始化全局变量
+----------------------+
|    代码段 (.text)     |  程序指令(只读)
+----------------------+
低地址 (0x08048000)

---

## 动态内存分配:brk 系统调用

`sys_brk`(系统调用号 45)通过调整程序的 数据段边界(program break) 来分配/释放内存。

program break 是数据段(包括 .data、.bss 和堆)的结束位置,在其之上的内存程序不能访问。

| 调用方式      | 说明            | 返回值                        |
| --------- | ------------- | -------------------------- |
| ebx = 0   | 获取当前 break 地址 | EAX = 当前 break 地址          |
| ebx = 新地址 | 设置新的 break 地址 | EAX = 新的 break 地址(失败返回原地址) |

## 实例

; 文件路径:brk_demo.asm  
; 使用 sys_brk 动态分配内存

section .data  
 alloc_msg db 'Memory allocated successfully at: 0x'  
 alloc_len equ $ - alloc_msg  
 newline db 0xA

section .bss  
 orig_break resd 1 ; 保存原始 break 地址

section .text  
global _start

_start:  
; 1. 获取当前 program break  
mov eax, 45 ; sys_brk  
mov ebx, 0 ; ebx=0 表示查询当前 break  
int 0x80  
mov [orig_break], eax ; 保存原始 break 地址

; 2. 分配 4096 字节(4KB)内存  
mov ebx, eax ; 当前 break 地址  
add ebx, 4096 ; 增加 4096 字节  
mov eax, 45 ; sys_brk  
int 0x80  
; EAX = 新的 break 地址(即分配后的区域末尾)

; 3. 新分配的内存在 orig_break 到 eax-1 之间  
; 可以安全地读写这片内存  
mov ebx, [orig_break] ; 获取分配区域的起始地址  
mov dword [ebx], 42 ; 在新内存中写入 42  
mov eax, [ebx] ; 读取回来

; 4. 释放内存:将 break 恢复到原位  
mov eax, 45 ; sys_brk  
mov ebx, [orig_break] ; 恢复到原始 break  
int 0x80

mov eax, 1  
mov ebx, 0  
int 0x80  

> sys_brk 只能调整连续的数据段边界,不能释放中间的内存。要释放某块已分配的内存,必须先释放之后分配的所有内存。这就是为什么现代程序更多使用 mmap 或 malloc 库函数。

---

## 使用 mmap 分配内存(推荐)

`sys_mmap2`(系统调用号 192)更灵活,可以独立分配和释放多块内存:

## 实例

; 文件路径:mmap_demo.asm  
; 使用 mmap 分配可独立释放的内存

section .data  
; mmap 相关常量  
 PROT_READ equ 1  
 PROT_WRITE equ 2  
 MAP_PRIVATE equ 2  
 MAP_ANONYMOUS equ 0x20

section .bss  
 mem_block resd 1 ; 保存分配的内存地址

section .text  
global _start

_start:  
; mmap 调用:分配 4096 字节的匿名内存  
mov eax, 192 ; sys_mmap2  
mov ebx, 0 ; 让内核选择地址  
mov ecx, 4096 ; 分配大小:4KB  
mov edx, PROT_READ | PROT_WRITE ; 可读可写  
mov esi, MAP_PRIVATE | MAP_ANONYMOUS ; 私有匿名映射  
mov edi, -1 ; 文件描述符(匿名映射用 -1)  
mov ebp, 0 ; 偏移量(匿名映射用 0)  
int 0x80  
; EAX = 分配的内存地址(失败返回负值)

cmp eax, -4096 ; 检查是否失败  
ja mmap_failed ; 如果在 -4095 ~ -1 之间,失败

mov [mem_block], eax ; 保存内存地址

; 使用分配的内存:写入数据  
mov ebx, [mem_block]  
mov dword [ebx], 0x12345678 ; 写入 4 字节  
mov dword [ebx + 4], 'runo' ; 写入 "runo"  
mov dword [ebx + 8], 'ob!!' ; 写入 "ob!!"

; 释放内存:munmap  
mov eax, 91 ; sys_munmap  
mov ebx, [mem_block] ; 内存地址  
mov ecx, 4096 ; 释放大小  
int 0x80

jmp exit

mmap_failed:  
; 处理错误...

exit:  
mov eax, 1  
mov ebx, 0  
int 0x80  

---

## 内存读写操作

汇编语言中,所有内存访问通过 `mov` 指令配合方括号完成:

## 实例

; 内存读写基本操作

section .data  
 var1 db 0x55 ; 1 字节  
 var2 dw 0x1234 ; 2 字节  
 var3 dd 0x12345678 ; 4 字节

section .text  
global _start

_start:  
; 读取不同大小的内存  
mov al, [var1] ; 读取 1 字节:al = 0x55  
mov ax, [var2] ; 读取 2 字节:ax = 0x1234  
mov eax, [var3] ; 读取 4 字节:eax = 0x12345678

; 写入不同大小的内存  
mov byte [var1], 0xAA ; 写入 1 字节  
mov word [var2], 0xABCD ; 写入 2 字节  
mov dword [var3], 0xDEADBEEF ; 写入 4 字节

; 通过指针操作内存  
mov ebx, var3 ; ebx 指向 var3  
mov eax, [ebx] ; 间接读取 var3 的值  
add dword [ebx], 1 ; var3 = var3 + 1

mov eax, 1  
mov ebx, 0  
int 0x80  

---

## 内存操作的安全规范

在汇编中操作内存没有"安全网",以下规范需要牢记:

| 规范        | 说明                | 违反后果                    |
| --------- | ----------------- | ----------------------- |
| 不越界访问     | 读写不超过变量/缓冲区的大小    | 数据损坏、段错误                |
| 不读未初始化内存  | .bss 段的值不确定       | 不可预测的结果                 |
| 不在无效地址上读写 | 确保指针指向有效内存        | 段错误(Segmentation Fault) |
| 不写只读内存    | .text 段和常量区不可写    | 段错误                     |
| 地址对齐      | 字访问用偶地址,双字用 4 的倍数 | 性能下降或总线错误               |

## 实例

; 常见内存错误示例(警告:会导致崩溃!)

section .data  
 small_buf db 0, 0, 0, 0 ; 只有 4 字节

section .text  
global _start

_start:  
; 危险操作1:越界写入  
; mov dword [small_buf + 3], 0x12345678  
; 这会覆盖 small_buf 之后 3 字节的数据!

; 危险操作2:写入代码段(只读)  
; mov dword [_start], 0x90 ; 尝试修改代码,会导致段错误

; 危险操作3:空指针/无效地址  
; mov eax, [0] ; 读取地址 0,会导致段错误  
; mov [0], eax ; 写入地址 0,会导致段错误

; 安全做法:始终在分配的内存范围内操作  
mov dword [small_buf], 'runo' ; 正确:在 4 字节内操作

mov eax, 1  
mov ebx, 0  
int 0x80  

---

## 栈内存管理

栈是另一种重要的内存区域,由 PUSH/POP 指令和 ESP 寄存器管理:

## 实例

; 栈操作全面示例

section .data  
 original_esp dd 0

section .text  
global _start

_start:  
mov [original_esp], esp ; 保存原始栈指针

; PUSH:压栈(ESP 减 4,写入数据)  
push dword 100 ; 压入 100  
; ESP = ESP - 4, [ESP] = 100

push dword 200 ; 压入 200  
push dword 300 ; 压入 300

; 栈布局:  
; ESP+0: 300  
; ESP+4: 200  
; ESP+8: 100

; POP:弹栈(读取数据,ESP 加 4)  
pop eax ; eax = 300, ESP += 4  
pop ebx ; ebx = 200, ESP += 4  
pop ecx ; ecx = 100, ESP += 4

; 通过栈分配局部空间  
sub esp, 256 ; 在栈上分配 256 字节  
; 现在 ESP 到 ESP+255 这 256 字节可以安全使用  
mov dword [esp], 42 ; 在局部空间写入 42  
add esp, 256 ; 释放局部空间

; 确保 ESP 回到原始位置!  
mov eax, 1  
mov ebx, 0  
int 0x80  

> 栈操作最重要的规则:PUSH 和 POP 必须配对。函数返回前必须确保 ESP 回到正确的位置,否则 RET 会弹出错误的返回地址,导致程序崩溃或执行任意代码。这是汇编编程中最致命的 bug 之一。

---

## 内存管理方案对比

| 方案               | 灵活性        | 复杂度 | 适用场景      |
| ---------------- | ---------- | --- | --------- |
| 全局变量(.data/.bss) | 低(编译时固定)   | 最低  | 固定大小的数据   |
| 栈分配(sub esp)     | 中(函数内动态)   | 低   | 函数局部变量    |
| brk/sbrk         | 中(只能增长/收缩) | 中   | 连续内存区域    |
| mmap             | 高(任意分配释放)  | 高   | 需要灵活管理内存时 |