转移指令分类

  1. 无条件转移 JMP
    1. 段内转移:只修改IP,如jmp ax
    2. 段间转移:同时修改CS:IP,如jmp 1000:1
  2. 近转移:IP变化范围-32768~32767
  3. 短转移:IP变化范围-128~127
  4. 条件转移
  5. 循环 LOOP
  6. 过程
  7. 中断

    操作符 offset

    获取标号的偏移地址

  1. start:mov ax,offset start ; 等价于 mov ax,0
  2. s:mov ax,offset s ; 等价于 mov ax,3

mov ax只占 1 个字节,剩下两个字节为 0000H(offset start),所以s的偏移地址是3

复制两字节的指令

  1. assume cs:code
  2. code segment
  3. s:mov ax,bx
  4. mov si,offset s
  5. mov di,offset s0
  6. mov ax,cs:[si]
  7. mov cs:[di],ax
  8. s0:nop
  9. nop
  10. code ends
  11. end s

指令跳转 jmp

short

jump 指令的机器码为EB + 8位偏移地址(IP)差

  1. assume cs:code
  2. code segment
  3. s:mov ax,100
  4. mov ax,100
  5. s0:mov ax,100
  6. jmp short s ; 跳转至 s
  7. s1:mov ax,100
  8. jmp short s2 ; 跳转至 s2
  9. mov ax,100
  10. s2:mov ax,100
  11. mov ax,4c00H
  12. int 21H
  13. code ends
  14. end s

执行jmp前的IP为下一条指令的IP,因此
image.png

由于是短转移,范围为 -128~127,因此 00~7f表示偏移0~127个字节,ff~80表示偏移-1~-128 偏移使用补码表示,即-128的补码为10000000B``80H

near ptr

近转移,转移值也是补码表示,EP + 16位偏移差

  1. assume cs:code
  2. code segment
  3. s1:mov ax,100
  4. jmp near ptr s2
  5. mov ax,100
  6. ; 此处省略一堆代码
  7. s2:mov ax,100
  8. mov ax,4c00H
  9. int 21H
  10. code ends
  11. end s1

image.png

far ptr

远转移,段间转移,EA + 转移地址

  1. assume cs:code
  2. code segment
  3. s1:mov ax,100
  4. jmp far ptr s2
  5. dw 256 dup (0)
  6. s2:mov ax,100
  7. mov ax,4c00H
  8. int 21H
  9. code ends
  10. end s1

image.png

dword

根据两个字(32位,4字节)进行转移,高位等于CS,低位等于IP

  1. mov ax,1234H
  2. mov ds:[0],ax ; 代表低位
  3. mov word ptr ds:[2],0 ; 代表高位
  4. jmp dword ptr ds:[0]

最终跳转代码为jmp 0000:1234

奇怪的代码

  1. assume cs:code
  2. code segment
  3. mov ax,4c00H
  4. int 21H
  5. start:mov ax,0
  6. s:nop
  7. nop
  8. mov di,offset s
  9. mov si,offset s2
  10. mov ax,cs:[si]
  11. mov cs:[di],ax
  12. s0:jmp short s
  13. s1:mov ax,0
  14. int 21h
  15. mov ax,0
  16. s2:jmp short s1
  17. nop
  18. code ends
  19. end start

这段代码其实是把s2的代码放在s的两个nop上,即
image.png
EBF6实际上是相对于000A往前F6个字节,即0000

条件判断 jcxz

cx=0时跳转到指定地址

  1. ; 2000 段中查找第一个为 0 的字节的偏移地址, 并送入 dx
  2. assume cs:code
  3. code segment
  4. start:mov ax,2000H
  5. mov ds,ax
  6. mov bx,0
  7. ; 将前 6 个数据变成非零
  8. mov word ptr ds:[0],1020 ; fc 03
  9. mov word ptr ds:[2],1020
  10. mov word ptr ds:[4],1020
  11. mov ch,0 ; [bx] 是字节型数据, 因此使 ch=0
  12. s:mov cl,[bx]
  13. jcxz ok
  14. inc bx
  15. jmp short s
  16. ok:mov dx,bx
  17. mov ax,4c00H
  18. int 21H
  19. code ends
  20. end start

循环 loop

cx≠0时跳转到指定地址

  1. ; 使用 loop 实现上述功能
  2. assume cs:code
  3. code segment
  4. start:mov ax,2000H
  5. mov ds,ax
  6. mov bx,0
  7. mov word ptr ds:[0],1020
  8. mov word ptr ds:[1],1020
  9. mov word ptr ds:[2],1020
  10. mov ch,0
  11. s:mov cl,[bx]
  12. inc cx ; 因为 loop 先将 cx-1 再判断是否为 0 才退出, 所以这里使 cx+1
  13. inc bx ; 在退出循环前 bx+1
  14. loop s
  15. ok:dec bx ; 因此这里要把 bx-1
  16. mov dx,bx
  17. mov ax,4c00H
  18. int 21H
  19. code ends
  20. end start

ret/retf

ret

相当于(IP)=((ss)×16+(sp))``(sp)=(sp)+2pop IP

数据出栈后赋值给IP寄存器 近转移

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. mov ax,4c00H ; 076A:0000
  7. int 21H
  8. start:mov ax,stack
  9. mov ss,ax
  10. mov sp,16
  11. mov ax,0
  12. push ax ; 0000
  13. mov bx,0
  14. ret ; pop ip(0000) cs 默认就是 076A
  15. code ends
  16. end start

retf

相当于(IP)=((ss)×16+(sp))``(sp)=(sp)+2``(CS)=((ss)×16+(sp))``(sp)=(sp)+2pop IP``pop CS

数据出栈后赋值给IP``CS寄存器 远转移

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. mov ax,4c00H ; 076A:0000
  7. int 21H
  8. start:mov ax,stack
  9. mov ss,ax
  10. mov sp,16
  11. mov ax,0
  12. push cs ; 076A
  13. push ax ; 0000
  14. mov bx,0
  15. retf ; pop ip(0000) pop cs(076A)
  16. code ends
  17. end start

call

近转移、远转移 call 指令会对下一条指令的内存地址进行保存(入栈)

call 标号

相当于(sp)=(sp)-2``((ss)×16+(sp))=(IP)``IP 变成标号 IPpush IP``jmp near 标号
机器码E8 + 偏移差

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. start:mov ax,0
  7. call s ; push 0006 jmp 0007
  8. inc ax
  9. s:pop ax ; ax=0006
  10. mov ax,4c00H
  11. int 21H
  12. code ends
  13. end start

image.png

call far ptr 标号

相当于push CS``push IP``jmp far ptr 标号
机器码9A + IP + CS

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. start:mov ax,0
  7. call far ptr s
  8. inc ax
  9. s:pop ax
  10. add ax,ax
  11. pop bx
  12. add ax,bx
  13. mov ax,4c00H
  14. int 21H
  15. code ends
  16. end start

image.png

call IP

相当于(sp)=(sp)-2``((ss)×16+(sp))=(IP)

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. start:mov ax,6
  7. call ax
  8. inc ax
  9. pop ax
  10. mov ax,4c00H
  11. int 21H
  12. code ends
  13. end start

image.png

call word ptr […]

[...]里的数值当作跳转的IP,同理,入栈的是下一条指令的IP

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. start:mov ax,0123h
  7. mov ds:[0],ax
  8. call word ptr ds:[0]
  9. mov ax,4c00H
  10. int 21H
  11. code ends
  12. end start

call dword ptr […]

[...]第一个字为IP第二个字为CS但是入栈是CS先入

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. start:mov ax,0123h
  7. mov ds:[0],ax
  8. mov word ptr ds:[2],0
  9. call word ptr ds:[0]
  10. mov ax,4c00H
  11. int 21H
  12. code ends
  13. end start

image.png

子程序(类似线程)

  1. assume cs:code
  2. stack segment
  3. db 16 dup (0)
  4. stack ends
  5. code segment
  6. main:mov ax,1
  7. mov cx,3
  8. call sub ; 将当前 IP 保存起来, 并跳转至子程序 sub
  9. mov bx,ax
  10. mov ax,4c00H
  11. int 21H
  12. sub:add ax,ax
  13. loop sub
  14. ret ; 执行完 sub 后重新跳转回 call 后面的指令
  15. code ends
  16. end main

模块化设计

子程序带参数

  1. assume cs:code
  2. data segment
  3. dw 1,2,3,4,5,6,7,8
  4. dd 0,0,0,0,0,0,0,0
  5. data ends
  6. code segment
  7. main:mov ax,data
  8. mov ds,ax
  9. mov si,0
  10. mov di,16
  11. mov cx,8
  12. s:mov bx,[si] ; 取出要计算的值保存在 bx
  13. call cube
  14. mov [di],ax ; 将计算的结果低位放到 [di]
  15. mov [di].2,dx ; 将计算的结果高位放到 [di].2
  16. add si,2
  17. add di,4
  18. loop s
  19. mov ax,4c00h
  20. int 21h
  21. ; 说明:计算 N 3 次方
  22. ; 参数:(bx)=N
  23. ; 结果:(dx:ax)=N^3
  24. cube:mov ax,bx ; bx 中取出要计算的参数
  25. mul bx
  26. mul bx
  27. ret
  28. code ends
  29. end main

image.png

批量参数

将参数放在内存中,只需要使用寄存器传入第一个参数的内存地址

  1. assume cs:code,ds:data
  2. data segment
  3. db 'sjashdeidede'
  4. data ends
  5. code segment
  6. start:mov ax,data
  7. mov ds,ax
  8. mov si,0 ; 第一个参数的偏移地址
  9. mov cx,12 ; 循环 12
  10. call capital
  11. mov ax,4c00h
  12. int 21h
  13. capital:and byte ptr [si],11011111B ; 将字符串变成大写
  14. inc si
  15. loop capital
  16. ret
  17. code ends
  18. end start

使用 jcxz 优化

  1. data segment
  2. db 'sjashdeidede',0 ; 末尾 0 表示字符串结束, 因此不需要传入字符串长度
  3. data ends
  1. capital:mov cl,[si]
  2. mov ch,0
  3. jcxz ok
  4. and byte ptr [si],11011111B
  5. inc si
  6. jmp short capital
  7. ; loop capital 也行
  8. ok:ret

由于capital中使用了寄存器cx因此这个程序要求主程序start中不能使用cx

使用栈优化

主程序的cx可以使用栈保存起来

  1. assume cs:code,ds:data
  2. data segment
  3. db 'word',0
  4. db 'unix',0
  5. db 'wind',0
  6. db 'good',0
  7. data ends
  8. code segment
  9. start:mov ax,data
  10. mov ds,ax
  11. mov bx,0
  12. mov cx,4
  13. s:mov si,bx
  14. call capital
  15. add bx,5
  16. loop s
  17. mov ax,4c00h
  18. int 21h
  19. capital:push cx ; 保存循环次数
  20. push si ; 保存数据偏移
  21. change:mov cl,[si]
  22. mov ch,0
  23. jcxz ok
  24. and byte ptr [si],11011111B
  25. inc si
  26. loop change
  27. ok:pop si
  28. pop cx
  29. ret
  30. code ends
  31. end start