汇编语言
将机器代码抽象成方便人类方便理解的符号
组成:
汇编指令、伪指令、符号
由于 汇编指令和机器指令关系密切,不同的指令集架构对应的汇编指令也有所差别,所以必须在某一种具体的指令集架构下进行学习。
以下的讨论基于x86架构 8086cpu
16位机器字长,20位地址线
由于地址线有20位,机器字长只有16位,所以一次能处理的地址只有16根,为此8086计算实际物理地址的方法是 高16位作为段地址,低16位作为偏移量,通过这种方式来描述物理地址。段地址*16+偏移地址=实际地址
可以简单的描述为 段地址:偏移地址
段 的含义:
一块连续的内存地址称为段,内存本身并没有将内存分段,而是cpu通过设置段地址和偏移地址来寻址,段本身只是一个概念
两点:
- 由于段为20位地址的高16位,所以段地址必定是16的倍数
- 由于偏移地址只有16位,所以一个段的最大长度为64kb
寄存器 :
8086cpu有16个寄存器,AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PWD
寄存器长度为16位
通用寄存器
AX,BX,CX,DX,用于存放一般数据,以AX为例,可以分为高字节AH 和低字节AL
如果用(AL+某个数)超过8位溢出的位会被舍弃,不会进位到AL
add al 255
段寄存器
CS,DS,SS,ES,存放的都是段地址
CS 存放的代码段地址 ,配合IP指令寄存器,CS:IP 就能确定指令的具体位置
8086cpu 刚启动时(复位或通电)CS=FFFFH ,IP=0000H
所以计算机执行的第一条指令的地址就是FFFF0H
当读取一条指令后 ip=ip+指令长度
DS 存放的数据段地址 ,DS :[x] 就能从数据段中找到位置,x是某个寄存器
辅助寄存器
SI和DI
si和di的功能和ax,bx类似,不同的是,他们不能拆分成单独8位寄存器使用
汇编指令:
mov ax 12 将12放到寄存器ax中
mov ah 12 将12放到寄存器ax中的高字节位置
mov ax bx 将bx的内容放到ax中
在debug 中 [10] 是如下解释的
mov ax [10] 将内存地址为 DS:10的字存到ax
mov ax [bx] 将内存地址为 DS:bx的字存到ax
在编译器中,[10]解释为10
add ax bx 等价于 ax=ax+bx 或 ax+=bx
sub ax bx 等价于 ax=ax-bx 或 ax-=bx
栈
栈操作:栈操作是以字长为单位的
任意时刻 ss:sp 指向栈顶元素
push ax sp=sp-2
pop bx sp=sp+2
跳转
jmp 段地址:偏移地址 同时设置cs和ip
jmp 偏移地址 只设置ip
dw abcdH,efghH,abcdH 定义字型数据,开辟内存空间
dw 0,0,0,0,0,0 分配6字节的空间
db 数据 定义字节型数据,和dw类似
db ‘abcdefgxx’
dd 定义双字型数据
为了表达更清晰:使用() 描述存储单元的内容
()中的内容只能是(寄存器名)或者(20位内存地址)
由于(内存地址)可能取的字或字节,具体取决于左值
约定用 idata表示常量比如 mov ax idata或 add mov bx [idata]
循环指令
s: …
…
loop s
当执行到 loop s 时 ,会 对cx-- ,判断cx是否大于0,是则跳转到s继续执行。
汇编代码要求
不能直接对段寄存器进行赋值
汇编程序源代码,数据不能以字母开头,所以地址FFFFH 要写成 0FFFFH
[常数] 表示 常数,除非使用 段地址:[常数] 才代表地址的内容
[寄存器] 表示 寄存器中的值
写代码前思考
运算的单位是字节还是字
运算是否会溢出
是否要经过转换再计算
汇编程序的基本结构
assume cs:code
code segment
start : …
…
…
…
code ends
end start
end除了告诉编译器一个段的结束,还能说明程序的入口在哪
将不同的内容放在不同的段中
使结构更加清晰,并且不易出错
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0
stack ends
code segment
start:…
…//可以使用每段的段名,当做段地址,比如 data stack code,相当于常量
//比如:
// mov ax,data
//mov ds,ax
…
code ends
end
实验结果
在win10中的dosbox 中运行 每个段的偏移若小于64k,则下个段的地址为上个段的段地址+1
栈的地址空间中,起始地址是 空间的最大地址,
汇编大小写转换
由于ascii码中 小写字母a的16进制表示 为 61H ,大写字母A 的十六进制表示为41H
6对应的二进制为0110,4对应的二进制为0100,
所以使用汇编进行大小写转换时,不需要判断字母是大写还是小写,只需要
转换大写 and al,01000000B
转换小写 or al,01100000B
访问内存地址的方法总结(在编译情形下)
段地址:内存地址
[常数] 比如[1aaa]
[寄存器] 比如[bx]
[寄存器+常量] 比如[bx+1000]
[寄存器+寄存器] 比如[bx+si] ,可以写成 [寄存器][寄存器]
[寄存器+寄存器+常数] 可以写成 常数 [ax] [bx] 或者 [ax].常数 [bx] 或者[ax] [bx].200
能作为内存地址的寄存器
在8086cpu上,只有 bx ,si ,di ,bp 能作为[…] 里的内容
或者他们的固定组合方式, bx和si,bx和di,bp和si,bp和di
而其他寄存器寻址,比如 [ax] 等都是非法的操作,
bp的段地址默认为ss
指令要处理的数据
1.寄存器中
2.内存中
3.cpu指令缓冲区中 比如 add ax,1
8086数据寻址方式
直接寻址 [idata]
寄存器间接寻址
[bx]
[bp]
[si]
[di]
寄存器相对寻址
[bx+idata] //用于结构体寻址,数组,二维数组
[bp+idata]
[si+idata]
[di+idata]
基址变址寻址
[bx+si] //二维数组
[bx+di]
[bp+si]
[bp+di]
基址变址相对寻址
[bx+si+idata] //二维数组 或者结构体成员有数组
[bx+di+idata]
[bp+si+idata]
[bp+di+idata]
8086指令指定的数据长度
1.通过寄存器指定
ax ,ah,al
2.使用符号指定
word ptr
byte ptr
比如 mov word ptr [0],[10]
mov byte ptr [2],[3]
3.push 和pop操作
push和pop只能进行字操作
div 除法指令
除数:有8位和16位,可以在内存或寄存器中
被除数:默认放在 ax或dx中,
如果除数是8位,则被除数为16位在ax中。
如果除数是16位,被除数则为32位,dx中存放高16位,ax中存放低16位
结果:如果除数为8位,AL作为除法的商,AH存放余数,
如果除数为16位,ax作为除法的商,dx存放除法的余数
div 指令的格式
div reg
div 内存单元 比如 div byte ptr [bx]
编译器识别的指令总结
db dw dd 分别定义 字节, 字, 双字型数据
dup,和上面的指令配合使用,用于数据的复制
比如
dw 3 dup (0,1,2) //表示定义了9个字型数据,分别是0,1,2,0, 1,2, 0,1, 2
使用dup能非常方便在内存中开辟一块很大的区域,
比如
stack segment
db 200 dup(0)
stack ends
172页 实验7
转移指令
段间转移
段内转移:根据修改ip的范围不同,可以分成短转移和近转移
短转移:-128到127
近转移:-32768到32767
转移指令分类:
无条件转移 jmp
条件转移
循环指令 loop
过程
中断
offset操作符
由编译器解释,用于获取标号的偏移地址
比如
mov ax,offset s
mov dx,offset start
jmp指令
- jmp 地址在 指令中
jmp short 标号(比如 s,start,s0)//short表明最多向前向后越过 -128到127个字节
注意 对于 jmp short 标号 ,这种指令,查看二进制内容会发现,jmp 后面紧跟的,二进制内容不是标号对应的地址,而是相对于当前位置的偏移量
jmp 的地址是 03 , jmp指令本身占两字节的 长度 ,jmp 的下一条指令 是05, EB 03 中的03 则表明,偏移是3 , 5+3=8 是jmp指令想要跳转的目的地址
jmp near ptr 标号
和 jmp short 标号类似,也是近转移 功能为 (ip)=(ip)+16位位移
16位位移 ,也是从jmp的下一条指令起始到标号处起始的 长度 范围 -32768-32767
jmp far ptr 标号
段间转移, 实现的功能 和 jmp cs:ip 相同
- jmp 指令在寄存器中
jmp 寄存器
- jmp指令在内存单元中