1.8086汇编语言

部分寄存器对照(8086和win32(对应x86))

  1. 通用寄存器:

    ax:累加器,用于算数和逻辑操作 eax:和8086相同

    bx:基址寄存器,存储内存地址 ebx:和8086相同

    cx:计数器,用于循环和计数操作 ecx:相同

    dx:数据寄存器,用于存储数据和i/o操作 edx:相同

  2. 段寄存器:(两者同名同作用)

    cs:代码段寄存器,存储指令代码的段地址

    ds:数据段寄存器,存储数据的段地址

    ss:堆栈段寄存器:存储堆栈的段地址

    es:附加段寄存器:可用于存储额外的数据段地址

  3. 指针寄存器:

    si:源索引寄存器:用于字符串操作 esi:相同

    di:目的索引寄存器:用于字符串操作 edi:相同

  4. 基址指针寄存器:

    bp:基址指针寄存器,指向当前栈帧的基址 ebp:相同

  5. 栈指针寄存器

    sp:指向当前栈顶的地址 esp:相同

  6. 指令指针寄存器

    ip:存储下一条要执行的指令的地址 eip:相同

导入

为什么学习汇编?

  1. 开发软件的核心部件,快速执行和实时响应
  2. 涉及底层,如操作系统内核和嵌入式
  3. 加深对计算机原理和操作系统的理解
  4. 底层的调试和分析

要学的内容:8088\8086指令集(现在的计算机系统过于复杂,直入x86难度高,以简单的先引入)

学习的方法:

  1. 贯穿实践
  2. 观察机器内部状态
  3. 学习进程

机器指令到汇编指令

机器语言是机器指令的集合

指令是机器可直接执行的命令,由一串二进制数表示

汇编指令是机器指令的助记符(便于记忆的书写格式)

不用了解具体电子结构

使用汇编的工作流程

  1. 编写汇编指令
  2. 通过编译生成可执行文件
  3. 直接执行

汇编语言:汇编指令,伪指令,和其他符号

计算机组成

1.主板

​ 其中有cpu,总线,内存条,扩展槽(接外部设备).总线分为地址总线,数据总线,和控制总线

2.存储指令

​ 指令和数据都存储在内存和磁盘中,开始运行时由外存到内存,表示数据和指令时多用十六和十进制..

3.存储单元

​ 从0开始编号,FFFFF对应1mb的最后一位

4.总线

​ 对应芯片的引脚,链接cpu和其他芯片

屏幕截图 2023-12-20 160038

地址总线随cpu性能提高而增宽,决定可寻址的存储单元大小,

2023-12-20 160259

2023-12-20 160403

2023-12-20 160547

cpu对内存读写

三类信息交互:存储单元的地址,器件的选择(读或写命令),读或写的数据

对应着地址信息,控制信息,数据信息

MOV AL,[3]//从三号单元读取数据送入寄存器AL

首先由地址线把要读的三号单元,由地址线传到内存

再由控制线从cpu发出读的信号

最后由数据线从内存处读出三号单元

内存地址空间

8086为20宽的地址总线,可寻址1mb的内存单元,内存空间为1mb

​ 从cpu的角度看,地址空间包含ram(随机,能读写,易失数据,动态)和rom(只读,静态)(如今也可擦除了),它们统一编址,把两种看成一个逻辑存储器,每一个物理存储器都在逻辑存储器中占有一个地址段,即一段地址空间2023-12-20 161854

​ 操纵对应地址空间可实现对应功能(如显卡和显示内容相关)

内存地址空间的分配,以8086pc机为例

2023-12-20 162020

在各个机器内应有该内容

实践,环境搭建,常用命令

在dos系统环境下,最好的是dos模拟器

命令:(不包含已有可执行文件)

  1. dir:查看当前文件目录

访问寄存器和内存

寄存器及信息存储

cpu由运算器,寄存器和控制器组成

​ 运算器进行信息处理,寄存器进行信息存储,控制器进行器件协调工作总线实现cpu内各器件的联系

寄存器(8086)(16位两字节)(16位字长 )

  1. 通用寄存器:AX,BX,CX,DX

    ​ 16位最大可存2的16次方减一

    例:存储18D,转为12H,即10010B(前面全0)

  2. 变址寄存器:SI,DI,

  3. 指针寄存器:SP,BP

  4. 指令指针寄存器:IP

  5. 段寄存器:CS,SS,DS,ES

  6. 标志寄存器:PSW

寄存器从8位到16位的兼容性8086中把AX分为AH(高位)和AL,平分使用(bx,cx,dx一样)

2023-12-20 185943

mov和add指令

mov ax,78//ax=78

mov ah,18//ah=18

add ax 8//ax=ax+8

add ax,bx //ax=ax+bx

低位相加溢出时,不会给到高位

确定物理地址的方法(地址加法器)

每一个内存单元都有唯一的物理地址,存储空间构成一个一维线性的空间:

​ 8086中有20位的地址总线,寻址能力1m

​ 8086是16位结构的cpu

​ 运算器最多可以处理16位的数据,寄存器最大宽度为16位

​ 在8086内部处理的,运输的,暂存的地址也是16位,寻址能力只有64kb(矛盾)

解决方法:用两个16位地址:段地址和偏移地址合成一个20位的物理地址.即:

​ 物理地址=段地址*16(此时16位+4位)+偏移地址

物理地址对应的段地址不唯一

偏移地址为汇编程序直接使用的地址

相关拓展博客:物理地址和逻辑地址

地址分段管理内存

内存没有分段,cpu划分段,同一段内存可有不同分段方案.

2023-12-20 200703

2023-12-20 201436

debug使用

调试,观察cpu内存和寄存器情况

命令:

  1. R:查看改变cpu寄存器内容
  2. D:查看内存内容
  3. E:改变内存内容
  4. U:将内存中的机器指令改为汇编指令
  5. A:以汇编指令的形式在内存中写入机器指令
  6. T:执行机器指令

多多实操

cs和ip关键寄存器

cs:代码段寄存器

ip:指令指针寄存器

任意时刻,cs和ip指向的内容都将被cpu当作指令执行(提供cpu要使用的所有指令)

ffff0h为8086pc机开机后的第一条指令

修改cs,ip指令

使用比如mov的传送指令,可改变大多数寄存器的值(cs,ip除外)

可改变cs和IP的都被称为转移指令,如jmp

若想同时修改cs和ip的内容,可使用”jmp 段地址:偏移地址”的指令

这将使cs的值等于段地址,ip的值等于偏移地址

只改变ip的内容,则可用形如”jmp 某一合法寄存器”的指令完成(即不改变cs的值,将被选中寄存器的值赋给ip)

2.寄存器和内存访问

内存中字的存储

cpu中使用16位寄存器存储一个字,高8位存放高位字节,低8位存放低位字节

而在内存中,一个字要用两个地址连续的内存单元存储,低位存储在低地址单元,高位字存储在高地址单元中

​ 字单元的概念:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成

​ 将起始地址为N的字单元简称为N地址字单元,如2地址字单元由2,3两个内存单元组成

DS和[address]

mov bx,1000H

mov ds,.bx

mov al,[0]

上面三条命令将内存10000H中的数据读到al中

ds的值将自动被8086取为内存的段地址,而[内]表示内存单元的偏移地址

反之则可将字存入内存中

字的传送

mov ax,data(一个字)//ax可换为任意一个16位寄存器或内存字单元

mov,add,sub指令

已知mov指令的使用方法:

MOV:

  1. 寄存器,(数据/寄存器/内存单元)
  2. 内存单元,寄存器
  3. 段寄存器,寄存器

推测:

  1. mov 寄存器,段寄存器

  2. mov 内存单元,段寄存器

    1. mov 段寄存器,内存单元

对add和sub也可推出并使用debug验证

数据段

​ 可根据需要将一组内存单元定义为一个段,如123b0H~123b9H,定义了一个段地址为1232BH,长度10字节的数据段

相关指令:push,pop

相关寄存器:ss,sp

  1. push指令的使用(push ax为例)
    1. sp=sp-2,指向新栈顶
    2. 将ax的值赋给ss:sp的内存单元
  2. pop指令的使用(pop ax为例)
    1. ss:sp的值赋给ax
    2. sp+=2

栈顶越界

8086cpu无记录栈底和栈顶的寄存器,无法保证不会越界

栈段

push和pop仅改变sp

而sp取值范围为0~FFFFH(64KB)

若sp=0时仍然压栈,栈顶将环绕

3.汇编程序的编写

程序编写过程

大体和高级语言相同(编译\链接\可执行+描述)

使用的代码分为伪指令和汇编指令

伪指令是由编译器执行,用于执行编译工作

伪指令例子:

  1. XXX segment&XXX ends

    这是在写可被编译的汇编程序时,必须用到的一组伪指令,用于定义一个段来存放代码,一个有意义的汇编程序必有至少一个段

  2. end

    该指令标记着汇编程序的结束

  3. assume

    将某一段寄存器和程序中的某一个segment定义的段相关联

debug a指令可执行简单的汇编程序

2023-12-23 182946

注释:使用;在一行末尾,;后即注释

报错:语法和逻辑错误

形成可执行文件

文本->源程序.asm->目标文件.obj->可执行文件.exe

  1. 编译
  2. 链接
  3. 运行

编译

指定目标文件(.obj),列表文件(.lst),交叉引用文件(.crf)

编译时如果没有warnin errors和severe errors,则完美通过编译

具体报错:

2023-12-23 185821

链接

2023-12-23 190151

debug装载文件

debug 可执行.exe

2023-12-23 191236

常用命令:

  1. p命令:类似t,逐条执行,但在子程序,中断时,将直接执行然后显示结果
  2. g命令:从 指定地址处开始运行程序,直到遇到断点或 程序正常结束

[…]和(…)的约定

2023-12-23 192702

以及idata约定为常量

LOOP指令

类似goto和go-while

mov cx,11

s: add ax,ax

loop s

此时执行loop的操作:

  1. (cx)=(cx)-1
  2. 判断cx的值,非0则循环,0则向下执行

debug和汇编编译器对masm对指令的不同处理

example:

mov ax,[0];表示将ds:0处的数据传入ax中(ax)=((ds)*16+0)

mov ax,0;实际运行的代码(ax)=0

为避免以上歧义情况,应在[0]前加上段前缀ds:

8位数据在16位寄存器上的计算

提出问题:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中.

考虑的情况:

  1. 会溢出嘛?

    12个8位数据累加不会溢出

  2. 可否直接累加进入dx中?

    8位数据无法直接加到16位寄存器中

  3. 可否将每一个累加数先存入al中,在设ah=00H,执行累加?

    程序过于冗长

  4. 可否使用loop

    每个循环中使用的命令如下

    mov al,ds:[X] ;X递增

    mov ah,0

    add dx,ax

    使用循环表示:

    mov cx,12 ;此之前ds=ffffH,bx=0,dx=0

    s: mov al,[bx] ;最好表示为ds:[bx]

    ​ mov ah,0

    ​ add dx,ax

    ​ inc bx

    loop s

一段安全的空间

​ 8086中随意写入一段内存空间是很危险的,因为此时此段空间内可能存有重要的系统数据或代码

​ mov ax,1000H

​ mov ds,ax

​ mov al,0

​ mov ds:[0],al

此时1000段上的数据未经验证,可能将引发错误

2023-12-24 183829

​ 8086cpu仍然可操作本应该收到保护的系统数据,但在现有的系统中的cpu保护模式下,不理会操作系统,直接操作真实的硬件是不可能的.因此我们应当使用安全分配的内存,比如dos系统中因避免使用0:200~0:2ff空间的内存.

4.包含多个段的程序

​ 在操作系统允许的情况下,程序可取得任意容量的空间.

​ 程序取得所需的空间有两个方法,其一是在加载程序的时候为其分配,再一则是程序在执行时向系统申请.如果要一个程序在被加载时取得空间,需要在源程序中进行说明,即所谓定义段

在代码段中使用数据

2023-12-25 011116

​ 程序第一行的dw含义是定义字型数据.(define word),此处定义了八个字型数据(以逗号分隔),共占16字节

他们的地址在cs中,从0开始,即cs:0,cs:2~cs:e,代码段中的代码则改为从16之后开始(执行时需要说明)

2023-12-25 011949

start将指定cs:ip所指向的第一行指令,所以start之前可以存放数据,

start属于可执行文件的描述信息,应用于编译,链接程序对源程序中相关伪指令的处理所得到的信息

同样的end也可指明cpu从何处开始执行程序

在代码中使用栈

应在数据区域提前用dw申请内存,用来指明栈的段内存和栈空间大小,再将ss和sp使用寄存器正确赋值

将数据,代码,栈放入不同的段

2023-12-25 014031

5.灵活地定位内存地址的方法

处理字符问题

​ 汇编程序中,用”……”是方式指明数据是以字符的方式给出的,编译器将把他们转为响应的ascii码

2023-12-26 003338

此段代码中,’a’变成可赋值的数据

2023-12-26 003615

大小写的区别在6位是否为1(加减32)

如果要统一转为大写或小写,则需要位与(and)运算,将第6位取为0或1

即and dest,src(取1101 1111时为大写)

同样有位或运算,or dest,src(取0010 0000时为小写)

[(bx)+idata]方式寻址

mov ax,[200+bx]

mov ax,200[bx]

mov ax,[bx].200

以上指令表示相同含义,即(ax)=(ds)*16+200+(bx)

非常类似c中的数组(实际上区别不大)

尝试使用以上技巧处理字符串??

si和di变址寄存器

si:source index,源变址寄存器

di:destination index,目标变址寄存器

si和di不能分成两个8位寄存器使用

2023-12-26 010116

2023-12-26 010330

[bx+si],[bx+di]

猜猜小标题是什么意思?

这种方法称为基址+变址的寻址方法

[bx+si+idata],[bx+di+idata]

2023-12-26 013440

dup指令

重复定义一系列数据的伪指令

db 3 dup (0) ;三个字节的0

db 3 dup (0,1,2) ;九个字节,由0,1,2重复三次

db 3 dup (‘abc’,’ABC’) ;定义18个字节,构成‘abcABC’*3

即db 重复的次数 dup 重复的字节型/字型/双字符数据

6.流程转移和子程序

转移!

分为 段内转移和段间转移(只修改ip或同时修改cs,ip)

分为段内短转移和近转移(范围分别为-128,127或-32768,32767)

分为条件转移、无条件转移、循环、过程、中断等

offset取得标号的偏移地址

image-20240306145614755

可通过mov和offset配合做到复制某处的指令

jmp指令

无条件转移,可以只修改ip,也可同时修改cs和ip

image-20240306150731009

用伪指令标记段内指令地址时,底层是用立即数实现的

assume cs:codesg

codeseg segment

​ start: mov ax,0

​ jmp short s

​ add ax,1

​ s: inc ax

codesg ends

end start

dbg对应数据如图

image-20240306151602235

左侧机器码(EB03)中传入jmp的值为03,实际上跳转的地址为03+下一条将执行的ip偏移地址05=跳转到的08(在dbg中被最终显示)

image-20240306152457363

远转移:jmp far ptr s ;对应机器码实例:EA+016A+07(目标地址[016A+cs]:07+ip)

转移地址在寄存器中的jmp指令

如jmp ax

image-20240306154143024

其他跳转

jcxz指令、loop(在32位以上很少出现,依赖cx的值进行条件跳转)

flag寄存器

image-20240306163924512

内中断

cpu具备中断能力,即当检测到外部或内部的一种特殊信息后,立即对此信息进行处理的能力。对于8086cpu内部,有以下中断信息:

  • 除法错误,如div指令产生的除法溢出
  • 单步执行
  • 执行into指令
  • 执行int指令

对应中断类型码:0、1、4、传入int 指令的值

端口

(施工ing)

外中断