[huayang]
本文的学习资料完全取自于王爽—《汇编语言(第三版)》
也就相当于读书笔记,基本全文就是我择抄的重点(我认为的重点)和一些个人的理解
自我感觉勉强能够入门,请轻喷,也请别一天天大佬大佬的叫。
仅供复习使用!!!
推荐教学视频:https://www.bilibili.com/video/BV1mt411R7Xv?p=46
一些重要的东西
先看书后看这个
mov ax,123 //把123给ax
add ax,123 //ax=ax+123
sub ax,123 //ax=ax-123
jmp 2AE3:3 //CS=2AE3H,IP=0003H
push ax //将寄存器ax中的数据送入栈中
pop ax // 从栈顶取出数据送入ax
inc bx //bx=bx+1
dw 123h,0456h //定义字型数据
db 'flag' //定义单字节数据段 ,意思是编译时flag为数据而不是指令
mov al,0101110B
and al,1000110B
//al=0000110B //简单来说就是一假为假
mov al,0101110B
or al,1000110B
//al=1101110B //简单来说就是一真为真
8086 有 4 个段寄存器:CS、DS、SS、ES
通用寄存器ax,bx,cx,dx
CS 为代码段寄存器,IP 为指令指针寄存器
8086CPU 有 14 个寄存器,每个寄存器有一个名称。分别为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW
8086CPU 中的 DS 寄存器通常用来访问数据的地址段
mov al,[bx]也可以写为mov al,ds:[bx] 主要用来查看地址 //可查看《debug 和汇编编译器 masm 对指令的不同处理》图片
[address] 表示一个偏移地址为 address 的内存单元
任何时刻,SS:SP 指向栈顶元素
栈顶的地址存放在ss中,偏移地址存放在sp中
栈段,将它的段地址放在 SS 中
bx 中存放的数据作为一个偏移地址 EA,段地址 SA 默认在 ds 中
通常我们使用 loop 指令来执行循环功能,cx 存放循环次数
内存单元是字节单元(一个单元存放一个字节)
两个内存单元用来储存一个字,这两个单元可以看作为一个字单
segment //定义一个段的开始
ends //定义一个段的结束
assume cs:code //将用作代码段的段code和CPU中的段寄存器cs联系起来
end是一个汇编程序结束标记
end 标号 //指明CPU从何处开始执行程序(建议参照‘在代码段中使用数据’进行理解)
cs:[bx]是什么意思呢,cs是因为在cs的代码段里,assume cs:codesg定义的,[bx]偏移地址
mov ax,data将名称为data的段的段地址送入ax,其实就相当于送入ds的信息
mov ax,[bx] //(ax)=((ds)*16+(bx))
mov [bx],ax //((ds)*16+(bx))=(ax)
si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用
[bx+si]和[bx+di]含义相近
储存单元
8个bit组成一个Byte,也就是通常讲的一个字节。微型机储存器的存储单元可以储存一个Byte,及8个二进制单位。一个存储单元的储存器有128个储存单元,它可以储存128个Byte。
上面的是对于微型储存器
对于大容量的储存器一般还是用以下单位进行计算容量(以下用B来代表Byte)
1KB=1024B 1Mb=1024KB 1GB=1024MB 1TB=1024GB
CPU对储存器的读写
在计算机中专门有连接cpu和其他芯片的导线,通常称为总线,总线从逻辑上又分为三类,地址总线、控制总线和数据总线
地址总线
10根导线可以传送10位二进制数据,也就是2的10次方(2^10)
最小数位0,最大数为1023
一个CPU有N根地址导线,可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
地址总线的宽度决定了CPU的寻址能力
数据总线
CPU与内存或其他器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了CPU和外界的传输速度,8根数据总线一次可传送一个8位二进制数据(一个字节)
8086CPU的数据总线宽度为16
数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量
控制总线
控制总线的宽度决定了CPU对系统中其他器件的控制能力
检测点1.1
- 13
- 1024、0、1023
- 8192、1024
- 10^30、10^20、10^10
- 64、1、16、4
- 1、1、2、2、4
- 512、256
- 二进制
一些别的
2^4 //16B
2^5 //32B
2^6 //64B
2^10 //1KB
2^20 //1MB
2^30 //1GB
寄存器—2021.5.8(核心)
8086CPU有14个寄存器,每个寄存器有一个名称。分别为:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW
通用寄存器
8086CPU的所有寄存器都是16位的,可以存放两个字节。AX、BX、CX、DX这四个寄存器通常用来存放一般性的数据,被称为通用寄存器。
8086CPU的AX、BX、CX、DX这4个寄存器都可以分为两个可独立使用的8位寄存器来用:
- AX可分为AH和AL
- BX可分为BH和BL
- CX可分为CH和CL
- DX可分为DH和DL
以AX为例,8086CPU的16位寄存器分为两个8位寄存器的情况
AX的低8位构成了AL寄存器,高8位构成了AH寄存器。
AH和AL寄存器是可以独立使用的8位寄存器。
字在寄存器中的储存
- 字节:记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
- 字:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和地位字节
几条汇编指令
注:在写一条汇编指令或一个寄存器的名称时不区分大小写。
如:mov ax,18和MOV AX,18的含义相同;bx和BX的含义相同
思考:
注:存多少位数据则是2的几次方,比如:
十六进制可以存四位则是2^4
八进制可以存两位则是2^2
如过改为al或ah也是一样的
al或ah为8位寄存器只能放两位十六进制数据
所以也要把最高位1丢失,ax中的数据为:0058H
注意:操作对象的位数不一致不能传数据,如下面的就是错误的:
检测点2.1
要记住ax分为ah与al,ah与al分别代表前两位与后两位,后面的以此类推
位数只有四位多的要去掉
注意进制!!!
(1):
- F4A3H
- 31A3H
- 3123H
- 6246H
- 826CH
- 6246H
- 826CH
- 04D8H
- 0482H
- 6C82H
- D882H
- D888H
- D810H
- 6246H
(2):
mov ax,2
add ax,ax
add ax,ax
add ax,ax
物理地址
CPU访问内存单元时会给出内存单元地址,每一个内存单元都有唯一的地址,我们称其为物理地址
16位结构的cpu
8086是16位机
具有以下特点
- 运算器以此最多可以处理16位的数据
- 寄存器的最大宽度为16位
- 寄存器和运算器之间的通路为16位
8086CPU给出物理地址的方法
8086CPU有20位地址总线但8086CPU又是16位结构
所以8086CPU采用了一种内部用两个16位地址合成的方法来形成一个20位的物理地址
地址加法器
段地址 * 10H = 基础地址
基础地址 + 偏移地址(0~FFFFH) =物理地址
比如:
F230H * 10H + C8H = F23C8H
这里有个思考问题简单的说一下
物理地址 段地址 偏移地址
21F60H 1F00H 2F60H
怎么得来的呢
1F00H * 10H + 2F60H = 21F60H
1F000
2F60
21F60 //因为F为15加2进一位所以就是1F + 2 = 21
有点简单但一下子真没转过来
检测点2.2
- 00010H 、1000fH
- 0001H 、2000H
段寄存器
8086有4个段寄存器:CS、DS、SS、ES
CS和IP
CS为代码段寄存器,IP为指令指针寄存器
这段还是看书里的图比较好理解
总的来说
CPU将CS:IP所指向的内容全部当做指令来执行
- CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
- IP=IP+所读取指令的长度,从而指向下一条指令
- 执行指令。转到步骤1,重复这个过程。
内存中的一段信息曾被CPU执行过的话,它所在的内存单元必然被CS:IP指向过。
修改CS、IP的指令
ax等等的值可以用mov进行设置
但mov指令不能用于设置cs、ip的值
若想同时修改CS、IP的内容,可用形如“jmp段地址:偏移地址”
jmp 2AE3:3 //CS=2AE3H,IP=0003H,CPU将从2AE33H处读取指令
若想仅修改IP的内容,可用“jmp某一合法寄存器”的指令完成
jmp ax, 指令执行前:ax=1000H,CS=2000H,IP=0003H
指令执行后:ax=1000H,CS=2000H,IP=1000H
其实就是用寄存器中的值修改IP
jmp ax,就好似:mov IP,ax
注意!!!是好似没说真有mov IP,ax这样的指令
代码段
比如:
mov ax,0000 (B8 00 00)
add ax,0123H (05 23 01)
mov bx,ax (8B D8)
jmp bx (FF E3)
上面123B0H~123B9H这段内存是用来存放代码的,是一个代码段,段地址为123BH,长度为10个字节
对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将进行我们定义的的代码段中的指令
要使代码段中的指令被执行就必须要将CS:IP指向所定义的代码段中的第一条指令的首地址
x想要这段代码得到执行让CS=123BH、IP=0000H
检测点2.3
4
1.mov ax,bx 2.sub ax,ax 3.jmp ax 4.执行jmp ax(不懂看上面)
0
预备知识
Debug
需要windows虚拟机一台
常用debug功能:
- -r 查看改变cpu寄存器的内容
- -d 查看内存中的内容
- -e 改写内存中的内容
- -u 将内存中的机器指令翻译成汇编指令
- -t 执行一条机器指令
- -p 一次执行完
- -a 以汇编指令的格式在内存中写入一条机械指令
- -g 直接从一个地址进行跟踪
- -q 退出debug
r命令
若想改变一个寄存器的值,比如ax的值可用r后面接寄存器名进行修改
我们想把ax改为1234
首先r ax回车,会出现如下页面
在冒号后面输入想要修改的数值,比如这里我们修改为1234,然后再用r即可查看
当然r也可以单独修改CS和IP
d命令
我们想要知道内存1000H处的内容,可以用“d 段地址:偏移地址”
如果紧接着再用d即可看见后续的内容
如过想查看内存单元
e命令
比如要将内存1000:0~1000:9单元中的内容分别写为0、1、2、3、4、5、6、7、8、9
如下图
然后用d 1000:0查看一下
如果直接输入e 1000:0回车则是以提问的方式进行输入
u命令
t命令
e命令写入机械码完后要用t命令进行执行
还可以继续用t继续执行命令
a命令
按两下回车推出编辑x
g命令
一直执行到这里
然后用t继续进行跟踪
寄存器(内存访问)
内存中字的储存
在内存中储存时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,低字节存放在低地址单元,高字节存放在高地址单元
两个内存单元用来储存一个字,这两个单元可以看作为一个字单元
比如
上图0、1为一个字单元,这里我们就称为0号单元0为低字节单元,1为高字节单元
0号单元的地址就为4E20H
0为低位,1为高位,后面2为低位,3为高位以此类推(这里说的是上图)
(这里可能有点绕)
任何两个地址连续的内存单元,N号单元和N+1号单元,可以将他们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元
DS和[address]
8086CPU中的DS寄存器通常用来访问数据的地址段
mov也可以将一个内存单元的内容送入一个寄存器
用mov指令访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中
[address]表示一个偏移地址为address的内存单元
mov al,[0]
[0]表示一个内存单元,里面的0表示内存单元的偏移地址
只有偏移地址是不能定位一个内存单元的。
又点疑惑就看看这个
字的传送
偏移0
偏移1
以此类推
回头再把内存中字的储存看熟
mov、add、sub
- mov 寄存器,数据 比如:mov ax,8
- mov 寄存器,寄存器 比如:mov ax,bx
- mov 寄存器,内存单元 比如:mov ax,[0]
- mov 内存单元,寄存器 比如:mov [0],ax
- mov 段寄存器,寄存器 比如:mov ds,ax
- add 寄存器,数据 比如:add ax,8
- add 寄存器,寄存器 比如:add ax,bx
- add 寄存器,内存单元 比如:add ax,[0]
- add 内存单元,寄存器 比如:add[0],ax
- sub 寄存器,数据 比如:sub ax,9
- sub 寄存器,寄存器 比如:sub ax,bx
- sub 寄存器,内存单元 比如:sub ax,[0]
- sub 内存单元,寄存器 比如:sub [0],ax
mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。
数据段
将一组长度为N(N<=64KB)、地址连续、起始地址为16的倍数的内存单元当做专门储存数据的内存空间,从而定义了一个数据段
对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问
上面的理解了这个不难
检测点3.1
是看下面的那段
2662H
E626H
E626H
2662H
D6E6H
FD48H
2C14H
0000H
00E6H
0000H
0026H
000CH
栈
栈是一种具有特殊的访问方式的储存空间,特殊性就在于最后进入这个空间的数据最先出去
CPU提供的栈机制
基于8086CPU编程的时候可以将一段内存当做栈来使用
PUSH(入栈)POP(出栈)
任何时刻,SS:SP 指向栈顶元素
任何时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址
栈顶超界的问题
当栈满的时候在使用push指令入栈,或栈空的时候在使用pop指令出栈都将发生栈顶超界的问题
8086CPU只考虑当前的情况:当前的栈在何处、当前要执行的指令是哪一条
push、pop指令
- push 寄存器 //将一个寄存器中的数据入栈
- pop 寄存器 //出栈,用一个寄存器接收出栈的数据
- push 段寄存器 //将一个段寄存器中的数据入栈
- pop 段寄存器 //出栈,用一个段寄存器接收出栈的数据
- push 内存单元 //将一个内存单元处的字入栈(注意:栈操作都是以字为单位)
- pop 内存单元 //出栈,用一个内存单元接收出栈的数据
比如:
- mov ax,1000H
- mov ds,ax //内存单元的段地址要放在ds中
- push [0] //将1000:0处的字压如栈中
- pop [2] //出栈,出栈的数据送入1000:2处
栈段
长度为N(N<=64KB)的一组连续、起始地址为16的倍数的内存单元,当做栈空间来用,从而定义一个栈段
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当做栈空间来用
要想使得如push、pop等栈操作命令访问我们定义的栈段就要将SS:sp指向我们定义的栈段
一个栈段最大容量为64KB
第一个程序
终于度过了漫长枯燥的大理论时间了
动手操作才能学得更快
源程序
伪指令
汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。
汇编指令是有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
伪指令没有对应的机器指令,最终不被CPU所执行
伪指令是由编译器来执行的指令
segment和ends是一对成对使用的伪指令
segment //定义一个段的开始
ends //定义一个段的结束
一个汇编程序是由多个段组成,这些段被用来存放代码、数据、或当做栈空间来使用。
end
注意这里的end不要和上面的ends搞混了
end是一个汇编程序结束标记
我们在写程序的时候要在结尾处加上伪指令end,否则在编译的时候无法知道程序在哪里结束
assume
assume cs:code //将用作代码段的段code和CPU中的段寄存器cs联系起来
这个指令有点麻烦,以后复习的时候再来概括
源程序中的”程序“
源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据称为程序
标号
程序的结构
2^3运算程序
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
程序返回
一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们成这个过程为:程序返回
mov ax,4c00H
int 21H
这两条指令所实现的功能就是程序返回
这里暂时不必去理解这两天指令
语法错误和逻辑错误
上面我们写的运算程序没有返回。当然这个错误在编译的时候不会表现出来
也就是说这个程序对编译器来说是正确的的程序
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
abc ends
end
程序发生语法错误很容易被发现,因为变异的时候会出错
而逻辑错误则不容易发现
我们把上面的逻辑错误更正过来
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H
int 21H
abc ends
end
编译&连接
工具在已给出
分别会用到编译工具(masm)和连接工具(link)
首先去win-xp把文件后缀打开
在工具目录下创建一个1.asm文件
最好把工具都搞到一起免得麻烦
写入代码
cd进工具目录
使用masm进行编译
masm 文件名
注:这里文件名要注意一下,一定要是tab出来或是一个一个打出来的才行,拖进去的无法执行
一直回车就行,会发现目录下多出个OBJ文件
然后使用link进行连接
link.exe obg文件
注:这里文件名要注意一下,一定要是tab出来或是一个一个打出来的才行,拖进去的无法执行
然后同样一直回车就行
出来了
我们运行1.exe竟发现任何结果,程序当然是运行了的,只不过我们程序根本没有向显示器输出任何信息
输出信息后面会讲
谁将可执行文件中的程序中的程序装载进入内存并使它运行
程序执行过程的跟踪
可以用Debug来进行跟踪一个程序的运行过程。
对于简单的错误仔细检查源程序即可发现,对于隐藏较深的错误就必须对程序的执行过程进行跟踪分析才容易发现
下面以1.exe为列说说如何用debug对程序执行过程进行跟踪
具体操作
用r看看各个寄存器的情况
dos系统中exe文件中的程序的加载过程有点难
这里我们要知道一个东西叫psp
有点晦涩难懂,我则抄了一些网上的东西方便大家进一步理解
原文地址:https://blog.csdn.net/sinat_34938176/article/details/78077022
物理地址=段地址×10H+偏移地址
明白了吧?PSP区的物理地址就是SA×10H,程序区的物理地址就是(SA+10H)×10H,即SA×10H+100H,刚好比PSP高了100H(即256)个字节。其实就是把偏移地址本来应该负责的100H的偏移量转移到了段地址上面,这样就能尽可能扩充程序区的大小了。
所以我们可以看出来,其实上面这个公式相当重要,它贯穿了全书,在不同的章节看到它都会有不同的体会,掌握它也会让我们更加容易地理解很多问题。
用t我们可以清晰的看见执行的结果
注意看ax
我这里多执行了一步,退出来重新搞
当执行到int 21的时候执行p命令
注意这里必须要使用p命令执行int 21,至于为什么书上
这里说一下-p命令和t命令的区别
p命令是全部执行完,t命令是一个一个的执行
显示这个表示程序正常结束
所以程序加载顺序为:command加载debug,debug加载1.exe。返回的顺序是:从1.exe中的程序返回到debug,从debug返回到command
实验3 编程、编译、连接、跟踪
(1)看上面
(2)这题要把CPU提供的栈机制完全搞懂才好做
assume cs:codesg
codesg segment
mov ax, 2000h ;把2000h地址上的值给ax
mov ss, ax ;令2000h为段地址
mov sp, 0h ;栈长为0
add sp, 10h ;栈长为16
pop ax ;弹出到ax,然后sp = 10H + 2 = 12H
pop bx ;弹出到bx 然后sp = 12H + 2 = 14H
push ax ;入栈 然后sp = 14H - 2 = 12H
push bx ;入栈 然后sp = 12H - 2 = 10H
pop ax ;出栈 然后sp = 10H + 2 = 12H
pop bx ;出栈 然后sp = 12H + 2 = 14H
;后面的语句就实现了交换
mov ax, 4c00h
int 21h
codesg ends
end
我们来说说第五行那个怎么来的
pop ax //sp = 10H + 2 = 12H
ss为栈的段地址,sp为栈的偏移地址
因为ax=2000H,pop弹出了两个字节多了两个空位所以sp向下偏移两位,下加上减
感觉这理解有问题,后续复习的时候再想想
(3)着重看看上面关于psp的问题
[BX]和loop指令
[bx]
mov ax,[bx]
bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA处的数据送入ax中,即:
(ax)=((ds)*16+(bx))
mov [bx],ax
bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处,即:
((ds)*16+(bx))=(ax)
问题
这里不是很懂,后面会写
Loop指令
loop指令的执行格式是:loop 标号
CPU执行loop指令的时候进行如下两步的操作
- (cx)=(cx)-1
- 判断cx中的值
通常我们使用loop指令来执行循环功能,cx存放循环次数
我们来看看下列代码
assume cd:code
code segment
mov ax,2
mov cx,11
s:add ax,ax
loop s
mov ax,4c00h
int 21h
code ends
end
总的来说loop s其实就是条件,s:add ax,ax则为触发条件要执行的指令
在debug中跟踪用loop指令实现的循环程序
assume cs:codesg
codesg segment
start:
mov ax,0ffffh//编译器要求数字不能以ABCDEF开头,需加上0
mov ds,ax
mov bx,6h
mov dx,0h
mov al,[bx]
mov ah,0h //mov ax,[bx]是否可以替代
mov cx,3h //非2h,loop指令会先-1再判断
s: add dx,al
loop s
mov ax,4c00h
int 21h
codesg ends
end start
注意程序中的第一条指令 mov ax,0ffffh。大于9fffh的十六进制数据A000H、A001H、FFFFH等在书写的时候都是以字母开头的
而在汇编源程序中,数据不能以字母开头所以要在前面加上0
比如9138h可以直接写为9138h,而A000h在汇编中要写为0A000h。
这里不好写得用t慢慢看就行了
debug和汇编编译器masm对指令的不同处理
在debug使用类似指令:
mov ax,[0]
表达的意思是将ds:0处的数据送入到ax中
但是在汇编源程序中,指令’mov ax,[0]’会被当做‘mov ax,0’执行
在汇编源程序中,如果用指令访问一个内存单元,则在指令中必须用[…]表示内存单元
如果在[]里用一个常量idata直接给出内存单元的偏移地址,就要在[]的前面显式地给出段地址所在的寄存器
比如:
mov al,ds:[0]
如果在[]里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中,当然也可以显式地给出段地址所在的寄存器
loop和bx的联合应用
把ffff:0~ffff:b中的8位数据累加到16位寄存器dx中我们有如下几种方法
- (dx)=(dx)+内存中的8位数据
- (dl)=(dl)+内存中的8位数据
第一个方法中的问题是两个运算对象的类型不匹配,第二种结果则有可能超界
目前有一种方法就是得用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,从而使两个运算对象的类型匹配并且结果不会超界
这样写固然很麻烦,所以我们用到loop
难度不高,inc bx 看做是bx++就行了
段前缀
指令mov ax,[bx]中,内存单元的偏移地址由bx给出,而段地址默认在ds中。
用于显式地指明内存单元的段地址的ds、 c s、ss、 es,在汇编语言中被称为段前缀
一段安全的空间
汇编源程序中的指令mov ds:[26h],ax和debug中的汇编指令mov [0026],ax同义
dos方法下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码
段前缀的使用
优化后
实验4 bx和loop的使用
1.2题
assume cs:codes
codes segment
mov ax,20h
mov ds,ax
mov cx,64
mov bx,0
s: mov ds:[bx],bl
inc bx
loop s
mov ax,4c00h
int 21h
codes ends
end
包含多个段的程序
在代码段中使用数据
dw 123h,0456h //定义字型数据
assume cs :code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
mov ax,0
mov bx,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00H
int 21H
code ends
end
assume cs :code
code segment
dw 0123H,0456H,0789H,0ABCH,0DEFH,0FEDH,0CBAH,0987H
start: mov ax,0
mov bx,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00H
int 21H
code ends
end start
注意对比上下两段代码
在程序的第一条指令前面加上一个标号start,而这个标号在伪指令end的后面出现
end除了通知编译器程序技术外,还可以通知编译器程序的入口在什么地方
也就是说‘mov bx,0’是程序的第一条指令
我们若要知道CPU从何处开始执行程序只需要在‘end 标号’指明就可以了
我们可以这样安排框架:
assume cs:code
code segment
:
:
数据
:
:
start:
:
:
代码
:
:
code ends
end start
在代码段中使用栈
利用栈将程序中定义的数据逆序存放
assume cs:codesg
codesg segment
dw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
?
codesg ends
end
解题
assume cs:codeseg
codeseg segment
dw 0123H,0456H,0789H,0abcH,0defH,0fdeH,0cbaH,0987H
dw 0,0,0,0,0,0,0,0 ;用dw来定义8个字型数据,在程序加载后,
;将取得8个字的内存空间,存放这8个数据,在后面的
;程序中,将这段空间作为栈来使用
start: mov ax,cs
mov ss,ax
mov sp,32 ;设置栈顶指向cs:32
mov bx,0 ;初始化bx
mov cx,8
s: push cs:[bx]
add bx,2
loop s ;以上代码将0~15单元中的数据依次压栈
mov bx,0
mov cx,8
s0: pop cs:[bx]
add bx,2
loop s0 ;以上代码将0~15单元中的数据依次出栈
codeseg ends
end start
这里需要借助视频进行理解《10.2 在代码段中安排自己定义的栈空间》
检测点6.1
下面程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h # 定义数据
start: mov ax,0
mov ds,ax # 定义内存起始段
mov bx,0 # 定义内存偏移
mov cx,8 # 定义循环次数
s: mov ax,[bx] # 将ds:[bx]内存单元中的数据写入ax寄存器
mov cs:[bx],ax # 将ax寄存器中的数据写入cs:[bx]代码单元
add bx,2 # 相邻两个字节单元为一个字单元,因此偏移2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
(2)下面的程序实现依次用内存0:0~0:15单元中的内容改写程序中的数据,数据的传送用栈来进行
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 定义数据
dw 0,0,0,0,0,0,0,0,0,0 ; 10个字单元用作栈空间
start: mov ax,cs ; 定义代码段
mov ss,ax ; 定义栈段
mov sp,36h ; 定义栈底为36h (8*2 + 10*2)
mov ax,0
mov ds,ax ; 定义内存起始段
mov bx,0 ; 定义内存偏移
mov cx,8 ; 定义循环次数
s: push ds:[bx] ; 将ds:[bx]内存单元中的数据写入栈单元ss:sp(sp=36h) -> sp=36h - 2h = 34h
pop cs:[bx] ; 将ss:sp(sp=34h)为栈顶的字单元中的数据写入cs:[bx]中 -> sp=34h + 2h = 36h
add bx,2 ; 相邻两个字节单元为一个字单元,因此偏移2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
将数据、代码、栈放入不同的段
我们应该考虑用多个段来存放数据、代码和栈
我们用个定义代码段一样的方法来定义多个段,然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0 ;8个字节用于缓存
stack ends
code segment
start:
mov ax,stack ;mov ss,cs,不能这么写
mov ss,ax
mov sp,16 ;巨坑!!!,单独有一个栈段后,sp的值应重新考虑
mov ax,data
mov ds,ax
mov bx,0
mov cx,8
s:push [bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop [bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
code ends
end start
mov ax,data将名称为data的段的段地址送入ax
一个段中的数据的段地址可由段名代表,偏移地址就要看他在段中的位置了
程序中‘data’段中的数据‘0abch’的地址是:data:6。要将他送入bx中,代码如下:
mov ax,data
mov ds,ax
mov bx,ds:[6]
这样写则是错误的:
mov ds,data
mov bx,ds:[6]
mov ds,data指令是错误的,因为8086CPU不允许将一个数值直接送入段寄存器
多看几遍,有点晦涩难懂
实验5编写、调试具有多个段的程序
一、
assume cs:code, ds:data, ss:stack
data segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
data ends
stack segment
dw 0, 0, 0, 0, 0, 0, 0, 0
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 16 ;ss:sp stack
mov ax, data
mov ds, ax ;ds data
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax, 4c00h
int 21h
code ends
end start
(1)0123, 0456, 0789, 0abc, 0def, 0fed, 0cba, 0987
(2)CS=0B3F 、SS=0B3E、DS=0B3D(这题每个人的都不一样,以自己的为准)
(3)X-2,X-1
二、
assume cs:code, ds:data, ss:stack
data segment
dw 0123h, 0456h
data ends
stack segment
dw 0, 0
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 16 ;ss:sp stack
mov ax, data
mov ds, ax
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax, 4c00h
int 21h
code ends
end start
(1)0123、0456
(2)cs=0b3f ss=0b3e ds=0b3d
(3)X-2,X-1
后面的再来分析
建议看看这篇文章:汇编实验五 编写,调试具有多个段的程序
更灵活的定位内存地址的方法
and和or指令
and
mov al,0101110B
and al,1000110B
//al=0000110B
or
mov al,0101110B
or al,1000110B
//al=1101110B
以字符形式给出的数据
在汇编程序中可以用’…’的方式指明数据是以字符的形式给出的,编译器会转换为对应的ascii码
assume cs:code,ds:data
data segment
db 'unIX'
db 'foRK'
data ends
code segment
start:
mov ax,'a'
mov bx,'b'
mov ax,4c00h
int 21h
code ends
end start
在上面程序中db ‘unIX’ 相当于 db 75H,6EH,49H,58H(u,n,I,X)是16进制的ascii码
大小写转换的问题
我们先来看看同一个字母的大写和小写有什么规律
可以看到小写字母的ascii码值比大写字母的ascii码值打了20h
所以最终的规律就是:大写字母+20H=小写字母,小写字母-20H=大写字母
我们要如何判断是大小写呢
一个字母不管它原来是大写还是小写,它的第5位置是0他就必将是大写字母(从右到左)
同理它的第5位置是1他就必将是小写字母
用or和and指令的完整程序
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax ;设置ds指向datasg段
mov bx,0 ;设置(bx)=0,ds:bx指向BaSiC的第一个字母
mov cx,5 ;设置循环次数5,因为BaSiC有5个字母
s:mov al,[bx] ;将ASCII码从ds:bx所指向的单元取出
and al,11011111B ;将al中的ASCII码的第5位置变为0,
mov [bx],al ;将转变后的ASCII码写回原单位
inc bx ;(bx)加1,ds:bx指向下一个字母
ioop s
mov bx,5 ;设置(bx)=5,ds:bx指向iNfOrMaTiOn的一个字母
mov cx,11 ;设置循环次数11,因为iNfOrMaTiOn有11个字母
s0:mov al,[bx]
or al,00100000B ;将al的ASCII码的第5位置为1,变为小写字母
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
[bx+idata]
mov ax,[bx+200]的含义:
(ax)=((ds)*16+(bx)+200)
也可以写作下面的格式:
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
用[bx+idata]的方式进行数组的处理
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'MinIX'
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
S:mov al,[bx]
and al,11011111b
mov [bx],al
mov al,[a+bx]
or al,00100000b
mov [5+bx],al
inc bx
loop s
codesg ends
end start
理解这段代码即可
SI和DI
si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用
[bx+si]和[bx+di]
[bx+si]和[bx+di]含义相近
mov ax,[bx+si]的含义如下:
(ax)=((ds)*16+(bx)+(si))
也可以写成如下格式:
mov ax,[bx][si]
不同的寻址方式的灵活应用
[idata]用一个常量来表示地址,可用于直接定位一个内存单元
[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元
[bx+idata]用一个变量和常量表示地址,可在一个其起始地址的基础上用变量间接定位一个内存单元
[bx+si]用两个变量表示地址
[bx+si+idata]用两个变量和一个常量表示地址
将datasg段中每个单词的头一个字母改变为大写字母
assume cs:codesg,ds:datasg
datasg segment
db '1. file '
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax
mov cx,6
mov bx,0
mov si,0
mov dx,0
s:
mov dl,[bx + si + 3] ;这里操作的是字节,借助dl保存
and dl,11011111B
mov [bx + si + 3],dl
add si,10h
loop s
mov ax,4c00h
int 21h
codesg ends
end start
本题的难点在于在只有cx一个循环计数器的情况下如何实现双重循环。这里可以把cx中的值暂时保存在内存中再取出
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
dw 0
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax
mov bx,0 ;行
mov cx,4
s:
mov ds:[40h],cx
mov si,0 ;列
mov cx,3
s0:
mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s0
add bx,10h
mov cx,ds:[40h]
loop s
mov ax,4c00h
int 21h
codesg ends
end start
理解即可
[/huayang]
2021.5.25
(第七章已正式完结,因比赛后面的已经没那么重要了,咱们0day见)
FROM:浅浅淡淡[hellohy]
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论