汇编语言笔记

寄存器

通用寄存器

以16位寄存器为例,我们记为AX[15:0]

AX、BX、CX、DX、均可分为高八位[15:8](AH,BH,CH,DH)和低八位[7:0](AL、BL、CL、DL)

字在寄存器中的存储

字节:记为byte,一个字节由8bit组成,可以存在8位寄存器中。

字:记为word,一个字由2字节组成,这两个字节记为高位字节和低位字节。

几条汇编指令

  1. mov ax,18:将18送入ax寄存器
  2. mov ah,78:将78送入ah
  3. add ax,8:将ax的值加8
  4. mox ax,bx:将bx的值送入ax
  5. add ax,bx:将ax与bx相加放入ax

注意:在执行加法运算时,视al与ah为独立的寄存器,也就是说如果单独对al进行加法运算时,在最高位产生进位则不会对ah产生影响。

在进行数据传输或运算时,应注意指令的两个操作对象位数应该统一,否则视为错误指令,示例如下:

注意:如下指令均为错误指令!!!

  1. mov ax,bl ;八位向十六位传输
  2. mov bh,ax ;十六位向八位传输
  3. mov al,20000 ;超过寄存器储存限制
  4. add al,100H ;将高于八位的数传入八位寄存器

8086CPU给出物理地址的方法

A1

当8086CPU读写内存时:

  1. CPU中的相关部件提供两个十六位地址,一个称为段地址,另一个称为偏移地址;
  2. 段地址和偏移地址通过内部总线送入地址加法器;
  3. 地址加法器将两个16位地址合为20位物理地址;
  4. 地址加法器通过内部总线将物理地址送入控制电路;
  5. 输入输出控制电路将物理地址送入地址总线;
  6. 物理地址被地址总线送入存储器

地址加法器的合成方法:物理地址=段地址<<4+偏移地址

偏移地址为16位,16位的寻址能力位64KB,所以一个段的长度最大为64KB。

段寄存器

我们介绍关于段地址的一些寄存器,段地址在8086CPU的段寄存器中存放,这四个寄存器为:CS,DS,SS,ES

CS&IP

CS和Ip是8086CPU中最关键的两个寄存器:CS是代码段寄存器;IP是指令指针寄存器。指令地址即CS<<4+IP。

8086CPU的工作过程可简要描述如下:

  1. 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓存器;
  2. IP=IP+所读取的指令长度,从而指向下一条指令;
  3. 执行指令。转到步骤(1),重复这个过程。

修改CS、IP的指令

mov指令未提供修改CS、IP指令的功能(大概是因为这会引起混乱)

但我们可以通过“jmp 段地址:偏移地址”修改CS,IP。若想仅修改IP的内容,可用“jmp 某寄存器”的形式完成,如:jmp ax这个指令形式上等价于mov IP,ax

内存中字的存储

CPU中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。由于内存单元是字节单元,所以一个字需要两个地址连续的内存单元来存放。

由此我们引出字单元的概念:字单元即存放一个字形数据(16位)的内存单元,由两个地址连续的内存单元组成。

DS和[address]

CPU读取一个内存单元时,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址组成。DS寄存器通常存放要访问数据的段地址。比如要读取/写入10000H单元的内容过程可如下所示:

1
2
3
4
5
6
7
8
;read
mov bx,1000H
mov ds,bx
mov al,[0]
;write
mov bx,1000H
mov ds,bx
mov [0],cx

注意:8086CPU不支持将立即数传入ds寄存器的操作,故我们需要先将地址装入另一个无关寄存器,再通过这个寄存器放入ds。

1
2
3
4
5
;一些基础指令
mov ax,bx ;将bx移入ax
add ax,bx ;将ax与bx求和放入ax
sub ax,bx ;将ax-bx放入ax
;这三个指令支持的类型有三个:寄存器,存储单元,立即数

CPU的栈机制

1
2
3
push ax	;将ax的数据入栈
pop ax ;将栈顶的数据弹出并存入ax
;栈的操作支持数据存储类型:存储单元,寄存器

关于栈,8086CPU存在两个寄存器:段寄存器SS和寄存器SP。

栈顶段地址存放在SS中,偏移地址存放在SP中。任意时刻SS:SP指向栈顶元素。push/pop指令执行时,CPU从SS和SP中得到栈顶的地址。

注意到:SS:SP的机制与CS:IP的机制相同,故一个栈段的大小也为64KB。

[bx]表示一个内存单元,它的偏移地址在bx中

我们定义(reg)表示寄存器中的数据

Loop指令

loop指令的格式:loop标号,CPU执行loop指令时,要进行两部操作:

  1. (cx)=(cx)-1
  2. 判断cx的值,不为0则转至标号处执行程序,如果为0则向下执行

eg:计算2^12

1
2
3
4
5
6
7
8
9
10
11
assume cs:code
code segment
mov ax,2

mov cx,11
s: add ax,ax
loop s
mov ax,4c00h
int 21h
code ends
end

段前缀

在普通的mov ax,[bx]指令中,内存单元的偏移地址由bx给出,而段地址默认在ds中。我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器:

1
2
3
4
mov ax,ds:[bx]
mov ax,cs:[bx]
mov ax,ss:[bx]
mov ax,es:[bx]

以上的ds,cs,es,ss均为段前缀

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

and & or指令

and指令:做按位与

or指令:做按位或

[bx + idata]

寄存器相对寻址

[bx + idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加idata)

eg:

1
mov ax,[200+bx]

这个命令是将((ds)*16+(bx)+200)送入ax中

有了这种记录方式,就可以利用loops循环操作数组(不断改变bx来改变偏移量)

SI和DI

基址变址寻址

SI和DI是x86中和bx功能相似的寄存器,它们不能被分为两个8位寄存器。

1
2
mov ax,[200+si]
mov ax,[s00+di]

相对基址变址寻址

可以用bx,si,di,idata互相组合构成偏移量:

1
2
3
4
5
6
mov ax,[bx+idata]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bx+si+idata]
mov ax,[bx+di+idata]
mov ax,[bx+si+di+idata]

可以操作二,三维数组。bp寄存器与si,di作用相同。

指令要处理的数据长度

通过寄存器名指明要处理的数据尺寸

字操作

1
2
3
4
mov ax,1
mov bx,ds:[0]
inc ax
add ax,1000

字节操作

1
2
3
4
5
mov al,1
mov al,bl
mov al,ds:[0]
inc al
add al,100

用操作符 X ptr 指明内存单元的长度

X可以为word或byte

word ptr指明内存单元为字单元

1
2
3
mov word ptr ds:[0],1
inc word ptr [bx]
add word ptr [bx],2

byte ptr指明内存单元为字节单元

1
2
3
mov byte ptr ds:[0],1
inc byte ptr [bx]
add byte ptr [bx],2

div指令

div为除法指令:

  1. 除数:有8位或16位两种,在一个reg或内存单元中。

  2. 被除数:默认放在ax/dx&ax中,若除数8位,被除数则为16位,默认在ax中存放;若除数为16位,被除数则为32位,在dx和ax中存放,dx放高16位,ax放低16位。

  3. 结果:如果除数为8位,则al存储除法操作的商,ah放余数;若除数为16位,则ax存储商,dx存储余数。

格式如下:

div reg

div 内存单元

1
2
3
4
div byte ptr ds:[0]
div word ptr es:[0]
div word ptr [bx+si+8]
div byte ptr [bx+si+8]

伪指令dd

db为字节型数据,dw为字型数据,dd为double型数据(32位)

dup操作符

dup用来与dw,db,dd等数据定义伪指令配合使用的,用来进行数据的重复:

1
2
3
db 3 dup (0)				;等价于db 0,0,0
db 3 dup (0,1,2) ;等价于db 0,1,2,0,1,2,0,1,2
db 3 dup ('abc','ABC') ;等价于db‘abcABCabcABCabcABC'

转移指令

可以修改IP,或同时修改CS和IP的指令称为转移指令;概括的说,转移指令就是可以控制CPU执行内存中某处代码的指令。

x86中转移有以下几类:

  1. 只修改IP时,称为段内转移,比如jmp ax
  2. 同时修改CS和IP时,称为段间转移,比如jmp 1000:0

由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移。

  1. 短转移的IP修改范围为-128~127
  2. 近转移的IP修改范围为-32768~32767

x86的转移指令分为以下几类:

  1. 无条件转移指令
  2. 条件转移指令
  3. 循环指令
  4. 过程
  5. 中断

操作符offset

offset的功能是取得标号的偏移地址,比如:

1
2
3
4
5
6
7
assume cs:codesg
codesg segment
start:mov ax,offset start ;相当于mov ax,0
s:mov ax,offset s ;相当于mov ax,3

codesg ends
end start

jmp指令

jmp指令要给出两种信息:

  1. 转移的目的地址
  2. 转移的距离(段间转移、段内短转移、段内近转移)

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

eg:

1
2
3
4
5
;示例:jmp short 标号		;转到标号处执行指令
start: mov ax,0
jmp short s
add ax,1
s: inc ax

add ax 1这个指令由于jmp被跳过未执行,故ax中的数据为1。

注意:s标号在机器语言中也是立即数,表示为偏移地址(类似于c语言中的函数指针)

在jmp指令转换为机器语言后,机器语言只会保留jmp指令的指令号(EB)和跳转地址相对当前地址的偏移量作为标号

jmp short 标号的功能为:(IP)=(IP)+8位位移

8位位移=标号处地址-jmp指令后第一个字节的地址

8位位移的范围是-128~127,用补码表示

类似于jmp short 标号,我们还有jmp near ptr 标号,其功能为IP=(IP)+16位位移。这个指令实现了段内近转移。

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

上述jmp指令,其对应的机器指令是相对于当前IP的转移位移

“jmp far ptr 标号“实现的是段间转移,又称为远转移:

far ptr指明了指令用标号的段地址和偏移地址修改CS和IP

eg:

1
2
3
4
5
6
start:mov ax,0
mov bx,0
jmp far ptr s
db 256 dup (0)
s:add ax,1
inc ax

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

指令格式:jmp 16位register

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

  1. jmp word ptr 内存单元地址(段内转移)
  2. jmp dword ptr 内存单元地址(段间转移)

第一个指令的功能是在指定内存中读一个数据作为IP的偏移地址,并执行jmp指令

第二个指令的功能是在指定内存中的读取连续存放的两个字,高地址处存目的段地址,低地址存目的偏移地址,

(CS) = (内存单元地址+2)

(Ip) = (内存单元地址)

jcxz指令

jcxz指令为有条件转移指令,所有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围:-128~127

指令格式:jcxz 标号

当(cx)=0时,(IP)=(IP)+8位位移;当(cx)$$0时,什么都不做(程序向下执行)。

用c代码来翻译即是:

1
if((cx) == 0)jmp short 标号

loop指令

loop指令为循环指令,所有循环指令都是短转移,对应的机器码包含转移地址,对IP的修改范围都是:-128~127

指令格式: loop 标号((cx)=(cx)-1,如果(cx)$$0,则转到标号处执行)

根据位移进行转移的意义

保存位移地址的原因是:使得程序存储在内存任何位置都可以得到正确执行,否则若用绝对地址存储的话,改变指令位置则会出错。

CALL和RET指令

ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移;

retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。

执行ret指令进行如下操作:

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

执行retf指令进行如下操作:

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

call指令

执行call指令时进行两步操作:

  1. 将当前的IP或CS和IP压入栈中
  2. 转移

call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同

依据位移进行转移的call指令

call指令的格式: call 标号(将当前的IP压栈后,转到标号处执行指令)

执行call指令时,具体操作为:

  1. (sp)=(sp)-2

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

  2. (IP)=(IP)+16位位移

call 标号指令等价于:

1
2
push IP
jmp near ptr 标号

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

前面讲的call指令,其对应的机器指令中并没有转移的目的地址,而是相对于当前IP的转移位移。

“call far ptr 标号"实现的是段间转移。

执行此种格式的call指令时,进行如下操作:

  1. (sp)=(sp)-2

    (cs)=((ss)*16+(sp))

    (sp)=(sp)-2

    (ip)=((ss)*16+(sp))

  2. (cs)=标号所在段的段地址

    (IP)=标号在段中的偏移地址

call far ptr 标号等价于:

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

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

指令格式:call 16bits register

功能:

(sp)=(sp)-2

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

(IP)=(16位register)

此种指令等价于:

1
2
push IP
jmp 16位reg

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

转移地址在内存中的call指令有两种格式。

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

mul指令

  1. 两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存单元字节单元中;如果是16位,一个默认在ax中,;ing一个放在16位reg或内存单元字节中。
  2. 结果:如果是八位,结果默认放在ax中;如果是16位,高位默认在dx中存放,低位在ax中存放。

标志寄存器

CPU内部寄存器中,有一种特殊的寄存器,具有以下三种作用:

  1. 用来存储相关指令的某些执行结果
  2. 用来为CPU执行相关指令提供行为依据
  3. 用来控制CPU的相关工作方式

这种特殊的寄存器在8086CPU中被称为标志寄存器。标志寄存器有16位,其中储存的信息通常被称为程序状态字,之前使用的ax,bx,cx,dx,si,di,bp,sp,IP,cs,ds,es皆是标志寄存器,本章的标志寄存器(以下简称flag)是最后一个。

flag与其它寄存器不一样,其它寄存器用来放数据,都是整个寄存器具有一个意义,而flag寄存器是按位起作用的,其每一位都有专门的含义:

flag[11]=OF

flag[10]=DF

flag[9]=IF

flag[8]=TF

flag[7]=SF

flag[6]=ZF

flag[4]=AF

flag[2]=PF

flag[0]=CF

ZF标志

flag第六位是0标志位,它记录相关指令操作后其结果是否为0:是0则ZF为1;非0则ZF为0

eg:

1
2
3
4
5
6
mov ax,1
sub ax,1
;ax为0,所以ZF为1
mov ax,2
sub ax,1
;ax为1,所以ZF为0

注意:运算指令是有可能影响标志寄存器的,要注意是否对标志寄存器的某些标志位产生了影响

PF标志

flag的第二位为PF,它记录了相关指令执行后,其结果的所有bit位中1的个数是否为偶数:是偶数则PF为1;反之为0

SF标志

flag的第七位为SF,符号标志位,它记录了相关指令执行后其结果是否为负:是负数则SF为1;反之为0

注:无符号数的SF位无意义,只有有符号数的SF位才会影响结果

CF标志

flag的第0位为SF,进位标志位。一般情况下,在进行无符号数的运算时,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。

OF标志

flag的第11位为OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出:若发生溢出,OF=1;反之OF=0

注:OF是对有符号数运算有意义的标志位;CF是对无符号数运算有意义的标志位。

adc指令

adc是带进位加法指令,它利用了CF位上记录的进位值。

eg:

1
adc ax,bx	;ax = (ax) + (bx) + CF

adc指令的意义:执行加法运算中高位-低位分离的大数加法(与add指令一同完成)

eg:计算1EF0001000H+2010001EF0H,结果放在ax(最高16位),bx(次高16位),cx(低16位)中。

计算分三步:

  1. 先将低16位相加,完成后CF记录本次相加的进位值
  2. 再将次高16位和CF(低16位)相加,完成后CF记录本次相加的进位值
  3. 最后高16位和CF(次高16位)相加,完成后CF记录本次相加的进位值

sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。

指令格式:

1
sbb ax,bx	;(ax) = (ax) - (bx) - CF

cmp指令

cmp是比较指令,功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

指令格式:

1
cmp ax,bx	;若ax-bx=0,ZF=1,否则ZF=0,其余对标志位影响与减法指令相同

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!