作为汇编语言的课程笔记,方便之后的复习与查阅
本篇为课程第三、四次课内容
以VS2019为例,对C语言程序进行反汇编:
C语言程序对应的汇编代码如下:
先打开 内存 和 寄存器 窗口
下面进行程序跟踪:
第一步是将4
存储到变量a
的地址中,执行完成后查看内存地址(在监视窗口中查看&a
即可找到a
的地址),如下所示:
可以看出,在内存中,低字节在低地址,高字节在高地址
按照这个方法,很容易理清上面这个c语言程序在底层是如何进行操作的
CPU主要包含指令执行的运算和控制部件,以及多种寄存器。对程序员来说,CPU就抽象为了以名称存取的寄存器
早期计算机系统的程序通常是被硬连线到电路中的,也就是说,计算机的电路完全决定了计算机能够执行什么算法。为了使用这种计算机来解决不同的问题,我们不得不改变计算机的电路
后来,计算机设计的一大进步是可编程计算机系统,它允许计算机操作员使用插孔(socket)和插线(plug wire)(插接板系统,patch board system)很容易地“重写”计算机系统
在这种系统中,一个程序由多排插孔组成,每排代表程序执行过程中的一个操作。程序员可以通过插入一个插线到某条指令对应的插孔中来决定哪些指令将被执行
毫无疑问,这种方案的一大问题是支持的指令个数严格受限于物理上每行可以安排的插孔数
CPU设计师们很快就发现,加上一点点额外的逻辑电路就可以减少需要的插孔个数,支持n个不同指令的插孔数由n降到了log2(n)个。他们的做法是给每个指令赋一个唯一的数值编码,然后使用二进制数来表示这些编码
计算机设计的一个主要进步就是存储程序计算机的发明。早期的计算机设计师们发现可以将机器指令的数值表示存储在主存中,当CPU需要执行指令的时候,可以从内存中取得指令的数值表示,将该二进制数装入到一个特殊的寄存器中并对指令进行编码
方法就是给CPU添加电路,也就是控制单元(control unit,CU)。控制单元使用一个专用的寄存器,指令指针,来保存指令的二进制值(也称为操作码,opcode)的地址。控制单元从内存中取得指令的操作码,放入指令译码寄存器(instruction decoding register)执行。在指令执行完毕之后,控制单元递增指令指针。
8086内部结构有两个功能模块,完成一条指令的取指和执行功能
以下面的汇编指令执行过程为例进行说明:
mov ax, 64h
add ax, 100h
mov [2000h], ax
CPU先通过IP
寄存器找到它要执行的代码指令在内存中的位置10100H
,如上图所示,指令1即为B8 64 00
(mov ax, 64h
对应的机器码)。CPU先将B8
译码,知道要向ax
寄存器放两个字节的数据。然后将B8
放入指令队列,等待执行该指令。同时IP
寄存器又找到10101H
,把64
拿过来放到ax
寄存器低八位,然后IP
寄存器又找到10102H
,把00
拿过来放到ax
寄存器高八位
然后IP
寄存器指向指令2,05
译码后是将ax
寄存器的值与内存中数据相加,于是CPU先把ax
寄存器的值拿出来做准备。同时IP
寄存器又拿来了00
和 01
分别是内存中数据的低八位和高八位,之后就进行加法运算后结果再存回ax
寄存器
第三条指令的执行过程类似,a3
代表取出ax
寄存器的值,再依次读入00
20
送入地址加法器与段地址相加,得到目标的内存地址。注意最后是低八位放在了低地址处
16
位通用寄存器:
ax
, bx
, cx
, dx
si
, di
, bp
, sp
其中 ax
, bx
, cx
, dx
4个数据寄存器还可分为高8位和低8位两个独立寄存器:
ah
, bh
, ch
, dh
al
, bl
, cl
, dl
32
位通用寄存器(在32位的环境下,原来的16位寄存器扩展成了32位):
eax
, ebx
, ecx
, edx
esi
, edi
, ebp
, esp
64
位通用寄存器(在64位的环境下,原来的16位寄存器扩展成了64位):
rax
, rbx
, rcx
, rdx
rsi
, rdi
, rbp
, rsp
通用寄存器(除了指针寄存器)都可以用在通用计算中存储一些值
ax
, bx
, cx
, dx
数据寄存器用来存放计算的结果和操作数,也可以存放地址
每个寄存器又有它们各自的专用目的:
AX
--累加器(Accumulate Register),使用频度最高,用于算术、逻辑运算以及与外设传送信息等;BX
--基址寄存器(Base Register),常用做存放存储器地址;CX
-- (Counter Register)计数器,作为循环和串操作等指令中的隐含计数器;DX
-- (Data Register)数据寄存器,常用来存放双字长数据的高16位,或存放外设端口地址。si
, di
变址寄存器常用于存储器寻址时提供地址
SI
是源变址寄存器 (Source index)DI
是目的变址寄存器 (Destination index)串操作类指令中,SI
和DI
分别表示串的源地址和目的地址
bp
, sp
指针寄存器用于寻址内存堆栈内的数据
SP
为堆栈指针寄存器(Stack Pointer),指示栈顶的偏移地址SP
不能再用于其他目的,具有专用目的BP
为基址指针寄存器(Base Pointer),表示数据在堆栈段中的基地址SP
和BP
寄存器与SS段寄存器联合使用以确定堆栈段中的存储单元地址ip
ip
用来指示代码段中指令的偏移地址,它与代码段寄存器cs
联用,确定下一条指令的物理地址
计算机通过cs
和ip
寄存器来控制指令序列的执行流程
标志(Flag)用于反映指令执行结果或控制指令执行形式
8086处理器的各种标志形成了一个16位的标志寄存器FLAGS
(程序状态字PSW
寄存器)
CF
, ZF
, SF
, PF
, OF
, AF
DF
, IF
, TF
对于无符号数运算,当运算结果的最高有效位有进位(加法)或借位(减法)时,CF
= 1;否则CF
= 0。
Debug
表示: CY
(Carry) CF = 1; NC
(No Carry) CF = 0
计算机不知道数到底是有符号数还是无符号数。因此都先把它当作无符号数进行相加,再把进位标志存到标志寄存器中
若运算结果为0,则ZF
= 1;否则ZF
= 0
Debug
表示: ZR
(Zero) ZF = 1; NZ
(No Zero) ZF = 0
运算结果最高位为1,则SF
= 1;否则SF
= 0
Debug
表示: NG
(Negative) SF = 1; PL
(Plus) SF = 0
有符号数据用最高有效位表示数据的符号,所以最高有效位就是符号标志的状态
对于有符号数运算,若算术运算的结果有溢出,则OF
=1;否则 OF
=0
Debug
表示: OV
(Overflow) OF = 1; NV
(No Overflow) OF = 0
如果你做的是有符号数相加,就看OF
位,无符号数相加就看CF
位
当运算结果最低字节中“1”的个数为零或偶数时,PF
= 1;否则PF
= 0
Debug
表示: PE
(Parity Even) PF = 1; PO
(Parity Odd) PF = 0
运算时 D 3 D_3 D3(低半字节)有进位或借位时,AF
= 1;否则AF
= 0。
Debug
表示: AC
(Auxiliary Carry) AF = 1; NA
(No Auxiliary Carry) AF = 0
用于串操作
指令中,控制地址的变化方向:
cld
指令:设置DF
=0
,存储器地址自动增加;std
指令:设置DF
=1
,存储器地址自动减少。Debug
表示: DN
(Down) DF = 1; UP
(Up) DF = 0
用于控制外部可屏蔽中断是否可以被处理器响应:
sti
指令:设置IF
=1
,则允许中断;cli
指令:设置IF
=0
,则禁止中断。Debug
表示: EI
(Enable Interrupt) IF = 1; DI
(Disable Interrupt) IF = 0
用于控制处理器进入单步操作方式:
TF
=0
,处理器正常工作TF
=1
,处理器单步执行指令(处理器在每条指令执行结束时,便产生一个编号为1的内部中断,这种内部中断称为单步中断,所以TF也称为单步标志)。利用单步中断可对程序进行逐条指令的调试8086有4个16位段寄存器
CS
(代码段)指明代码段的起始地址SS
(堆栈段)指明堆栈段的起始地址DS
(数据段)指明数据段的起始地址ES
(附加段)指明附加段的起始地址每个段寄存器用来确定一个逻辑段的起始地址,每种逻辑段均有各自的用途
在DOSBox中输入debug
进入调试模式
注意:在debug
下,所有数都默认为16
进制
调试模式下的常用命令:
a
: 汇编。显示当前的内存地址,在后面可以输入一些汇编指令。在输入指令的同时,debug
就会自动把他翻译为2进制机器指令
这里显示内存地址为100h
是因为程序段前缀psp
是100h
个字节
e
: edit 后面加上地址,可以修改内存中的数据
每输入完一个字节按空格继续输入下一个字节,如果不输入而直接按空格代表保持原来数据不变
也可以这样修改:
t
: step into 单步执行
t=100
代表从内存地址100
的地方开始执行,执行一步,执行完之后打印寄存器的值,可以看到ax
的值变了,ip
变了,指向103
即下一条指令所在的地址
t
代表直接执行下一条语句
p
: step over 单步跳过
用法和t
差不多
g
: 断点执行。直接运行,在后面接断点(内存地址)如:g 103
n
: 文件命名
w
: 保存文件
q
: 退出debug
可以看到,hello.com
的程序其实是上面的前5行
int 21
是一个函数,再执行t
就会跟到函数内部,因此使用p
在debug
环境下也可以进行汇编程序编写,只不过编译过程都是由debug自动进行的
debug
环境不支持变量
mov ah,4c
和int 21
是调用DOS系统的4c
号功能,从当前运行的程序中退出回到DOS命令行,即 .exit 0
rcx
用来指定我们要写的数据要写多少字节,把相关要写的字节数放到rcx
中
因为指令存在100h
,数据存在200h
,因此200h-100h
再加上字符串长度即为要写的字节数。这里直接写200
保证足够