《汇编语言》
前言
正式工作之后打算着手做一些逆向方面的研究,听前辈们的建议,必须先把汇编学会,于是我用第一个月把《汇编语言》(第三版-王爽著)看了一遍,但是人的记忆力是有限的,所以打算以博客的形式再回忆一遍,相信通过这种形式,能让自己对知识理解的更加模块化和具体化,也方便自己日后复习,同时也方便了看到这篇博客的同道中人。
作用
汇编语言在整个计算机编程语言中的地位可以说是没什么用,很少有人会直接拿汇编语言去写项目,如果这么干的话,不得麻烦死(但是不排除确实有这种需求的时候的做法)。更多时候汇编语言的使用场景是在反编译别人的二进制代码之后,对汇编代码的逻辑还原,并且对于计算机系统整体的理解,我相信没有什么比学习汇编语言更快、更好。
名词、汇编指令的示例和说明(8086cpu)
-
寄存器:用于存放cpu的数据信息,共14个,分别是:AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW。
-
字节、字和双字:1双字(DWROD)=2字(WORD)=4字节(byte)。1字节(byte)=8位(bit),比如,寄存器ax是16位寄存器,在内部可以在分为ah和al,分别代表高8位和低8位。
-
内存访问:一般利用寄存器ds,es,ss才可以间接访问内存,比如要访问2000:0200位置的数据,并将该位置的数据传送入ax,应该这样:
mov ax,2000h
mov ds,ax
mov si,200h
mov ax,ds:[si]
- 栈内存:在内存访问中有一类特殊的访问,就是栈内存,在操作栈的时候,最好指定一块用于自己使用的内存区域,以便自定义栈的大小和位置。示例:
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,128 ;目前栈顶在ss:sp处(即stack:128),也就是上面定义的stack中的128字节
mov ax,'a'
push ax ;将'a'入栈,栈顶在ss:sp处(即stack:126)
push ax
pop bx ;出栈,将数据保存在bx中
mov ax,4c00h
int 21h
code ends
end start
- 标志位:在内存中占1字节大小,用于运算和程序中的状态等。常用的标志有ZF,PF,SF,CF,OF,DF
ZF:是否为0
PF:1的个数是否为偶数
SF:是否为负数
有符号整数,只看结果是不是大于7,也就是首位是不是0
CF:是否存在进位或借位(无符号整数)
直接进行无符号运算,首位进位就置1
OF:是否存在溢出(有符号整数)
16位cpu,有符号溢出范围79H(127),-80H(128)
DF:方向标志位,与movsb配合使用
- 指令
数据传送指令:
mov:数据传送
示例:mov ax,bx
说明:将bx中的数据传送到ax中,因为使用的是ax和bx,所以数据的长度是16位,下面的例子都相同,如果有操作数据中有寄存器,那么按照寄存器的数据长度计算
push:入栈
示例:push ax
解释:将ax的数据入栈,传送到ss:sp栈顶处
pop:出栈
示例:pop ax
解释:将ss:sp位置的数据出栈,传送入ax中
pushf:所有标志位入栈
示例:pushf
解释:标志位入栈,防止后面的操作对标志位产生影响的通常做法
popf:标志位出栈
示例:popf
解释:标志位出栈,用于还原入栈的标志位
xchg:交换,目前没用到
算数运算指令
add:加法
示例:add ax,2
解释:将ax中的数据加2,即ax+0002h
sub:减法
示例:sub ax,2
解释:将ax中的数据减2,即ax-0002h
adc:加法(带符号位)
示例:adc ax,2 ;CF=1
解释:将ax中的数据加3,即ax+0002h+1h
sbb:减法(带符号位)
示例:sbb ax,2 ;CF=1
解释:将ax中的数据减3,即ax-0002h-1h
inc:自增
示例:inc si
解释:将si中的数据加1,常用在循环或条件转移中
dec:自减
示例:dec ax
解释:将ax中的数据减1
cmp:比较
示例:cmp ax,0
解释:相当于减法,ax-0,只不过不影响寄存器的值,而只影响标志寄存器,因为条件转移指令是依据标志寄存器的指令,所以cmp常与条件转移指令配合使用进行条件转移
mul:乘法
示例:mul bx或mul bl
解释:分为两种情况:
1. 指令参数是8位寄存器如bl时,乘数1默认放在al寄存器中,另一个乘数2放在8位寄存器如bl中,结果存在ax中
2. 指令参数是16位寄存器如bx时,乘数1默认放在ax寄存器中,另一个乘数2是指定的16位寄存器如bx中的数据,结果的高16位存在dx,低16位存在ax中
div:除法
示例:div bx或div bl
解释:同样分为两种情况:
1. 指令参数是8位寄存器时,被除数(除法前面那个数。。。)则为16位,默认存放于ax中,除数则是存放于指定8位寄存器如bl中,结果为:al存储商,ah存储余数
2. 指令参数是16位寄存器时,被除数则为32位,默认存放于ax和dx中,dx存高16位,ax存低16位,除数存放于指定16位寄存器如bx中,结果:ax存储商,dx存储余数
aaa:目前没有用到
逻辑指令
and:逻辑与
示例:and al,11011111b
解释:示例中结果是将al中第6位置为0,其他位保持不变,常用与简化运算如,将小写字母转化为大写字母,只需要将字母与11011111做逻辑与运算即可实现
or:逻辑或
示例:or al,00100000
解释:示例中结果是将al中第6位置为1,其他位保持不变,同样也能实现将大写字母转化为小写字母的简化运算,需要将字母与00100000做逻辑或运算即可实现
not:逻辑非,不常用
示例:
解释:
xor:逻辑异或,目前不常用
示例:
解释:
test:不常用
shl:逻辑左移
示例:shl al,1或mov cl,3;shl al,cl
解释:逻辑左移的意思就是左移后,移出的数据存放在标志寄存器CF中,而最低位用0补齐,也分为两种情况:
1. 左移1位:直接shl al,1即可
2. 左移超过1位:需要先将欲移动的位数据存入cl中,再通过左移cl个位的数据来实现
shr:逻辑右移
示例:shr al,1或move cl,2;shr al,cl
解释:逻辑右移与逻辑左移类似,这里就不多讲了,同样也是两种情况
sal
sar
rol
ror
rcl
rcr
转移指令
无条件转移指令
jmp:无条件转移指令
示例:jmp short s;s:inc ax
解释:jmp转移指令可以实现段内短转移、段内近转移和段间转移这三种基本需求
1. 段内短转移:只对IP寄存器修改,修改范围为-128~127,也就是说向前最多转移+127个字节,向后最多转移-128个字节
2. 段内近转移:与短转移基本相同,不过是16位的位移,即修改IP的范围是-32768~32767
3. 远转移(段间转移):可以转移到指定的内存处,上面的两个转移只是在同一个段中的转移,是根据位移定位的转移方式,而远转移可以指定转移的目的地址
条件转移指令
jcxz:如果cx寄存器的值为0,则转移到指定标号
示例:mov cx,0;jcxz s
解释:如果条件满足cx寄存器的值为0,则转移到指定的标号处,常用的场景是:遍历一个以'0'字符结尾的字符串,根据这个0判断字符串是否到末尾的简单实现方式
je:如果cmp得差结果等于0,则转移到指定标号
示例:mov bx,3;cmp bx,3;je s
解释:将会转移到s处
jb:如果cmp的差结果小于0,则转移到指定标号
示例:mov bx,3;cmp bx,4;jb s
解释:将会转移到s处
ja:如果cmp的差结果大于0,则转移到指定标号
示例:mov bx,3;cmp bx,2;ja s
解释:将会转移到s处
jnb:如果cmp的差结果不小于0,则转移到指定标号
示例:mov bx,3;cmp bx,3;jnb s
解释:将会转移到s处
jna:如果cmp的差的记过不大于0,则转移到指定标号
示例:mov bx,3;cmp bx,3;jna s
解释:将会转移到s处
循环指令
loop:汇编语言中的循环语句
示例:mov cx,10;loop s
解释:将s程序段循环执行10次(循环次数由cx的值指定)
过程
call:调用子程序,常与ret成对使用
示例:call s;s:ret
解释:转移到子程序,类似于转移指令,但相当于执行了
push IP
jmp near s这两条指令,记录了转移的位置,可以使用ret返回此IP的位置
ret:在子程序中同于返回call的指令处,常与call成对使用,并且是近转移
示例:call s;s:ret
解释:从子程序跳出,相当于执行了
pop IP,程序执行的下一条语句就是原来call的IP的地址,从而实现了近转移
retf:在子程序中同于返回call的指令处,常与call成对使用,并且是远转移
示例:call s;s:retf
解释:从子程序跳出,相当于执行了
pop IP
pop CS程序执行的下一条语句就是原来call的IP的地址,从而实现了远转移
中断
int:系统中断
示例:mov ax,4c00h;int 21h
解释:BIOS和DOS都提供了一些默认的中断进程,用于持续检测中断码,如果接受到中断码,则会在TF=1的情况下在下一条指令去执行可屏蔽中断进程,我们也可以自定义中断进程去替代系统的中断进程。示例中的中断是去执行21号中断的ah=4c的子程序,子程序为退出当前DOS
iret:与int配合使用,在子程序中返回,与ret,retf类似
处理机控制指令
cld:设置DF为0(即正向拷贝)
示例:cld;rep movsb
解释:设置标志寄存器DF为0,即设置拷贝的方向为正向
std:这是DF为1(即反向拷贝)
示例:std;rep movsb
解释:设置标志寄存器DF为1,即设置拷贝的方向为反向
cli:设置TF标志位为1
示例:cli
解释:设置TF为1后,当接受到可屏蔽中断时,会在下一条指令执行中断
sti:设置TF标志位为0
示例:sti
解释:设置TF为0后,当接受到可屏蔽中断时,会忽略中断继续执行当前程序直至结束
nop:添加一个占位的一字节数据
示例:funcend:nop
解释:常用来记录子程序的段结束的位置,比如offset funcend就可以获取func结束位置的偏移地址
clc
cmc
stc
hlt
wait
esc
lock
串处理指令
movsb:复制字符串
示例:rep movsb
解释:可以正向或反向复制指定为的字符串到目标地址,参数必须将ds:si源地址、es:di目的地址、cx长度、标志DF指定,然后调用rep movsb
movsw
示例:
解释:
cmps
scas
lods
stos
配合使用:
rep
repe
repne
书中的一些值得注意的问题(自己的笔记上的内容,某些描述可能不是很准确,可以选择性跳过)
mov ax,0 可以用sub ax,ax替代,而且后者2字节,前者3字节
当16进制数,首位数字为英文,则需要在前面加上0,比如:0A432H
在汇编程序中,mov al,[0]指令与debug程序不同,汇编编译器会将它解释为mov al,0
DOS和合法程序都不会使用0:200~0:2ff这段256字节的空间
直接DEBUG显示的CS就是当前程序段的字节长度
为什么mov 4c00h;int 21h长度为5字节
暂存数据的时候,一般用栈
bp寄存器默认使用ss作为段寄存器
没有寄存器名存在的情况下指定内存单元的长度:mov word/byte ptr [bx],1
push/pop只对字操作
在10.1节,ret和retf的示例中,在执行命令之前,为什么都要mov bx,0
mul乘法,与div除法原理相似,都分为8位和16位的计算
16位cpu,有符号溢出范围79H(127),-80H(128)
DF:方向标志位,与movsb配合使用
adc指令的意义:进行大数的加法或减法
inc和loop不影响cf位
cmp指令只会影响标志位
CF能说明操作符的大小
SF=1并不能说明运算的结果的正负,因为可能发生溢出
但SF和OF同时可以说明正负
SF=1,OF=1:(ah)<(bh)
SF=1,OF=0:(ah)>(bh)
SF=0,OF=1:(ah)<(bh)
SF=0,OF=0:(ah)>=(bh)
cld和std分别设置DF为0(正向)和1(反向)
Debug中的标志位对应关系
flag:1--0
OF:OV,NV
SF:NG,PL
ZF:ZR,NZ
PF:PE,PO
CF:CY,NC
DF:DN,UP
loop执行分两步
cx--
cx不等于0,转移
中断过程
取得中断类型吗N
pushf,将标志寄存器入栈保存
TF=0,IF=0
push cs
push ip
(IP)=4*N,(CS)=4*N+2
动态获取到一段代码的长度,可以设置一个nop字节,获取长度只需要:
mov cx,offset do0end-offset do0
do0:
mov xx,xxx
mov 4c00h
int 21h
do0end:nop
jmp short s占2字节内存
设置栈顶的ss和sp之间不会响应任何中断,所以在实验二的时候,执行mov ss,0后,mov ax,0执行了,只是debug不能将它中断显示
一般在中断例程中还存在子程序,一般通过ah来指定
如果将字符串后的0写成字符'0',则利用jcxz无法跳转
在进行逻辑移位shl或shr时,移位大于1,必须用cl保存位移数,并将最后一次移出的保存到CF中
在CMOS RAM中,端口为70h(地址端口)和71h(数据端口)存储时间信息的单元分别为:秒:0分:2时:4日:7月:8年:9,都占一个字节
在PC中,外中断可分为可屏蔽中断和不可屏蔽中断
可屏蔽中断在IF=1时,cpu会在执行完当前指令后响应中断,IF=0则不响应此中断
不可屏蔽中断是cpu必须响应的中断,一般不会使用
设置IF:sti,设置IF=1; cli,设置IF=0,IF=0不会执行屏蔽中断
通码:按下键盘的一个键产生的扫描码;断码:松开一个键产生的扫描码,
断码=通码+80h
都被送到60h端口
扫描码一个字节中,通码的第7位为0,断码第7位为1
地址标号和数据标号
地址标号,只能在代码段使用
数据标号,不仅表示内存单元的地址,还表示内存单元的长度,与地址标号不同是标号后无符号“:”
直接定址表:可以通过依据数据,直接计算出所要找的元素的位置的表
键盘缓冲区的字单元中,高位字节存储扫描码,低位字节存储ASCII码。
int 16h用于从键盘缓冲区读取字符,ah存储扫描码(高位),al存储字符(低位)