[从零学习汇编语言] - 转移指令进阶


前言

点赞再看,养成习惯!


回顾

1.转移指令原理

还记得什么叫做转移指令吗?我们之前说过:能够修改IP寄存器或者同时修改CS及IP寄存器的指令叫做转移指令。 而通过定义我们又可以推断转移指令之所以可以跳转,主要是由于它可以修改负责存储指令指针信息的CS:IP 地址寄存器的值
让我们写个小程序加深一下理解:

assume cs : codea 
codea segment 
start: 
       mov ax,0
	   inc ax
	   jmp short s 
	   mov ax,0
s:     inc ax
codea ends 
end start 

程序分析:

  1. 首先将ax寄存器的值初始化为0
  2. ax寄存器自增加一,此时AX寄存器的值为1
  3. 执行 jmp short s 进行短转移
  4. 若转移成功应该执行标号s处指令inc ax 进行自增,此时寄存器值应为2
  5. 若转移失败则继续按照顺序执行mov ax,0,将ax重置为0
  6. 观察运行结果

运行结果:

请添加图片描述


2. 已接触过的操作符

  1. jmp : 无条件转移指令,可以同事修改CS:IP两个寄存器的值来改变指令指针指向。
  2. jcxz: 有条件的转移指令,它的判断条件为此时CX寄存器是否为零,若为零则进行转移,若不为零则什么都不做。
  3. loop : 有条件的短转移指令。判断CX寄存器的值进行循环,每次循环CX寄存器的值减一,直到为零。

3. 寄存器回顾

最后让我们回顾一下我们接触过的寄存器,首先让我们看下寄存器列表:
在这里插入图片描述

通用数据处理寄存器

  1. AX寄存器(Accumulator): 累加地址寄存器,简称累加器。AX寄存器可以拆分为AH,AL两个八位的寄存器进行分别使用。(外设输入的值只能存放在AL寄存器或者AX寄存器中,后续使用到会讲。)
  2. BX寄存器(Base) : 基地址寄存器,常被用作数据寄存器,访问存储器时可以存放被读写单元的地址,也可被拆分为BL和BH两个8位寄存器分别使用。
  3. CX寄存器(Count): 操作数寄存器,常用存放条件指令,如loopjcxz 等指令的判断条件。
  4. DX(DATA): 数据寄存器,在乘除法中作为数据累加器,再输入输出操作中存放端口的地址。

指针寄存器

  1. SP寄存器(Stack Point): 堆栈指针寄存器,用来存放栈顶的偏移地址,供栈操作使用。
  2. BP寄存器(Base Point): 基指针寄存器,存放堆栈中数据的基地址。

变址寄存器

  1. SI(Source Index): 源变址寄存器,通常用来搭配DS段地址寄存器存放偏移地址。基本用法与BX寄存器 相似,只不过不能拆分位高低八位寄存器。
  2. DI(Destination Index): 目的变址寄存器,与SI寄存器类似,搭配ES段地址寄存器使用。

段地址寄存器

  1. CS(Code Segment): 代码段地址寄存器,通常搭配IP寄存器一起使用,CS:IP指向CPU当前需要执行的指令地址。
  2. DS(Data Segment): 数据段地址寄存器,常用来指向我们自己定义的数据段地址。
  3. SS(Stack Segment): 栈段地址寄存器,用来存放栈顶段地址。
  4. ES(Extra Segment): 附加段地址寄存器,常用于存放当前执行程序的辅助数据段地址,常搭配DI目的地址寄存器一起使用。

其他寄存器

  1. IP(Instructoin Pointer): 指令指针寄存器,常搭配CS代码段地址寄存器一起使用,标识指针指针地址。
  2. FLAG: 标志寄存器,也称为程序状态字PSW。 FLAG寄存器的用法与其他寄存器都不相同,其16位存储空间每一位都可以存放信息(程序状态字,PSW,暂不涉及)

一、ret及retf

1.1 ret指令

ret指令常用于实现近转移,它通过使用栈中的数据修改IP寄存器的内容来达到转移目的。
CPU在执行ret指令时会进行以下两步操作:

  1. IP = (SS × 16) + SP
  2. SP = SP + 2

首先解释下:IP = (SS × 16) + SP ,是不是看起来很眼熟,没错这就是我们的寻址公式,对应的含义其实就是讲此时SS:SP表达的栈空间的栈顶数据,而第二步的SP = SP + 2则是取出栈顶数据,这两步加起来其实等同于:
pop ip


我们写个小程序来试验下:

assume cs:codeA 
       stack segment 
			db 16 dup(0)
	   stack ends 
start:  mov ax,stack
        mov ss,ax
		mov sp,16
		mov ax,8
		push ax
		ret
codeA ends 
end start 

流程解析:

  1. mov ax,stack 将stack数据段标号指向ax寄存器
  2. mov ss,ax 将栈段地址更新到ss段地址寄存器
  3. mov sp,16 修改sp寄存器的值,更改栈顶空间
  4. mov ax,8 修改ax寄存器的值
  5. push axax寄存器中的值压入栈空间
  6. ret 弹栈,将栈顶值赋予ip寄存器

验证:
请添加图片描述

1.2 retf指令

retfret用法相似,细微的不同点是retf指令是远转移的一种,其行为是通过栈中数据同时修改CSIP的内容来达到转移的目的。当CPU调用retf指令时会进行以下的操作:

  1. IP = SS × 16 + SP
  2. SP = SP + 2
  3. CS = SS×16 + SP
  4. SP = SP + 2

用通俗的话就是当我们调用retf指令时栈空间会进行两次弹栈,第一次弹栈的值会赋予IP寄存器,第二次弹栈的值会赋予CS寄存器。当CS:IP同时修改时可达到实现远转移的目的。


代码验证:

assume cs:codea
stack segment
      db 16 dup(0)
stack ends

code segment
      mov ax,4c00H
	  int 21h
start : mov ax,stack
        mov ss ,ax
		mov sp,16
		mov ax,0
		push cs
		push ax
		mov bx,0
		retf
code ends 
end start 

流程解析:

  1. mov ax,stack mov ss ,ax 将栈段初始值赋予SS寄存器
  2. mov sp ,16 mov ax,0 初始化栈空间大小
  3. push cs push ax 记录指令指针位置
  4. mov bx,0 重置BX寄存器
  5. retf 远转移

结果验证:
在这里插入图片描述


1.3 小练习

题目: 写一个程序,实现从内存1000:000处开始执行指令。
代码实现:

assume cs:codea
stack segment
		db 16 dup(0)
stack ends 

codea segment
      start: 	mov ax,stack
	            mov ss,ax
				mov sp,16
				mov ax,1000
				push ax
				mov ax,0000
				push ax
				retf
codea ends 
end start 

二、Call指令

call指令通常都是搭配ret指令一起使用的,当我们调用call指令时,CPU会进行以下操作:

  1. 将当前的IP或CS和IP压入栈中
  2. 转移

call指令不能够实现远转移,除此之外,call指令实现转移的方式和jmp相同。接下来我们通过call指令搭配不同方式进行转移来详细讲一下call指令的使用。

2.1 依据位移进行转移

格式: call + 标号
CPU遇到这种格式的指令时会进行:

  1. SP = SP -2
  2. SS × 16 + SP = IP
  3. IP = IP + 16位位移

其中16位位移 = 标号处地址 - call指令后的第一个字节的地址

以此推断,当我们执行call + 标号时就等于执行:
push IP
jmp near ptr 标号

2.2 包含目的地址进行转移

格式: call far ptr 标号 实现段间转移
当CPU遇到call far ptr 标号格式的指令时会进行以下操作:

  1. SP = SP - 2
  2. SS*16 + SP = SP
  3. SP = SP-1
  4. SS*16 + SP = IP
  5. CS = 标号所在段的段地址
  6. IP = 标号所在段中的偏移地址

以此推断,当我们执行call far ptr 标号时就等于执行:
push cs
push ip
jmp far ptr 标号

2.3 搭配寄存器进行转移

格式: call 16位reg
当CPU遇到call 16位reg格式的指令时会进行以下操作:

  1. sp = sp -2
  2. ss × 16 + sp = ip
  3. ip = (16位REG)

以此推断,当我们执行call 16位reg时就等于执行:
push ip
jmp 16位reg

2.4 转移地址在内存单元

call word ptr 内存单元地址 相当于:
push ip
jmp word ptr 内存单元地址

或者
call dword ptr 内存单元地址 相当于:
push cs
push ip
jmp dword ptr 内存单元地址

三、 ret搭配call使用

3.1 分析两段程序

问题: bx寄存器的值在执行后是多少

assume cs:codea 
codea segment
    start : mov ax,1
	        mov cx,3
			call s 
			mov bx,ax
			mov ax,4c00h
			int 21h
		s:  add ax,ax
		    loop s 
			ret 
	codea ends 
	end start 

思路图解:
在这里插入图片描述


3.2 反思

通过上面这段程序 兄弟们有没有发现callret搭配使用可以组成一个简单的框架:

assume cs:code
code segment
main : 
      call sub1 ; 调用子程序sub1 ,也可以说是方法1
	  ...
	  mov ax,4c00h
	  int 21h
sub1: 
     ...
	 ret       ;返回主程序
code ends 
end main 

看到这里是不是很熟悉,是不是很像其它语言的method调用?恭喜坚持学习到这里的小伙伴,我们已经离学习写一个完整的汇编程序不远了。

四、 mul指令(乘法指令)

4.1 回顾

简单讲一下四则运算,我们下章会用到
我们首先要回顾下已经学习过的四则运算:

4.1.1 add

加法指令,格式如下:

add reg,reg
使用时需要注意两点:

  1. 两个相加的数可以是八位或者十六位,但是类型需要匹配,不能一个是八位一个是十六位
  2. 至少需要一个寄存器参与运算并放到最前面

4.1.2 sub

减法指令,语法及注意点与add相同

4.2 mul指令

语法: mul reg
注意点:

  1. 乘法的第一个乘数默认存放在AX寄存器中
  2. 结果是8位乘8位,则存放在AX寄存器中
  3. 若是16位乘16位,则高位默认存放在DX寄存器中,低位在AX寄存器中

比如,我们计算100×10:

assume cs:code
code segment
main :    
      mov al,100
      mov bl,10
      mul bl	  
code ends 
end main

是不是很简单

结语

今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
Spring通用架构及工具已上传到gitee仓库,需要的小伙伴们可以自取:
https://gitee.com/xiaolong-oba/common-base

码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓龙oba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值