0x00 基础知识
- Central Processing Unit为中央处理单元,简称CPU。CPU是一种微处理器,计算机是指由CPU和其他受CPU直接或间接控制的芯片、器件、设备组成的计算机系统,比如常见的PC机。每一种CPU都有自己的汇编指令集。
- 汇编指令通过编译器翻译为机器码,供计算机直接使用。
- 汇编语言由汇编指令、伪指令和其他符号组成。
- 指令和数据在存储器中存放,也就是平时所说的内存。在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
- 存储器被划分成若干个存储单元,每个存储单元从0开始顺序编号,微机存储器的容量以字节为最小单位计算。
- CPU通过地址总线指定存储器单元,地址总线的宽度决定了CPU的寻址能力。
- CPU通过数据总线实现自身与内存或其他器件之间的数据传送,数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量。8088CPU数据总线宽度为8,8086CPU数据总线宽度为16。
- CPU通过控制总线实现对外部器件的控制,控制总线的宽度决定了CPU对系统中其他器件的控制能力。
- 每台PC机都拥有一个主板,主板上有核心器件和一些主要器件,包括CPU、存储器、外围芯片组、扩展插槽等,这些器件通过总线相连。
- 存储器从功能和连接上可分为三类:随机存储器RAM、装有BIOS(Basic Input/Output System,基本输入输出系统)的只读存储器ROM、接口卡上的RAM(如显存)。
- PC机在实际运作过程中,所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。
0x01 寄存器
一个典型的CPU由运算器、控制器、寄存器等器件构成。各器件功能如下:
- 运算器进行信息处理。
- 寄存器进行信息存储。
- 控制器控制各种器件进行工作。
- 内部总线连接各种器件,在它们之间进行数据的传送。
通用寄存器
8086CPU的所有寄存器都是16位的,可存放两个字节。AX、BX、CX、DX这四个寄存器通常用来存放一般性数据,称为通用寄存器。同时,这四个寄存器可分为两个可独立使用的8位寄存器来使用。
以寄存器AX为例,AX的低8位构成AL寄存器,高8位构成AH寄存器。AH和AL寄存器是可以独立使用的8位寄存器。注意,诸如mov ax,bl
这类的汇编语句是错误的,因为其尝试将一个8位寄存器中的值赋值至16位寄存器中。
8086CPU可以一次性处理以下两种尺寸的数据:
- 字节:记为byte,一个字节由8个bit组成,可存在8位寄存器中。
- 字:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节(H)和低位字节(L)。
汇编指令
在写一条汇编指令或一个寄存器的名称时,不区分大小写。
8位寄存器只能存放两位十六进制的数字,若溢出则丢弃最高位。
1
2mov al, C5H
add al, 93H以上代码执行结果为:
158H
,而寄存器al
是8位寄存器,故舍弃最高位1,保存结果58H
。
物理地址
所有的内存单元构成的存储空间是一个一维的线性空间,每个内存单元在这个空间中都有唯一的地址,将该地址称为物理地址。在CPU向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址。
8086CPU是16位结构的CPU,具有以下特性:
- 运算器一次最多可以处理16位的数据。
- 寄存器的最大宽度为16位。
- 寄存器和运算器之间的通路为16位。
8086CPU有20位地址总线,可以传送20位地址。然而,该CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址均为16位。故8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位物理地址。
具体来讲,地址加法器采用物理地址=段地址*16+偏移地址的方法使用段地址和偏移地址合成物理地址。该方法的本质含义是:CPU在访问内存时,用一个基础地址(段地址16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。一般来说,这种寻址功能是”*基础地址+偏移地址=物理地址“寻址模式的一种具体实现方案。
以上寻址方式有以下几个关键点:
- CPU可以用不同的段地址和偏移地址形成同一个物理地址。
- 偏移地址16位,变化范围为
0~FFFFH
,仅用偏移地址来寻址最多可寻64KB个内存单元。
段寄存器
可以根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。8086CPU有4个段寄存器:CS、DS、SS、ES。当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。
CS和IP是8086CPU中两个最关键的寄存器,指示了CPU当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器。任意时刻,CPU将CS:IP
指向的内容当作指令执行。
8086CPU工作过程如下:
- 从
CS:IP
指向的内存单元读取指令,读取的指令进入指令缓冲器。 IP = IP + 所读取指令的长度
,指向下一条指令。- 执行指令,转到第一步并重复。
能够改变CS、IP内容的指令被统称为转移指令,最简单的转移指令是jmp
指令。可以使用形如jmp 段地址:偏移地址
的指令实现对CS、IP内容的同时修改。若只想修改IP的内容,可以使用形如jmp 某一合法寄存器
的指令完成,该指令表示使用寄存器中的值修改IP。
0x02 Debug的使用及相关指令
Windows10系统中已经剔除Debug相关的插件,需要下载DOSBox并手动安装debug.exe才可以使用Debug程序。安装指南链接如下:win10环境下如何安装和运行DOSBox和debug_mengjizhiyou的博客-CSDN博客。
Debug程序中重要指令如下:
R命令
查看、修改CPU中寄存器的内容。r命令可以直接查看所有寄存器的值,使用
r+寄存器名称
语句以修改寄存器的值。D命令
查看内存中的内容。
直接使用d命令将列出Debug预设的地址处的内容。使用命令
d 段地址:偏移地址
的格式来指定查看内存的起始地址,随后接着使用d命令可列出后续内容。采用d 段地址:起始偏移地址 结尾偏移地址
来规定d命令的查看范围。Debug将输出的内容分为三部分:中间是从指定地址开始的128个内存单元的内容,以十六进制的格式输出,每行的输出从16的整数倍地址开始,最多输出16个单元的内容。左边是每行的起始地址。右边是每个内存单元中的数据对应的可显示的ASCII码字符。
同时,D命令支持
d 段寄存器:偏移地址
指令格式。E命令
修改内存中的内容,可以写入数据和指令,在内存中它们实际上没有区别。
可以使用
e 起始地址 数据 数据 数据 ...
的格式修改指定内存范围中的内容。也可以直接输入e 起始地址
,随后以询问的方式逐个修改内存单元中的内容,空格键表示指定内存单元修改完成,Enter键表示e命令执行结束。U命令
将内存中的内容解释为机器指令和对应的汇编指令。
T命令
执行
CS:IP
指向的内存单元处的指令。若需要执行指定内存处存放的指令,则需要利用r命令修改寄存器CS和IP的值,随后才可使用t命令完成指令执行。值得注意的是,T命令在修改寄存器SS的指令时,下一条指令也紧接着被执行,这涉及中断机制。
A命令
以汇编指令的形式向内存中写入指令。
0x03 寄存器内存访问
字的存储
在内存中存储时,由于内存单元是字节单元,则一个字需要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。我们将起始地址为N的字单元简称为N地址字单元。
任何两个地址连续的内存单元既可被看作两个内存单元,也可 被看作一个地址为N的字单元中的高位字节单元和低位字节单元。
DS&[address]
8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。
可以利用mov
指令将一个内存单元中的内容送入一个寄存器中。寄存器通过寄存器名标识,而内存单元需要用内存单元地址指明。[address]
表示一个内存单元,address表示内存单元的偏移地址。指令执行时,CPU自动区ds寄存器中的数字作为内存单元的段地址。
值得注意的是,8086CPU不支持将数据直接送入段寄存器的操作,故需要使用一般寄存器完成中转。
1 | mov ds,1000H ;错误语句 |
mov&add&sub
以上三个指令均支持以下操作:
1 | * 寄存器,数据 |
其中*代表mov,add和sub三类运算。
0x04 栈
基本概念及特性
栈是一种具有特殊访问方式的存储空间,具有后进先出(Last In First Out, LIFO)的特性。
基本操作
8086CPU提供入栈和出栈指令,分别为PUSH和POP,前者将一个新的元素放到栈顶,后者从栈顶去除一个元素。8086CPU的入栈出栈操作都是以字为单位进行的。
入栈时,栈顶从高地址向低地址方向增长,出栈则相反。栈顶元素出栈后,内存对应单元的元素依然存在,但已不在栈中,当再次执行PUSH指令时,写入的新数据会将其覆盖。
SS&SP
8086CPU中存在段寄存器SS和栈指针 寄存器SP。栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP
指向栈顶元素。在对栈进行操作前,首先需要初始化寄存器SS和SP。
例如,若对范围为22000H~2200FH范围的栈进行操作,需首先初始化 SS指向2200H,初始化SP指向000FH+1=0100H。
PUSH&POP
PUSH指令执行过程分为两步:首先执行SP = SP-2
,使SP指向新的栈顶元素;随后向SS:SP
指向的字单元中送入数据。POP指令执行过程同样分为两步:首先从SS:SP
指向的字单元中读取数据;随后执行SP = SP-2
移动SP指针。
可以看出,PUSH和POP等栈操作指令修改的只是SP寄存器,也就是说栈顶的变化范围最大为0~FFFFH。
当栈满的时候再次使用PUSH指令入栈,或栈空的时候再次使用POP指令出栈,都将发生栈顶越界问题。
0x05 段的综述
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。根据使用者的初始化情况,一段内存可以作为代码的存储空间、数据的存储空间和栈空间,并支持这三类空间类型的组合。
数据段
数据段的地址放在DS中,使用mov、add、sub等访问内存单元的指令时,CPU就将指定内存单元中的内容作为数据来处理。
代码段
代码段的段地址放在CS中,段中第一条指令的偏移地址放在IP中,CPU将执行指定内存单元中的指令。
栈段
栈段的段地址 放在SS中,栈顶单元的偏移地址放在SP中,CPU在执行PUSH、POP等栈操作时就将指定内存单元作为栈空间使用。