内存寻址与堆栈

一、上节课作业回顾

  • 寄存器结构:32位、16位、8位寄存器的包含关系(如 EAX → AX → AH/AL)。
  • MOV、ADD、SUB 等指令的所有合法形式,强调操作数宽度一致。
  • 内存读写练习:加深对数据宽度和内存编号的理解。

二、内存数据窗口显示

数据窗口与堆栈窗口的数值顺序

  • 在寄存器窗口和反汇编窗口中,多字节数值按“高位在前,低位在后”显示(与我们书写习惯一致)。
  • 在数据窗口(内存 dump)中,顺序相反:低位在前,高位在后,以字节为单位倒序排列。
  • 例如:内存地址 0x12FFDC 处若存储 0x12345678,则数据窗口中显示为 78 56 34 12

内存编号与宽度

  • 每个内存编号对应 1 字节
  • 通过 MOV 读/写内存时,用 BYTEWORDDWORD 指定操作宽度,会同时影响连续的几个字节。
  • 示例:
    • MOV BYTE PTR DS:[地址], 值 → 只改动 1 字节。
    • MOV WORD PTR DS:[地址], 值 → 改动连续 2 字节。
    • MOV DWORD PTR DS:[地址], 值 → 改动连续 4 字节。

三、内存寻址公式

核心:所有复杂的地址表达式最终都只为了得到一个内存编号。以下五种方式涵盖了 CPU 能识别的所有寻址格式。

1. 寻址公式一:[立即数]

1
2
3
4
5
6
; 读取内存的值
MOV EAX, DWORD PTR DS:[0x13FFC4]
; 向内存写入数据
MOV DWORD PTR DS:[0x13FFC8], EBX
; 获取内存地址编号(而非内存中的内容)
LEA EAX, DWORD PTR DS:[0x13FFC4]

2. 寻址公式二:[reg]

reg 代表任意 8 个 32 位通用寄存器之一。

1
2
3
4
MOV ECX, 0x13FFD0
MOV EAX, DWORD PTR DS:[ECX] ; 读取
MOV DWORD PTR DS:[EDX], 0x87654321 ; 写入
LEA EAX, DWORD PTR DS:[EDX] ; 取地址编号

3. 寻址公式三:[reg + 立即数]

1
2
3
4
MOV ECX, 0x13FFD0
MOV EAX, DWORD PTR DS:[ECX+4] ; 读取
MOV DWORD PTR DS:[EDX+0xC], 0x87654321 ; 写入
LEA EAX, DWORD PTR DS:[EDX+4] ; 取地址编号

4. 寻址公式四:[reg + reg * {1,2,4,8}]

乘数只能是 1, 2, 4, 8。

1
2
3
4
5
MOV EAX, 0x13FFC4
MOV ECX, 2
MOV EDX, DWORD PTR DS:[EAX+ECX*4] ; 读取
MOV DWORD PTR DS:[EAX+ECX*4], 0x87654321 ; 写入
LEA EAX, DWORD PTR DS:[EAX+ECX*4] ; 取地址编号

5. 寻址公式五:[reg + reg * {1,2,4,8} + 立即数]

1
2
3
4
5
MOV EAX, 0x13FFC4
MOV ECX, 2
MOV EDX, DWORD PTR DS:[EAX+ECX*4+4] ; 读取
MOV DWORD PTR DS:[EAX+ECX*4+4], 0x87654321 ; 写入
LEA EAX, DWORD PTR DS:[EAX+ECX*4+2] ; 取地址编号

四、堆栈

1. 为什么需要堆栈?

当需要临时存储大量数据(远超 8 个寄存器)时,必须有规律地使用内存,且满足:

  • 记录存了多少数据。
  • 快速定位任意一个数据。
  • 使用完毕能方便地“丢弃”。

2. 堆栈的基本设计

用两个寄存器分别记录起始地址(栈底)和当前地址(栈顶)。

  • 栈底(BASE)固定不动。
  • 栈顶(TOP)随数据的存入和弹出而上下移动。
  • 每次存取 4 字节(演示用),栈顶的值减 4(向低地址增长)表示入栈,加 4 表示出栈。
  • 读取中间数据时,可以用 BASE + 偏移TOP + 偏移 来定位。
TOP:0X100C
BASE:0X1000

3. 手动模拟堆栈操作

压入数据(入栈)的多种实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; 假设 EBX 存栈底,EDX 存栈顶,初始都指向 0x13FFDC
MOV EBX,13FFDC BASE
MOV EDX,13FFDC TOP
; 方式一:先写数据,后移动栈顶
MOV DWORD PTR DS:[EDX-4], 0xAAAAAAAA
SUB EDX, 4

; 方式二:先移动栈顶,后写数据
SUB EDX, 4
MOV DWORD PTR DS:[EDX], 0xBBBBBBBB

; 方式三:先写数据,后用 LEA 移动栈顶
MOV DWORD PTR DS:[EDX-4], 0xDDDDDDDD
LEA EDX, DWORD PTR DS:[EDX-4]

; 方式四:先用 LEA 移动栈顶,后写数据
LEA EDX, DWORD PTR DS:[EDX-4]
MOV DWORD PTR DS:[EDX], 0xEEEEEEEE

读取中间数据

1
2
3
4
5
; 通过栈底加偏移:读第 1 个压入的数(最后一个位置)
MOV ESI, DWORD PTR DS:[EBX-4]

; 通过栈顶加偏移:读第 2 个压入的数
MOV EDI, DWORD PTR DS:[EDX+4]

弹出数据(出栈)

1
2
3
4
5
6
7
; 方式一:先读值,后移动栈顶
MOV ECX, DWORD PTR DS:[EDX]
LEA EDX, DWORD PTR DS:[EDX+4] ; 或 ADD EDX,4

; 方式二:先移动栈顶,后读值
LEA EDX, DWORD PTR DS:[EDX+4]
MOV ESI, DWORD PTR DS:[EDX-4]

4. 正式堆栈操作:PUSH / POP

CPU 提供了专门的寄存器(ESP 栈顶,EBP 栈底)和指令,但我们自己也可以用任何寄存器模拟。

PUSH 指令(可压入寄存器、内存、立即数):

1
2
3
4
PUSH EAX          ; 压入 32 位寄存器
PUSH AX ; 压入 16 位寄存器
PUSH DWORD PTR DS:[地址] ; 压入内存
PUSH 0x1234 ; 压入立即数

功能等价于:SUB ESP,4 + MOV [ESP], 源操作数

POP 指令

1
2
POP EAX           ; 弹出到 32 位寄存器
POP WORD PTR DS:[地址] ; 弹出到内存

功能等价于:MOV 目标, [ESP] + ADD ESP,4

PUSHAD / POPAD

  • PUSHAD:将所有 8 个 32 位通用寄存器按固定顺序压栈。
  • POPAD:按相反顺序弹出,恢复寄存器。

五、课后作业

  1. 内存寻址练习:对五种寻址公式,逐一实现:读取内存、写入内存、获取内存地址编号。
  2. 堆栈模拟练习
    • 用 EBX 做栈底、EDX 做栈顶,连续压入 5 个不同的数。
    • 分别通过栈底加偏移和栈顶加偏移的方式读取这 5 个数到寄存器中。
    • 将这 5 个数依次弹出,并恢复栈顶到初始位置。
  3. 指令变形挑战(选做):
    • 用至少两种不同的指令组合,实现 PUSH ECX 的功能。
    • 用至少两种不同的指令组合,实现 POP ECX 的功能。
    • 思考:PUSH ESPPOP ESP 如何用其他指令等效替代?
作者

萌新知

发布于

2026-07-27

更新于

2026-07-27

许可协议