返回介绍

第 9 章:转移指令的原理

发布于 2024-10-04 13:37:13 字数 18846 浏览 0 评论 0 收藏 0

检测点 9.1

(1)程序如下。若要使程序中的 jmp 指令执行后, CS:IP 指向程序的笫一条指令,在 data 段中应该定义哪些数据?

assume cs:code

data segment
    ?
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov bx,0
    jmp word ptr [bx+1]
code ends

end start

答案:word ptr [bx+1] 指的是一个字单元,也即 2 个字节,也就是说在 data 段中是第 2~3 字节,如果 jmp 转移到程序第一条指令,也即 IP 的值为 0000 即可。故只需第 2、3 字节均为 0 即可。如下:

data segment
    dw 3 dup (0)
data ends

(2)程序如下。补全程序,使 jmp 指令执行后, CS:IP 指向程序的笫一条指令 。

assume cs:code

data segment
    dd 12345678H
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    mov bx,0
    mov [bx],_____
    mov [bx+2],_____
    jmp dword ptr ds:[0]
code ends

end start

答案:[bx] 代表低 16 位的字,对应 IP;[bx+2] 代表了高 16 位的字,即 CS。所以只需要保证 data 段中第 1、2 字节是 IP 的值,第 3、4 字节是 CS 的值即可。

    mov [bx],offset start  ; IP
    mov [bx+2],code        ; CS

(3)用 Debug 查看内存,结果如下:

2000:1000    BE 00 06 00 00 00 ...

则此时,CPU 执行下述命令后,(CS)=?,(IP)=?:

mov ax,2000H
mov es,ax
jmp dword ptr es:[1000H]

答案:(CS)=0006h,(IP)=00BEh。

检测点 9.2

补全编程,利用 jcxz 指令,实现在内存 2000H 段中查找笫一个值为 0 的字节,找到后,将它的偏移地址存储在 dx 中 。

assume cs:code

code segment
start:
    mov ax,2000H
    mov ds,ax
    mov bx,0
s:
    mov ch,0       ;
    mov cl,[bx]    ;
    jcxz ok        ;
    inc bx         ;
    jmp short s
ok:
    mov dx,bx
    mov ax,4c00h
    int 21h
code ends

end start

答案:这里我们需要 jcxz 实现循环的跳出,需要想办法将 cx 的内容赋值为 ds:bx,也就是说从 ds:[0] 开始,逐个字节的将单元内容赋值给 cx,然后执行 jcxz 语句。

由于是逐个字节的比较,bx 的偏移量应以字节为单元。我们使用的 cx 寄存器是 16 位的,因此只需要低 8 位的 cl 寄存器就可以了。为了保证 ch 为 0,首先必须置零。它们组合在一起就是 cx 的整体值。

检测点 9.3

补全编程,利用 loop 指令,实现在内存 2000H 段中查找笫一个值为 0 的字节,找到后,将它的偏移地址存储在 dx 中。

assume cs:code

code segment
start:
    mov ax,2000H
    mov ds,ax
    mov bx,0
s:
    mov cl,[bx]
    mov ch,0
    inc cx          ;
    inc bx
    loop s
ok:
    dec bx          ; dec 指令的功能和 inc 相反,dec bx 进行的操作为:(bx)=(bx)-1
    mov dx,bx
    mov ax,4c00h
    int 21h
code ends

end start

答案:保证这个 loop 循环的动力是:cx!=0,首先搞清这点。其次,理解 loop 指令的动作,首先是 cx=cx-1,然后才是跳转到标号执行。因此,若 cx==0,需要先 +1 才能保证 loop 中判断的是实际的 cx 值。

参考链接

实验 8

分析下面的程序,在运行前思考:这个程序可以正确返回吗?

运行后再思考:为什么是这种结果?

通过这个程序加深对相关内容的理解。

assume cs:codesg

codesg segment
    mov ax,4c00h
    int 21h

start: 
    mov ax , 0
s:  
    nop
    nop
    mov di,offset s
    mov si,offset s2
    mov ax,cs:[si]
    mov cs:[di],ax
s0:
    jmp short s
s1:
    mov ax,0
    int 21h
    mov ax,0
s2:
    jmp short s1
    nop

codesg ends
end start

答案:

1、程序入口为有 start 标号的地方。

2、s 标号中语句 nop 会在运行时在代码段分配一个字节的空间。(机器码为 90,在内存中就是90H),其作用是方便在程序运行时代码段分配空间,在此写入代码(实际是机器码)。执行 2 次 nop 后,在 CS 段中分配了 2 个字节空间,内容都是 90H。(这个空间目前是空的,CPU 遇到 这2 个字节,就不执行,顺序执行下面的机器码。)

3、12~15 行这四条语句的作用是将 s2 处的机器码赋值给 s 标号开始的 2 个连续空间中,也就是说,将 jmp short s1 这个指令的机器码(2 个字节)赋值给了 s 标号后面的 2 个字节。

jmp short s1 机器码到底是多少?(理解 jmp 指令偏移的是相对位移)它应该是从 si 标号偏移地址到 _**_jmp 指令后的第一个字节的偏移地址。我们所说的偏移地址就是 IP。我们计算下。

  • mov ax,0 == 3 字节(B80000h);
  • int 21H == 2 字节(CD21h);
  • mov ax,0 == 3 字节(B80000h);
  • jmp short s1 == 2 字节(EBXXh)。

那么我们可以计算出此代码位移的偏移量是 10 字节,也就是说 IP 变化了 10。由于 s1 处标号(IP值) - s2 标号后的第一个字节地址(IP 值)是 -10,转换成 16 进制(补码)为 00001010(源码10)>> 11110101(取反)>>11110110(加一)== F6H;故这个机器代码是:EBF6。(EB 代表 jmp 指令,F6 代表了自此偏移地址开始,向前偏移 10 个字节。)

jmp short s1 对应的机器码为 EBF6

因此,在标号 s 处存储的是 EBF6 这二个字节。它代表了一个 jmp 指令,无条件向前移动 10 个字节的内存单元处,并执行相应代码。

程序一直自顶向下执行,直到遇到 s0 标号。s0: jmp short s 程序运行到此处,表示跳转到 s 标号处,机器码为 EBF6,执行它,向前偏移10个字节,正好是 codesg segment 的第一条指令 mov ax,4c00H

  • s 标号占 2 个字节(此处为 jmp 指令 EBF6,占 2 个字节。程序从 jmp 指令后面的第一个字节地址开始偏移,偏移量为 -10。下面三条指令按照从下往上的顺序写);
  • mov ax,0 == 3 字节;
  • int 21H == 2 字节;
  • mov ax,4c00H == 3 字节。

因此,程序从 mov ax 4c00H 开始执行,直到 int 21H 正常结束程序。

所以程序的执行顺序为:先执行 s,然后执行 s0,再跳到 s,再跳到 codesg 开头,执行 mov ax,4c00Hint 21H 后结束。也就是说,没有执行 s1 和 s2。

程序执行完后,代码段的内容

参考链接

实验 9

编程:在屏幕中间分别显示绿色 、 绿底红色 、 白底蓝色的字符串 'welcome to masm!'。

一些实现细节:

  • 显示缓冲区的地址范围:B800:0000 ~ B800:7FFF
  • 80 * 25 的区域中间,也即第 12~14 行
    • 每行 80 个字符,即 160 个字节,偏移地址 00A0h
    • 第 12~14 行的偏移地址为:06E0h、0780h、0820h
  • 'welcom to masm!' 共计 16 个字符,也即 32 个字节
    • 若要在区域中间,每行还需加上列偏移,也即 (160-32)/2 = 64 字节 = 40h
    • 故首字符的偏移地址为:0720h、07C0h、0860h
  • 需要双重循环
    • 外循环:输出每一行
    • 内循环:输出每个字符和属性
  • es 指向 data,ds 指向显示缓冲区
assume cs:code

data segment
    db 'welcome to masm!'
    db 02h,24h,71h
data ends

stack segment
    db 10 dup (0)
stack ends

code segment

start:

    mov ax,data
    mov es,ax
    mov di,0        ; es:di 指向 data

    mov ax,0b800h
    mov ds,ax       ; ds:bx 指向 显示缓冲区

    mov bx,0720h    ; 第 12 行首字符偏移地址
                    ; bx 用于显示缓冲区中的字符偏移

    mov si,16       ; 字符串属性在 data 段中的偏移量

    mov ax,stack
    mov ss,ax
    mov sp,0        ; 建栈,并初始化

    mov cx,3        ; 一共有 3 行

s:  
    push cx         ; 保护外循环计数
    mov cx,16       ; 内循环次数,共 16 个字符

disp:
    mov al,es:[di]      ; di 用于 data 中的字符偏移,0~15
    mov ds:[bx],al      ; 将字符写入显存

    mov ah,es:[si]      ; si 用于 data 中的属性偏移,0~2
    mov ds:[bx+1],ah    ; 将属性写入显存

    inc di
    add bx,2
    loop disp

    add bx,128      ; 160-32 = 128

    mov di,0
    inc si

    pop cx          ; 恢复外循环计数
    loop s


    mov ax,4c00h
    int 21h

code ends

end start

程序执行结果,在显示缓冲区输出了对应的字符串

参考链接

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文