Assembly language


基础知识

·汇编语言的组成(3类指令)

1)汇编指令:有对应的机器码。
2)伪指令:没有对应的机器码,由编译器执行。
3)其他符号:如+、-、*、/等,没有对应的机器码,由编译器执行。

·CPU对存储器的读写

与芯片进行3类信息的交互:
1)存储单元地址。
2)器件选择,读或写命令。
3)读或写的数据。

·外部总线

1)地址总线:N根地址线可以选找2的N次方个内存单元(一个内存单元1byte)。
2)数据总线:宽度为8k的总线一次能传送 k Btye的数据。
3)控制总线:宽度决定CPU对外部期间的控制能力。

·存储器芯片

1)随机存储(RAM):可读可写,关机数据丢失。
2)只读存储(ROM):只读,关机数据不丢失。


寄存器

·Registers Architecture(寄存器结构)

register Accumulator Counter Data Base Stack Pointer Stack Base Pointer Source Destination
64-bit RAX RCX RDX RBX RSP RBP RSI RCI
32-bit EAX ECX EDX EBX ESP EBP ESI EDI
16-bit AX CX DX BX SP BP SI DI
8-bit AH/AL CH/CL DH/DL BH/BL

·通用寄存器

1)AX BX CX DX 这四个寄存器存放一般性数据。
2)这四个通用寄存器都可以分为两个八位寄存器使用(参见上表16-bit和8-bit)
其中,低八位构成AL,高八位构成AH。

·几条汇编指令

1)汇编指令举例

汇编指令 控制CPU完成的操作
mov ax,18 AX=18
mov ah,78 AH=78
mov al,8 AL=8
add ax,9 AX+=9
mov ax,bx AX=BX
mov al,bh AL=BH
add ax,bx AX+=BX
add bh,al BH+=AL

2)注意事项:
16位寄存器中,若是数据值相加超过4位十六进制的数据,则只保存低四位的十六进制数据。
当16位寄存器被用作两个八位寄存器时,若寄存器数据值超过两位十六进制的数据,则只保存低二位的十六进制数据(若是AL寄存器中数据超过内存,高位不是真的被CPU丢失)
指令的两个操作对象的位数应该一致,而:

1
2
3
4
mov ax,bl
mov bh,ax
mov al,2000(超出范围)
add al,100H(超出范围)

都是错误的指令。

·8086CPU给出物理地址的方法

1)地址加法器采用物理地址 = 段地址x16 + 偏移地址的方法(地址数据用16进制表示)。
2)基础地址 = 段地址 x 16。
3)“段地址x16”实际上表示16进制数左移一位(即二进制数左移4位)。

·段地址

1)“段地址”划分来自CPU,不是内存本身分段。
2)CPU可以用不同的段地址和偏移地址形成同一个物理地址。
3)给定段地址,仅用偏移地址寻址最多可寻64KB个内存单元。

·段寄存器

1)8086CPU有四个段寄存器:CS、DS、SS、ES。
2)CS:代码段寄存器(段地址),IP:指令指针寄存器(偏移地址)。
3)
4)8086CPU工作过程:
从CS:IP之乡的内存单元读取指令,指令进入指令缓冲区;
IP = IP + 所读取的指令长度,从而指向下一条指令;
执行指令。重复以上过程。
5)CPU只认被CS:IP指向的内存单元的内容为指令。

·修改CS、IP的指令

1)同时修改CS、IP的内容:“jmp 段地址:偏移地址”;
2)只修改IP的内容:“jmp 某一合法寄存器”(用寄存器里的值修改IP,eg: jmp ax)。


寄存器(内存访问)

·DS和[address]

1)DS段寄存器通常来存放要访问的数据的段地址。
2)“[]”表示ds中的数据为内存单元的段地址,“[address]”中的“address”表示偏移地址。
3)假设读取10000H单元的内容(字节型数据的传送):

1
2
3
4
mov bx,1000H
mov ds,bx
mov al,[0](使用mov指令将一个内存单元中的(8位)字节数据送入一个8位寄存器中)
ds是段寄存器,不能直接传入1000H,只能用一个寄存器来进行中转。

4)字的传送:
eg:

1
2
3
4
mov bx,1000H
mov ds,bx
mov ax,[0] ;1000:0处的(16位)字型数据送入ax
mov [0],cx ;将cx中的(16位)字数据传送到1000:0处

·mov、add、sub指令

1)这三个指令都带有两个操作对象。
2)以mov为例,mov、add、sub指令可以有以下几种形式:
mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器

4)但是mov还存在以下四种形式:
mov 段寄存器,寄存器
mov 寄存器,段寄存器
mov 内存单元,段寄存器
mov 段寄存器,内存单元
5)add,sub指令不能对段寄存器进行操作。

·栈

1)栈是一种具有特殊访问方式的存储空间:最后进入这个空间的数据,最先出去(LIFO)。
2)入栈(push)和出栈(pop)都是以字为单位进行的。
3)
4)段寄存器SS:存放栈顶的段地址;寄存器SP:存放栈顶的偏移地址;任意时刻,SS:SP指向栈顶元素
5)栈空,SS:SP指向栈空间最高地址单元的下一个单元。
6)一个数据出栈后,该地址单元的数据依然存在,只是不在栈中,当下次有数据入栈时,它将被覆盖。
7)8086CPU不保证对栈操作是否超界,栈顶超界将会覆盖栈外数据。

·push和pop指令

1)push指令和pop指令格式有如下形式(以push为例):
push 寄存器
push 段寄存器
push 内存单元
2)push指令执行步骤:(1)SP=SP-2;(2)向SS:SP指向的字单元送入数据。
3)pop指令执行步骤:(1)从SS:SP指向的字单元读取数据(2)SP=SP+2。
4)push和pop指令中修改的只是SP,所以栈顶的变化范围最大为:0-FFFFH。

·段的综述

1)将一段连续的内存定义为一个段,用段地址指示段,偏移地址访问段内单元,数据段、代码段、栈段都是我们自己定义的。
2)数据段:段地址存放在DS中,用mov,add,sub等访问内存单元的指令时,CPU数据段的内容当作数据访问。
3)代码段:段地址存放在CS中,段中第一条指令的偏移地址放在IP中,CPU就执行代码段中的指令。
4)栈段:段地址存放在SS中,栈顶单元的偏移地址放在SP中,CPU执行栈操作时将我们定义的栈段当作占空间来用。
5)同一段内存,同时可以是代码段、栈段和数据段,也可以什么都不是,关键在于CS、IP、SS、SP、DS的指向。


初识汇编程序

·3个伪指令

1)segment和ends伪指令:这是一对成对使用的伪指令,作用是定义一个段,其格式为:

1
2
3
段名 segment    ;段从此处开始
:
段名 ends ;段到此处结束

2)程序是由多个段组成的,指令、数据、栈被划分到了不同的段中。
3)end:汇编程序结束标记。
4)assume:假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联,例如:
assume cs: 代码段的名字 将一个代码段和CS寄存器联系起来。

·程序返回

1)指令:
mov ax,4c00H
int 21H


[BX]和loop指令

·约定两个符号

1)“( )”:表示一个寄存器或者内存单元里的内容。
2)“idata”:表示常量。

·[BX]

1)同[0]一样,[bx]也表示一个内存单元,只是它的偏移地址在bx中。
2)bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中。

·Loop指令

1)loop指令格式:loop 标号;通常用loop指令来实现循环功能,cx中存放循环次数。
2)eg:

1
2
3
4
5
6
   mov ax,2
mov cx,11
s: add ax,ax
loop s
mov ax,4c00h
int 21h

3)标号代表一个地址,如上例s标识了一个地址,这个地址处有一条指令:add ax,ax.
4)CPU执行 loop s 的时候,进行两步操作:
(1)(cx)=(cx)- 1
(2)判断cx中的值,不为0则转至标号s所标识的地址处执行,如果为零则执行下一条指令。

·汇编程序中的一些小变动

1)用一个长度位1字节地内存单元向16位寄存器赋值(如把ffff:0006单元给ax赋值),则应该另令(ah)=0,(al)=(ffff6H)。
2)在汇编程序中,数据不能以字母开头,例如代码中mov ax,0ffffh,不能写成mov ax,ffffh。
3)汇编程序中,指令“mov ax,[0]”被当作“mov ax,0”处理,因此有如下两种方法实现将内存单元中的数据送入寄存器(举例说明):

1
2
3
mov al,ds:[0]
mov al,[bx]
mov al,ds:[bx]

·段前缀

1)出现在访问内存单元的指令中,用于显式地指明内存单元的段地址,形如“mov al,ds:[bx]”,在汇编语言中称为段前缀。
2)将一段内存单元的数据复制到另一段单元中,显式使用段前缀,可以提高程序效率。


包含多个段的程序

·在代码段中使用数据

1)end的另一作用:指明编译器程序入口,用法:end 标号
2)在代码段中使用数据可以使用如下程序框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
:
:
数据
:
:
start:
:
:
代码
:
:
code ends
end start

来指明CPU从何处开始执行程序。

·在代码段中使用栈

1)在代码段中使用栈挥着数据实质上都是开辟空间。
2)在代码段中使用栈可以使用如下程序框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
:
:
数据
栈空间
:
:
start:
:
:
代码
:
:
code ends
end start

来指明CPU从何处开始执行程序。

·将数据、代码、栈放入不同的段

1)定义多个段。eg:

1
assume cs:code,ds:data,ss:stack

2)对段地址的引用:段名就相当于标号,它代表了段地址。eg:

1
mov ax,data ;将名称为“data”的段地址送入ax。

3)


更灵活的定位内存地址的方法

·and和or指令

1)and指令:逻辑与指令,按位进行与运算。该指令可以将操作对象的相应位设为0,其他位不变。
2)or指令:逻辑或指令,按位进行或运算。该指令可以将操作对象的相应位设为1,其他未不变。
3)eg:

1
2
3
4
5
mov al,01100011B
and al,00111011B 执行后al=00100011B

mov al,01100011B
or al,00111011B 执行后al=01111011B

·以字符形式给出的数据

1)用’……’的方式指明数据是以字符的形式给出的,编译器将其转化为ASCII码。
2)eg:

1
2
db 'unIX'     ;相当于“db 75H,6EH,49H,58H”
mov al,'a' ;相当于“mov al,61H”

·大小写转换问题

1)除了大写字母=小写字母-20H外,可以用and 11011111B将小写转换为大写字母。
2)可用or 01100000B将大写转换为小写字母。

·[bx+idata]

1)[bx+idata]表示一个偏移地址为(bx)+idata的内存单元。
2)常用格式:

1
2
3
4
mov ax,[bx+idata]
mov ax,[idata+bx]
mov ax,idata[bx]
mov ax,[bx].idata

3)[bx+idata]的方式处理数组更加便利。与C语言比较:
C语言:a[i],b[i]
汇编语言:0[bx],5[bx]

·SI和DI

1)si和di是8086CPU中和bx功能相近的寄存器,si和di不能分成两个8位寄存器来使用。
2)复制字符串汇编程序举例:

·[bx+si]和[bx+di]

1)[bx+si]和[bx+di]含义相似,以[bx+si]为例,其表示一个偏移地址位(bx)+(si)的内存单元。

·[bx+si+idata]和[bx+di+idata]

1)[bx+si+idata]和[bx+di+idata]含义相似,以[bx+si+idata]为例,其表示一个偏移地址位(bx)+(si)+idata的内存单元。
2)常用格式:

1
2
3
4
5
mov ax,[bx+idata+si]
mov ax,[idata+bx+si]
mov ax,idata[bx][si]
mov ax,[bx].idata[si]
mov ax,[bx][si].200


数据的位置和长度

·约定两个描述性符号

1)reg:寄存器
2)sreg:段寄存器

·bx、si、di、bp

1)在8086CPU中只有这四个寄存器可以用在“[…]”中来进行内存单元的寻址。
2)在“[…]”中,这四个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。
3)在“[…]”中使用寄存器bp,且指令中没有显性地给出段地址,则段地址默认在ss中。

·寻址方式

1)

·数据的长度

1)通过寄存器名指明处理数据的尺寸。
eg:

1
2
mov ax 1  ;字操作
mox al,bl ;字节操作

2)在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte。
eg:

1
2
mov word ptr ds:[0],1   ;指明指令访问的内存单元是一个字单元
add byte ptr [bx],2 ;指明指令访问的内存单元是一个字节单元

·div指令

1)div是除法指令。需要注意以下问题:
(1)除数:有8位和16位两种,在一个reg或内存单元中;
(2)被除数:默认放在AX或DX和AX中,除数为8位,被除数则为16位,默认在AX中存放;除数为16位,被除数则为32位,DX存放高16位,AX存放低16位。
(3)除数为8位,AL存储商,AH存储余数;除数为16位,AX存储商,DX存储余数。
2)格式:div reg或者div 内存单元。

·伪指令db、dw、dd

1)db: define btye
2)dw: define word
3)dd: define double word(双字型数据,占两个字)

·dup

1)dup操作符的作用:进行数据重复。
2)用法:
db 3 dup (0) ;定义了三个值都是0字节
db 3 dup (0,1,2) ;定义了九个字节,他们是0、1、2、0、1、2、0、1、2


转移指令的原理

·转移指令

1)可以修改IP,或同时修改CS、IP的指令统称为转移指令。即控制CPU执行内存中某处代码的指令。
2)段内转移:只修改IP,比如:jmp 1000:0。
3)段内转移分为短转移(IP的修改范围为-128~127)、近转移(IP的修改范围为-32768~32767)。
4)8086CPU的转移指令分为如下几类:
无条件指令转移(如:jmp)
条件转移指令
循环指令(如:loop)
过程
中断

·操作符offset

1)offset是由编译器处理的符号,功能是取得标号的偏移地址。
2)

·依据位移指令进行转移的jmp指令

1)jmp short 标号(段内短转移,转到标号处执行指令)实现功能是:(IP)=(IP)+8位位移。
2)CPU在执行jmp指令的时候并不需要转移目的地址,而是包含转移的位移。
3)
4)jmp near ptr 标号(段内近转移)实现功能是:(IP)=(IP)+16位位移。

·转移的目的地址在指令中的jmp指令

1)“jmp far ptr 标号”实现段间转移,far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。

·转移地址在寄存器中的jmp指令

1)指令格式:jmp 16位reg,功能:(IP)=(16位reg)。eg: jmp ax。

·转移地址在内存中的jmp指令

1)jmp word ptr 内存单元地址(段内转移)
功能:从内存单元地址处开始存放一个字,是转移的目的偏移地址。eg:jmp word ptr ds:[0]
2)jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。
eg:jmp dword ptr ds:[0]

·jcxz指令

1)jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移。
2)“jcxz 标号”的功能相当于:if((cx)==0) jmp short 标号。(判断语句)

·loop指令

1)所有的循环指令都是短转移。
2)“loop 标号”的功能相当于:(cx)–; if((cx)!=0) jmp short 标号(do while循环)

·注意

1)在之前jmp指令中,“jmp 2000:0100”的转移指令,是在Debug中使用汇编指令,汇编编译器并不认识。


CALL和RET指令

·ret和retf

1)ret指令用栈中的数据,修改IP的内容,实现近转移;ret指令实现下面两步操作:

1
2
(IP)=((ss)*16+(sp))
(sp)=(sp)+2

相当于进行:pop IP

2)retf指令用栈中的数据,修改CS和IP的内容,实现远转移;retf指令实现4步操作:

1
2
3
4
(IP)=((ss)*16+(sp))
(sp)=(sp)+2
(CS)=((ss)*16+(sp))
(sp)=(sp)+2

相当于进行:pop IP pop CS

·call指令

1)执行call指令时,先将当前IP或CS和IP压入栈中,再进行转移。
2)call指令不能实现短转移。

·根据位移进行转移的call指令

1)call 标号(将当前的IP压栈后,转到标号处执行指令)。
2)CPU执行“call 标号”时,相当于进行:

1
2
push IP
jmp near ptr 标号

·转移的目的地址在指令中的call指令

1)call far ptr 标号(实现段间转移)。
2)CPU执行“call far ptr 标号”时,相当于进行:

1
2
3
push CS
push IP
jmp far ptr 标号

·转移地址在寄存器中的call指令

1)call 16位reg。
2)CPU执行“call 16位reg”时,相当于进行:

1
2
push IP
jmp 16位reg

·转移地址在内存中的call指令

1)call word ptr 内存单元地址
2)CPU执行“call word ptr 内存单元地址”时,相当于进行:
push IP
jmp word ptr 内存单元地址
3)call dword ptr 内存单元地址
4)CPU执行“call dword ptr 内存单元地址”时,相当于进行:

1
2
3
push CS
push IP
jmp dword ptr 内存单元地址

·call和ret配合使用

1)实现子程序(函数)的机制,框架如下:

1
2
3
标号:
指令
ret

2)当往子程序(函数)传参时,常用的方法是用栈传递参数;传字符串时,将首地址存放在寄存器中传递给子程序。
3)在子程序(函数)中用到相同的寄存器时,一般把子程序中的寄存器中的值在子程序开始时存入栈中,在子程序返回前把值出栈给相应寄存器。

·mul指令

1)mul乘法指令,需要注意以下两点:
(1)相乘的两个数位必须一样(8位和8位相乘),如果是8位,一个默认在AL存放,另一个存放在8位reg或内存字节单元中;如果是16位,一个默认存在AX中,另一个放在16位reg或内存字单元中。
(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认存放在DX中,低位在AX中存放。
2)格式:mul reg;mul 内存单元(内存单元可以用不同的寻址方式给出)。
eg:

1
2
3
mov ax,1000
mov bx,10000
mul bx


标志寄存器

·概述

1)作用:
用来存储相关指令的某些执行结果;
用来为CPU执行相关指令提供行为依据;
用来控制CPU的相关指令工作方式;
2)标志寄存器(flag寄存器)按位起作用:

3)flag中空位没有使用。

·ZF标志

1)ZF,零标志位。它记录相关指令执行后结果是否为零。结果为零,zf=1;反之,zf=0。
2)在8086CPU中一般运算指令如add、sub、and等的执行会影响标志寄存器,而传送寄存器如mov、push、pop大都对标志寄存器没有影响。

·PF标志

1)PF,奇偶标志位。它记录相关指令执行后结果所有bit位中1的个数是否为偶数。偶数pf=1;反之pf=0。

·SF标志

1)SF,符号标志位。它记录相关指令执行后记过是否为负。结果为负,sf=1;反之,sf=0。
2)当我们把数据当作有符号数来运算时,可以通过sf判断结果正负;若把数据当作无符号数来运算,sf的值没有意义,虽然相关指令影响了它的值。

·CF标志

1)CF,进位标志位。在进行无符号数运算时,它记录运算结果的最高有效位向更高位的进位值,或从更高位的借位值。进位或借位cf=1。
2)eg:

1
2
3
4
mov al,98H
add al,al ;执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值。
mov al,97H
sub al,98H ;执行后:(al)=FFH,CF=1,CF记录了向更高位的借位值。

·OF标志

1)OF,溢出标志位。记录有符号数运算结果是否发生了溢出。如果溢出,of=1;反之,of=0。

adc指令

1)adc是带进位加法指令,它利用了CF位上的进位值。
2)指令格式:adc 操作对象1,操作对象2
3)功能:操作对象1 = 操作对象1 + 操作对象2 + CF
eg:

1
adc ax,bx实现:(ax)=(ax)+(bx)+CF

相当于:低位相加,高位相加再加上低位相加产生的进位值。
4)adc指令执行后也可能产生进位值。

·sbb指令

1)sbb是带借位减法指令,它利用了CF位上的进位值。
2)指令格式:sbb 操作对象1,操作对象2
3)功能:操作对象1 = 操作对象1 - 操作对象2 - CF
eg:

1
sbb ax,bx实现:(ax)=(ax)-(bx)-CF

·cmp指令

1)cmp是比较指令,功能相当于减法指令,但是不保存结果。
2)格式:cmp 操作对象1,操作对象2
3)cmp指令可以对无符号数间和有符号数间进行比较。
4)以cmp ah,bh为例,总结CPU在执行cmp指令后,sf和of的值是如何说明比较结果的:
(1)如果sf=1,而of=0;所以(ah)<(bh)。
(2)如果sf=1,而of=1;所以(ah)>(bh)。
(3)如果sf=0,而of=1;所以(ah)<(bh)。
(4)如果sf=0,而of=0;所以(ah)>=(bh)。

·检测比较结果的条件转移指令

1)无符号数(检测zf,cf的值):

2)将cmp和je等指令配合使用,与高级语言中的if语句相似。
eg:

1
2
3
4
5
6
cmp ah,bh
je s
add ah,bh
jmp short ok
s:add ah,ah
ok:...

·DF标志和传送指令

1)DF,方向标志位。在串处理指令中,控制每次操作后si,di的增减。(df=0,每次操作后si、di递增;df=1,每次操作后si、di递减)。
2)一个串传送指令格式:
movsb(传送内存单元中的字节到es:di,然后根据标志寄存器df位的值,将si、di递增或递减)
movsw(传送内存单元中的字到es:di,然后根据标志寄存器df位的值,将si、di递增2或递减2)
3)配合rep使用(rep,根据cx值重复执行后面的串传送指令。)rep movsb可以循环实现(cx)个字符的传送。
4)8086CPU提供两个指令对df位进行修改:
cld指令:将df位置0
std指令:将df位置1

·pushf和popf

1)pushf:将标志寄存器的值压栈;popf:从栈中弹出数据,送入标志寄存器中。

·标志寄存器在Debug中的表示

1)

-------------Thanks for Reading-------------

本文标题:Assembly language

文章作者:asKylin

发布时间:2018年09月03日 - 19:09

最后更新:2019年02月10日 - 23:02

原始链接:http://askylin.top/2018/09/03/Assembly-language/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。