汇编学习笔记(九)

汇编学习笔记(九)

转移指令

转移指令是重要的汇编指令,一般有无条件转移指令和条件转移指令,无条件转移指令是直接转移到目的处执行,条件转移指令是根据条件转移,如前jcxz,当cx=0条件满足时才转移到标号处执行。

JMP指令

jmp是一种典型的无条件转移指令,jmp指令可以只修改IP,也可以同时修改CS和IP。
jmp 按照转移的距离分为:

1、段内短转移,对IP修改的范围为:-128-127,即存储jmp指令转移偏移地址使用1B单元存储,段内短转移可以用jmp short 标号来明确。
2、段内近转移,对IP修改范围为:-32768 - 32767 偏移地址使用2B(word)单元存储,近转移可以用jmp near 标号明确
3、段间转移,同时修改CS和IP,使用jmp far ptr 标号

jmp 可以有以下形式:
1、jmp 标号转移到标号处执行。
2、jmp 寄存器转移到寄存器中存储的偏移地址处执行,注意此处寄存器是16位寄存器,相当于IP=寄存器。
3、jmp 内存地址转移到内存存储的偏移地址处执行。段内转移可以使用jmp word ptr [0],段间转移用jmp dword ptr [0]其中IP=低位word,CS=高位word。
4、jmp 段地址:偏移地址转移到段地址:偏移地址处执行指令,如下:

.....
jmp end
.....
end:	
mov ax, 4c00H
int 21h

CALL和RET指令

CALL和RET指令也是转移指令的一种。

RET指令

RET指令包括两条指令ret和retf,两者之间有一定的区别,执行ret指令时,CPU相当于做以下动作:

  1. pop ip ;将栈顶元素弹出到IP寄存器中,以实现近转移

执行retf指令相当于以下动作:

  1. pop ip ;弹出第一个元素到IP寄存器中。
  2. pop cs ;弹出第二个元素到CS寄存器

可以看出ret和retf的区别主要在于是否修改CS段寄存器,CS:IP决定了指令执行的顺序,所以使用ret/retf指令可以实现程序的转移,如下所示,程序中转移到0000:7c00H处执行:

......		; 程序中其他指令
mov ax, 0
push ax		; 段地址0压栈
mov ax, 7c00H
push ax		; 偏移地址压栈
retf
CALL指令

CALL指令如call 标号相当于以下动作:

  1. push IP
  2. jmp 标号

当然实现段间转移,可以使用call far pr 标号,相当于以下动作:

  1. push cs
  2. push ip
  3. jmp far ptr 标号

CALL指令如jmp指令也有call 寄存器或者call 内存地址形式,相关方式也类似,简略。

子程序

CALL指令和RET指令配合即可实现子程序,将自己的汇编程序按功能分成不同的子程序,有利于更好的组织。

1、call 子程序标号即可转到子程序处执行。
2、子程序执行结束后通过ret,又可返回到主程序调用处后继续执行。

子程序示例(nasm片段):

org 0x1000
base_of_stack   equ 0x7c00  ; 栈基地址

mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, base_of_stack

; 调用子函数清屏
; ====================
call sub_clear_scr

mov si, msg_hello
call sub_print_str
loader_end: hlt
    jmp short loader_end

; 以下静态数据
; ==========================
msg_hello       db 'Hello world! ', 0x00

; 以下为子函数
; ===========================
sub_clear_scr:
    ; 调用BIOS 10h中断清屏
    ; ========================
    push ax
    push bx
    push cx
    push dx
    mov ax, 0x0600      ; ah: 子功能号 06H - 向上滚屏, al: 滚动的行数,0-清除窗口 
    mov bx, 0x0700      ; bh: 空白区域的属性
    mov cx, 0           ; ch:cl 窗口的左上角位置 行:列
    mov dx, 0x184f      ; dh:dl 出口的右下角位置 行:列, 屏幕默认是 25行×80列16色模式,即 19H*50H
    int 10h
    pop dx 
    pop cx
    pop bx
    pop ax
    ret

sub_print_str:
    ; 在显存中写入数据来输出字符。
    ; ========================
    ; ds:si 指向字符串首地址, 字符串以0结尾
    push ax
    push si
    push di

    mov ax, es
    push ax
    mov ax, 0xb800
    mov es, ax
    mov di, 0
    _loop_sps_read_char:
    mov al, [si]
    mov ah, 0x07
    cmp al, 0
    jz _label_sps_end
    mov [es:di], ax
    inc si
    add di, 2
    jmp _loop_sps_read_char

    _label_sps_end:
    pop ax
    mov es, ax
    pop di
    pop si
    pop ax
    ret

编写子函数需要注意几个问题:
1、子程序中一般会变更寄存器,为了不影响调用程序的正常运行,一般在子程序内将要用到的寄存器先push到堆栈中,子程序结束时再pop出来。
2、子程序怎样传参和传递返回结果?一般通过寄存器来传递参数和返回结果,但是寄存器的数量有限,如果参数数量过多,如何传递?内存。怎样通过内存传递多个参数?
3、子程序中怎样分配临时变量?如果寄存器不够怎么办?内存。一般在栈中分配临时变量,怎样分配?

参考教材

[1]: 王爽老师的 汇编语言(第3版)
[2]: 李忠 / 王晓波 / 余洁 x86汇编语言-从实模式到保护模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值