第一节 跳过数据区
jmp near start
;跳过下面的数据区
mytext db 'L', 0x07, 'a', 0x07, 'b', 0x07, 'e', 0x07, '1', 0x07, ' ', 0x07, 'o', 0x07, \ 'f', 0x07, 'f', 0x07, 's', 0x07, 'e', 0x07, 't', 0x07, 0x07, ':', 0x07
;标号:mytext,通过db指令预先存储指令的开头字符,从而使数据和指令分离
number db 0, 0, 0, 0, 0
start:
mov ax, 0x07c0
... ...
\
作用:连接字符串jmp near start
:使处理器跳转到指定的标号位置处执行程序- 第一条指令执行完成后,处理器会跳转到
start
位置处执行start
位置处的指令
- 第一条指令执行完成后,处理器会跳转到
number db 0, 0, 0, 0, 0
:预留内存,以备将来用于存储除法运算所得到的余数(标号十进制各个数位上的值)start
:标号,可单独占一行,由于start
后没有指令,所以也就不存在汇编地址,故start
的值等于紧挨着的下一行指令的汇编地址,处理器执行改行指令,效果与start: mov ax, 0x07c0
相同
第二节 初始化段地址
- 程序被加载到段地址为
0000
,偏移地址为7c00
的位置处,由于程序不是从段开始的位置处加载的,所以在访问程序中内存单元的时候需要用7c00
到0000
之间的部分加上内存单元在程序中的偏移地址(汇编地址)得到内存单元在段内的偏移地址7c00+汇编地址
- 段地址
0000
与程序加载的起始位置7c00
会形成一个距离差,故每次访问内存单元的时候需要给内存单元中的汇编地址加上一个偏移地址
- 段地址
- 程序开始加载时的内存地址为
07c00
,对于内存地址来说,可以拥有多个逻辑地址(0000:7c00
和07c00:0000
),内存地址是由段地址加偏移地址所组成,故内存地址的段地址可以看作0000
或07c0
,偏移地址则可以看作7c00
或0000
- 第一种情况:
0000:7c00
- 段地址(
0000
)左移4位,得到一个20位的段地址(00000
),加上偏移地址(7c00
),结果仍为7c00
- 段地址(
- 第二种情况:
07c0:0000
- 段地址(
07c0
)左移4位,得到一个20位的段地址(07c00
),加上偏移地址(0000
),结果仍为7c00
- 段地址(
- 两种情况效果相同,指向同一位置
7c00
,但第二种情况较第一种情况简单,在第二种情况中,将数据段寄存器DS
中的值设置为07c0
,也就是说段的开始位置是7c00
,程序开始加载的位置是0000
,处于同一行,换句话说,程序是从段开始的位置加载的,不需要再加偏移地址
- 第一种情况:
- 内存地址可以有多个逻辑地址,只要段地址左移四位以后加上偏移地址能够指向目标地址即可
mov ax, 0x07c0
mov ds, ax
;将07c0存储到数据段寄存器DS中,作为程序的段地址(此时未对程序进行分段,不能作为数据段的段地址)
;此处两行代码的目的是为后续访问的程序中的内存单元做准备
mov ax, 0xb800
mov es, ax
;将b800存储到附加段寄存器ES中,b800为显存的段地址
;此处两行代码的目的是为后续访问显存中的内存单元做准备
第三节 Movsb
和Movsw
Movsb
(byte)和Movsw
(word)- 作用:数据的批量传送
- 使用规则:
- 所要传送的数据:
DS:SI
- 传送数据的位置由数据段寄存器
DS
和源变址寄存器SI
指定,数据的段地址存储在DS
寄存器中,偏移地址存储在SI
寄存器中
- 传送数据的位置由数据段寄存器
- 所要传送的位置:
ES:DI
- 目标位置由附加段寄存器
ES
和目的变址寄存器DI
指定,数据的段地址存储在ES
寄存器中,偏移地址存储在DI
寄存器中
- 目标位置由附加段寄存器
- 传送的次数:必须存储到计数寄存器
CX
中 - 指定方向:正向或反向
- 正向:
DI、SI自动 +1 或 +2(使用Movsb指令自动 +1,使用Movsw指令自动 +2)
- 要传送的下一个字节的位置在高位地址中,每传送一次
DI
和SI
的值会自动 +1或 +2 - 传送方向:低地址 ----> 高地址,与内存加载地址方向一致
- 传送位置:低地址 ----> 高地址
- 要传送的下一个字节的位置在高位地址中,每传送一次
- 反向:
DI、SI自动 -1 或 -2(使用Movsb指令自动 -1,使用Movsw指令自动 -2)
- 要传送的下一个字节的位置在低位地址中,每传送一次
DI
和SI
的值会自动 -1或 -2 - 传送方向:高地址 ----> 低地址,与内存加载地址方向相反
- 传送位置:高地址 ----> 低地址
- 要传送的下一个字节的位置在低位地址中,每传送一次
- 正向:
- 格式:
Movsb
----> 传送一次Movsw
----> 传送一次- 由于所要传送的数据、所要传送的位置和传送次数都被指定,故
Movsb
和Movsw
指令后不需要任何操作数
-
Rep Movsb
----> 批量传送Rep Movsw
----> 批量传送- 批量传送的指令会根据指令指针寄存器
CX
中的值决定传送次数 - 每传送一次数据,计数寄存器
CX
中的值就会自动减1,直到计数寄存器cx
中的值等于0,停止传送
- 所要传送的数据:
第四节 方向标志位和零标志位
PSW(Processor Status Word Register)
:微处理器状态字(又叫做FLAG Register:标志寄存器)
- 0:
CF(Carry Flag)
---- 进位标志位 ---- 运算时最高位产生进位(或借位)时:CF = 1
- 2:
PF(Parity Flag)
---- 奇偶标志位 ---- 运算结果的低8位中1的数量为偶数时:PF = 1
- 4:
AF(Auxiliary carry Flag)
---- 辅助进位标志位 ---- 运算时低4位向高4位产生进位(或借位)时:AF = 1
- 6:
ZF(Zero Flag)
---- 零标志位- 决定
Movsb
和Movsw
什么时候停止数据的传送 - ---- 运算结果为0时:
ZF = 1
- 疑问:第三节中说由指令指针寄存器
CX
中的值决定,为什么在这一节又说由零标志位ZF
决定呢?- 批量传送状态下(Rep前缀),每传送一次数据,
CX
寄存器中的值会自动减1,但处理器的运算结果会影响到零标志位,所以当CX
寄存器中的结果减1等于0时,零标志位的值会被设置为1;当CX
寄存器中的结果减1不等于0时,零标志位的值会被设置为0,当进行运算的时候,便会影响到零标志位的值,零标志位中的值才是真正决定Movsb
和Movsw
指令是否停止传送的关键
- 批量传送状态下(Rep前缀),每传送一次数据,
- 零标志位、
Movsb
和Movsw
、CX
之间的关系:Movsb
和Movsw
先进行数据的传送,每传送一次数据,CX
寄存器中的值会自动减1,减1的运算则会影响到零标志位,零标志位又决定了Movsb
和Movsw
指令是否停止传送
- 决定
- 7:
SF(Sign Flag)
---- 符号标志位 ----SF = 运算结果最高位的值
- 8:
TF(Trap Flag)
----跟踪标志位- ----
TF = 1时
,CPU处于单步状态,每执行一条指令自动产生一次内部中断 - ----
TF = 0时
,CPU恢复正常
- ----
- 9:
IF(Interrupt enable Flag)
---- 中断允许标志位- ----
IF = 1时
,处理器可以响应外部中断 - ----
IF = 0时
, 处理器不响应外部中断
- ----
- 10:
DF(Direction Flag)
----方向标志位- 决定
Movsb
和Movsw
的方向 - ----
DF = 1时
,处理器以递减的顺序处理字符串,地址从高到低递减(反向) - ----
DF = 0
时, 处理器以递增的顺序处理字符串,地址从低到高递增(正向)
- 决定
- 11:
OF(Overflow Flag)
---- 溢出标志位 ---- 运算时产生溢出:OF = 1
- 注:8、9、10这三个标志位用来控制CPU的状态,它们的值由指令进行设置,其他标志位的值根据当前的情况自动的刷新
第五节 向显存写入数据
cld ;clear direction 的缩写,作用:为movsw指令设置方向“正向”
;作用:将方向标志位清0:正向
;std指令(set direction的缩写):与cld指令相对应,将方向标志位设置为1(简称“置1”):反向
mov si, mytext
;将mytext标号存入到源变址寄存器SI中
;SI寄存器存储的是所要传送的数据的偏移地址,所要传送数据的段地址存储在第二节的DS寄存器中,并且将ds的值设置为7c0,左移四位变为7c00得到真实的段地址,DS:SI组成的逻辑地址指向数据所在的位置(第一节中的字符`L`处),表示将要传送的数据从字符`L`开始
mov di, 0
;将目的变址寄存器DI的值设置为0
;对于movsw指令来说,DI寄存器存储的是数据所要传送位置的偏移地址(0),所要传送数据的段地址(b800)存储在第二节的ES寄存器中,ES:DI决定了所要传送的位置,指向显存的第一个存储单元,对于movsw指令来说,代表了数据所要传送的位置
mov cx, (number - mytext)/2
;number - mytext的值=他们之间数据的字节数,movsw每次传送1个字(2个字节)的数据
;number - mytext/2得到所要传送的次数,将其存入到CX寄存器中
rep movsw
;将 ('L', 0x07, 'a', 0x07, 'b', 0x07, 'e', 0x07, '1', 0x07, ' ', 0x07, 'o', 0x07, \ 'f', 0x07, 'f', 0x07, 's', 0x07, 'e', 0x07, 't', 0x07, 0x07, ':', 0x07)字符及其属性从显存的第一个存储单元开始,依次存储到显存中
第六节 运算前的准备
mov ax, number
;在屏幕上显示number标号的十进制数,将其存储到AX寄存器中,
;AX中存储的是被除数的低16位
;目的:让number参与第一次除法运算,得到标号各个数位上的值
mov bx, ax
;等价于 mov bx, number 将number的值存储到BX寄存器中,作为第一个存储单元的偏移地址,程序的段地址为第二节中的ds,DS:BX指向第一节中的number db 0, 0, 0, 0, 0
;目的,用于以后存储计算机所得到的标号各个数位上的值
mov cx, 5
;将运算的次数5(0, 0, 0, 0, 0为5个字节的空间)存储到CS寄存器中,
mov si, 10
;将10存入源变址寄存器SI中,做为除数
;上述四行代码为运算前的准备工作
digit:
xor dx, dx
;将高16位寄存器DX中的值清零,低16位寄存器AX中的值为number
;意图:做32位÷16位的运算,所以除数必须是16位,SI寄存器为16位寄存器
;所以将10存入源变址寄存器SI中,做为除数
第七节 分解各个数位
digit:
;标号独占一行
xor dx, dx
;将DX寄存器中的指令清零
div si
mov [bx], dl
;地址内为BX寄存器,编译器在进行编译时会将第六节bx中的值(number)拿过来并覆盖当前BX寄存器中的值
;dl:8位寄存器,存储余数,本质上存储的是标号在个位上的值
;DS寄存器中存储段地址(7c0),bx寄存器中存储偏移地址(number),DS:bx指向第一节中db(number db 0, 0, 0, 0, 0)指令声明的第一个存储单元的地址
;[bx]:指向第2条db指令所声明的第1个存储单元,在进行loop循环时会分别存储标号个十百千万位上的值
inc bx
;inc(increase)将bx寄存器中的值 +1
;目的:下一个循环中,让[bx]指向第一节中db指令所声明的第2个存储单元,本质上存储标号十位上的数
;dec(decline):将BX寄存器中的值 -1
loop digit
;循环执行digit,直到CX寄存器中的值为0,跳出digit循环,向下执行
第八节 LOOP指令
-
作用:循环
-
循环次数:
CX
-
执行效果:
CX - 1
-
CX = 0
,处理器继续执行后面的代码,不跳转CX ≠ 0
,处理器将跳转到指定的标号处去执行
Movsb
、Movsw
指令执行效果:CX - 1
-
CX = 0
,停止数据的传送CX ≠ 0
,继续数据的传送CX - 1
的值会影响到零(ZF)标志位
,再由零标志位直接影响movsb、movsw
的执行效果(CX
中的值会间接影响)
-
-
-
注意事项(跳转功能):
-
-
格式(和
JMP指令
进行对比):LOOP 标号 / JMP 标号
-
编译器是如何编译它的,或者说操作数是怎么来的
E2 F7
为loop指令中的机器指令,E2
为操作码,F7
为操作数F7
操作数的计算方法(与JMP指令一致
):操作数 = 标号的汇编地址 - 当前指令的汇编地址 - 当前指令的长度- 跳转是如何实现的?
原理:修改
CS:IP
的值 但
loop指令和jmp指令
不会改变代码段寄存器CS的值,只会修改指令指针寄存器IP
的值 修改
IP
值的方法:
-
-
第九节 变址寄存器
- SI(Source Index Register)用于存储所要传送数据(数据源)的偏移地址
- DI(Destination Index Register)用于存储所要传送数据位置(目的地)的偏移地址
- 由于SI和DI寄存器中存储的是指令的偏移地址,偏移地址会随着指令的执行而发生变化,所以叫做变址寄存器
- 8个通用寄存器中包含2个变址寄存器
- 通用寄存器含义:除了可以用于指定的目的以外,也可以用在其他一些常用场合,例如存储数据、中转数据、参与运算等
- 段寄存器和控制寄存器则只能用于指定的目的
第十节 显示各个数位
mov bx, number
;bx:基址寄存器,保持不变
mov si, 4
;SI:源变址寄存器,发生变化
show:
;标号
mov al, [bx + si];此处more使用ES或者DS寄存器作为段地址(7C00)指向第一节中db指令的最后一个0
;第1次循环,将标号百位上的值存储到al寄存器中
;第2次循环,将标号千位上的值存储到al寄存器中
add al, 0x30
;获取标号各个数位值的ASCII码
mov ah, 0x04
;0x04:字符属性
mov [es: di], ax
;标号的ASCII码及其属性存入显存中
;第1次循环 ----> 万位 第2次循环 ----> 千位
add di, 2
;将di的值+2,为后面的循环存储标号数位上值得ASCII码及其属性做准备
dec si
;减1操作,为后面的循环访问标号各个数位的值做准备
jns show
;jns:条件转移指令,处理器在执行完jns指令后,下一个机器周期会跳转到指定的标号处(show)继续执行
第十一节 JNS
指令
- 7:
SF(Sign Flag)
---- 符号标志位 ----SF = 运算结果最高位的值
JNS指令
根据符号标志位SF
的值来决定是否进行跳转- SF = 0,跳转
- SF = 1,不跳转
dec
指令的值会影响到符号标志位,jns
指令根据符号标志位的值来决定是否跳转
- 格式、操作码的计算方法、实现跳转的原理:
- 与第八节中
loop指令
相同,但执行效果完全不同
- 与第八节中
第十二节 JCC
指令族
JCC指令 | 中文含义 | 英文原意 | 检查符号位 |
---|---|---|---|
JZ/JE | 若为0则跳转; 若相等则跳转 | jump if zero; jump if equal | ZF = 1 |
JNZ/JNE | 若不为0则跳转; 若不相等则跳转 | jump if not zero; j ump if not equal | ZF = 0 |
JS | 若为负则跳转 | jump if sign | SF = 1 |
JNS | 若为正则跳转 | jump if not sign | SF = 0 |
JP/JPE | 若1出现次数为偶数则跳转 | jump if Parity(Even) | PF = 1 |
JNP/JPO | 若1出现次数为奇数则跳转 | jump if parity(odd) | PF = 0 |
JO | 若溢出则跳转 | jump if overflow | OF = 1 |
JNO | 若无溢出则跳转 | jump if not overflow | OF = 0 |
JC/JB/JNAE | 若进位则跳转; 若低于则跳转; 若不高于等于则跳转 | jump if carry; jump if below; jump if not above equal | CF = 1 |
JNC/JNB/JAE | 若无进位则跳转; 若不低于则跳转; 若高于等于则跳转 | jump if not carry; jump if not below; jump if above equal | CF = 0 |
JBE/JNA | 若低于等于则跳转; 若不高于则跳转 | jump if below equal; jump if not above | AF = 1或 CF = 1 |
JNBE/JA | 若不低于等于则跳转; 若高于则跳转 | jump if not below equal; jump if above | ZF = 0或 CF = 0 |
JL/JNGE | 若小于则跳转; 若不大于等于则跳转 | jump if less; jump if not greater equal | SF != OF |
JNL/JGE | 若不小于则跳转; 若大于等于则跳转 | jump if not less; jump if greater equal | SF = OF |
JLE/JNG | 若小于等于则跳转; 若不大于则跳转 | jump if less equal; jump if not greater | SF != OF或 ZF = 1 |
JNLE/JG | 若不小于等于则跳转; 若大于则跳转 | jump if not less equal; jump if greater | SF = OF且 ZF = 0 |
JCXZ | 若CX为零则转移 | jump CX zero | 无 |
第十三节 CMP
指令
SUB
:减法指令- 格式:
SUB OP1, OP2
- 执行效果:
OP1 - OP2 --> OP1
CMP
:比较指令- 格式:
cmp op1, op2
- 执行效果:
op1 - op2 --> CF\OF\SF\ZF\AF\PF(标志位)
第十四节 “跳转指令”之间的对比
- 无条件跳转指令:
jmp
- 条件跳转指令:
JCC指令族
- 处理器在执行到
JCC指令
时,会根据当前各个标志位的值
来决定是否跳转当前各个标志位的值
必须满足跳转所要求的条件,否则处理器不会进行跳转,而是继续向下执行
- 处理器在执行到
- 通常会将
JCC指令
和cmp指令
配合在一起执行运算,通过cmp指令
来改变标志位的值,JCC指令
根据标志位的值来进行判断,选择是否跳转 - 也可在
JCC指令
前通过其他运算指令改变标志位的值,进而决定JCC指令
的执行效果 - 循环指令:
LOOP
- 根据
零标志位ZF
决定是否跳转 - 处理器在执行loop指令的时候,会先执行
CX - 1
的运算,该运算直接影响到零标志位- 无论loop指令前的运算将
ZF
的值改为多少,都不会影响loop指令
的运算,- 原因:loop指令在执行运算之前,会先执行
CX - 1
运算,该运算会将ZF
标志位刷新,保证loop指令
根据最新的零标志位的值做出是否跳转的指令
- 原因:loop指令在执行运算之前,会先执行
- 无论loop指令前的运算将
loop指令
在执行运算之前,会先执行CX - 1
运算,故不会受loop前任何其他指令的影响
- 根据
第十五节 $和$$
标记
mov word [es: di], 0x0744
;该条指令的数据宽度为1个字(该指令的目的操作数是一个地址,源操作数是一个立即数,编译器无法进行判断,故需要在前面显示的添加一个word修饰符来明确表示该条指令是按字操作的)
;高字节07:字符属性 低字节44:字符D的ASCII码
;将字符D的字符属性和ASCII码存入到显存中标号值得后面
;由于intel处理器是小端存储,故从右向左进行存储
jmp near $ ;无限循环
;在指令中使用$标记,那么在指令开头会默认隐藏式的存在一个$标记
; == $ jmp near $
;执行效果 == infi jmp near infi
; $:当作标号来用
times 510 - ($ - $$) db 0
; == $ times 510 - ($ - $$) db 0
;$ = 当前指令的汇编地址
;$$ = 当前段($$所在的段)的起始汇编地址,若未分段,则起始汇编地址为程序开头的地址
;510 - ($ - $$) = 为保证0x55和0xaa的位置,所需要添加的字节数
db 0x55, 0xaa