16位汇编-堆栈传参案例

436 阅读2分钟
assume cs:code,ds:data,ss:stacks
;统计字符串长度
stacks segment ;手动开辟一个内存,默认是自动的
    db 16 dup(0) ;申请了16字节空间大小
stacks ends

data segment
    strmessage db 'abcdefg',0,24h
    len dw ?,0,24h
data ends

code segment
    start:
        ;存储堆栈
        mov ax,stacks
        mov ss,ax
        mov sp,10h ;SP堆栈指针寄存器,存放堆栈的偏移地址,这里的10h对应的是十进制的16,

        ; 存储数据
        mov ax,data
        mov ds,ax

        ; 获取strmessage的偏移地址
        mov si,offset strmessage
        push ds
        push si
        call strlen ; 子程序调用;CALL指令段内调用,会将将指令指针IP压入堆栈
        add sp,4h

        ; 将16进制结果按照ASCII码输出
        lea si,len
        add ax,30h
        mov ds:[si],ax
        lea dx,len
        
        ; 在这里需要输出一个回车,不然会有异常符号显示
        call enterkey

        ; 调用9号功能输出
        mov ax,0900h
        int 21h

        mov ax,4c00h
        int 21h

    ; 回车输出
    enterkey proc
        push ax
        push dx
        mov dl,0dh
        mov ah,02h
        int 21h
        pop dx
        pop ax
        ret
    enterkey endp

    ;入口参数:堆栈中字符串的起始位置和偏移地址
    ;出口参数:AX
    strlen proc
        push bp
        mov bp,sp ;利用bp基址指针寄存器在堆栈中寻址
        ;保护寄存器
        push ds
        push si

        ;取入口参数,根据堆栈图可以得到以下偏移
        mov ds,ss:[bp+6]
        mov si,ss:[bp+4]

        ;进行比较
        mov al,0
    strlen1:
        cmp ds:[si],al
        jz strlen2
        inc si
        jmp strlen1

    strlen2:
        mov ax,si
        sub ax,ss:[bp+4]
        ;恢复寄存器,堆栈平衡
        pop si
        pop ds
        pop bp
        ret
    strlen endp

code ends
    end start

从call指令进到堆栈的时候,要画一下堆栈图

image.png 堆栈遵循先进后出的规则,最底下的DS是第一条push DS的值,将DS压入堆栈,以下的push以此类推,

在获取取入口参数的地方,因为默认段前缀是ds,但是我们其实是要在堆栈区获取参数,就要声明为ss堆栈寄存器,否则获取的就还是ds自己的数据

最后在POP完之后,还剩下一个最开始的的DS和和SI,要修正一下sp指针的值,可以用add sp,4h,或者ret 4,都可以恢复堆栈平衡