1. 子程序实现的基础——跳转:
1) 汇编中的子程序即等价于C语言的函数,即实现程序的模块化;
2) 在汇编语言中,子程序其实就是以一个标号起始,最后有类似C函数的返回指令的一段代码块,主程序可以在中途调用该代码块(其实就是跳转到子程序执行),调用结束后再从子程序返回到调用处(其实就是从子程序处跳转回调用它的地方);
3) 也就是说子程序实现的基础就是跳转,即需要转移指令的支持;
2. 利用call和ret指令来调用子程序和从子程序返回:
1) call的使用方法是:call 子程序入口处地址,该地址可以是标号,也可以存放在寄存器和内存中;
2) ret的使用方法就是直接在子程序中使用ret指令即可,可以没有参数,执行该指令会直接返回至调用子程序的位置处;
3) 这两条指令是如何实现的?
i. 由于调用和返回都是通过跳转实现的,因此两条指令的背后肯定都修改了cs:ip;
ii. 考虑到最后要返回至调用处,则肯定需要在调转至子程序处之前先保存好返回时的位置(即call后面一条指令的地址),待子程序运行完后再恢复该位置并赋给cs:ip,而保存和恢复的工具必然是栈了;
iii. 因此call、ret其实是一组复合动作,其执行流程是:
call proc_addr:
push ++(cs:ip)
jmp -> proc_addr
ret:
pop (cs:ip)
4) 由于call和ret背后也是转移,因此也是要分段内跳转和段间元跳转的:
i. 段内跳转call:
*1. call near ptr proc_tag:等价于jmp near ptr,因此也是一种位移转移,其中near ptr可以省略,但不建议这样做,将near ptr写上可以联想到jmp near ptr,这样不会导致记忆混乱,并且使程序清晰易读;
*2. call 16-bit-register:子程序段内偏移地址存在16位寄存器中,等价于jmp 16-bit-register,因此是直接修改ip但不修改cs;
*3. call word ptr 内存单元:子程序段内偏移地址存在内存单元中,等价于jmp word ptr 内存单元,也是只修改ip
*注意:它们都会在跳转之前先push ip进行备份!没有段内你短转移!
ii. 远跳转call:直接同时修改cs:ip
*1. call far ptr proc_tag:等价于jmp far ptr
*2. call dword ptr 内存单元:等价于jmp dword ptr,第16位是偏移地址,高16位是段地址;
*注意:在跳转之前都会先保存cs:ip的值,顺序是先push cs再push ip;
iii. 段内跳转ret:直接ret即可,其实就等价于pop ip
iv. 远跳retf:注意,远跳是retf,即return far的缩写,f不要忘了,等价于pop (ip, cs),注意和远跳的call对应(栈是后进先出的!)
5) call和ret要对应使用,即段内跳的call就和ret配合使用,远跳的call就和retf配合使用,千万不能交叉配合使用,虽然这样编译不会报错,但是一定会发生运行时错误或者是不可预料的错误,因为段内跳push和pop16位的地址,而远跳push和pop32位的地址,交叉使用就会相差16位,从而导致cs:ip指向异常!
一定要牢记这点!
4. 乘法指令mul:
1) mul有两种类型,一种是两个8位相乘得到一个16位的结果,另一种是两个16位相乘得到一个32位的结果;
2) 双8位相乘:一个乘数默认放在al中,另一个乘数可以放在任意一个8位寄存器或者内存中,结果默认放在ax中;
3) 双16位相乘:一个乘数默认放在ax中,另一个乘数可以放在任意一个16位寄存器或者内存中,结果的高16位默认放在dx中,低16位默认放在ax中;
4) 使用格式:
; 8-bit mul 8-bit
mov al, XXX
mov 8-bit-register/memory, XXX
mul 8-bit-register/memory
; -> ax
; 16-bit mul 16-bit
mov ax, XXX
mov 16-bit-register/memory, XXX
mul 16-bit-register/memory
; -> [dx:ax]
5) 示例:
100 × 10和100 × 10000:
assume cs:code
code segment
dd 0
start:
mov al, 100
mov ah, 10
mul ah
mov ax, 100
mov dx, 10000
mul dx
mov word ptr cs:[0], ax
mov word ptr cs:[2], dx
mov ax, 4C00H
int 21H
code ends
end start
运行结果:
*1. ax -> 03E8H
*2. 将结果保存在了代码段开头定义的数据区中,结果是000F4240H