内存分段
一丶分段(汇编指令分段)
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 segmentSTART: 在这里首次提出了标号的概念,就相当于C语言的Goto语句,可以定义标号 ...你的汇编代码MyCode segmentend 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