前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >汇编笔记(四)长文警告

汇编笔记(四)长文警告

作者头像
天钧
发布2019-08-23 12:39:08
6850
发布2019-08-23 12:39:08
举报
文章被收录于专栏:渗透云笔记渗透云笔记

转移指令的原理

可以修改IP,或同时修改CS和IP的指令通称为转移指令。

8086CPU的转义行为有一下几类。

  • 只修改IP时,称为段内转移,比如:jmp ax
  • 同时修改CS和IP时,称为段间转移,比如:jmp 1000:0

由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移。

  • 短转移IP的修改范围为-128~127。
  • 近转移的IP的修改范围为-32768~32767。

8086CPU的转移指令分为以下几类。

  • 无条件转移指令(如:jmp)
  • 条件转移指令
  • 循环指令(如:loop)
  • 过程
  • 中断

这些转移指令的区别在于前提条件不同,但转移的原理是相同的。

我们在这里通过深入学习无条件转移指令jmp来理解CPU执行转移指令的基本原理

操作符offset

offset的功能是取的标号的偏移地址,该指令由编译器执行。

jmp指令

jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。

jmp指令需要给出两种信息:

  • 转移的目的地址
  • 转移的距离(段间转移、段内段转移、段内近转移)

根据位移进行转移的jmp指令

jmp short 标号(转到标号处执行指令)

我们从一段汇编程序开始。

观察这段汇编指令对应的机器码,汇编指令中的[idata]立即数,不论是否是数据还是内存单元的偏移地址,都会在对应的机器指令中出现,CPU执行的机器指令,它必须要处理这些数据和地址。

注意jmp指令一行,机器指令中不包含转移的目的地址。

多次测试可以发现,CPU执行jmp指令的时候不需要转移的目的地址。

回忆CPU执行指令的过程。

  • 从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
  • (IP)=(IP)+所读取指令的长度,指向下一条指令
  • 执行指令,跳到第一步,重复这个过程

jmp指令对应的机器码EB03中的’03‘其实是转移的位移。

jmp short 标号的功能为:(IP)=(IP)+8位移。

  • 8位位移=标号处的地址-jmp指令后第一个字节的地址;
  • short 指明此处的位移长度
  • 8位移范围为-128~127,用补码表示(计算机中没有加法,正数二进制取反加一得到负数的二进制)
  • 8位位移在编译算出

类似的指令jmp near ptr 标号,实现的是段内近转移,不过它的位移范围是-32768~32767。

这里简单介绍下这个位移范围如何得到,这和补码的含义有关,正数的补码就是其本省,负数的补码是在原码的基础上,符号位(第一位)不变,其余各位取反,最后+1,8位位移的范围只能11111111~01111111(-128~127),16位位移也类似。

转移的目的地址在指令中的jmp指令

前面说的jmp far ptr 标号指令,对应的机器码为”EA 0801 6C07”,这里我特意空格分隔开,方便观察。

“EA 0801 6C07”是在指令中的内存中的排列顺序,高地址“6C07”是转移的段地址:076C,低地址的“0801”是偏移地址”0108”

转移地址在寄存器中的jmp指令

指令格式:jmp 16位 reg,功能(IP)=(16位reg),这里不在详述。

转移地址在内存中的jmp指令

  • jmp word ptr 内存单元地址(段内转移)

功能:从内存单元地址处开始存放着一个字,是转移的目的偏移地址。

内存单元地址可用寻址方式的任一格式给出。

  • jmp dword ptr 内存单元地址(段间转移)

功能:从内存中单元地址处存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。

jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,对IP的修改范围:-128~127(用补码表示)。

8位位移由编译程序在编译时算出。

功能:如果$()(CX)=0$,转移的标号处执行;如果$(CX) \neq0$,什么也不做(程序向下执行)。

jcxz= jmp CX zero

loop指令

loop指令为循环指令,所有的循环指令都是短转移。

  • 对应的机器码中只包含转移的位移
  • 8位位移有编译时算出
  • 8位位移范围为-128到127,用补码表示。

可能有读者说,这不和jcxa指令、jmp short 标号的性质差不多。是的,确实类似,所有有部分我忽略没写。

它的功能和jcxz类似又有不同:先将$(CX)=(CX)-1$,如果$(CX) \neq0$,转移到标号处执行;如果$()(CX)=0$,什么也不做(程序向下执行)

使用位移转移的意义

到现在为止,已经用过的指令有:

jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号

它们对应的机器码中只有到目的地址的位移,这种设计方便了程序段在内存中的浮动装配,

如果在程序段写入内存地址,则对程序的执行有了前提限制。

编译器对转移位移超界的检测

根据位移进行转移的指令,它的转移范围受到转移位移的限制,如果源程序中出现了转移位移超界的问题,编译时,编译器将报错。

前面在Debug模式中使用过jmp 2000:0100的转移指令,汇编编译器并不认识。源程序中使用编译会出错。

实验八

程序如上,我们一步步来理解。

  • 程序从start开始执行,使用offset分别获取了标号“s”、”s2“处的偏移地址,并保存在DI、DI寄存器中。
  • 使用mov指令将标号”s2“处的字型数据复制的AX中,也就是指令jmp short s1
  • 标号”s2”处的指令“jmp short s1”,我们回忆下位移如何计算,前面说过

8位位移=标号出的地址-jmp指令后的第一个字节的地址,联系CPU执行指令的过程,这个位移实际上就是标号与jmp指令的长度,也就是说从nop指令到标号“s1”处的指令长度为位移,这里为10个字节。往前面移动,也就是负的,得到补码”F6“,机器码为”EBF6”。

  • 将AX中的数据复制到标号“s”处的,标号”s”的前两条nop指令被覆盖
  • 指令jmp short s,这里的位移计算不在详述,跳转到标号”s”处,
    • CS:IP指向内存单元读取指令
  • 这时候第一条指令为”EBF6“了,读取”EBF6“进入指令缓冲器;
    • (IP)=(IP)+2(所读取指令的长度)
    • 执行指令,”EBF6“的效果是IP向前位移10个字节,向前移动10个字节之后的IP所指向的指令即为mov ax,4c00h

CALLRET指令

CALLRET指令都是转移指令,它们都修改IP,或同时修改CS和IP。

这一章,我们讲解callret指令的原理

retretf

ret指令用栈中的数据,修改IP的内容,从而实现近转移。

retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。

CPU执行ret指令时,进行了下面两步操作:

  • (IP)=((SS))*16+(SP))
  • (SP)=(SP)+2

CPU执行retf指令时,进行了下面4不操作:

  • (IP)=((SS)*16+(SP))
  • (SP)=(SP)+2
  • (CS)=((SS)*16+(SP))
  • (SP)=(SP)+2

估计你有点懵,用汇编指令来解释retretf指令,则:

CPU执行ret指令时,相当于pop IP

CPU执行retf指令时,相当于进行:

pop IP
pop CS

call指令

CPU执行call指令时,进行两步操作:

  • 将当前的IP或CS和IP压如栈中;
  • 转移

依据位移进行转移的call指令

call 标号(将当前的IP压入栈后,转到标号出执行指令)

CPU执行此种格式的call指令时,进行了如下的操作:

  • (SP)=(SP)-2 ((SS)*16)+(SP))=(IP)
  • (ip)=(IP)+16位位移

16位位移=标号出的地址-call指令后的第一个字节的地址;

16位位移范围为-32768~32767,用补码表示;

16位位移由编译程序在编译时算出

用汇编指令解释,相当于。

转移的目的地址在指令中的call指令

前面讲的call指令,对应的机器码中并没有转移的目的地址,而是位移。

call far ptr 标号实现的段间转移

CPU执行此格式的call指令是,进行了如下操作。

  • (SP)=(SP)-2 ((ss)*16+(sp))=(CS) (SP)=(SP)-2 ((SS)*16+(SP))=(IP)
  • (CS)=标号所在段的段地址 (IP)=标号所在段的偏移地址

用汇编指令解释,相当与进行。

push CS
push IP ;IP为call指令的下一条指令的IP
jmp far ptr 标号

转移地址在寄存器中的call指令

指令格式:call 16位 reg

功能:

  • (SP)=(SP)-2 ((SS)*16+(SP))=(IP) (IP)=(16位reg)

汇编指令解释,相当于进行:

push IP;同上
jmp 16位reg

注意:这里的地址应理解为偏移地址(IP寄存器中存放),请回忆jmp指令的原理。

转移地址在内存中的call指令

转移指令在内存中的call指令有两种格式。

  • call word ptr 内存单元地址

用汇编指令解释:

push IP;同上
jmp word ptr 内存单元地址
  • call dword ptr 内存单元地址

用汇编指令解释:

push CS
push IP

这里检测点第一题居然书貌似错了,看视频果然不一样。 因为call指令执行过程中对栈进行了操作,为了分析,可以手动记录栈空间的变化,这样易于分析。

callret的配合使用

先分析一段程序。

  • 前三条指令执行后,栈空间为16个字节,且用零填充
  • call指令读取后,IP指向下一条指令mov ax,4c00h,指令执行,将IP中的值入栈,IP寄存器指向标号”s”处。
  • CPU从标号“s”处开始执行,执行add ax,ax
  • ret指令执行后,相当于进行了pop IP,IP更改,IP重新指向mov ax,4c00h

回忆一下call指令和ret指令的功能。

  • call 标号指令相当于。
push IP
jmp near ptr 标号
  • ret指令相当于。
pop IP

在回顾这个程序,我们将标号“s”处的具有一定功能的程序段称为子程序,call指令在转去执行子程序之前,call指令将后面指令的地址存储到栈中,在子程序末,使用ret指令,用栈中的数据设置IP中的值,回到call指令后的代码处继续执行。

我都说到这里,有没有人有大胆的想法:-P。

标号:
	指令
	ret

我们在指令中可以使用call 标号,再通过ret返回不断的执行后续的call

这样写有什么好处?

我们说要保持源程序的阅读性,随着功能的不断增加,代码阅读性下降是必然的,而上述的这种结构,为我们提供了一种组织代码的方式,是的大量代码的情况下仍然有一定的阅读性。

mul指令

因下面会用到,这里介绍下mul指令,mul是乘法指令,使用mul做乘法时,需要主要以下几点。

  • 两个相乘的数位数需要相同,即8位和8位,16位和16位。
  • 如果是8位,一个默认在AL中,另一个在8位reg中或者内存字节单元中;如果是16位一个默认在AX中,另一个在16位reg中或者内存字单元中。
  • 结果:8位乘法结果在AX中;16位乘法,结构高位默认在AX中,低位在AX中存放

模块化程序设计

从上面我们看到,callret指令共同支持了汇编语言程序编程中的模块化设计。利用callret指令,可以用简洁的方法,实现多个相互联、功能独立的子程序来解决一个复杂的问题。

下面,我们来看一下子程序设计过程中的相关问题和解决方法。

参数和结果传递的问题

讨论参数和返回值传递的问题,实际上就是在探讨,应该如何存储子程序需要的参数和产生的返回值。

我们最先想到的是用寄存器了存储,对于存放参数的存储器和存储结果的存储器,调用者和子程序的读写恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入寄存器中。

这是一段将data段第一组数据的3次方,结果保存在后面一组的dword单元中的程序,请理解此过程中的参数和结果传递。

注意,如果写好的程序你自己还要使用,或者要给别人使用,都请写好注释。

批量数据的传递

前面的程序参数和结果只有一个,可以用两个寄存器来存放,寄存器数量终究有限的,我们不可能简单第用寄存器来存放多个需要传递的数据。返回值也一样。

这里将批量数据放在内存中,所在内存空间的首地址放在寄存器中,传递给需要的子程序。

我们也可以用以前使用loop指令来实现。

call指令给我们的启发

call指令的原理不在详述,call指令告诉了我们一种组织数据和组织代码的方式

上述过程可以描述为。

  • 设置参数
  • 程序处理
  • 得到程序的返回值

从组织的方式解释。

  • 组织数据
  • 组织代码
  • 组织数据

从计算机原理层面抽象。

  • 输入
  • 程序处理
  • 输出

寄存器冲突的问题

有一个一般化的问题:子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。

解决这个问题的简捷方法是,在子程序开始将子程序所有用到的寄存器的内存都保存起来,在子程序返回前恢复,可以用栈来保存寄存器中的内容。

实验10

实现字符串

要求使用子程序“show_str”,在指定的位置,用指定的颜色,显示一个用0结束的字符串。

提供的参数有:(DH)=行号,(DL)=列号

回忆一下实验九,这里我实现一种颜色。

根据要求不难写出如下代码(笔者其实边调试编写写了半小时)

优化一下。

该程序的内部处理和显存的结构相关,通过调用子程序,进行字符串的显示可以不必了解显存的结构,为编程提供了方便。

解决除法溢出问题

问题:div指令可以做除法。进行8为除法的时候,用AL存储接结果的商,AH存储结果的余数;进行16位除法的时候,用AX存储结果的商,DX存储结果余数。

那么,下面的程序段呢?

mov ax,1000h
mov dx,1
mov bx,1
div bx ;商应为11000H,而11000H在AX中存放不下。

当CPU执行div等除法指令的时候,如果出现这样的情况,将引发CPU的内部错误:除法溢出。

我们这里写个子程序来解决除法溢出的问题。

详细的解释过程,注释都有,这道题主要是这个公式:X/N=int(H/N)*65526+[rem(H/N)*65536+L]/N,证明过程不重要,其实和我们用的十进制除法是一样的。

数值显示

功能:将word型数据转变为十进制数的字符串,字符串以0为结尾符。

如果不看提示,这个感觉做不出来。

分析:

  1. 要得到字符串,其实是要得字符串的ASCII码,十进制数码字符对应的ASCII码=十进制数码值+30H(可观察ASCII码表得出)。
  2. 如何得到十进制数码值?利用上上面的除法不断相除,没出的余数就是每位的值。
  3. 对于2这里有一个问题,除多少次?除到商为0,用jcxz指令就可以完成。

无能嘤嘤……

这个地方卡了一周(太难了,不想做,做一半,想不出,不想。重新开始想,圆原地踏步).使用8位除法得到余数,排列重新求余,B站找了别人的代码。你们感受一下。

和题意有点区别,代码我都注释了,看不懂的看我注释。

看不清的请移步个人博客

总结

个人状态有点问题,最后一部分思考偏了,我本人很同一往一个地方深挖,后来明白是挖不完的,适度就好。想不通了不妨换个方向。

文;伍默

排版;喵喵

正文共:6167 字 21 图

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 渗透云笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 操作符offset
  • jmp指令
  • 根据位移进行转移的jmp指令
  • 转移的目的地址在指令中的jmp指令
  • 转移地址在寄存器中的jmp指令
  • 转移地址在内存中的jmp指令
  • jcxz指令
  • loop指令
  • 使用位移转移的意义
  • 编译器对转移位移超界的检测
  • 实验八
  • CALL和RET指令
    • ret和retf
      • call指令
        • 依据位移进行转移的call指令
          • 转移的目的地址在指令中的call指令
            • 转移地址在寄存器中的call指令
              • 转移地址在内存中的call指令
                • call和ret的配合使用
                  • mul指令
                    • 模块化程序设计
                      • 参数和结果传递的问题
                        • 批量数据的传递
                          • call指令给我们的启发
                            • 寄存器冲突的问题
                              • 实验10
                                • 实现字符串
                                • 解决除法溢出问题
                                • 数值显示
                            • 总结
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档