8086汇编(16位汇编)学习笔记08.函数-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net
函数结构
函数结构的演变
函数的结构并不是随随便便就出来的而是解决了很多问题之后,大家统一认为那个结构是最好的一种方式
例如:模拟函数实现2个数相加
不用函数实现两个数相加
;这是栈段
stack segment stack
db 512 dup(0)
stack ends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
add si,di
mov si,10H
mov di,6H
add si,di
mov si,0A0h
mov di,6H
add si,di
mov ax,4C00H ; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
模拟函数实现
;这是栈段
stack segment stack
db 512 dup(0)
stack ends
;这是数据段
data segment
data ends
MYADD:
add si,di
mov ax,si
;这里是代码
CODE segment
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
jmp MYADD
mov si,10H
mov di,6H
mov si,0A0h
mov di,6H
jmp MYADD
mov ax,4C00H ; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
此时跳过去了,但函数无法返回,如果使用 jmp返回,那么其他地方调用就还是没法返回 而且无法重复的调用,那如何让他正确返回呢 , 解决办法是 jmp 跳转的地址不固定,把它存起,要跳转的时候再把它取出来, 存的地址如果用全局变量保存,可需要的变量过多,而且不确定,最好的方法是用栈保存
普通的 函数跳转与返回
;这是栈段
stack segment stack
db 512 dup(0)
stack ends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
add si,di
mov ax,si
;jmp MYADD
pop bx ;取出函数返回地址
jmp bx ;跳转到指定的地址
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
mov ax, MYRET
push ax ;将返回地址入栈 ,不直接用 push MYRET 是不能直接push立即数,需要用寄存器中转
jmp MYADD ;跳转到函数
MYRET:
mov si,10H
mov di,6H
;jmp MYADD
mov ax, MYRET1
push ax ;将返回地址入栈 ,不直接用 push MYRET1 是不能直接push立即数,需要用寄存器中转
jmp MYADD ;跳转到函数
MYRET1:
mov si,0A0h
mov di,6H
jmp MYADD
mov ax,4C00H ; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
6汇编关键字 的 函数跳转与返回
这样每次我们跳转前都需要 将返回地址入栈 , 再进行跳转, .函数返回时 需要先将地址出栈 ,再跳转.比较麻烦,因此汇编提供了相同功能的指令 call
;这是栈段
stack segment stack
db 512 dup(0)
stack ends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
add si,di
mov ax,si
;jmp MYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
ret ;等同于上面注释2行代码,返回地址出栈,跳转
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
;mov ax, MYRET
;push ax ;将返回地址入栈 ,不直接用 push MYRET 是不能直接push立即数,需要用寄存器中转
;jmp MYADD ;跳转到函数
;MYRET:
call MYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,10H
mov di,6H
;jmp MYADD
;mov ax, MYRET1
;push ax ;将返回地址入栈 ,不直接用 push MYRET1 是不能直接push立即数,需要用寄存器中转
;jmp MYADD ;跳转到函数
;MYRET1:
call MYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,0A0h
mov di,6H
jmp MYADD
mov ax,4C00H ; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
参数
上面的 参数使用 si 和 di 传递的参数 ,但是如果参数多的话,寄存器可能不够使用,而且如果之前 si 和 di 保存了其他数据,我们需要先进行保存,用完之后再恢复,寄存器用的越多,那么使用发之前存的就越多,用完之后恢复的也就越多,所以传参尽量也不用寄存器 而是用栈,用的时候再到栈里面去拿出来
;这是栈段
stack segment stack
db 512 dup(0)
stack ends
;这是数据段
data segment
data ends
;这里是代码
CODE segment
MYADD:
mov bp,sp
mov si,[bp+2] ;第二个参数
mov di,[bp+4] ;第一个参数
add si,di
mov ax,si
;jmp MYADD
;pop bx ;取出函数返回地址
;jmp bx ;跳转到指定的地址
ret ;等同于上面注释2行代码,返回地址出栈,跳转
START:
assume ds:data
mov ax, data
mov ds, ax
mov si,5H
mov di,6H
push di
push si
;mov ax, MYRET
;push ax ;将返回地址入栈 ,不直接用 push MYRET 是不能直接push立即数,需要用寄存器中转
;jmp MYADD ;跳转到函数
;MYRET:
call MYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,10H
mov di,6H
;jmp MYADD
;mov ax, MYRET1
;push ax ;将返回地址入栈 ,不直接用 push MYRET1 是不能直接push立即数,需要用寄存器中转
;jmp MYADD ;跳转到函数
;MYRET1:
call MYADD ;等能等同于上面注释的5行代码,取地址,入栈,跳转
mov si,0A0h
mov di,6H
jmp MYADD
mov ax,4C00H ; 带参数返回的退出
int 21h ;使用21号中断
CODE ends
end START
此是运行结果结果是正确的,但是栈顶确实错误的,没有平栈,因此需要需要平参数的栈
- 由调用方平栈 调用函数后 add sp , 入栈参数总长度 cdecel c调用约定
- 由被调用方平栈 ret 入栈参数总长度 stdcall 标准调用约定
保存和恢复寄存器环境
当我们调用函数时,函数如果使用了我们之前保存数据的寄存器,那么函数是用之后,我们保存的数据将会丢失,因此函数在使用寄存器之前,需要先保存寄存器数据,使用之后再恢复,但是调用者并不知道函数用到了哪些寄存器,因此这件事需要函数内部来实现,但是函数自己保存寄存器环境好之后有一个问题,就是参数取值问题,解决方法是 bp,sp分离,
通过bp取参数,这样不管sp如何变化,bp的值是不变的,此时bp将无法在干其他事了,因此 bp又称为 栈帧指针
注意: ax 不需要保存,因为所有函数 ax默认当做返回值,因此任何函数都有可能修改 ax