重点难点内容
- 掌握堆栈指令
-
- 指令:
push
、pop
、CALL
、RET
、PROC
、ENDP
和USES
。 - 应用:理解堆栈操作的原理,包括入栈和出栈操作,以及堆栈在程序中的作用。
- 指令:
堆栈操作
1. 堆栈概念
- 定义:堆栈是内存中的一部分区域,用于存储数据,遵循先进后出(LIFO)的原则。
- 寄存器:
-
- ESP(32位寄存器)用于指示堆栈顶部的地址。
- SP(16位寄存器)在16位模式下使用。
- 堆栈的地址从高地址向低地址增长。
2. 入栈与出栈操作
- 入栈指令(
push
):
-
- 用于将数据压入堆栈。常见格式包括:
-
-
PUSH reg/mem16
、PUSH reg/mem32
、PUSH imm32
。- 特殊指令:
pushfd
(将标志寄存器压入堆栈)、pushad
(将所有32位通用寄存器压入堆栈)、pusha
(将所有16位通用寄存器压入堆栈)。
-
-
- 示例:
push eax ; 将EAX寄存器值压入堆栈
push 10h ; 将立即数10h压入堆栈
pushfd ; 将标志寄存器(EFLAGS)压入堆栈
- 出栈指令(
pop
):
-
- 用于将数据从堆栈弹出,常见格式包括:
-
-
POP reg/mem16
、POP reg/mem32
。- 特殊指令:
popfd
(将堆栈中的内容弹出到标志寄存器)、popad
(按顺序弹出所有32位通用寄存器)、popa
(按顺序弹出所有16位通用寄存器)。
-
-
- 示例:
pop eax ; 将堆栈顶的值弹出到EAX寄存器
popfd ; 将堆栈顶的标志寄存器值弹出
过程(子程序、函数)
1. 定义与调用
- 定义过程:使用
PROC
和ENDP
伪指令定义过程。
-
- 示例:
main PROC
; 主程序代码
main ENDP
- 调用过程:使用
CALL
指令调用子程序。
-
- 示例:
main PROC
call sample ; 调用子程序sample
main ENDP
- 返回指令:
RET
指令从子程序返回,恢复主程序的执行流。
-
- 示例:
sample PROC
; 子程序代码
ret ; 返回主程序
sample ENDP
-
- 记住 call 和 ret 对 EIP 和栈的处理。(call 存入下一条指令的地址入栈,将过程地址给 EIP;ret 出栈给到 EIP)
2. 嵌套调用
- 示例:过程可以嵌套调用,即在一个过程内部调用另一个过程。
call Sub1
Sub1 PROCcall Sub2Sub2 PROCcall Sub3Sub2 ENDP
Sub1 ENDP
3. 参数传递
- 寄存器传递:可以通过寄存器传递参数。
mov eax, 10000h ; 参数1
mov ebx, 20000h ; 参数2
mov ecx, 30000h ; 参数3
call Sumof ; 调用子程序
- 指针和计数器传递:通过指针和计数器传递数组参数。
.data
array DWORD 10000h, 20000h, 30000h, 40000h, 50000h
theSum DWORD
.code
main PROC
mov esi, OFFSET array ; ESI指向数组
mov ecx, LENGTHOF array ; ECX为数组计数器
call ArraySum ; 计算和数
mov theSum, eax ; 将结果存储在theSum
4. 保存与恢复寄存器
- 保存和恢复寄存器:使用
push
和pop
指令保存和恢复在调用过程中被修改的寄存器值。 - 法一:使用
push
和pop
指令
MyProc PROCpush eax ; 保存 EAXpush ebx ; 保存 EBX; ... 过程代码 ...pop ebx ; 恢复 EBXpop eax ; 恢复 EAXret
MyProc ENDP
- 法二:使用了
USES
指令,列出所有需要保存的寄存器;汇编器会自动在过程开始时生成 PUSH 指令,在过程结束时生成 POP 指令
ArraySum PROC USES esi ecxmov eax, 0
L1:add eax, [esi] ; 累加数组元素add esi, TYPE DWORD ; 指向下一个数组元素loop L1 ; 循环遍历数组ret ; 返回
ArraySum ENDP
链接到外部库
1. 链接库概念
- 定义:链接库包含已汇编为机器代码的过程(子程序)。通过链接库,可以重用其他程序中定义的过程和函数。
2. Irvine32链接库
参考:Visual Studio 配置Irvine32外部链接库
- 概念:Irvine32链接库是专为初学者设计的,提供简单的输入输出接口和一些常用的过程。常见的过程包括:
-
WriteInt
:将整数输出到控制台。WriteString
:向控制台输出字符串。WriteChar
:输出一个字符。Crlf
:在控制台窗口写行结束符
以下是对 Irvine32 链接库提供的简单输入输出接口和常用过程的详细介绍:
1. 输出函数:
- WriteString:
-
- 功能:将一个以零结尾的字符串输出到标准输出设备(通常是控制台)。
- 使用方法:
.datamessage BYTE "Hello, World!",0
.codemov edx, OFFSET message call WriteString
- 解释:将 `message` 字符串的偏移地址存储在 `edx` 寄存器中,然后调用 `WriteString` 函数,该函数会输出 `edx` 所指向的字符串,直到遇到零终止符。
- WriteChar:
-
- 功能:将一个字符输出到标准输出设备。
- 使用方法:
.codemov al, 'A'call WriteChar
- 解释:将字符存储在 `al` 寄存器中,然后调用 `WriteChar` 函数将其输出。
- WriteDec:
-
- 功能:将
eax
寄存器中的有符号十进制整数输出到标准输出设备。 - 使用方法:
- 功能:将
.codemov eax, 12345call WriteDec
- 解释:将有符号十进制整数存储在 `eax` 寄存器中,然后调用 `WriteDec` 函数将其输出。
- WriteHex:
-
- 功能:将
eax
寄存器中的无符号十六进制整数输出到标准输出设备。 - 使用方法:
- 功能:将
.codemov eax, 0ABCDEFhcall WriteHex
- 解释:将无符号十六进制整数存储在 `eax` 寄存器中,然后调用 `WriteHex` 函数将其输出。
- WriteBin:
-
- 功能:将
eax
寄存器中的无符号二进制整数输出到标准输出设备。 - 使用方法:
- 功能:将
.codemov eax, 10101010bcall WriteBin
- 解释:将无符号二进制整数存储在 `eax` 寄存器中,然后调用 `WriteBin` 函数将其输出。
2. 输入函数:
- ReadChar:
-
- 功能:从标准输入设备读取一个字符,并将其存储在
al
寄存器中。 - 使用方法:
- 功能:从标准输入设备读取一个字符,并将其存储在
.codecall ReadChar
- 解释:调用 `ReadChar` 函数,从标准输入设备读取一个字符,结果存储在 `al` 寄存器中。
- ReadString:
-
- 功能:从标准输入设备读取一个字符串,并存储在指定的缓冲区中。
- 使用方法:
.databuffer BYTE 80 DUP(0)
.codemov edx, OFFSET buffermov ecx, 80call ReadString
- 解释:将 `buffer` 的偏移地址存储在 `edx` 寄存器中,将 `buffer` 的最大长度存储在 `ecx` 寄存器中,然后调用 `ReadString` 函数,将读取的字符串存储在 `buffer` 中。
- ReadInt:
-
- 功能:从标准输入设备读取一个有符号十进制整数,并存储在
eax
寄存器中。 - 使用方法:
- 功能:从标准输入设备读取一个有符号十进制整数,并存储在
.codecall ReadInt
- 解释:调用 `ReadInt` 函数,从标准输入设备读取一个有符号十进制整数,结果存储在 `eax` 寄存器中。
3. 其他常用过程:
- Crlf:
-
- 功能:输出一个换行符,将光标移到下一行。
- 使用方法:
.codecall Crlf
- 解释:调用 `Crlf` 函数,在输出中插入一个换行符。
4. 示例程序:
.386
.model flat, stdcall
.stack 4096
include Irvine32.inc
ExitProcess PROTO, dwExitCode:DWORD.datahelloMsg BYTE "Enter your name: ",0nameBuffer BYTE 30 DUP(0)welcomeMsg BYTE "Welcome, ",0 .code
main PROC; 输出提示信息mov edx, OFFSET helloMsgcall WriteString; 读取用户输入的名字mov edx, OFFSET nameBuffermov ecx, 30call ReadString ; 输出欢迎信息mov edx, OFFSET welcomeMsgcall WriteStringmov edx, OFFSET nameBuffercall WriteStringcall Crlf; 程序结束invoke ExitProcess, 0
main ENDP
END main
总结
- 堆栈操作:通过
push
和pop
指令实现数据入栈和出栈,堆栈用于存储返回地址、局部变量和保存寄存器状态等。 - 过程定义与调用:使用
PROC
和ENDP
定义过程,使用CALL
指令调用,RET
指令返回。 - 参数传递:可以通过寄存器、指针和计数器传递参数,子程序可以保存和恢复寄存器的状态,避免修改主程序的寄存器值。
- 外部库链接:通过使用Irvine32链接库,可以调用常见的输入输出和控制过程,简化程序开发。