汇编语言 自己动手写一个程序,实现debug的D命令

发布于:2022-11-02 ⋅ 阅读:(358) ⋅ 点赞:(0)

文章目录

目录

前言

一、实现思路

1.程序整体框架

2.子程序1:将一个字节的数据转为16进制字符形式

3.子程序2:将2个16进制字符显示在屏幕指定位置

二、功能优化

1.在行首显示内存地址

2.显示多行数据

3.封装一个默认函数

三、完整代码

总结



前言

在学习汇编语言时,在调试程序的过程中,经常需要反复使用debug中的D命令,来查看程序运行是否正确,这样做有点麻烦。目前已经学完王爽老师《汇编语言》(第四版) 第11章,掌握了一点点汇编语言编程的语法,于是决定自己动手写一个子程序,模拟实现debug中的D命令。这样就可以让程序将指定的内存地址的数据显示在屏幕上,而无需调用debug的D命令。

效果图如下。


 

一、实现思路

1.程序整体框架

现在的目标是:手动模拟实现debug的D命令的功能。而debug中的D命令,实现的基本功能是查看指定地址中的数据。要执行这个命令,需要为程序指定要查看的段地址、偏移地址,以及要查看的数据长度。

如何实现呢?首先,可以用ds:si指向要查看的数据的首地址,之后用循环遍历所有要查看的字节。而对于其中的每一个字节数据,在dos系统中是用十六进制显示的,如2AH。要想将这个内存中的“2AH”数据,显示为屏幕上的字符“2A”,首先就要将十六进制数据转换为对应的字符,然后再送入显存中进行显示。有一点要注意:内存中的一个字节的数据,转换为字符的话,应当是两个字符,占2个字节的空间。

根据这个思路,可以写出程序的大体框架,如下。至于具体的子程序如何实现,这是接下来才考虑的事情,现在先不管它。

assume cs:code,ds:data,ss:stack
data segment
    dw 2022H,0      ;假定要显示的数据
data ends

stack segment
    dw 32 dup(0)    ;预留足够的栈空间
stack ends

code segment
start:
    mov ax,data     ;设置ds指向data段
    mov ds,ax
    mov ax,stack    ;设置ss指向stack段
    mov ss,ax
    mov sp,4H

    mov si,0        ;ds:si指向要显示的首地址
    mov cx,3        ;cx指定显示几个字节的数据
    mov dx,0302H    ;dx指定屏幕显示的起始行号、列号
    mov bl,02H      ;bl指定字符显示的属性
    call debug_D    ;将指定地址、指定长度的数据显示在屏幕指定位置上

    mov ax,4c00H    ;程序返回
    int 21H

debug_D:   
            ;功能:将指定地址、指定长度的数据显示在屏幕指定位置上
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定要显示几个字节;
            ;       dh指定屏幕显示的行号;
            ;       dl指定屏幕显示的列号;
            ;       bl指定字符显示的属性;
            ;返回:无

    push ax     ;将寄存器的值压入栈暂存
    push si
    push dx

    debug_D_s:
    mov al,ds:[si]      ;取当前字节数据
    call byte_to_char   ;将当前字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    inc si
    add dl,3            ;每一个字节的数据,用2个字符及一个空格
    loop debug_D_s      ;取下一个字节数据

    pop dx
    pop si
    pop ax
    ret

byte_to_char:   ;功能:将al中数据转为2个十六进制字符形式,存储在ax中


    ret

show_char:      ;将ax中的2个十六进制字符,显示在屏幕指定位置


    ret

code ends
end start

   

接下来,逐个实现需要用到的子程序。

2.子程序1:将一个字节的数据转为16进制字符形式

下面需要实现的是 byte_to_char 这个子程序,主要功能是将al中一个字节的数据,转为2个十六进制字符,存入ax中。

一个字节的数据,以十六进制表示,就有高、低两位。如“3CH”这个数据,高为是 3,低位是 C。而要分别获取这两个十六进制位也容易做到:将x/16取整,就得到高位数据,而x%16(取余),就得到低位数据。

之后,再将这两个数字分别转为对应的字符。字符0-9对应的ASCII码是30H-39H,而字符A-F对应的ASCII码是41H-46H。

代码如下。

byte_to_char:   ;功能:将al中数据转为2个十六进制字符形式,存储在ax中
        ;参数:al中是待转换的一个byte数据
        ;返回:ax中是转换后的2个十六进制字符形式数据

    push bx     ;将寄存器的值压入栈暂存

    mov ah,0    ;先用除法获取一个字节数据的高低2个16进制位的数字
    mov bl,16
    div bl      ;byte数据除以16,商在al中,余数在ah中
    mov bh,ah   ;将低位数字ah送入bh暂存
    mov bl,al   ;将高位数据送入bl进行转字符的处理
    call hex_to_char    ;将bl中数据转为一个16进制字符,存在bl中
    mov ah,bl   ;将处理好的高位字符转入ah

    mov bl,bh   ;将低位数据送入bl进行转字符的处理
    call hex_to_char    ;将bl中数据转为一个16进制字符,存在bl中
    mov al,bl   ;将处理好的低位字符转入al

    pop bx
    ret
hex_to_char:    ;功能:将bl中<16的数据转为一个16进制字符,存入bl中
            ;参数: bl中的待转为字符的数据,数据<16
            ;返回:bl中存放转换后的一个16进制字符
    cmp bl,0AH  ;比较商与10的大小,判断是否小于10
    jb smallchar    ;如果是<10的数字,就转为对应的0-9字符
    add bl,37H      ;如果不是,那就转为对应的A-F字符
    jmp to_char_ok
    smallchar: add bl,30H      ;将数字转为对应的0-9字符
    to_char_ok:
    ret

3.子程序2:将2个16进制字符显示在屏幕指定位置

现在,内存中的一个字节的数据,已经转为了2个16进制字符形式,存放在ax中。接下来要实现的是 show_char 子程序,功能就是将ax中存放的2个十六进制字符,显示在屏幕的指定位置。

代码如下。

show_char:      ;功能:将ax中的2个十六进制字符,显示在屏幕指定位置
        ;参数:ax中存放2个十六进制字符
        ;   dh指定屏幕显示的起始行号;
        ;   dl指定屏幕显示的起始列号
        ;   bl指定字符显示的属性;
        ;返回:无
    
    push ax     ;将寄存器的值压入栈暂存
    push es 
    push bx
    push cx
    push di
    push bp
    push dx

    mov cx,ax       ;用cx暂存要显示的字符数据ax
    mov bp,bx       ;用bp暂存bx中的字符显示属性

    mov ax,0B800H   ;设置es指向显存段
    mov es,ax

    mov bx,00A0H    ;计算屏幕显示的首地址
    mov ax,0
    mov al,dh
    mul bl
    mov dh,0
    add ax,dx 
    add ax,dx      
    mov di,ax       ;di指向屏幕显示的首地址

    mov bx,bp       ;从bp中取出字符属性字节
    mov ax,cx       ;从cx中取出待显示的字符ax

    mov es:[di],ah  ;将高位字符送入显存
    inc di
    mov es:[di],bl  ;将字符属性字节送入显存
    inc di
    mov es:[di],al  ;将低位字符送入显存
    inc di
    mov es:[di],bl  ;将字符属性字节送入显存

    pop dx
    pop bp
    pop di
    pop cx
    pop bx
    pop es
    pop ax
    ret

以上,就实现了在指定地址、屏幕指定位置,显示指定长度的内存数据 的功能。


二、功能优化

1.在行首显示内存地址

以上功能可以显示指定地址的数据,但是还可以进一步优化。

平时使用debug的D命令时,每行的行首会显示当前行的首地址,这样看起来比较方便。

于是就考虑写一个子程序,用于在显示内存数据前,先显示一下段地址和偏移地址。那么就需要将段地址和偏移地址,分别地转成字符,然后再送入显存进行显示。因为转字符、显示字符这两个子程序之前已经写好了,这里可以直接在程序中调用。

子程序代码如下。

show_addr:      ;功能:将段地址:偏移地址显示在屏幕指定行的行首
            ;参数:ds是要显示的段地址
            ;       si是要显示的偏移地址
            ;       dh是指定的屏幕显示行号
            ;       dl是指定的屏幕显示列号
            ;       bl是指定的字符显示属性
            ;返回:dl是继续显示其它内容的起始列号

    push ax         ;将寄存器的值压入栈暂存
    
    mov ax,ds           ;取ds的高16位
    mov al,ah
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2

    mov ax,ds           ;取ds的低16位
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2   

    mov ax,3A00H        ;显示一个冒号,冒号的ASCII码是3AH
    call show_char
    add dl,2

    mov ax,si           ;取si的高16位
    mov al,ah
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2

    mov ax,si           ;取si的低16位
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2
    add dl,2            ;多显示两个空格     

    pop ax
    ret

然后把显示一行数据(包括行首地址和后边的一行数据)的功能封装为一个函数。

代码如下。

show_line:      ;功能:在屏幕指定位置,显示指定地址的一行内存数据
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定这一行要显示几个字节;
            ;       dh指定屏幕显示的行号;
            ;       dl指定屏幕显示的列号;
            ;       bl指定字符显示的属性;
            ;返回:无

    push ax     ;将寄存器的值压入栈暂存
    push si
    push dx

    call show_addr  ;显示行首地址

    debug_D_s:      ;显示内存数据
    mov al,ds:[si]      ;取当前字节数据
    call byte_to_char   ;将当前字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    inc si
    add dl,3            ;每一个字节的数据,用2个字符及一个空格
    loop debug_D_s      ;取下一个字节数据

    pop dx
    pop si
    pop ax
    ret

2.显示多行数据

以上程序,只能将数据显示在一行中。而debug的D命令,是将数据分行显示的,每行只显示16个字节的数据,便于查看。于是考虑对程序进行优化,实现分行显示的功能。

代码如下。

debug_D:   
            ;功能:将指定地址、指定长度的数据显示在屏幕指定位置上
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定要显示几个字节,<200;
            ;       dh指定屏幕显示的行号;
            ;       dl指定屏幕显示的列号;
            ;       bl指定字符显示的属性;
            ;       bh指定每行显示几个字节的数据
            ;返回:无

    push cx         ;将寄存器的值压入栈暂存
    push ax
    push si
    push bx 

    mov ax,cx
    div bh         ;商在al中,余数在ah中
    mov cx,0
    mov cl,al

    cmp al,0      ;判断商是否为0
    je show_yushu   ;如果为0,就去继续判断余数是否为0

    s_show_line:    ;如果商不为0,那么循环显示每一行的内容
    push cx
    mov cx,0
    mov cl,bh      ;每行显示几个内存数据
    call show_line      ;显示一行的内容
    inc dh          ;将行号对应增加
    mov cx,0        ;将si内存偏移地址对应增加   
    mov cl,bh
    add si,cx
    pop cx
    loop s_show_line    ;下一行

    show_yushu:
    cmp ah,0        ;判断余数是否为0
    je debug_D_ok   ;如果为0,那么显示完毕,程序返回
    mov cx,0        ;如果不为0,那么就再显示一行
    mov cl,ah
    call show_line

    debug_D_ok:
    pop bx
    pop si
    pop ax
    pop cx

    ret

3.封装一个默认函数

可以看到,上边这个程序,可以实现既定功能,但是需要指定的参数比较多,使用起来不方便。于是考虑参考java中函数的设计思路,写一个具有默认参数的子程序,把这个功能进一步封装起来。

代码如下。

debug_D_default:    ;功能:将指定地址、指定长度的数据显示在屏幕上(参数默认)
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定显示几个字节的数据
            ;返回:无       
    push dx
    push bx

    mov dx,0405H    ;dx指定屏幕显示的起始行号、列号
    mov bl,02H      ;bl指定字符显示的属性
    mov bh,08H      ;bh指定一行显示几个字节的数据
    call debug_D    ;将指定地址、指定长度的数据显示在屏幕指定位置上

    pop bx
    pop dx
    ret

三、完整代码

assume cs:code,ds:data,ss:stack
data segment
    db '1234567890abcdef'      ;假定要显示的数据
data ends

stack segment
    dw 32 dup(0)    ;预留足够的栈空间
stack ends

code segment
start:
    mov ax,data     ;设置ds指向data段
    mov ds,ax
    mov ax,stack    ;设置ss指向stack段
    mov ss,ax
    mov sp,4H

    mov si,0        ;ds:si指向要显示的首地址
    mov cx,20H        ;cx指定显示几个字节的数据
    call debug_D_default

    mov ax,4c00H
    int 21H

debug_D_default:    ;功能:将指定地址、指定长度的数据显示在屏幕上(参数默认)
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定显示几个字节的数据
            ;返回:无       
    push dx
    push bx

    mov dx,0405H    ;dx指定屏幕显示的起始行号、列号
    mov bl,02H      ;bl指定字符显示的属性
    mov bh,08H      ;bh指定一行显示几个字节的数据
    call debug_D    ;将指定地址、指定长度的数据显示在屏幕指定位置上

    pop bx
    pop dx
    ret

debug_D:   
            ;功能:将指定地址、指定长度的数据显示在屏幕指定位置上
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定要显示几个字节,<200;
            ;       dh指定屏幕显示的行号;
            ;       dl指定屏幕显示的列号;
            ;       bl指定字符显示的属性;
            ;       bh指定每行显示几个字节的数据
            ;返回:无

    push cx         ;将寄存器的值压入栈暂存
    push ax
    push si
    push bx 

    mov ax,cx
    div bh         ;商在al中,余数在ah中
    mov cx,0
    mov cl,al

    cmp al,0      ;判断商是否为0
    je show_yushu   ;如果为0,就去继续判断余数是否为0

    s_show_line:    ;如果商不为0,那么循环显示每一行的内容
    push cx
    mov cx,0
    mov cl,bh      ;每行显示几个内存数据
    call show_line      ;显示一行的内容
    inc dh          ;将行号对应增加
    mov cx,0        ;将si内存偏移地址对应增加   
    mov cl,bh
    add si,cx
    pop cx
    loop s_show_line    ;下一行

    show_yushu:
    cmp ah,0        ;判断余数是否为0
    je debug_D_ok   ;如果为0,那么显示完毕,程序返回
    mov cx,0        ;如果不为0,那么就再显示一行
    mov cl,ah
    call show_line

    debug_D_ok:
    pop bx
    pop si
    pop ax
    pop cx

    ret

show_line:      ;功能:在屏幕指定位置,显示指定地址的一行内存数据
            ;参数:ds:si指向要显示的数据首地址;
            ;       cx指定这一行要显示几个字节;
            ;       dh指定屏幕显示的行号;
            ;       dl指定屏幕显示的列号;
            ;       bl指定字符显示的属性;
            ;返回:无

    push ax     ;将寄存器的值压入栈暂存
    push si
    push dx

    call show_addr  ;显示行首地址

    debug_D_s:      ;显示内存数据
    mov al,ds:[si]      ;取当前字节数据
    call byte_to_char   ;将当前字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    inc si
    add dl,3            ;每一个字节的数据,用2个字符及一个空格
    loop debug_D_s      ;取下一个字节数据

    pop dx
    pop si
    pop ax
    ret

show_addr:      ;功能:将段地址:偏移地址显示在屏幕指定行的行首
            ;参数:ds是要显示的段地址
            ;       si是要显示的偏移地址
            ;       dh是指定的屏幕显示行号
            ;       dl是指定的屏幕显示列号
            ;       bl是指定的字符显示属性
            ;返回:dl是继续显示其它内容的起始列号

    push ax         ;将寄存器的值压入栈暂存
    
    mov ax,ds           ;取ds的高16位
    mov al,ah
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2

    mov ax,ds           ;取ds的低16位
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2   

    mov ax,3A00H        ;显示一个冒号,冒号的ASCII码是3AH
    call show_char
    add dl,2

    mov ax,si           ;取si的高16位
    mov al,ah
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2

    mov ax,si           ;取si的低16位
    call byte_to_char   ;将al字节转为2个十六进制字符形式,存储在ax中
    call show_char      ;将ax中的2个十六进制字符,显示在屏幕指定位置
    add dl,2
    add dl,2            ;多显示两个空格     

    pop ax
    ret

byte_to_char:   ;功能:将al中数据转为2个十六进制字符形式,存储在ax中
        ;参数:al中是待转换的一个byte数据
        ;返回:ax中是转换后的2个十六进制字符形式数据

    push bx     ;将寄存器的值压入栈暂存

    mov ah,0    ;先用除法获取一个字节数据的高低2个16进制位的数字
    mov bl,16
    div bl      ;byte数据除以16,商在al中,余数在ah中
    mov bh,ah   ;将低位数字ah送入bh暂存
    mov bl,al   ;将高位数据送入bl进行转字符的处理
    call hex_to_char    ;将bl中数据转为一个16进制字符,存在bl中
    mov ah,bl   ;将处理好的高位字符转入ah

    mov bl,bh   ;将低位数据送入bl进行转字符的处理
    call hex_to_char    ;将bl中数据转为一个16进制字符,存在bl中
    mov al,bl   ;将处理好的低位字符转入al

    pop bx
    ret
hex_to_char:    ;功能:将bl中<16的数据转为一个16进制字符,存入bl中
            ;参数: bl中的待转为字符的数据,数据<16
            ;返回:bl中存放转换后的一个16进制字符
    cmp bl,0AH  ;比较商与10的大小,判断是否小于10
    jb smallchar    ;如果是<10的数字,就转为对应的0-9字符
    add bl,37H      ;如果不是,那就转为对应的A-F字符
    jmp to_char_ok
    smallchar: add bl,30H      ;将数字转为对应的0-9字符
    to_char_ok:
    ret

show_char:      ;功能:将ax中的2个十六进制字符,显示在屏幕指定位置
        ;参数:ax中存放2个十六进制字符
        ;   dh指定屏幕显示的起始行号;
        ;   dl指定屏幕显示的起始列号
        ;   bl指定字符显示的属性;
        ;返回:无
    
    push ax     ;将寄存器的值压入栈暂存
    push es 
    push bx
    push cx
    push di
    push bp
    push dx

    mov cx,ax       ;用cx暂存要显示的字符数据ax
    mov bp,bx       ;用bp暂存bx中的字符显示属性

    mov ax,0B800H   ;设置es指向显存段
    mov es,ax

    mov bx,00A0H    ;计算屏幕显示的首地址
    mov ax,0
    mov al,dh
    mul bl
    mov dh,0
    add ax,dx 
    add ax,dx      
    mov di,ax       ;di指向屏幕显示的首地址

    mov bx,bp       ;从bp中取出字符属性字节
    mov ax,cx       ;从cx中取出待显示的字符ax

    mov es:[di],ah  ;将高位字符送入显存
    inc di
    mov es:[di],bl  ;将字符属性字节送入显存
    inc di
    mov es:[di],al  ;将低位字符送入显存
    inc di
    mov es:[di],bl  ;将字符属性字节送入显存

    pop dx
    pop bp
    pop di
    pop cx
    pop bx
    pop es
    pop ax
    ret

code ends
end start

   


总结

本文介绍了如何手动模拟debug中的D命令,将指定地址、指定长度、指定颜色的内存数据显示在屏幕的指定位置上。通过这个练习,进一步熟悉了汇编语言编程的方法,提高了编程能力。写好的子程序,以后可以随时调用,还可以自定义位置、长度、颜色等参数,很方便。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到