本文是16位CPU的汇编语法,用于汇编语言基础知识的学习,实验环境配置参考:
【&】 段前缀
ds是段寄存器,后面的偏移地址用寄存器或者数字表示。
ds:[bx]
当(ds)=10000h,(bx)=1200h,那么ds:[bx]=1001200h
【&】 栈访问
我们在CPU里面这样的逻辑过程:先进先出
我们设置栈寄存器的原因是方便我们对栈空间地址的管理和访问,并非用于储存栈内容。
我们指定一段内存空间作为栈空间,需要两个寄存器完成,SS和SP,SS用于指定栈头,SP初始指定栈尾,使用的时候用于指定当前元素。SP我们管它叫 “栈指针寄存器”。
栈的进出对于指针而言是减增。
比如现在我们需要使用0-10空间作为栈,那么初始化的时候就需要SP=10,每当我们PUSH一次的时候,SP=SP-2倒退(-2是因为两个字节,PUSH是默认以字进行操作的)
初始化栈
和ds一样不能直接mov赋值
mov ax,1000h
mov ss,ax
mov sp,0010h
栈数据存放
push ax //把ax的数据存到栈空间里面
pop ax //弹出栈顶元素存到ax里面
【&】程序结束和格式
在8086PC上面使用这样的代码格式
assume cs:code
code segment
main:
...
mov 4c00h
int 21h
code ends
main end
ends是对于每一段的结束,而end则是对于整个程序的结束。
我们会使用
mov ax,4c00h
int 21h
来结束程序
【&】循环loop和cx
我们会把loop指令和寄存器cx配合使用,cx里面存放循环的次数,格式:loop 标号
例子:
mov cx,3
s:
add ax,2
loop s
【&】自增 inc 和 自减 dec
对于寄存器的内容自增或者自减。
inc bx
dec bx
【&】数据段的使用
如果我们先在程序固定下我们需要访问数据的空间,就需要在数据段定义。一般来说,我们存放数据的类型分为三种:db,dw,dd,分别是以字节存放,以字存放和以双字存放。
举例:
db 01h
dw 01h
dd 01h
在内存里面分别是
01 FF FF FF FF …
01 00 FF FF FF…
01 00 00 00 FF…
如果说:
db ‘welcome’
dw ‘welcome’
dd ‘welcome’
我们会在代码开始的时候申请数据段,我们可以随便命名。
【例子】把data1数据段的数据倒序拷贝到data2数据段里面
assume cs:code
data1 segment
dw 0123h,045h,0789h
data1 ends
data2 segment
dw 0,0,0,0
data2 ends
code segment
start:
mov ax,data2
mov ss,ax
mov sp,04h
mov ax,data1
mov ds,ax
mov bx,0
mov cx,3
s:push [bx]
add bx,2
loop s
mov 4c00h
int 21h
code ends
end start
【&】如何查看内存数据进行调试
首先,我们需要一段代码:
mov ax,数据段标号
mov ds,ax
然后打开调试模式,输入-t
看ax的数值,就是内存数据段的起始地址,比如是0770。
再输入:
-d 0770:00
就能看到内存的数据了
【&】逻辑运算指令
- 逻辑运算:and or not xor
- 移位:shl shr sal sar rol ror rcl rcr
shl和sal
逻辑左移和算术左移:
mov ax,00010101b
shl ax,1 //此时ax=00101010
最低位补0,最高位进入CF
汇编语言中 sal(算术百左移指令)和shl(逻辑左移指令)指令的寻址方式、控制移位方式等都一样,区度别其实只有一处:
SAL算术移位指令在执行时,实际上把操作数看成有符号数进行移位,最高位符号位移入CF,但本身保道持原值;其余位顺序左移,次高位被舍弃专。
SHL逻辑移位指令在执行时,实际上把操作数看成无符号数进行移位,属所有位顺序左移,最高位移入CF。
举例如下:
记住:sal是保持有符号数的最高位不变。
MOV AX,8001H//(AX)=1000 0000 0000 0001B
SAL AX,1 //(AX)=1000 0000 0000 0010B
MOV AX,8001H//(AX)=1000 0000 0000 0001B
SHL AX,1 //(AX)=0000 0000 0000 0010B
rol
和shl的区别在于,他是循环补位的,也就说在高位丢弃的位在低位补上。
mov al,40h ; AL = 01000000b
rol al,1 ; AL = 10000000b, CF = 0
rol al,1 ; AL = 00000001b, CF = 1
rcl:
rcl和rol的区别在于补位的时候是否延后一位。
and指令和or指令和xor指令
mov al,01100011B
and al,10111111B
al=00100011B
mov al,01100001B
or al,10111101B
al=11111101B
mov al,01100001B
xor al,10111101B
al=11011100B
【实验】字母大小写转换
我们首先知道:‘A’+20h=‘a’,我们当然可以对其进行一次加法运算,但是这样操作的前提是我们知道他是大写还是小写,一旦设计到条件判断的语句就会消耗大量的资源,我们可以利用逻辑运算来实现大小写转换。
我们对20h分析:20h=2*16+0=32
十进制的32刚好是二进制的第六位,也就是说第六位的数字是1和0,导致整个数字相差20h。比如:【??1????】和 【??0????】之间就相差20h。
当我们对一个字符串db 'welcome’操作的时候,我们如果想让l这个字母大写,如果他是大写那就不修改,第六位的转变:
0->0
1->0
其他:
?->?
所以,设置逻辑运算:
and al,11011111B
完整代码 :
assume cs:code
data segment
db 'welcome$'
data ends
code segment
main:
mov bx,0
mov ax,data
mov ds,ax
mov al,ds:[0]
and al,11011111B
mov ds:[0],al
lea dx,ds:[0] ;需要把字符串地址给dx
mov ah,9 ;9是输出字符功能
int 21h ;21号中断是执行dos系统事件
ret
code ends
end main
输出’Welcome’
【&】si,di,bp寄存器的使用
这些寄存器都是用于偏移地址,注意的是si和di是不能拆开两个八位的寄存器使用的。
格式:
mov ax,[bx/bp+si/di+idata]
【实验】字符串的复制
assume cs:code
data segment
str1 db 'welcome$'
str2 db 0,0,0,0,0,0,0,0
data ends
code segment
main:
mov ax,data
mov ds,ax
mov si,0
mov cx,8
s: mov al,ds:[si]
mov ds:[str2+si],al
add si,1
loop s
lea dx,ds:[str2] ;需要把字符串地址给dx
mov ah,9 ;9是输出字符功能
int 21h ;21号中断是执行dos系统事件
ret
code ends
end main
实现把str1的数据复制到str2
需要注意的是:若以db单位储存字符,16bit=2B,2个字母,一个寄存器能储存两个字母,如果是以dw就是一个字母。
【&】访问内存区分字节和字
mov word ptr ds:[0],1
mov byte ptr ds[0],1
第一种存到内存里面:01 00 FF FF FF FF
第二种存到内存里面:01 FF
【&】除法指令 乘法指令
除法 div
乘法 mul
除法:结果储存在ax的拆开两半al和ah,al放商,ah放余数
乘法:al放被乘数,ax放结果
div byte ptr ds:[0]
(al)=(ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的余数
格式:mul reg
mul 内存单元
例子:
mul byte ptr ds:[0]
(ax)=(al)*((ds)*16+0)
【&】拓展数据 dup指令
data segment
db 3 dup (0,1,2)= db 0,1,2,0,1,2,0,1,2
db 3 dup ('abc','ABC')
data ends
【&】取标志的偏移地址 offset指令和坑nop
这样就相当于吧start的偏移地址0存到了ax里面
start:
mov ax,offset start
代码坑:nop
【实验】复制指令到空白的代码坑
assume cs:codesg
codesg segment
s:
mov ax,bx
mov si,offset s
mov di,offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0:
nop ;相当于萝卜坑
nop
codeseg ends
end s
【&】无条件转移指令 jmp
使用格式:jmp 标号段/内存地址
例子1:
mov ax,0123h
mov ds:[0],ax
jmp word ptr ds:[0]
例子2:
start:
mov ax,0
jmp short s
mov ax,1
s:
inc ax
code ends
end start
jmp可以同时修改cs和ip,例子:
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]
cs:ip=0000:0123
【&】有条件转移指令 jcxz
条件看cx。(cx是个条件工具人,无论是loop还是jcxz都需要看cx作为标志)
格式:jcxz 标号/内存地址
假如cx=0的时候执行,反之什么也不做。
【&】返回出栈 ret 指令和调用入栈指令 call
ret指令事利用栈中的数据修改ip的数据从而实现转移,一般和call配合使用
格式:
call s
s:
xxxx
ret
假如我们需要参数传递,我们一般利用内存空间传入参数,现在写一个把所指单元的字母小写改成大写的函数。
!!!!!注意的事:函数体需要写在main的ret的后面!!!!!
func_head:
push si ;保存参数
call func_main
pop si ;恢复参数
ret
func_main:
mov al,ds:[si]
and al,11011111b
mov ds:[si],al
ret
完整版:
assume cs:code
data segment
str1 db 'welcome$'
data ends
code segment
main:
mov ax,data
mov ds,ax
mov si,0
mov cx,7
s:
call func_head
inc si
loop s
lea dx,str1
mov ah,9 ;9是输出字符功能
int 21h ;21号中断是执行dos系统事件
ret
func_head:
push si
call func_main
pop si
ret
func_main:
mov al,ds:[si]
and al,11011111b
mov ds:[si],al
ret
code ends
end main
把‘welcome’改成‘WELCOME’
【&】标志寄存器 flag
flag寄存器是按照位来起作用的。
这些都是布尔标志。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CF | PF | AF | ZF | SF | TF | IF | DF | OF | |||||||
是否进位 | 是否是偶数 | 是否是0 | 是否是负数 | 是否中断 | 是否递减 | 是否溢出 |
举例子:
比如:
#zf是零标志位,如果某个指令的结果是0,zf=true
比如:
mov ax,1
sub ax,1
那么这个时候zf=1
#pf是奇偶标志位,是否是偶数
mov al,1
add al,10
al=11
pf=0
#sf是正负标志,是否是负数
mov al,10000001b
add al,1
#cf是进位标志位,是否进位
首先我们必须知道为什么要进位和什么时候进位?
当结果溢出了我们原本所给容器的范围的时候就会发生进位。
mov al,98h
add al,al
al=30h,cf=1
另外cf也会记录做减法的时候从高位的借位。
mov al,97h
sub al,98h
这个时候al=ff,cf=1
#of 溢出标志
如何区分of和cf的差异?
cf是对于无符号数有意义的标志位,而of是对于有符号数的有意义的位,也就是说我们用负数操作对于cf是没有意义的。
举个例子:
mov al,98
add al,99
此时,cf=0,of=1,因为,按无符号数看的话,兵没有进位发生,而对于有符号数而言已经溢出了。
【&】cmp指令
cmp实际上减法指令的变种,但是他会对减法结果进行一次判断,不储存减法结果。
用法:
mov ax,8
mov bx,3
cmp ax,bx
执行之后:
ax=8 zf=0 cf=0
通过看zf和cf的标志位,我们就可以知道ax和bx数值的大小关系。
zf=true
→
\rightarrow
→ax
=
=
=bx
zf=false
→
\rightarrow
→ax
≠
\neq
=bx
cf=true
→
\rightarrow
→ax
<
<
<bx
cf=false
→
\rightarrow
→ax
>
=
>=
>=bx
无非就是cf和zf的组合使用。
【&】cmp的搭配跳转指令们 jne je jna ja jnb jb
je 等于就转
jne 不等于就转
jb 低于就转
jnb 不低于就转
ja 高于就转
jna 不高于就转
below和above
举例子:
cmp ah,bh
je s
xxxx
s:
xxxx
【&】rep指令和movsb
movsb指令用于把字节从ds:si 搬到es:di;rep是repeat的意思,rep movsb 就是多次搬运。
【&】DF递减标志
对于寄存器的第十位的df,是方向标志位,表示是否递减,对于用于控制每次操作之后的si和di递增和递减。
指令cld可以让df=0,std可以让df=1
指令movsb用于把某个内存单元复制到某个内存单元
((es)*16+(di))=((ds)*16+(si))
当这轮操作之后,看df?=0,
df=0
si++
di++
反之
si–
di–
【例子】用movsb指令把某个内存单元都知道某个内存单元
assume cs:code
data segment
str1 db 'welcome$'
str2 db 16 dup(0)
data ends
code segment
main:
mov ax,data
mov ds,ax
mov si,0
mov es,ax
mov di,8
mov cx,8
cld
rep movsb
lea dx,str2
mov ah,9
int 21h
ret
code ends
end main
输出’welcome’
【&】键盘输入输出
使用
mov ah,0
int 16h
输入内容存到al里面
使用
lea dx,起始地址
mov ah,9
int21h
把地址的内容输出到屏幕上
示例代码:
assume cs:code
data segment
char db '$$'
data ends
code segment
main:
s:
mov ax,data
mov ds,ax
mov ah,0
int 16h
mov ds:[char+0],al
lea dx,char
mov ah,9
int 21h
jmp s
code ends
end main
当我们需要检验当前输入的字符是什么的时候,我们可以利用
cmp al,ascii数字码
比较大小,在利用jne,jbe等命令进行跳转