首页 > 编程语言 >IA-32汇编语言笔记(10)—— 子程序设计

IA-32汇编语言笔记(10)—— 子程序设计

时间:2022-11-22 10:38:11浏览次数:48  
标签:10 调用 int 32 MOV 堆栈 IA AX 子程序


  • 记录汇编语言课笔记,可能有不正确的地方,欢迎指出
  • 教材《新概念汇编语言》—— 杨季文
  • 这篇文章对应书第二章 IA32处理器基本功能 3.5部分

文章目录

  • ​​一、子程序设计要点​​
  • ​​二、子程序设计举例​​
  • ​​三、子程序调用方法​​
  • ​​(1)调用指令​​
  • ​​1. 分类​​
  • ​​2. 段内直接​​
  • ​​3. 段内间接​​
  • ​​4、函数指针​​
  • ​​(2)返回指令​​
  • ​​1、分类​​
  • ​​2、 段内返回不带立即数​​
  • ​​3、 段内返回带立即数​​
  • ​​四、示例​​

一、子程序设计要点

  1. 两种传参方法
  1. 寄存器
  2. 堆栈
  1. ​调用约定​​​决定了到底怎么传参,在C语言写函数定义时,写以下关键词来显示指定调用约定,
    如​​void _fastcall cf330(unsigned m, char *buffer)​​指定了约定方式为_fastcall
  • 注意都是从右向左入栈
  • 注意不同调用约定对于清理堆栈的情况不同,如果函数自己不清,一定要手动清,维持堆栈平衡
  1. 安排局部变量的方法
  1. 子程序往往需要定义一些局部变量。所谓的局部,也就是限于子程序,或者限于代码片段。
  2. 寄存器作为局部变量可以提高效率。但寄存器数量较少,一般不把局部变量安排在寄存器中。
  3. 利用堆栈来安排局部变量。这个方法虽然较复杂,但可以安排足够多的局部变量。
  1. 用堆栈安排,关键在于移动esp指针位置
  2. 如果局部变量数量少,可以push一个寄存器进去;如果数量多,可以直接修改esp的值,然后用堆栈操作赋值
  1. 保护寄存器的约定
  1. 子程序可能会破坏某些寄存器内容。为此必须对有关寄存器的内容进行保护与恢复。
  2. 事前压入堆栈,事后从堆栈弹出。在利用堆栈进行寄存器的保护和恢复时,一定要注意堆栈的先进后出特性,一定要注意堆栈平衡
  3. 可能会降低效率。
  4. 需要主程序和子程序之间的“默契”和“约定”。子程序只保护主程序关心的那些寄存器,通常保护ebx、esi、edi和ebp。
  1. 描述子程序的说明
  1. 在给出子程序代码时,应该给出子程序的说明信息。
  2. 子程序说明信息一般包括:
  1. 子程序名(或者入口标号);
  2. 子程序功能描述;
  3. 子程序的入口参数和出口参数;
  4. 所影响的寄存器等情况;
  5. 使用的算法和重要的性能指标;
  6. 其他调用注意事项和说明信息;
  7. 调用实例。

二、子程序设计举例

//子程序名(入口标号):BTOHS
//功 能: 把32位二进制数转换为8位十六进制数的ASCII码串
//入口参数:(1)存放ASCII码串缓冲区的首地址(先压入堆栈)
// (2)二进制数据(后压入堆栈)
//出口参数: 无
//其他说明:(1)缓冲区应该足够大(至少9个字节)
// (2)ASCII串以字节0为结束标记
// (3)影响寄存器EAX、ECX、EDX的值
_asm
{
BTOHS: ;子程序入口标号
PUSH EBP
MOV EBP, ESP
PUSH EDI //保护EDI
MOV EDI, [EBP+12]
MOV EDX, [EBP+8]
MOV ECX, 8
NEXT:
ROL EDX, 4
MOV AL, DL
AND AL, 0FH
ADD AL, '0'
CMP AL, '9'
JBE LAB580
ADD AL, 7
LAB580:
MOV [EDI], AL
INC EDI
LOOP NEXT
MOV BYTE PTR [EDI], 0
POP EDI
POP EBP
RET
}


//子程序名(入口标号):ISDIGIT
//功 能:判断字符是否为十进制数字符
//入口参数:AL=字符
//出口参数:如果为非数字符,AL=0;否则AL保持不变
_asm
{
ISDIGIT:
CMP AL, '0' ;与字符'0'比较
JL ISDIG1 ;有效字符是'0'-'9'
CMP AL,'9'
JA ISDIG1
RET
ISDIG1: ;非数字符
XOR AL,AL ; AL= 0
RET
}

//演示调用上述子程序as334和子程序as335
#inclue <stdio.h>
int main( )
{
char buff1[16] = "328";
char buff2[16] = "1234024";
unsigned x1, x2;
unsigned sum;

_asm
{
LEA ESI, buff1 ;转换一个字符串
CALL DSTOB
MOV x1, EAX
LEA ESI, buff2 ;转换另一个字符串
CALL DSTOB
MOV x2, EAX
;
MOV EDX, x1 ;求和
ADD EDX, x2
MOV sum, EDX
; ;如这些代码位于前面,
JMP OK ;需要通过该指令来跳过随后的子程序部分!
}
//
//在这里安排子程序DSTOB和ISDIGIT的代码
//
OK:
printf("%d\n", sum);
return 0;
}

三、子程序调用方法

(1)调用指令

1. 分类
  1. 段内直接调用
  2. 段内间接调用
  3. 段间直接调用(不介绍)
  4. 段间间接调用(不介绍)
2. 段内直接

名称

call(段内直接调用指令)

格式

​CALL LABEL​

动作

把调用指令下一行指令地址压栈,然后转到LABEL处执行

注意

除了保存返回地址,其他同无条件转​​JMP​

3. 段内间接

名称

call(段内间接调用指令)

格式

​CALL OPDR​

动作

把调用指令下一行指令地址压栈,然后OPDR内容送到EIP,转到OPDR给出偏移地址处执行

合法值

OPDR:保护方式下,32位通用寄存器双字存储单元

注意

除了保存返回地址,其他同无条件转​​JMP​

#include  <stdio.h>
int subr_addr; //存放子程序入口地址
int valu; //保存结果
int main( )
{
_asm
{
LEA EDX, SUBR2 //取得子程序二的入口地址
MOV subr_addr, EDX //保存到存储单元
LEA EDX, SUBR1 //取得子程序一的入口地址
XOR EAX, EAX //入口参数EAX=0
CALL EDX //调用子程序一(段内间接,32位Reg)
CALL subr_addr //调用子程序二(段内间接,双字存储单元)
MOV valu, EAX
}
printf("valu=%d\n",valu); //显示为valu=28
return 0;
}
4、函数指针
//源C程序
#include <stdio.h>
int max(int x, int y); //声明函数原型
int min(int x, int y); //
int main()
{
int (*pf)(int,int); //定义指向函数的指针变量
int val1, val2; //存放结果的变量

pf = max; //使得pf指向函数max
val1 = (*pf)(13,15); //调用由pf指向的函数

pf = min; //使得pf指向函数min
val2 = (*pf)(23,25); //调用由pf指向的函数

printf("%d,%d\n",val1,val2); //显示为15,23
return 0;
}


//反编译(不优化)
//标号max_YAHHH、min_YAHHH分别是两个函数入口地址
push ebp
mov ebp, esp ;建立堆栈框架
sub esp, 12 ;安排3个局部变量pf、val1和val2

; pf = max;
mov DWORD PTR [ebp-4], OFFSET max_YAHHH

; val1 = (*pf)(13,15);
push 15
push 13
call DWORD PTR [ebp-4] ;间接调用指针所指的函数max
add esp, 8 ;平衡堆栈

; val1= 返回结果
mov DWORD PTR [ebp-12], eax

; pf = min;
mov DWORD PTR [ebp-4], OFFSET min_YAHHH

; val2 = (*pf)(23,25);
push 25
push 23
call DWORD PTR [ebp-4] ;间接调用指针所指的函数min
add esp, 8

; val2= 返回结果
mov DWORD PTR [ebp-8], eax
mov eax, DWORD PTR [ebp-8] ; eax= val2
push eax
mov ecx, DWORD PTR [ebp-12] ; ecx= val1
push ecx
push OFFSET FORMTS ;格式字符串
call _printf ;段内直接调用
add esp, 12 ;平衡堆栈
;
xor eax, eax ;准备返回值
mov esp, ebp ;撤销局部变量
pop ebp ;撤销堆栈框架
ret

可以看到

  1. 指针的本质就是地址
  2. 这里把函数入口和函数参数都放在堆栈,用堆栈传参。
  3. 注意传参时从ESP开始向高地址找参数,push参数的时候按从右到左的顺序
  4. 采用的是段内间接调用的方法

(2)返回指令

1、分类
  1. 按段内段间分
  1. 段内返回指令(对应段内调用)
  2. 段间返回指令(对应段间调用)(不介绍)
  1. 按返回时是否平衡堆栈
  1. 不带立即数的返回指令
  2. 带立即数的返回指令
2、 段内返回不带立即数

名称

RET(段内返回不带立即数指令)

格式

​RET​

动作

指令从堆栈弹出地址偏移,送到指令指针寄存器EIP,返回到call时压栈的返回地址处执行

3、 段内返回带立即数

名称

RET(段内返回带立即数指令)

格式

​RET count​

动作

指令从堆栈弹出地址偏移(当然这也会影响esp),送到指令指针寄存器EIP,还额外把count 加到ESP

注意

用于平衡堆栈

四、示例

  • 以下是一个全汇编程序示例,它将十六进制数字符串转为数值(二进制),再转十进制输出查看,可以看一下函数调用的各种方法。
  • 此程序是按8086机资源写的,在64位机器上运行此程序,需要:
  1. 保存以下代码为​​.asm​​文件
  2. 用nasm编译成​​.com​​文件
  3. 用DOSbox模拟8086环境运行
;说明:将十六进制数字符串转为数值(二进制),再转十进制输出查看    
segment code ;不分段,所有段共用一片内存空间
org 100H ;从100H开始

MOV AX, CS ;使得数据段与代码段相同
MOV DS, AX ;DS = CS

MOV AX, string ;取到要转换的字符串首地址
PUSH AX
CALL Hex2Bin ;转换
CALL PutWordDec ;显示转换结果

MOV AH, 4CH
INT 21H

;子程序名:Hex2Bin
;功 能:把十六进制字符串转数值
;入口参数:堆栈存字符串起始
;出口参数:ax
Hex2Bin:
PUSH BP
MOV BP, SP ;建立堆栈框架
MOV SI, [BP+4] ;字长16位

MOV CX, -1 ;避免提前结束
XOR AX,AX ;存结果

DEC SI ;方便循环
TOBIN:
INC SI
MOV DL, '$' ;字符串结尾用$标记,DX在MUL的时候会被刷掉,这里要重新赋值
CMP [SI],DL
JE DONE

MOV BX,16 ;乘数16,BX在下面Hex2Bin_WORD的时候会被刷掉,要重新赋值
MUL BX ;AX是被乘数,积的低16位仍在AX

PUSH WORD [SI] ;取一个16进制字符,转值存到BX(这里入栈后面要手动平衡)
CALL Hex2Bin_WORD
ADD SP,2 ;平衡堆栈

ADD AX,BX

;CALL PutWordDec
;CALL PutSpace
LOOP TOBIN
DONE:
POP BP ;撤销堆栈框架
RET

;子程序名:Hex2Bin_WORD
;功 能:把一个十六进制字符转成二进制值
;入口参数:堆栈
;出口参数:BX
Hex2Bin_WORD:
PUSH BP
MOV BP, SP ;建立堆栈框架

MOV BX,[BP+4]
MOV BH,0
CMP BL,'A'
JB NUM
SUB BL,'A'-10
JMP OK
NUM:
SUB BL,'0'
OK:
POP BP ;撤销堆栈框架
RET

;子程序名:PutWordDec
;功 能:把一个字的值转十进制输出
;入口参数:AX
;出口参数:无
PutWordDec:
PUSH BP
MOV BP, SP ;建立堆栈框架
PUSHA ;保护所有reg(关键是AX/BX/CX/DX)

MOV CX, -1
MOV BX,10
LoopPWD1:
XOR DX, DX
DIV BX
PUSH DX
CMP AX, 0
LOOPNE LoopPWD1

NOT CX
LoopPWD2:
POP DX
ADD DL, '0'
CALL PutChar
LOOP LoopPWD2

POPA ;恢复所有reg
POP BP ;撤销堆栈框架
RET

;子程序名:PutChar
;功 能:显示输出一个字符
;入口参数:DL = 显示输出字符ASCII码
;出口参数:无
PutChar:
PUSH AX ;简单的函数,可以不建立堆栈框架
MOV AH,2
INT 21H ;调用2号系统功能显示输出
POP AX
RET

;子程序名:PutSpace
;功 能:显示输出一个空格
;入口参数:无
;出口参数:无
PutSpace:
PUSH AX ;简单的函数,可以不建立堆栈框架
PUSH DX
MOV DL,20H
MOV AH,2
INT 21H ;调用2号系统功能显示输出
POP DX
POP AX
RET
;---------------------------------------------
string db "1234", '$' ;在这里写要转换的十六进制数


标签:10,调用,int,32,MOV,堆栈,IA,AX,子程序
From: https://blog.51cto.com/u_15887260/5876698

相关文章