汇编语言 第11章 标志寄存器

隐藏

8086寄存器总结

ax、bx、cx、dx通用寄存器

除了通用用途,且可分割为byte外(即al ah之类),还有额外用途:

  1. ax(Accumulator, 累加器)可用于乘法、除法,保存被除数或者某个乘数的值,以及结果,以及程序返回,用累加器进行操作可能需要更少时间。
  2. bx可以作为指定内存地址,例如 [bx] ,默认代表 ds:[bx]的内存地址, 也可用cs:[bx]、ss:[bx]等。
  3. cx可用与loop循环次数。
  4. dx可用于当ax的乘除法位数不够用时辅助, 例如32位除法dx表示被除数高位,或者32位结果的高位。

bx和si、di、bp寻址寄存器

bp与bx不同点在于bp默认是用于栈,ss:[bp], 而bx默认是普通内存地址ds:[bx] 此外bp不是通用寄存器,因此不能分割为byte计算

si、di则是辅助bx和bp做高维数据寻址: [bx+si] [bp+si] [bx+di] [bp+di] [bx+si+di] [bp+si+di]

ss、sp栈寄存器

ss(Stack Segment)是栈的段地址 sp(Stack Pointer)是栈的偏移地址 ss:sp指向栈顶。

注意sp赋值应该是 栈顶元素+2。

关于栈内数据和SP的操作顺序,可以把栈想象成一个弹夹, SP表示弹夹里的弹簧,数据表示子弹, 在push装子弹的过程中,首要要把弹簧压下去,所以先执行 sp-2 当pop弹出子弹的过程中,首先子弹出来,弹簧才会弹上去,所以后执行 sp+2

还有栈可用于call、ret中存储子函数的参数和子函数的指令地址, 注意参数的压栈顺序,而子函数的指令地址最后压栈,故在栈顶端。

8086不确定,现代的子函数存储在堆栈帧中, 所谓堆栈帧就是每个子函数独立一个栈,避免意外造成相互影响。 此外,猜测用栈来存储子函数猜测原因是调用\返回的访问顺序正好与栈的后进先出顺序一致。

cs、IP指令寄存器

cs(Code Segment)是指令的段地址 IP(Instruction Pointer)是指令的偏移地址 cs:IP指向内存之中正在执行的指令。

指令数据都是二进制的机器码,所以cs:IP指向的内存空间被指明为指令,否则都是二进制谁也不知道是指令还是数据。

注意当指令进入指令缓冲器还没有执行时,IP先增加到执行下一个指令,然后再执行。 这在确定jmp偏移量时非常重要。

ds内存数据地址寄存器

ds(Data Segment)是内存数据地址的段地址 ds:[idata]指向地址空间

[idata]可以是常数 [2] 或者寄存器[bx] 或者 组合[bx+di+2]等等。

通常默认情况下,ds:[0]都指向代码空间的最开头, 然后默认100H空间的psp段用于与系统shell交互 接着是cs:[IP]

es寄存器

用途不明,或许是Extra Segment的简称,额外的。 寄存器不够用的时候辅助一下, 目前已知特殊用途是用在串传递中(见下文)从ds:si到es:di

标志寄存器

以上用13个寄存器,8086有14个寄存器,最后一个寄存器是标志寄存器, 用16位,其中有9位用于状态标识,结构如下:

[15] [14] [13] [12] [OF] [DF] [IF] [TF] [SF] [ZF] [5] [AF] [3] [PF] [1] [CF]

ZF 零标志位

Zero Flag

如果某指令执行后其结果为0,则zf变为1,不为0,则zf变为0 例如:

mov ax, 1
sub ax, ax

结构为0,所以zf执行到此处时为 1

add、sub、inc、mul、div、or、and等运算指令会对zf有影响, push、mov、pop等传送指令对zf没有影响。

PF 奇偶标志位

Parity Flag,单词parity有奇偶性的意思,例如奇偶校验(Parity Check)

如果结果所有bit中1的个数是偶数则为1,奇数为0。

SF 正负号标志位

Sign Flag sign英文即正负号,

结果为负,则sf = 1 结果非负,则sf = 0

这个标志位与signed数的最高位用处是一样的

此外,计算机计算无符号数和有符号补码方式是一样的,结果的bits一样,神奇的地方。 所以可以无视到底算无符号还是有符号,只是输出结果时转化一下即可。

CF 进位标志位

Carry Flag,Carry英文:进位

在进行无符号数运算时,它记录运算结果的最高有效位进位值(是否发生进位,类似正溢出),或者从更高有效位借位值(是否发生借位,类似负溢出)。

正溢出的例子:1000 0000B + 1000 0000B = 80H+80H - 100H = 00H, cf = 1

负溢出的例子:97H - 98H,发生向更高位借位,实际197H - 98H = FFH结果, 此时cf = 1

OF 溢出标志位

Overflow Flag

在进行有符号数计算时,结果超过机器表示范围称为溢出

如果结果溢出,of = 1

CF和OF的区别

CF和OF并非同时变化 例如:

补码正溢出:
64+64 = 0100 0000B + 0100 0000B = 1000 0000B,
对于补码而言已经正溢出,结果为 -128
如果是无符号数,则没有发生借位。
因此补码正溢出不一定会发生对于无符号数的借位,
因为正溢出考虑的是次高位,而无符号借位考虑的是最高位。
&
1111 1111B + 1111 1111B = 1111 1110 = -2
如果是无符号数发生了借位,但是对于补码,没有溢出

总体而言: 对于无符号数,观察借位标志位CF,对于补码,观察溢出标志位OF。CF和OF没任何关系。

利用了标志位的指令

adc指令

adc(add-carry)是带进位加法指令,利用CF记录进位值。

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

adc的用途

扩展了可计算数的范围,任意整数加法都可计算 例如。add ax,bx 相当于:

add al,bl
adc ah,bh

可以看出从8位数计算扩展到16位数,类似扩展到32位、64位、128位。。。 只是计算复杂度增加了。

sbb指令

sbb即带借位的减法指令,利用CF记录借位值。

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

作用于adc类似,扩展了减法范围,实现任意大数减法计算。

cmp指令

cmp是比较指令,cmp相当于减法指令,只是不保存结果。 cmp执行后,将对标志位产生影响。接着,其他指令通过标志位判断比较结果。 cmp通过标志位,可以实现多种大小比较,

对于无符号数,主要通过CF标志位和ZF标志位判断,如下:

$$ \text{cmp ax, bx} \begin{cases} \text{(ax)} = \text{(bx)}, & zf = 1 \\ \text{(ax)} \neq \text{(bx)}, & zf = 0 \\ \text{(ax)} < \text{(bx)}, & cf = 1 \\ \text{(ax)} \geq \text{(bx)}, & cf = 0 \\ \text{(ax)} > \text{(bx)}, & cf = 0 并且 zf = 0\\ \text{(ax)} \leq \text{(bx)}, & cf = 1 或者 zf = 1\\ \end{cases} $$

以上结果是针对无符号数而言, 对于有符号数,判断等于、不等于根据zf判断,判断大小则考察sf和of

$$ \text{cmp ax, bx} \begin{cases} \text{(ax)} = \text{(bx)}, & zf = 1 \\ \text{(ax)} \neq \text{(bx)}, & zf = 0\\ \text{(ax)} < \text{(bx)}, & sf = 1 并且 of = 0 \\ \text{(ax)} < \text{(bx)}, & sf = 0 并且 of = 1 \\ \text{(ax)} > \text{(bx)}, & sf = 1 并且 of = 1 \\ \text{(ax)} \geq \text{(bx)}, & sf = 0 并且 of = 0 \\ \end{cases} $$

以上结果是根据sf和of的取值,判断大小关系。 如果是 <= 稍微麻烦一点,要么zf =0 或者 sf和of异号。

条件转移指令

对于8086,所有条件转移指令位移在[-128,127]之间。

大多数条件转移指令都检测标志寄存器相关标志位,以此决定是否修改IP。 通常都和cmp指令相配合。就像call和ret相配合一样。

然而,由于cmp既可以比较无符号,也可以比较补码, 因此对于的条件跳转检测zf、cf或者zs、sf、of有所不同。 因此应该是不同的条件跳转指令。

因本章节只介绍了无符号的条件跳转,有符号略。 在没有完整的内容的情况下就不做相关笔记了。

DF标志和串传送指令

DF(Direction Flag)方向标志位,在串处理指令中,控制每次操作后si、di的增减。

df = 0,每次操作后si、di递增; df = 1,每次操作后si、di递减;

串传送指令

格式:movsb(move string byte)

顺带说一下 string 串 和stream 流的区别,故名思意,一串是静态的, 所以静态的文本,字符串就是string。 stream 流 是动态的,所以比如输入输出之类,数据不会长期存在,后续数据会覆盖。 所以stream比较适合针对I/O

功能:相当于以下步骤:

1. ( (es)*16 + (di) ) = ( (ds)*16 + (si) )
2. if df = 0    then (si) = (si)++ ;(di) = (di)++
                else (si) = (si)-- ;(di) = (di)--

可以看出,movsb功能是将ds:si指向的内存空间字节送到es:di中,然后根据df标志位的值,将si、di递增或递减1,

movsb是复制byte,而movsw复制word,所以对于es、di 加2 或者 减2

一般movsb/movsw和rep指令配合使用

格式: rep movsb 功能是:

s:movsb
  loop s

可见,rep根据cx的值,重复执行串传送指令。

那么如何设置df来控制串复制的方向呢?

cld 指令(clear df),将df设置为0

std 指令(set df),将df设置为1, 例如栈数据复制就适合负方向。或者复制一个数据的末尾片段。

pushf 和 popf

pushf将标志寄存器的值压栈,popf将数据从栈中弹出,送入标志寄存器。 注意8086中可以pushf然后pop ax,也可以push ax然后popf, 即并没有为标志位寄存器单独开栈空间。

回顾

目前而言,13个寄存器全部涉及, 标志位寄存器中的 OF (Overflow Flag) DF (Direction Flag) SF (Sign Flag) ZF (Zero Flag) PF (Parity Flag) CF (Carry Flag) 已涉及

IF、TF、AF未涉及。(TF、IF用于中断) TF(Trap Flag)用于debug中的单步执行中断指令 IF(Interrupt-enable Flag)用于可屏蔽外中断的屏蔽指令,详见第15章

进阶

此文为第一次学习寄存器的总结,包括AX、BX、CX、DX的用途,BP的用途AF的用途等还没有彻底认识。

而如下level2 文中详解了相关部分,当然,level 2中有些地方不够详尽,例如借位、溢出的区别,比如IF屏蔽的本质,好在能从本章和第15章中得到解答,所以最好两篇文章综合来看。

level 2: [转载]寄存器总结 level 2

-----EOF-----

Categories: 汇编 Tags: 汇编 底层 寄存器