技术标签: 汇编语言 Powered by 金山文档 开发语言
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。
(1) 用来存储相关指令的某些执行结果;
(2) 用来为CPU执行相关指令提供行为依据;
(3) 用来控制CPU的相关工作方式。
8086CPU中的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),标志寄存器以下简称为flag。标志位如图:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF
flag的1,3,5,12,13,14,15位在8086CPU中没有使用,不具有任何含义。而0,2,4,6,7,8,9,10,11位都具有特殊的含义。
flag的第6位是ZF,零标志位。它是记录相关指令执行后,其结果是否为0.结果如果为0,那么zf=1;如果结果不为0,那么zf = 0。
比如,指令:
mov ax,1
sub ax, 1
执行后结果为0,则zf = 1.
mov ax,2
sub ax,1
执行后结果不为0,则zf = 0
在计算机中1表示逻辑真,0表示逻辑假,所以,zf = 1结果为0,zf = 0结果不为0.
注意,在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如, add, sub, mul, div, inc, or, and等,他们大都是运算指令(进行逻辑或算术运算):有的指令的执行对标志寄存器没有影响,比如,mov, push, pop等,他们大都是传送指令。在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的那些标志位造成影响。
flag的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,pf=1,如果为奇数,那么pf = 0.
比如,指令:
mov al,1
add al,10
执行后结果为0000 1011B,其中3(奇数)个1,则pf = 0;
mov al, 1
or al, 2
执行后,结果为 0000 0011B,其结果有2(偶数)个1,则pf = 1;
flag的第7位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf = 1;如果结果非负,sf = 0;
SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关执行影响了它的值。
这也就说,CPU在执行add等指令时,时必然要影响到SF标志位的值的。至于我们需不需要这种影响,
那就看我们如何看待指令所进行的运算了。
比如:
mov al,1000 0001B
add al,1
执行后,结果为1000 0010B,sf = 1,表示:如果指令进行的是有符号数运算,那么结果为负;
mov al,1000 0001B
add al, 0111 1111B
执行后,结果为0,sf = 0,表示:如果指令进行的是有符号数运算,那么结果为非负。
flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向跟高位的进位值,或者从跟高位的借位值。
比如:
mov al,98H
add al, al; 执行后:(al) = 30H, CF = 1, CF记录了从最高有效位向更高位的进位值
add al, al; 执行后:(al) = 60H, CF = 0, CF记录了从最高有效位向更高位的进位值
mov al, 97H
sub al, 98H ; 执行后:(al) = FFH,CF = 1, CF记录了向跟高位的借位值
sub al, al ; 执行后:(al) = 0,CF = 0, CF记录了向更高位的借位值
当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,两个8位数据:98H+98H,将产生进位。由于这个进位值在8位数中无法保存。其实CPU在运算的时候,并不丢弃这个进值位,而是记录在一个特殊的寄存器的某一位上。8086CPU就用flag的CF位来记录这个进位值。
flag的第11位是OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生了溢出,OF = 1,如果没有,OF = 0。
这里的溢出只针对有符号数
比如:
mov al, 98
add al, 99
执行后将产生溢出。因为进行有符号数运算是:
(al) = (al) + 99= 99 + 98 = 197 注意:这里是十进制的数。
197超出了机器所能表示的8位有符号数的范围: -128~127
add执行后:CF = 0,OF= 1,即他没有产生进位,但发生了溢出。进位对于无符号数,溢出对于有符号数。
所以对于机器码进行add或者sub等都会影响OF,CF。至于要使用那种影响要看如何看待这段机器码。
adc是带位加法指令,它利用了CF位上记录的进位值 (可以理解位带进位的加法)
指令格式:adc操作对象1,操作对象 2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
比如指令 adc ax, bx 实现的功能是:(ax) = (ax) + (bx) + CF
例:
mov ax, 2
mov bx, 1
sub bx, ax
adc ax ,1
执行后, (ax) = 4. adc执行时, 相当于计算:(ax) + 1 + CF = 2 + 1 + 1 = 4.
看一下CF的含义。在执行adc指令的时候加上的CF的值的含义,是由adc指令前面的指令决定的,也就是说,关键在于加上的CF值是被什么指令设置的。显然,如果CF的值是被sub指令设置的,那么它的含义就是错位值;如果是被add指令设置的,那么它的含义就是进位值。我们来看一下两个数据:0198H和0183H如何相加的:
01 98
01 83
+
03 1B
可以看出,加法可以分两步进行:1,低位相加;2高位相加再加上低位相加产生的进位值。
下面的指令和adc ax, bx 具有相同的结果:
add al, bl
adc ah,bh
看来CPU提供adc指令的目的,就是来进行加法的第二步运算的。adc指令和add指令相配合就可以对更大的数据进行加法运算。我们来举一个例子:
编程计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
因为两个数据的位数都大于16,用add指令无法进行计算。我们将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。程序如下。
mov ax , 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H
adc指令执行后,也可能产生进位值,所以也会对CF位进行设置。由于有这样的功能,我们就可以对任意大的数据进行加法运算。
例如:
编程,计算 1E F000 1000H + 20 1000 1EF0H, 结果放在ax(最高16位),bx(次高16位),cx(低位16位)中。
计算分3步进行:
(1)先将低16位相加, 完成后,CF中记录本次相加的进位值;
(2)再将次高16位和CF(来自低16位的进位制)相加,完成后,CF中记录本次相加的进位值;
(3)最后高16位和CF(来自次高16位的进位值)相加,完成后,CF中记录本次相加的进位值。
程序如下。
mov ax, 001EH
mov bx, 0F000H
mov cx, 1000H
add cx, 1EF0H
adc bx, 1000H
adc ax 0020H
sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sub操作对象1,操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
比如指令 sbb ax, bx 实现的功能是: (ax) = (ax) - (bc) - CF
sbb指令执行后,将对CF进行设置。利用sbb指令可以对任意大的数据进行减法运算。比如,计算003E1000H - 00202000H,结果放在ax,bx中,程序如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
sbb和adc是基于同样的思想设计的两条指令,在应用思路上和adc类似。
cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp指令格式:cmp 操作对象 1, 操作对象2
功能:计算操作对象1 -操作对象2但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
比如,指令cmp ax,ax,做(ax) - (ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位,指令执行后:zf = 1, pf = 1, sf = 0, cf = 0, of = 0.
下面指令:
mov ax, 8
mov bx,3
执行后:(ax) = 8, zf = 0, pf = 1, sf = 0, cf = 0, of = 0.
其实,我们通过cmp指令执行后,相关标志位的值就可以看出比较的结果。
无符号数比较
cmp ax, bx
如果(ax) = (bx) 则(ax) - (bx) = 0,所以:zf = 1;
如果(ax)(bx) 则(ax) - (bx) = 0,所以:zf = 0;
如果(ax)<(bx) 则(ax) - (bx)将产生借位,所以:zf = 1;
如果(ax)(bx)则 (ax) - (bx)不必借位,所以:cf = 0;
如果(ax)>(bx)则(ax) - (bx)既不必借位,结果又不为0,所以:cf = 0并且zf = 0;
如果(ax)(bx) 则(ax)-(bx)即可能借位,结果可能为0,所以:cf = 1或zf =1.
现在我们可以看出比较指令的设计思路,即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较结果。
即:cmp ax, bx
执行后:;
zf = 1,说明(ax)=(bx)
zf = 0,说明(ax) (bx)
cf = 1,说明 (ax) <(bx)
cf = 0,说明 (ax) ≥ (bx)
cf = 0 && zf = 0, 说明(ax) > (bx)
cf = 1或 zf = 1, 说明(ax) ≤(bx)
有符号数比较
cmo ah, bh
zf = 1,说明(ah)=(bh)
zf = 0,说明(ah) (bh)
sf = 1&& of = 0 (ah) < (bh)
// of = 0,说明没有溢出,逻辑上真正结果正负=实际结果正负;
sf = 1, 而of = 1 (ah) > (bh)
//of = 1, 说明有溢出,如果因为溢出导致了结果为负,那么逻辑上真正的结果必然为正
sf = 0, of = 1 (ah) < (bh)
//of = 1, 说明有溢出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。
sf = 0, of = 0 (ah)≥(bh)
单纯地考查sf的值不可能知道结果的正负。因为sf记录的是ah中的8位二进制信息所表示的数据的正负。cmp, ah, bh执行后,sf记录的是(ah) - (bh)所得到的8位结果数据的正负。
虽然这个结果没有在我们能够使用的寄存器或内存单元中保存,但是在指令执行的过程中,它暂存在CPU内部的暂存器中。
所得到的相应结果的正负,并不能说明,运算所应该得到的结果的正负。这是因为在运算的过程中可能发生溢出。如果有这种情况发生,那么,sf的值就不能说明任何问题。比如:
mov ah, 22H
mov bh, 0A0H
sub ah, bh
结果 sf = 1,运算实际得到的结果是(ah) = 82H,但是在逻辑上,运算所应该得到的结果是:34 -(-96) = 130. 就是因为130这个结果作为一个有符号数超出了-128~127这个范围,在ah中不能表示,而ah中的结果被CPU当作有符号数解释位-126。而sf被用来记录这个实际结果的正负,所以sf = 1。但是sf = 1不能说明在逻辑上,运算所得到的正确结果的正负。
又比如:
mov ah 08AH
mov bh,070H
cmp ah,bh
结果 sf = 0,运算(ah) - (bh)实际得到的结果是1AH,但是在逻辑上,运算所应该得到的结果是:(-118) - 112 = -230. sf记录实际结果的正负,所以sf = 0.但sf = 0不能说明在逻辑上,运算所得到的正确结果。
所以。我们应该在考察sf(得知实际结果的正负)的同时考查of(得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。
因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(他们检测zf,cf的值)和根据有符号数的比较结果进行转移的条件转移指令(他们检测sf,of和zf的值)
下面是常用的根据无符号数的比较结果进行转移的条件转移指令
指令 含义 检测的相关标志位
jcxz cx值为0则转移 cx = 0
je 等于则转移 zf = 1
jne 不等于则转移 zf = 0
jb 低于则转移 cf = 1
jnb 不低于则转移 cf = 0
ja 高于则转移 cf = 0且 zf = 0
jna 不高于则转移 cf = 1或zf = 1
这些指令比较常用,他们都很好记忆,他们的第一个字母都是j,表示jump;后面的字母表示意义如下。
e: 表示equal
ne:表示not equal
b:表示below
nb:表示not below
a:表示above
na:表示not above
比如:
编程,统计data段中数值为8的字节的个数,用ax保存统计结果
data段的8个字节如下:
data segment
db 8, 11, 8, 1, 8, 5, 63,38
data ends
程序如下:
mov ax, data
mov ds, ax
mov bx, 0 ;ds:bx指向第一个字节
mov ax, 0 ;初始化累加器
mov cx , 8
s: cmp byte ptr [bx], 8 ;和8进行比较
jne next ;如果不相等转到next。继续循环
inc ax ;如果相等就将计数值+1
next: inc bx
loop s ;程序执行后:(ax) = 3
这个程序也可以这样写:
mov ax, data
mov ds, ax
mov bx, 0 ;ds:bx指向第一个字节
mov ax, 0 ;初始化累加器
mov cx , 8
s: cmp byte ptr [bx], 8 ;和8进行比较
je ok ;如果相等转到ok
jmp short next ;如果不相等就转到next。继续循环
ok:inc ax ;如果相等就将计数值+1
next:inc bx
loop s
比起第一个程序,他直接遵循了“等于8则计数+1”的原则,用je指令检测等于8的情况,但是没有第一个程序精简。第一个程序用jne检测不等于8的情况,从而间接检测等于8的情况。要注意在使用cmp和条件转移指令时的这种编程思想。
flag的第10位是DF,方向标志位。在串处理指令中,控制每次操作后si,di的增减。
df = 0每次操作后si,di递增;
df = 1每次操作后si,di递减。
串传送指令
格式:movsb
功能:执行movsb指令相当于进行下面几步操作。
(1)((es)* 16 +(di) ) = ((ds)* 16 +(si) )
(2)如果df = 0则:(si) = (si) + 1
(di) = (di) + 1
如果df = 1则:(si) = (si) - 1
(di) = (di) - 1
movsb的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减
格式:movsw
movsw的功能是将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,格式如下:
rep movsb
用汇编语法来描述rep movsb的功能就是:
s:movsb
loop s
可见,rep的作用是根据cx的值,重复执行后面的串传送指令。由于每执行一次movsb指令si和di都会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)个字符的传送。
由于flag的df位决定着串传送指令执行后,si和di改变的方向,所以CPU应该提供相应的指令来对df位指令进行设置,从而是程序员能够决定传送的方向。
8086CPU提供下面两条执行对df位进行设置。
cld指令:将标志寄存器的df位置0
std指令:将标志寄存器的df位置1
编程,用串传送指令,将data段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!'
db 16 dup(0)
data ends
我们分析一下一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是:
1 传送的原始位置:ds:si;
2 传送的目的位置:es:di;
3 传送的长度:cx;
4 传送的方向:df;
在这个问题中,这些信息如下。
1 传送的原始位置:data:0;
2 传送的目的位置:data:0010;
3 传送的长度:16;
4 传送的方向:因为正方向传送(每次串传送指令执行后,si和di递增)比较方便,所以设置df = 0。
mov ax, data
mov ds, ax
mov si, 0 ;ds:si指向data : 0
mov es ,ax
mov di, 16 ;es:di指向data : 0010
mov cx, 16 ;(cx) = 16,rep循环16次
cld ;设置df = 0, 正向传送
rep movsb
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。
pushf和popf,为直接访问标志寄存器提供了一种方法。
标志寄存器在Debug中的表示
在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。在Debug中,我们可以看到下面的信息。
标志 值为1的标记 值为0的标记
of OV NV
sf NG PL
zf ZR NZ
pf PE PO
cf CY NC
df DN UP
文章浏览阅读1.6w次,点赞27次,收藏193次。从制造缺陷开始,介绍scan chain的基本原理_tessent lpct
文章浏览阅读222次。在APP中,用本地数据库好于频繁的联网去获取相关数据。我使用SQLite作为本地的数据库,比较轻巧。英雄首先先建立英雄的数据库,暂时我先只设置ID,name,loaclized_name这三种,我这是在窗体应用程序里写的,不要在意那些细节,其实我们只是要先创建这么一个.db文件 public Form1() { I...
文章浏览阅读231次。Project Reactor 是 Spring WebFlux 的御用响应式编程库,与 Spring 是兄弟项目。关于如何基于Spring的组件进行响应式应用的开发,欢迎阅读系列文章《响应式Spring的道法术器》。官方参考文档地址:http://projectreactor.io/docs/core/release/reference/中文翻译文档地址:http://htmlprevi..._reactor 指南中文版v3.0
文章浏览阅读727次。TeamTalk部署问题及解决方案1、部分源下载地址2、编译安装libiconv报错3、找不到tt4、编译im-server5、缺少daeml6、找不到mysql.h7、centos7 mini 安装后无法连接到网络8、使用mwget提高下载速度9、nginx: [emerg] unknown log format "access" in错误解决方法10、PHP报错1、部分源下载地址gmpwget ftp://ftp.gnu.org/gnu/gmp/gmp-5.1.3.tar.gzmpfrwge_teamtalk ../daeml: 没有那个文件或目录
文章浏览阅读2.4k次。java中将多文件字节流压缩成zip核心就是使用java.util.zip包中的ZipOutputStream直接上核心代码/** * * @param zipFilePath zip保存路径 * @param zipFileName zip文件名 * @param byteList 文件字节码Map,k:fileNam..._java将多个字节流压缩成zip
文章浏览阅读141次。该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程项目运行环境配置:Pychram社区版+ python3.7.7 + Mysql5.7 + HBuilderX+list pip+Navicat11+Django+nodejs。项目技术:django + python+ Vue 等等组成,B/S模式 +pychram管理等等。环境需要1.运行环境:最好是python3.7.7,我们在这个版本上开发的。其他版本理论上也可以。2.pycharm环境:pycharm都可以。_django在线考试系统
文章浏览阅读812次。前面所有的GIT远程端配置文件都放在是根目录的,所有请求默认都是根目录,但是有时候,项目很多,配置文件需要根据子目录来划分,这时候,就需要来配置搜索路径了;比如aaa项目的配置文件放aaa目录下,bbb项目的配置文件放bbb目录下,不配置的话是找不到的那些配置文件的,需要配置search-paths属性来实现在microservice-config-server-4001项目的yml文件加个配..._configservices路径
文章浏览阅读62次。多行代码的函数:std::cout << R"()";
文章浏览阅读2.2k次。简介:借助CSDN平台发布自己编写的需求文档。参考了前辈们的经验,编写如下(持续更新):正文:PRD:云迹扶教APP产品需求文档一、文档综述1.1版本历史 制订时间 制订人 制订内容 2021.06.13 Max 撰写文档 1.2输出环境 文档名称 云迹扶教A_公益类app用户画像描述
文章浏览阅读2k次,点赞2次,收藏8次。std::string cstring;QString qstring;//从std::string 到QStringqstring = QString(QString::fromLocal8Bit(cstring.c_str()));//从QString 到 std::stringcstring = std::string((const char *)qstring.toLocal8Bit().constData());_c++ 中文 string 转qstring乱码
文章浏览阅读2.6k次。中文的标点符号包括句号,逗号,感叹号,问号,引号,冒号等等,接下来分享常见的中文标点符号名称。常见的中文标点符号1.句号:【。】用于句子末尾,表示陈述语气。有时也可表示较缓和的祈使语气和感叹语气。2.问号:【?】用于句子末尾,表示疑问语气(包括反问、设问等疑问类型)。在多个问句连用或表达疑问语气加重时,可叠用问号。3.叹号:【!】用于句子末尾,主要表示感叹语气,有时也可表示强烈的祈使语气、反问语气..._html顿号
文章浏览阅读994次。那么上面提到的这些能力,我们都可以利用哪些加密算法来实现呢?咱们接着往下看。加密算法整体上可以分为:不可逆加密、可逆加密。可逆加密又可以分为:对称加密、非对称加密。_摘要加密