16位汇编第三讲 分段存储管理思想

Posted 王思聪6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了16位汇编第三讲 分段存储管理思想相关的知识,希望对你有一定的参考价值。

 

      内存分段

一丶分段(汇编指令分段)

1.为什么分段?

  因为分段是为了更好的管理数据和代码,就好比C语言为什么会有内存4区一样,否则汇编代码都写在一起了,执行的话虽然能执行,但是代码多了,数据多了,搞不清什么是代码

什么是数据了.

汇编分段代码

 1 e 1000:0 "Hello$" 首先给1000:0的物理地址写入Hello字符串
 2 d 1000:0  显示一下是否显示成功
 3 
 4 mov ax,1000        给ax赋值数据,下面要分段了,所以需要给ax赋值
 5 mov ds,ax          开始分段(分配数据段),把ax的值给段寄存器ds,可能有人会说,ds也是段寄存器,为什么不直接写 mov ds,1000, 这里因为是cs ds ss es等段寄存器是后面出来的,数据线没有连接他们,所以通过地址加法器先给ax赋值,再给ds...赋值
 6 mov es,ax          (分配附加段)ax的值也给es赋值(ds和es一般都是相同段地址)
 7 mov ax,2000        给ax赋值2000
 8 mov ss,ax          给ss段寄存器赋值2000 (分配栈段)
 9 mov dx,0           给dx赋值字符串的偏移 (因为在指令字典中,dx是字符串的首地址的偏移,但是他是和ds数据段连用的,所以ds已经改为了1000,而1000*16 + 0偏移就是字符串的首地址,所以直接给即可)
10 mov ah,9           调用显示hello,给参数9
11 int 21             系统调用(调用API)
12 mov ax,4c00        退出指令,给ax
13 int 21             系统调用(调用API)
14 ret                返回

指令图片,变为100偏移处了

注意一点,我们给mov dx,100的时候,其实是把100的偏移给dx,这样 ds内容的段地址是1000,dx是100, 他会联合起来去寻址,利用昨天的寻址公式找到物理地址, 1000 * 16 + 100 = 10100(实际物理地址)而实际物理地址就是字符串的首地址

所以下面调用可以正常显示hello了

但是我们如果写成 mov dx,[100] 那么就相当于对当前的物理地址取内容给dx, 变成了从100的偏移中取得内容给dx,dx的值就变味6548了,因为小端模式,所以65先读,又因为dx是16位寄存器,所以只能读取2个word,

那么这样寻址就会错误了,等价于他去寻找字符串的首地址变成了 1000 * 16 + 6548 = ???反正结果是不对了,就会出现各种各样的BUG

退出指令

mov ax,4c00 这个是操作系统提供的,用于退出汇编程序

如果不退出,ip的偏移就会出现错误,那么就可能随机的吧ip和cs联合寻找的物理地址当做代码段去执行,就会出现错误.所以直接退出.

int 21就是系统调用(也就是调用API)

二丶多个汇编程序变为一个汇编程序执行

想想以前,如果不能多人开发,那么就不会出现各种游戏和高级软件了.

怎么解决汇编程序多人开发

  上面说了,我们为了有效的区分代码,数据.我们分段了,但是多人开发,每个代码段怎么办,难道要规定好?

所以以前如果合并汇编程序,那么要修改代码段,然后修改偏移,最后让两个汇编程序执行到一起.

但是这样是有规律的,所以后来就出现了连接器 link(连接成Obj)link的作用就是专门修复段,还有修复段偏移的,达到两个程序就可以在一起都执行了

当然OBJ网上有开源的文件格式可以研究一下.

这样方法,叫做重定向,obj首次发明了出来,那么这个时候就有了连接的概念了,

obj最简单的文件格式

代码段 代码段长度

数据段 数据段长度

附加段 附加段长度

等等,当然可能更加详细.但是这样通过把另一个程序的段还有数据长度,都修改一下,就完成了两个汇编语言合并到一起就可以都执行了.

三丶编译器的出现

上面说的debug只是一个调试器,或者叫做翻译器

现在出现了一个编译器,编译器就规定了语法了,然后那个时候我们可以把我们的程序,按照编译器的语法,编译成汇编代码

比如分段

1.代码段

MyCode segment

  ....你的汇编代码

MyCode ends

那么这样就把代码段分好了(专门执行代码)(但是这样虽然分好了,但是永远不会执行)
因为CS和IP是确定代码执行的位置,显然我们这只是把段分好了,但是CS和Ip还没有修改,也不能修改,因为一开始就是默认的,怎么办,
所以现在在编译器中我们可以写成这样

MyCode segment
START: 在这里首次提出了标号的概念,就相当于C语言的Goto语句,可以定义标号
  ...你的汇编代码
MyCode segment
end START          这里有个end,代表了汇编程序结束, START代表跳转到START来执行我们的代码

 

2.分数据段

MyData segment

  db "helloworld$"  ;分号在编译器里面已经认为了是汇编代码的注视了,这里的db相当于是 #define byte,就是按字节定义,也可以写为 db 100 就是分配数据区为100

  ;dd        代表 #define Dword (4个字节)

  ;dw            代码 #define Word  定义两个字节的意思

MyData ends

 

3.分栈段

MyStack segment stack 这里后面要加个关键字,因为上面的地址是数据段,当我们压栈的时候,栈的方向是向上增长的(也就是压栈,然后数据不断的累积,压一个,那么数据就会向上增长,向低地址增长,那么就会把数据段给覆盖了,所以给个关键字,转换过来)

  ;db 100 dup(?) 这里我写的注释,意思就是 分配 100个字节, dup的意思就是是否初始化,给? 就是这个栈不初始化,(一般来说不会初始化的)

  ;db 100 dup(0) 这里就是分配了100个字节,都初始化为0

 org 64      这个意思就是当前的断寄存器分配64k,如果分配64k,那么在1MB的空间中,最多只能分配16个这样的段 org是贵求64k段

MyStack ends;     ends是结束

 四丶编译器

 编译器用微软独立开发的是 6.15版本,最后的版本,可以区第一课的连接中下载编译器

文件夹

 

其中 ml.exe是编译器

link.exe 是连接器,连接obj文件

edit 是微软以前的编辑器 (ALT+ F操作菜单,那时候没有键盘,TAB切换各个选项)

1.编译器的使用

  1.改名

    我们要使用编译器,第一步就是给编译器改个名字,为了不可vc++6.0自带的冲突,所以随便改一个

    这里我改成ml16.exe

  2.配置环境变量, 计算机 - > 属性- > 高级 - > 环境变量

打开属性

选择高级,然后选择环境变量

 

这里分为三步,第一步,复制ml编译器所在的文件夹路径,第二部点击环境变量的path,然后在最后面输入 ; 文件夹路径, 分号是结束上一个环境变量的语句,然后自己添加新的

第三不就是 ;文件夹路径即可. 确定 确定 确定.....

 输入自己编译器的名字测试是否完成

显示版本号完成

编译我们的汇编程序,编译我们的汇编程序,就要按照编译器的规范去写了.汇编文件的后缀名字是.asm

五丶第一个.asm程序 利用编译器分段,执行一个Hello

 

 1 MyData segment
 2 g_szHello db "HelloWorld$"   //这些是分数据段 还有个g_szHello标号,下面偏移的时候细说
 3 MyData ends
 4 
 5 MyStack segment stack
 6     org 64           //这些是分栈段
 7 MyStack ends
 8 
 9 MyCode segment
10 START:             //设置标号
11     mov ax,1234h
12     mov bx,1234h
13     ;因为分好段了,所以现在开始设置段寄存器
14     mov ax,MyData
15     mov ds,ax        //汇编代码分段,例如给ds分数据段,则可以直接给 MyData了,给栈分段,则直接可以给MyStack(当然这些段的名字都是自己定义的,自己随便定义主要是后面的关键字不要变即可)
16     mov es,ax
17     mov ax,MyStack
18     mov ss,ax
19     mov dx,offset g_szHello  //我们利用汇编分段的时候说过,以前是 mov dx,0 (代表了从 ds * 16 + 0的物理地址得出字符串的地址)现在有个标号的概念,我们可以利用关键字直接给标号了,这样就不用自己手写给地址了,大大的提升了开发的效率
20     mov ah,9h
21     int 21h
22     mov ax,4c00h        //退出汇编程序需要给的值
23     int 21h            //调用int 21h会看ax的值是否是4c00是就退出
24     ret
25 MyCode ends
26 end START

 编译出来是一个汇编写的可执行文件,也就是EXE这个可执行文件里面记录了各种段的信息,以及IP指令执行的位置(这也就是为什么通过exe文件格式,设计出来的入口函数,如果用Debug,你是没办法修改的)

EXE文件格式后面细讲,主要现在有个概念,就是EXE记录了段信息,各种寄存器的信息即可.

还需要注意,这里我们是按照编译器的规范写的第一个ASM程序,我们的数据都加上了h这种结束符号,因为从编译器开始就认为你给16进制就要给h了

比如mov ah,9 在debug里面就认为参数是9h, 而编译器认为虽然也是9,但是是10进制的9, 而且在编译器中,还可以写成二进制,八进制,10进制

比如  mov ah,9(debug的) ,在编译器可以写成 mov ah,1001b 在调用int 21一样调用

编译程序步骤

ml16 /c 文件名.asm

link 文件名.obj

(这里回车回车回车即可)

执行

三步走,第一步就是编译

第二步就是连接,连接的时候,我画了一个框框,因为光标会在这4个地方等待,直接回车 回车...即可.

第三步就是执行了

 六丶段超越

但是分段只是逻辑上的分段,比如你在代码段里面放数据,是一样可以执行的

比如上面的asm代码可以改成下面这样

 1 MyData segment
 2 g_szHello db "HelloWorld$"
 3 MyData ends                  //和上面一样分段
 4 
 5 MyStack segment stack
 6     org 64                   //给栈分配
 7 MyStack ends
 8 
 9 MyCode segment                //代码段
10 g_szNumber db "HelloWorldsssss$"     //我们要在START上面放数据,不然汇编程序会把数据当做代码执行,现在我们给了 一段数据
11 START:
12     mov ax,1234h
13     mov bx,1234h
14     ;因为分好段了,所以现在开始设置段寄存器
15     mov ax,MyCode              // 这个ax给的是代码段的段地址
16     mov ds,ax                //那么把ds数据段设置为代码段的位置,那么下面调用数据段的内容会从这里开始当做段基地址 * 16 + 偏移,找到数据内容 也就是 Helloworld sssss
17     mov es,ax
18     mov ax,MyStack
19     mov ss,ax
20     mov ax,cs:[0h]
21     mov dx, offset g_szNumber      //这里的dx 会把ds当做基地址,然后寻址找到Hellossss....... 所以说分段只是逻辑上的分段,现在数据段和逻辑段都重叠了
22     mov ah,9h
23     int 21h
24     mov ax,4c00h
25     int 21h 
26     ret
27 MyCode ends
28 end START

为什么要再举一个这样的例子,其实说以前主要是为了藏代码执行,就比如说你写个C语言程序,如果就是main函数对吧,(其实真正的入口点不是这个,不做简介,自己百度)

然后利用上面的手段,你会发现,我在main函数里面就写个return 0,但是程序一打开就是有很牛逼的界面,你说厉害不,其实最主要的就是,这种方法病毒程序都使用这种方法.

所以其实段只是逻辑的概念,比如C语言的内存4区,就是基于汇编的分段,C语言也可以在全局变量区执行代码,执行函数,有的是方法.只不过分段了只是为了更好的开发而已

真正底层这些都不会是问题的.

 执行结果:

 

段超越:

  什么是段超越,上面我们分段了,但是其实分段只是逻辑中的分段

  比如我们 mov dx,0 那么基地址就是 ds数据段,dx存的就是0偏移,然后通过寻址方法,找到物理地址所在的内存

  那么现在我们改成这样 dx的值不从ds数据段获取了

  改为 mov dx,CS:[0H] 代表了我们要从 CS代码段里面的0偏移处,取出的内容赋值给DX

比如

  CS的段基地址为 1000 :0  存放的数据为 1 2 3 4 5 6 7

  那么 mov dx,CS:[0H] 相当于 从CS数据段中的0偏移取出内容 给 dx,因为dx寄存器是16位,所以取出的内容是3412 dx的偏移就是3412

  我们也可以指定读取, mov dx,word ptr[0h]这个不是段超越,段超越是指定段读取,这个是默认从DS数据段中取出在0H位置处的两个字节的长度,给DX

  注意只要是从DS(数据段)取出的内容,都不是段超越

除了DS都是,默认的 mov dx,[0h] 则是在ds中取出数据,等价于 mov dx,DS:[0H]

七丶,8086的机器码寻址方式

这个比较着重要了,就是通过机器代码反汇编出来汇编代码
主要常用的有三种寻址方式
1.立即数寻址方式
2.寄存器寻址方式
3.存储器寻址方式
先介绍第一种,(第二种第三种,第四讲细讲)
第一种
比如我们写了一段汇编代码,反汇编的时候可以看出机器码
有的时候要通过机器码反汇编出来汇编代码
比如下面我写好了一个程序

 

1 <span style="font-size: 15px"><span style="font-size: 18pt"><img src="http://images2017.cnblogs.com/blog/1197364/201708/1197364-20170830005926796-1616123290.png" alt=""></span></span>

前边我们说过,每一条汇编指令对应一条机器码

上面从B83412去看

其中立即数寻址方式就是 ax后面的1234会按照小尾方式当做机器码存储

那么现在看的 B83412 其中3412就是操作数

B8是什么

B代表的是MOV指令

8转换成二进制是 1000B 我们推测可能是代表那个寄存器,最起码后边三位要代表寄存器

我们换一条指令,mov bx,1234看看有什么改变

我们发现变成了BB3412 前边知道了第一个B是mov指令的意思,3412是立即数

那么现在又多了一个B,我们变成二进制查看一下 

B 1011B 发现侯三给变成11了

那么我们利用e 指令,给指定位置写入二进制,看看能出来一个汇编指令吗 (e 地址 回车,然后输入第一个,空格则可以输入第二个地址,依次类推)

我们发现,我们写了一段二进制代码变成汇编代码成了 MOV CX,1234

9的二进制代码是 1001 代表的是CX

那么由此可以看出

8代表的是AX寄存器

9代表的是CX寄存器

B 代表的 BX寄存器

作业:

  求出 八位通用寄存器分别所代表的值, 包括低八位和高八位各个寄存器的值

  (AX BX CX DX SI DI SP BP     ah,al , bh,bl......)

 笔记代码连接:

链接:http://pan.baidu.com/s/1c2xVEBQ 密码:66cw

 

以上是关于16位汇编第三讲 分段存储管理思想的主要内容,如果未能解决你的问题,请参考以下文章

32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式

汇编知识扫盲之16位汇编跟32位汇编的保护模式以及汇编代码编写

逆向课程第三讲逆向中的优化方式,以及加减乘

C++反汇编第三讲,反汇编中识别继承关系,父类,子类,成员对象

汇编语言访问寄存器和内存篇---02

第四课-第三讲04_03_管理及IO重定向