- 记录汇编语言课笔记,可能有不正确的地方,欢迎指出
- 教材《新概念汇编语言》—— 杨季文
- 这篇文章对应书第二章 IA32处理器基本功能 3.5部分
文章目录
- 一、子程序设计要点
- 二、子程序设计举例
- 三、子程序调用方法
- (1)调用指令
- 1. 分类
- 2. 段内直接
- 3. 段内间接
- 4、函数指针
- (2)返回指令
- 1、分类
- 2、 段内返回不带立即数
- 3、 段内返回带立即数
- 四、示例
一、子程序设计要点
- 两种传参方法
- 寄存器
- 堆栈
-
调用约定
决定了到底怎么传参,在C语言写函数定义时,写以下关键词来显示指定调用约定,
如void _fastcall cf330(unsigned m, char *buffer)
指定了约定方式为_fastcall
- 注意都是从右向左入栈
- 注意不同调用约定对于清理堆栈的情况不同,如果函数自己不清,一定要手动清,维持堆栈平衡
- 安排局部变量的方法
- 子程序往往需要定义一些局部变量。所谓的局部,也就是限于子程序,或者限于代码片段。
- 寄存器作为局部变量可以提高效率。但寄存器数量较少,一般不把局部变量安排在寄存器中。
- 利用堆栈来安排局部变量。这个方法虽然较复杂,但可以安排足够多的局部变量。
- 用堆栈安排,关键在于移动esp指针位置
- 如果局部变量数量少,可以push一个寄存器进去;如果数量多,可以直接修改esp的值,然后用堆栈操作赋值
- 保护寄存器的约定
- 子程序可能会破坏某些寄存器内容。为此必须对有关寄存器的内容进行保护与恢复。
- 事前压入堆栈,事后从堆栈弹出。在利用堆栈进行寄存器的保护和恢复时,一定要注意堆栈的先进后出特性,一定要注意堆栈平衡
- 可能会降低效率。
- 需要主程序和子程序之间的“默契”和“约定”。子程序只保护主程序关心的那些寄存器,通常保护ebx、esi、edi和ebp。
- 描述子程序的说明
- 在给出子程序代码时,应该给出子程序的说明信息。
- 子程序说明信息一般包括:
- 子程序名(或者入口标号);
- 子程序功能描述;
- 子程序的入口参数和出口参数;
- 所影响的寄存器等情况;
- 使用的算法和重要的性能指标;
- 其他调用注意事项和说明信息;
- 调用实例。
二、子程序设计举例
三、子程序调用方法
(1)调用指令
1. 分类
- 段内直接调用
- 段内间接调用
- 段间直接调用(不介绍)
- 段间间接调用(不介绍)
2. 段内直接
名称 | call(段内直接调用指令) |
格式 | |
动作 | 把调用指令下一行指令地址压栈,然后转到LABEL处执行 |
注意 | 除了保存返回地址,其他同无条件转 |
3. 段内间接
名称 | call(段内间接调用指令) |
格式 | |
动作 | 把调用指令下一行指令地址压栈,然后OPDR内容送到EIP,转到OPDR给出偏移地址处执行 |
合法值 | OPDR:保护方式下,32位通用寄存器、双字存储单元 |
注意 | 除了保存返回地址,其他同无条件转 |
4、函数指针
可以看到
- 指针的本质就是地址
- 这里把函数入口和函数参数都放在堆栈,用堆栈传参。
- 注意传参时从ESP开始向高地址找参数,push参数的时候按从右到左的顺序
- 采用的是段内间接调用的方法
(2)返回指令
1、分类
- 按段内段间分
- 段内返回指令(对应段内调用)
- 段间返回指令(对应段间调用)(不介绍)
- 按返回时是否平衡堆栈
- 不带立即数的返回指令
- 带立即数的返回指令
2、 段内返回不带立即数
名称 | RET(段内返回不带立即数指令) |
格式 | |
动作 | 指令从堆栈弹出地址偏移,送到指令指针寄存器EIP,返回到call时压栈的返回地址处执行 |
3、 段内返回带立即数
名称 | RET(段内返回带立即数指令) |
格式 | |
动作 | 指令从堆栈弹出地址偏移(当然这也会影响esp),送到指令指针寄存器EIP,还额外把count 加到ESP |
注意 | 用于平衡堆栈 |
四、示例
- 以下是一个全汇编程序示例,它将十六进制数字符串转为数值(二进制),再转十进制输出查看,可以看一下函数调用的各种方法。
- 此程序是按8086机资源写的,在64位机器上运行此程序,需要:
- 保存以下代码为
.asm
文件 - 用nasm编译成
.com
文件 - 用DOSbox模拟8086环境运行