第一章
内存地址空间的地址段分配
- 地址:0~7FFFH的32kb空间为主随机存储器的地址空间
- 地址:8000~9FFFH的8kb空间为显存地址空间
- 地址:A000~FFFFH的24kb空间为各个rom的地址空间
第二章
进入DOS模式
- 重新启动计算机,进入DOS模式,此时进入的是实模式的DOS
- 在Windows中进入DOS方式,此时进入的是虚拟8086模式的DOS
使用命令
- -R 查看寄存器内容
- -R 寄存器 可以修改寄存器的内容
- -D查看内存中的内容
- –D 段地址:偏移地址 输入后会从该物理地址处开始,显示128个内存单元内容,
- 使用了一次D命令后,接着使用时,会显示接下来的内存地址中的内容
- 指定D命令查看的范围 D 段地址:其实偏移地址 结尾偏移地址
- 查看某一内存单元内容 D 段地址:偏移地址 0
- E命令改写内存中的内容
- E 段地址:偏移地址 数据(用空格段隔)
- E 段地址:偏移地址 enter 数据 空格 数据… 输入完成回车就可以停止修改内存数据
- 不能直接写入汇编指令,但是可以以十六进制的形式写入数据
- U命令查看内存中机器码的含义
- U 段地址:偏移地址 格式就可以查看内存中的指令
- T命令执行内存中的机器码
- 执行的是当前SC:IP指向的内容,如果想要执行你指定的代码,需要先通过使用R命令来修改寄存器CS、IP中的值
- A命令写入汇编代码
- 命令中使用段寄存器 eg d ds:0
疑问:
- jmp ax指令
在书上说jmp ax指令相当于是 mov ip,ax 在实际的实践过程中发现的确是这个样子的,当IP改变,也就相当于是地址发生了变化,当前执行的指令就指向了[ax]
- 下面3条指令执行后,CPU几次修改IP?都在什么时候?最后IP值是多少?
mov ax,bx ;1,4
sub ax,ax ;2
jmp ax ;3
要知道CPU修改几次IP就要先明白CPU什么时候会修改IP,CPU是在将指令读取到缓冲区的时候改变IP的,而不是执行一条指令而改变IP的。 所以COU修改4次IP,修改顺序在注释中列出了,最后IP值为0
第三章
关于栈:
- LIFO(Last In First Out,先进先出)
- CS:IP中存放当前指令的段地址和偏移地址
- SS:SP 任意时刻指向栈顶元素
- 8086CPU的入栈和出栈都是以字为单位
- 栈顶从高地址像低地址方向增长
- 栈空时,SS:SP指向栈空间最高地址单元的下一个单元
- push、pop等操作指令,修改的只是SP。所以栈顶的变化范围在0000~FFFFH
push ax执行过程
- SP=SP-2 ,SS:SP 指向当前栈顶前面的单元,以当前栈顶前一个单元为新的栈顶
- 将ax中的内容送入SS:SP指向的内存单元处
pop ax 出栈执行过程
- 将SS:SP指向的内存单元的数据送入ax
- SP=SP+2
pop/push指令格式
push/pop 寄存器
push/pop 段寄存器
push/pop 内存单元(一个内存字单元)
关于段
- 对于数据段,默认段地址DS,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问
- 对于代码段,默认段地址CS,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令
- 对于栈段,默认段地址SS,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当做栈空间来用
- 一段内存,可以是代码的存储空间,可以是数据的存储空间,还可以是栈空间,也可以什么也不是。
一些概念
- 内存单元--在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放。
地址单元:将起始地址为N的字单元简称为N地址字单元
问题
如果将10000H~1FFFFH这段空间当做栈段,初始状态为空,此时SS=1000H,SP=?
SP = 0 不管怎么样,我们只要记住当栈为空时,栈顶是指向栈的最后一个内存字单元的下一个内存字单元的
关于寄存器
- 通用寄存器,ax,bx,cx,dx
- 段寄存器cs,ds,es,ss,段寄存器不能直接复制,需要通过其他寄存器作为辅助
- ip指针寄存器,使用Jmp指令可以指定修改他的值
第四章
伪指令
- 定义一个段
段名 segment
段名 ends
- end
汇编程序的结束标记
- assume
将有特定用途的段和相关的段寄存器关联起来,比如我们在使用 codesg segment . . . codesg ends 时(指定该段为代码段),需要先用assume来关联一下 eg assume cs:codesg
源程序与程序
源程序是指由指令和伪指令构成(伪指令由编译器处理)
程序是指源程序中最终由计算机执行、处理的指令或数据
程序返回
在一个程序执行完后,需要将CPU控制权限交还给CPU,不然被暂停了的程序不能被执行
mov ax,4c00H
int 21H
编译连接:
使用masm5.0 链接:http://pan.baidu.com/s/1gdg2Bwn 密码:os0e
编译
- 进入masm.exe的目录 cd 目录 (运行masm后,会先显示masm的版本信息,然后提示输入将要被编译的源程序文件的名称。如果要编译的文件名的后缀名为.asm,则只需要输入文件名就可以了,如果需要编译的文件的后缀名不是.asm就需要输入完整的文件名+后缀,同时,一定要输入文件的路径。)
- Object filename[*.obj]:这里你可以选择直接enter,不修改名字和obj文件的存储路径。当然你也可以修改。如果不修改文件名,默认适合你代码名一样,不修改路径名,默认新生成的obj文件是存储在masm.exe所在的路径中
- Source listing [NUL.LIST]: 提示输入列表文件名称,这个是编译器将源程序编译为目标文件的过程中产生的中间结果,可以忽略直接enter
- Cross-reference[NUL.CRF]:提示输入交叉引用文件名称,同样也是中间结果,可以直接忽略按enter
因为连接的方法是和编译的步骤一样。需要注意的也是一样,所以就不在重复。
提示:
- 如果出现unable to openfile 错误,最好的解决办法就是将你存放的路径名修改为英文的,简单一点。
- 在连接的时候会出现一个警告, No stack segment,在这里我们可以不理会这个错误
- 在编译连接的过程中,因为中间产物都是尅忽略掉的,所以,这里提供了一个小技巧,可以直接忽略掉中间产物,而不需要我们一步一步的输入,就是在命令后面添加;号 eg masm c:/1;
汇编程序从写成到执行过程
编程→1.asm→编译→1.obj→连接→1.exe→加载→内存中的文件→运行
(edit) masm link command CPU
DOS系统中.EXE文件中的程序的加载过程
- 找到一段起始地址为SA:0000(即起始地址的偏移地址为0的容量足够的空闲内存区
- 在这段内存区的钱256个字节中,创建一个称为段前缀(PSP)的数据区,DOS利用PSP来和被加载程序进行通信
- 从这段内存区的256字节处开始(在PSP后面),将程序状图,程序的地址呗设为SA+10H:0(为了区分PSP和程序,DOS将他们划分好不同的段。所以PSP区→SA:0 程序区→SA+10H:0
- 将该内存区的段地址存入ds中(ds=sa)后,设置cs:ip指向程序入口。
换个角度来看就是知道了DS就可以很好的定位到程序代码段了
注意
- 在使用DOS单步执行的时候,执行 int 21H需要使用P命令
- 退出debug使用Q命令
第五章
[bx]和内存单元
内存单元:
完整描述一个内存单元需要两种信息
- 内存单元的地址
- 内存单元的长度(类型)
eg:[0]
用[0]表示内存单元,0表示单元的偏移地址,段地址默认在ds中,单元长度(类型)可以由具体指令中的其他指令中的其他操作对象(比如寄存器)指出
[bx]:
同样表示一个内存单元,他的偏移地址在bx中,段地址默认在ds中
()符号:
用()表示一个寄存器或内存单元中的内容,()中的元素可以有3中
- 寄存器名
- 段寄存器名
- 内存单元
loop符号:
CPU在执行loop指令时,会有两个步骤
- (cx)=(cx)-1(简单的说就是用来存放循环次数)
- 判断cx中的值是否为0,如果不为0 转至标号处执行程序,如果为0向下继续执行(简单的就是为0 就不循环,不为了就继续循环)
loop实例:
计算2^12
assume cs:codesg
codesg segment
mov ax,2
mov cx,11
s: add ax,ax
loop s
mov ax,4c00H
int 21H
codesg ends
end
计算ffff:0006单元中的数乘以3 记过存储在dx中 11
assume cs:codesg
codesg segment
//这里写错了,把[]与()给弄混了,()只是一个描述性符号,在汇编指令中并不存在。[]符号,是表示间接寻址,是表示的偏移地址。需要和段地址来结合使用
// mov al,[ffff6] ;因为是ffff:0006单元,一个单元只占一个字节。所以需要用一个8位寄存器来存储。所以这里用到的是al
mov ax,ffff
mov ds,ax
mov ah,0 ;清零ah
mov cx,3
mov al,[6]
mov dx,0
s add dx,al
loop
mov ax,4c00H
int 21h
codesg ends
end
DeBug和汇编编译器masm对指令的不同处理
指令 | Debug | 汇编编译器 |
mov ax,[0] | mov ax,[0] | mov ax,0 |
从表中可以看出DeBug将[idata]解释为内存单元,idata就是内存单元的偏移地址,而编译器将[idata]解释为立即数,那么如何实现在汇编编译器中将内存单元中的数据送入寄存器中?
解决方法:
1.将偏移地址送入bx寄存器中,用[bx]的方式来访问内存单元
([bx]:默认偏段地址是ds)
eg:
mov ax,2000h
moc ds,ax
mov bx,0
mov al,[bx]
2.明确给出段地址
mov al,ds:[0]
3.前两者的结合
mov ds:[bx]
解决8位寄存器不够用问题
当将12个8位数放入dx中时,因为不能直接给dx赋值,因为运算对象类型不匹配,也不能通过直接用dl累加,因为会越界所以,可以用下面的方法解决这两个问题
mov al,[bx]
mov ah,0
add dx,ax
注意
- 在汇编源程序中,数据不能以字母开头,所以如果数据是A000H,则需要写成0A000H
- 在调试循环的时候,如果循环次数太多,可用p命令一次执行完整个循环。或者也可使用g命令,g命令格式是 g 偏移地址
- 段前缀: ds,cs,ss,es
- 运行在CPU实模式下的DOS,没有能力去对硬件系统进行全面、严格的管理
- DOS方式下,一般情况,0:200~0:2ff空间中没有系统或其他程序的数据或代码
第六章 包含多个段的程序
注意:
- 我们若要CPU从何处开始执行程序,只要在源程序中用"end 标号"指明
- assume cs:code 这样做后并不带表cs就指向了code,assume只是一个伪指令,是有编译器执行的,也是仅在源程序中的信息,CPU并不知道他们。assume只是用来定义的具有一定用途的段和相关寄存器联系起来
- 对于命名为code的段,它并不是被当初了code,而只是方便我们阅读
第七章 更灵活的定位内存地址的方法
指令:
- and指令,逻辑与指令,安位进行与运算(二同一为1)
- or指令,逻辑或指令,按位进行或运算(有1则为1)
大小写转换
因为大写字母和小写字母的差别在于第6位,第6位如果为0则是大写,第六位如果为1则是小写,那么大小写转换的时候,就只需要控制好第6位就可以了。
转换成大写,即第六位为0 and 11011111
转换成小写,即第六位为1 or 00100000
[bx+idata]
- [bx+0],[bx+2]…
- 0[bx],2[bx]
上下两个是等价的
si,di
- si,di是8086CPU中和bx功能相近的寄存器,si,di不能够分成两个8位寄存器来使用。
[bx+si],[bx+di]
- [bx+si]
- [bx][si]
以上两条指令等价
[bx+si/di+idata]
- mov ax,[bx+200+si]
- mov ax,200[bx][si]
- mov ax,[bx].200[si]
- mov ax,[bx][si].200
注意:
- 不存在mov [di],[si]这样的指令,只能通过ax,等来作为中间的桥梁
- 使用双重循环是,注意cx的值的设置,可以用栈来保存数据,也可以用其他的地址来保存数据,但是最好使用第一种方法
第八章 数据处理的两个基本问题
reg:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
sreg:ds,ss,cs,es
内存单元寻址
- 只有bx,bp,si,di这四个寄存器有寻址功能,其他没有
- bx-bp,si-di是相对应的,良良不能同时使用
- 只要在[..]中填的是bp,那么段寄存器默认是ss
汇编指令 | 指令执行前数据的位置 |
mov bx,[0] | 内存,ds:0单元中 |
mov bx,ax | CPU内部,ax寄存器 |
mov bx,1 | CPU内部,指令缓冲器 |
指令执行前,它将要处理的数据所在位置
- CPU内部
- 内存
- 端口
div指令
- 除数:有8位和16位两种,在一个reg或内存单元中
- 被除数:默认放在ax或ax和dx中,如果除数为16位,被除数则为32位。在dx,ax中存放,dx存放高16位,ax存低16位 ,如果除数为8位,被除数为16位,默认放在ax中
- 结果:如果除数为8位,则al存商,ah存余,如果除数为16位,则ax存商,dx存余
第九章 转移指令的原理
8086CPU的转移指令分类
- 无条件转移指令(jmp)
- 条件转移指令
- 循环指令
- 过程
- 中断
offset指令:取得标号的偏移地址
jmp指令:无条件跳转指令,可以只修改ip,也可以同时修改ip和cs,在使用jmp的时候,需要给出下面两种信息
- 转移的目的地址
- 转移的距离(段间转移、段内短转移、段内近转移)
jmp short 标号(转到标号处执行指令) ip修改范围 -128~127 段内短转移
jmp near ptr 标号 -32768~32767 段内近转移
jmp far ptr 标号 段间转移,又称为远转移
jmp word ptr 内存地址单元
jmp dword ptr 内存单元地址 cs = (内存单元地址+2),ip=(内存单元地址)
jcxz指令:条件转移指令,所有的条件转移指令都是短转移
条件,如果cx=0,则跳转到标号处执行
loop指令
循环指令,所有的循环指令都为短指 cx=cx-1
实验9
assume cs:code,ds:data
data segment
db ‘Welcome to asm! ‘
data ends
code segment
start: mov ax,data
mov ds,ax
mov ax,0B800h
mov es,ax
mov bx,0
mov si,0
s: mov cx,16
mov al,[bx]
mov es:[si+720h],al //注意这里,写720h是因为显示的原因,前面第0行和第1行会被后面的覆盖掉,所以如果从0开始的,我们会看不到效果
mov al,01000010b
mov es:[si+721h],al
inc bx
add si,2
loop s
mov ax,4c00h
int 21h
code ends
end start
win7程序能够运行,但是不能显示效果,但是在xp下能够完美的显示
第十章 call和ret指令
ret:用栈中的数据,修改ip内容,从而实现近转移
- ip =ss*16+sp
- sp=sp+2
retf:硬栈中数据,修改cs和ip的内容,从而实现远转移
- (ip)=((ss)*16+(sp))
- sp=sp+2
- cs=ss*16+sp
- sp=sp+2
call
- 将当前的ip或cs和ip压入栈中
- 转移
call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理是相同的
call 标号
push ip
jmp near ptr 标号
call far ptr 标号
push cs
push ip
jmp far ptr 标号
转移地址在寄存器中的call指令
push ip
jmp 16位reg
cs:reg
换一个地址在内存中的call指令
push ip
jmp word ptr 内存单元地址
mul指令
- 两个乘数,要么都为8位(一个默认放在al中,另外一个放在8位Reg中或内存字节单元中),要么都为16位(一个默认放在ax中,一个默认放在16位Reg中或内存字单元中)
- 结果,如果是8位乘法,结果默认放在ax中,如果是16位乘法,默认高位放在dx中,低位放在ax中
第十一章 标识寄存器
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF | DF | IF | TF | SF | ZF | AF | PF | CF |
ZF标志
零标志位,记录相关指令执行后,结果是否为0,如果为0,ZF=1,如果不为0,ZF=0
PF标志
奇偶标志位,记录相关指令执行后,其结果的所有bit中1个个数是否为偶数位,如果为偶数,PF=1
SF标志
符号标志位,记录相关指令执行后,其结果是否为负,如果为负,SF=19(计算机中通常用补码来表示有符号数据)
我们在将数据当做有符号的数据来运算的时候,可以通过它来得知的值结果的正负
如果我们将数据当做无符号的数据来运算的时候,SF的值就没有意义了,虽然相关指令影响了它的值
CF标志
进位标志位,在进行无符号数运算的时候,他记录了运算结果的最高有效位向更高位的进位值,或从更高的借位值,相关执行执行后,进位CF=1,不进位CF=0
OF标志
溢出标志位,在有符号运算中,如果超出了寄存器所能存放的范围。
adc指令:带位加法指令
adc 操作对象1,操作对象2
操作对象1=操作对象1+操作对象2+CF
sbb指令带借位减法指令
sbb 操作对象1,操作对象2
操作对象1=操作对象1-操作对象2-CF
cmp指令:
比较指令,功能相当于减法指令,但是不保存结果,但是会影响标志寄存器
指令 | 含义 | 检测的相关标志位 |
je | 等于则转移 | ZF=1 |
jne | 不等于则转移 | ZF=0 |
jb | 低于则转移 | CF=1 |
jnb | 不低于则转移 | CF=0 |
ja | 高于则转移 | CF=0或ZF=0 |
jna | 不高于则转移 | CF=1或ZF=1 |
DF标志和串传送指令
方向标志位,控制每次操作后si,di的自增自减
DF=0 每次操作后,si,di自减
DF=1 每次操作后,si,di自增
DF指令配合串操作指令使用的
eg
movsb,将ds:si指向的内存单元中的字节送入es:di中,根据DF位,决定si,di是自增还是自减
pushf 和popf
pushf的作用是将标志级传奇的值压栈,而popf的作用是从栈中弹出数据送入标志寄存器