这里说一下使用栈来保存参数和调用过程的方法,和其中踩的一些坑。
代码目标
我们的目标是自己编写一个过程,在DosBox中显示数据段中的字符。(虽然有外中断可以完成,但是我还是自己写一个)
这个函数的描述如下:
;@fn 这个函数将数据段里面的文字输出一行(末尾换行)
;这个函数的参数放在栈里面
;@param
; 字符串开始的位置
; 字符串的长度
; 这一行字符串的属性信息(放在低字节中)
除了这些外栈的最底端还有一个word用于存放当前要写到的行数。
关于DosBox的屏幕输出,见这里
编写代码
预备工作
首先将assume和数据段,栈段写出来:
assume cs:codeseg, ds:dataseg, ss:stackseg
dataseg segment
db 'welcome to masm'
db 'created by VisualGMQ 2019.8.10'
dataseg ends
stackseg segment
dw 10 dup (0)
stackseg ends
过程的编写
从栈里面读取参数
pop ax ;先把call指令传入的原IP的值拿出来
pop bx ;取出这一行字符串的属性信息
pop cx ;取出字符串长度
pop di ;取出字符串开始位置
pop dx ;取出目前的行数
这里按照参数传递的反顺序将参数pop出来(因为在栈里面所以需要相反的顺序)
这里还要注意第一行:由于我们是通过call
指令调用函数的,call
指令会首先将IP
寄存器里面的值push到栈里面,所以我们也必须先拿出来。
对参数进行计算
然后就是通过这些参数对数据进行计算和传输:
inc dx ;将行数变为下一行
push dx
push ax
dec dx ;将行数变回来进行计算
mov al, 160
mul dl
mov si, ax ;将运算结果放在si中
这里首先将dx自增1,然后放入栈里面表示。这样表示下一次绘制字符就是从下一行开始了。然后将ax的值push进去(最后ret
指令会用到这个值来返回到原来call
指令执行的地方)。然后将dx变为原来的值参与计算。通过mul
指令计算出现在应该写到哪个地址去,并且将结果存储在si
中。
将数据传输到屏幕上
然后就是传输数据到屏幕上了:
s: mov bh, ds:[di]
mov byte ptr es:[si], bh
mov byte ptr es:[si+1], bl
inc di
add si, 2
loop s
这里di在数据段中移动,将数据段中的数据取出来。si在显示区域(BF000H~BFFFFH)中移动,将数据放入显示内存中。bh存储着字符的属性,bl存储着字符。
传输完成之后就要返回了:
ret
最后代码
;这个程序在屏幕上显示数据段内的字符
assume cs:codeseg, ds:dataseg, ss:stackseg
dataseg segment
db 'welcome to masm'
db 'created by VisualGMQ 2019.8.10'
dataseg ends
stackseg segment
dw 10 dup (0)
stackseg ends
codeseg segment
start: mov ax, stackseg
mov ss, ax
mov sp, 20
mov ax, 0
push ax ;将行数写入
;配置ds和es
mov ax , 0b800H ;这里如果开头是a~f的话必须再加上0,不然会被认为是字符而不是十六进制数字
mov es, ax
mov ax, dataseg
mov ds, ax
;开始写文字
mov ax, 0
push ax
mov ax, 15
push ax
mov ax, 0000000000001010B
push ax
call write
mov ax,15
push ax
mov ax, 30
push ax
mov ax, 0000000000000001B
push ax
call write
mov ax, 4c00H
int 21H
;@fn 这个函数将数据段里面的文字输出一行(末尾换行)
;这个函数的参数放在栈里面
;@param
; 字符串开始的位置
; 字符串的长度
; 这一行字符串的属性信息(放在低字节中)
write: pop ax ;先把call指令传入的原IP的值拿出来
pop bx ;取出这一行字符串的属性信息
pop cx ;取出字符串长度
pop di ;取出字符串开始位置
pop dx ;取出目前的行数
inc dx ;将行数变为下一行
push dx
push ax
dec dx ;将行数变回来进行计算
mov al, 160
mul dl
mov si, ax ;将运算结果放在si中
s: mov bh, ds:[di]
mov byte ptr es:[si], bh
mov byte ptr es:[si+1], bl
inc di
add si, 2
loop s
ret
codeseg ends
end start
结果是:
总结
- 不要忘记
call
指令会将IP的值放入栈中,要在所有参数之前读取出来 - 不要忘记参数是反过来读取的