汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug

作为汇编语言的课程笔记,方便之后的复习与查阅

本篇为课程第三、四次课内容

目录

  • 高级语言程序的运行过程
    • 反汇编
    • 跟踪程序运行过程
  • 8086CPU简介
    • 早期计算机系统
    • 8086CPU功能结构
    • 指令执行的基本过程
    • 8086的寄存器组
      • 8086的8个通用寄存器
        • 数据寄存器
        • 变址寄存器
        • 指针寄存器
      • 指令指针寄存器
      • 标志寄存器
        • 进位标志CF(Carry Flag)
        • 零标志ZF(Zero Flag)
        • 符号标志SF(Sign Flag)
        • 溢出标志OF(Overflow Flag)
        • 奇偶标志PF(Parity Flag)
        • 辅助进位标志AF(Auxiliary Carry Flag)
        • 方向标志DF(Direction Flag)
        • 中断允许标志IF(Interrupt-enable Flag)
        • 陷阱标志TF(Trap Flag)
      • 段寄存器
  • 调试程序DEBUG
    • 常用命令
    • 调试一个文件
    • 用debug实现 hello world
    • 用debug查看寄存器的变化

高级语言程序的运行过程

反汇编

以VS2019为例,对C语言程序进行反汇编:
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第1张图片
C语言程序对应的汇编代码如下:
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第2张图片

跟踪程序运行过程

先打开 内存寄存器 窗口
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第3张图片
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第4张图片
下面进行程序跟踪:
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第5张图片
第一步是将4存储到变量a的地址中,执行完成后查看内存地址(在监视窗口中查看&a即可找到a的地址),如下所示:
在这里插入图片描述
可以看出,在内存中,低字节在低地址,高字节在高地址

在寄存器窗口中可以查看eax寄存器的值:
在这里插入图片描述

按照这个方法,很容易理清上面这个c语言程序在底层是如何进行操作的

8086CPU简介

CPU主要包含指令执行的运算和控制部件,以及多种寄存器。对程序员来说,CPU就抽象为了以名称存取的寄存器

早期计算机系统

早期计算机系统的程序通常是被硬连线到电路中的,也就是说,计算机的电路完全决定了计算机能够执行什么算法。为了使用这种计算机来解决不同的问题,我们不得不改变计算机的电路

后来,计算机设计的一大进步是可编程计算机系统,它允许计算机操作员使用插孔(socket)和插线(plug wire)(插接板系统,patch board system)很容易地“重写”计算机系统

在这种系统中,一个程序由多排插孔组成,每排代表程序执行过程中的一个操作。程序员可以通过插入一个插线到某条指令对应的插孔中来决定哪些指令将被执行

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第6张图片
毫无疑问,这种方案的一大问题是支持的指令个数严格受限于物理上每行可以安排的插孔数

CPU设计师们很快就发现,加上一点点额外的逻辑电路就可以减少需要的插孔个数,支持n个不同指令的插孔数由n降到了log2(n)个。他们的做法是给每个指令赋一个唯一的数值编码,然后使用二进制数来表示这些编码

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第7张图片

计算机设计的一个主要进步就是存储程序计算机的发明。早期的计算机设计师们发现可以将机器指令的数值表示存储在主存中,当CPU需要执行指令的时候,可以从内存中取得指令的数值表示,将该二进制数装入到一个特殊的寄存器中并对指令进行编码

方法就是给CPU添加电路,也就是控制单元(control unit,CU)。控制单元使用一个专用的寄存器,指令指针,来保存指令的二进制值(也称为操作码,opcode)的地址。控制单元从内存中取得指令的操作码,放入指令译码寄存器(instruction decoding register)执行。在指令执行完毕之后,控制单元递增指令指针。

8086CPU功能结构

8086内部结构有两个功能模块,完成一条指令的取指执行功能

  • 总线接口单元BIU,主要负责与内存交互,读取指令和操作数
  • 执行单元EU ,主要负责指令译码和执行

指令执行的基本过程

  • 取指
  • 执行

以下面的汇编指令执行过程为例进行说明:

mov ax, 64h
add ax, 100h
mov [2000h], ax

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第8张图片
CPU先通过IP寄存器找到它要执行的代码指令在内存中的位置10100H,如上图所示,指令1即为B8 64 00(mov ax, 64h 对应的机器码)。CPU先将B8译码,知道要向ax寄存器放两个字节的数据。然后将B8放入指令队列,等待执行该指令。同时IP寄存器又找到10101H,把64拿过来放到ax寄存器低八位,然后IP寄存器又找到10102H,把00拿过来放到ax寄存器高八位

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第9张图片
然后IP寄存器指向指令2,05译码后是将ax寄存器的值与内存中数据相加,于是CPU先把ax寄存器的值拿出来做准备。同时IP寄存器又拿来了0001 分别是内存中数据的低八位和高八位,之后就进行加法运算后结果再存回ax寄存器

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第10张图片
第三条指令的执行过程类似,a3代表取出ax寄存器的值,再依次读入00 20送入地址加法器与段地址相加,得到目标的内存地址。注意最后是低八位放在了低地址处

8086的寄存器组

  • 执行单元EU 8个通用寄存器
  • 1个指令指针寄存器
  • 1个标志寄存器
  • 4个段寄存器

8086的8个通用寄存器

16位通用寄存器:
ax, bx, cx, dx
si, di, bp, sp

其中 ax, bx, cx, dx4个数据寄存器还可分为高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)

串操作类指令中,SIDI分别表示串的源地址和目的地址

指针寄存器

bp, sp

指针寄存器用于寻址内存堆栈内的数据

  • SP为堆栈指针寄存器(Stack Pointer),指示栈顶的偏移地址
  • SP不能再用于其他目的,具有专用目的
  • BP为基址指针寄存器(Base Pointer),表示数据在堆栈段中的基地址
  • SPBP寄存器与SS段寄存器联合使用以确定堆栈段中的存储单元地址

指令指针寄存器

ip

ip用来指示代码段中指令的偏移地址,它与代码段寄存器cs联用,确定下一条指令的物理地址

计算机通过csip寄存器来控制指令序列的执行流程

标志寄存器

标志(Flag)用于反映指令执行结果或控制指令执行形式
8086处理器的各种标志形成了一个16位的标志寄存器FLAGS(程序状态字PSW寄存器)

程序设计需要利用标志的状态,比如条件语句
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第11张图片

  • 状态标志:CF, ZF, SF, PF, OF, AF
    用来记录程序运行结果的状态信息,许多指令的执行都将相应地设置它
  • 控制标志:DF, IF, TF
    可由程序根据需要使用指令设置,用于控制处理器执行指令的方式

进位标志CF(Carry Flag)

对于无符号数运算,当运算结果的最高有效位有进位(加法)或借位(减法)时,CF = 1;否则CF = 0。

Debug表示: CY(Carry) CF = 1; NC(No Carry) CF = 0

计算机不知道数到底是有符号数还是无符号数。因此都先把它当作无符号数进行相加,再把进位标志存到标志寄存器中
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第12张图片

零标志ZF(Zero Flag)

若运算结果为0,则ZF = 1;否则ZF = 0

Debug表示: ZR(Zero) ZF = 1; NZ(No Zero) ZF = 0

在这里插入图片描述

符号标志SF(Sign Flag)

运算结果最高位为1,则SF = 1;否则SF = 0

Debug表示: NG(Negative) SF = 1; PL(Plus) SF = 0

有符号数据用最高有效位表示数据的符号,所以最高有效位就是符号标志的状态
在这里插入图片描述

溢出标志OF(Overflow Flag)

对于有符号数运算,若算术运算的结果有溢出,则OF=1;否则 OF=0

Debug表示: OV(Overflow) OF = 1; NV(No Overflow) OF = 0

在这里插入图片描述

  • 第一个例子对于八位有符号数来说已经溢出了 -128~127
  • 第二个例子是一个正数加一个负数,没有超过范围

如果你做的是有符号数相加,就看OF位,无符号数相加就看CF

奇偶标志PF(Parity Flag)

当运算结果最低字节中“1”的个数为零或偶数时,PF = 1;否则PF = 0

Debug表示: PE(Parity Even) PF = 1; PO(Parity Odd) PF = 0

辅助进位标志AF(Auxiliary Carry Flag)

运算时 D 3 D_3 D3(低半字节)有进位或借位时,AF = 1;否则AF = 0。

Debug表示: AC(Auxiliary Carry) AF = 1; NA(No Auxiliary Carry) AF = 0

这个标志主要由处理器内部使用,用于十进制算术运算调整指令中
在这里插入图片描述

方向标志DF(Direction Flag)

用于串操作指令中,控制地址的变化方向:

  • cld指令:设置DF0,存储器地址自动增加;
  • std指令:设置DF1,存储器地址自动减少。

Debug表示: DN (Down) DF = 1; UP(Up) DF = 0

中断允许标志IF(Interrupt-enable Flag)

用于控制外部可屏蔽中断是否可以被处理器响应:

  • sti指令:设置IF1,则允许中断;
  • cli指令:设置IF0,则禁止中断。

Debug表示: EI (Enable Interrupt) IF = 1; DI(Disable Interrupt) IF = 0

陷阱标志TF(Trap Flag)

用于控制处理器进入单步操作方式:

  • 设置TF0,处理器正常工作
  • 设置TF1,处理器单步执行指令(处理器在每条指令执行结束时,便产生一个编号为1的内部中断,这种内部中断称为单步中断,所以TF也称为单步标志)。利用单步中断可对程序进行逐条指令的调试

段寄存器

8086有4个16位段寄存器

  • CS(代码段)指明代码段的起始地址
  • SS(堆栈段)指明堆栈段的起始地址
  • DS(数据段)指明数据段的起始地址
  • ES(附加段)指明附加段的起始地址

每个段寄存器用来确定一个逻辑段的起始地址,每种逻辑段均有各自的用途

调试程序DEBUG

在DOSBox中输入debug进入调试模式

注意:在debug下,所有数都默认为16进制

常用命令

调试模式下的常用命令:

  • a: 汇编。显示当前的内存地址,在后面可以输入一些汇编指令。在输入指令的同时,debug就会自动把他翻译为2进制机器指令
    在这里插入图片描述
    这里显示内存地址为100h是因为程序段前缀psp100h个字节

  • u: 把内存中的二进制数据反汇编,看他是什么对应的汇编指令,与a是相反的过程
    汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第13张图片

  • d: display 显示内存中的数据
    汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第14张图片

  • e: edit 后面加上地址,可以修改内存中的数据
    汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第15张图片
    每输入完一个字节按空格继续输入下一个字节,如果不输入而直接按空格代表保持原来数据不变
    也可以这样修改:
    在这里插入图片描述

  • r: 显示CPU所有寄存器的值
    在这里插入图片描述
    上图中的 NV, UP, EI…均为标志寄存器的状态

  • rf: 显示标志寄存器的值
    在这里插入图片描述

  • t: step into 单步执行
    汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第16张图片
    在这里插入图片描述
    在这里插入图片描述
    t=100代表从内存地址100的地方开始执行,执行一步,执行完之后打印寄存器的值,可以看到ax的值变了,ip变了,指向103即下一条指令所在的地址
    在这里插入图片描述
    t代表直接执行下一条语句

  • p: step over 单步跳过
    用法和t差不多

  • g: 断点执行。直接运行,在后面接断点(内存地址)如:g 103

  • n: 文件命名

  • w: 保存文件

  • q: 退出debug

调试一个文件

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第17张图片
可以看到,hello.com的程序其实是上面的前5行
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第18张图片
int 21是一个函数,再执行t就会跟到函数内部,因此使用p
在这里插入图片描述

用debug实现 hello world

debug环境下也可以进行汇编程序编写,只不过编译过程都是由debug自动进行的

debug环境不支持变量

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第19张图片
mov ah,4cint 21是调用DOS系统的4c号功能,从当前运行的程序中退出回到DOS命令行,即 .exit 0

查看要显示的字符串:
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第20张图片
查看指令:
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第21张图片
保存文件:
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第22张图片

rcx用来指定我们要写的数据要写多少字节,把相关要写的字节数放到rcx
因为指令存在100h,数据存在200h,因此200h-100h再加上字符串长度即为要写的字节数。这里直接写200保证足够

运行:
在这里插入图片描述

用debug查看寄存器的变化

汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第23张图片
在这里插入图片描述
ax高位变为3aip2指向下一条指令,标志寄存器不变

在这里插入图片描述
OF, SF, AF置位
在这里插入图片描述
在这里插入图片描述
CF置位,SF, OF复位
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第24张图片
DI置位
汇编语言(三):高级语言程序的运行过程、8086CPU简介(寄存器组)、debug_第25张图片
DI复位

你可能感兴趣的:(汇编语言)