汇编语言的由来
向CPU输入一系列高低电平(1和0),就可以控制CPU完成一系列操作,这些1和0就是机器语言。
然而机器语言对人类而言十分难以理解,因此就诞生了汇编语言。机器语言尽管看上去只是一堆1和0,但它们内部仍有结构——存在机器指令,用来控制CPU完成对应指令所约定好的功能。汇编语言为每一条机器指令都规定了一条汇编指令,而汇编指令就是人类较能容易理解的了。
此外,为了进一步方便汇编语言的编写,又提供了一些由编译器来识别和处理的伪指令和符号,它们没有直接对应的机器指令。
这就是汇编语言的由来。
由于汇编语言的底层性,在编写汇编语言时无法像C语言那样即使避开了CPU的部分工作原理也能够进行编程,因此需要对CPU的部分工作原理进行了解
寄存器
寄存器是CPU本身的一部分,它们可以存储数据,而它们存储的数据就是指导CPU运行的依据,所谓“向CPU输入一系列1和0”,大抵就是逐个向对应的寄存器输入这些东西。
不同型号的CPU,其寄存器的个数和结构不同,8086有14个寄存器:AX, BX, CX, DX, SI, DI, SP, BP, IP, CS, SS, DS, ES, PSW
这些寄存器都是16位的,现代CPU基本上都是64位的,2010年左右一般个人电脑上装载的CPU还有很多是32位的,这些CPU上的寄存器就都是64位和32位的,事实上,在8086之前,还有一些8位的CPU。
为了保证新的CPU能够向下兼容,寄存器能够分解:
RAX是64位的,它的一半是EAX,32位;然后就到了之前提到的AX,16位;AX又可以分为两个8位的寄存器:AH和AL
BX, CX, DX同理。(带H的是高八位,带L的是低八位)
8086 中的数据段寄存器: DS
8086CPU会自动把 ds 寄存器中的数据当作是段地址,并与偏移地址address计算得到物理地址,以此来对内存中的指定位置进行寻址。
段寄存器并不支持直接使用mov ds, 数据
来直接修改
只能通过mov ds, ax
或者其他合法寄存器来修改。
这是8086CPU本身的设计所导致的,其他CPU有可能可以使用 mov ds, 数据
的形式来修改段寄存器的内容
汇编语言的“数据类型”
C语言中有诸如 int
,double
,unsigned long long
等数据类型,划分这些数据类型的依据有占的字节数和它们的意义(或者说,程序对其预设的处理方式)
而在汇编语言中基本上只有四种数据类型,划分它们的依据仅有其占的字节数:
qword
:8个字节,64个二进制位dword
:4个字节,32个二进制位word
:2个字节,16个二进制位byte
:1个字节,8个二进制位
而 word
仅在16位以上的CPU才有,dword
和 qword
同理,8086CPU就只有 word
和 byte
两种基本数据类型
字单元
16位寄存器用于存储一个字,高8位存放高位字节,低8位存放低位字节。
在内存中,使用两个内存单元存储一个字。
如图:可用内存单元0和1来存储一个字:4E20H。
0单元存放低位字节,1单元存放高位字节
定义:字单元。由两个连续的内存单元组成,以“N地址字单元”称呼起始地址为N的字单元。
0地址字单元由0和1组成,1地址字单元由1和2组成
1地址字节单元由1组成。
8086 特有的地址表示方式
8086的架构较为特殊,它是16位的但是地址总线却是20位
这就为8086提供了1MB的寻址能力。8086中的相关部件会提供两个16位的地址值,一个称作段地址,另一个称作偏移地址,两个地址通过地址加法器能够算出一个20位的地址值,然后通过地址总线即可获取到对应数据。算法为:**物理地址=段地址 * 0x16 + 偏移地址 **
一般而言,使用8086时会使用 CS, DS, SS, ES 作为段寄存器。
高位CPU,一根32位的地址总线的寻址能力就有4GB,一根64位的地址总线的寻址能力就有16EB(一百多万GB),就不需要段地址和偏移地址来定址了,一个地址即可完成定址的需求,也就没有了短地址和偏移地址的概念。
指令地址寄存器
IP寄存器就是指令地址寄存器,在高位 CPU 里,使用 EIP 和 RIP。当然,由于8086在寻址上的特性,又有一个 CS 寄存器,称作代码段寄存器。
指令地址寄存器的作用是保存正在执行的指令的地址,当执行完这条指令后,寄存器存储的地址值会自动变为下一条指令的地址。
对于64位的CPU而言,它正在执行的指令的地址就是 RIP,但是对8086而言则是 CS:IP(CS * 0x10 + IP)
mov
指令无法修改 CS 和 IP 寄存器,要修改它们需要使用 jmp
指令:
同时修改CS和IP中的内容的指令:
jmp 段地址: 偏移地址
只修改IP中内容的指令:
jmp 某一合法寄存器
mov
-
mov 寄存器名, 数据
:把数据存放到寄存器中 -
mov 寄存器名, 寄存器名
:把一个寄存器中的数据复制到另一个寄存器中 -
mov 寄存器名, [address]
:address
是偏移地址,把它与ds中的段地址合成的物理地址指向的内存中的数据读入寄存器。 -
mov [address], 寄存器名
:将寄存器中的数据存放到ds:address
指向的内存中。
在8086CPU中,mov 寄存器名, 数据
对段寄存器无法使用
汇编代码中数据的明文表示
mov ax, 1FFFH
,表示把十六进制数 0x1FFF 存入寄存器 ax
mov ax, 01100110B
,表示把二进制数 01100110 存入 ax
mov ax 'aa'
,表示把 0x6161 存入 ax(ASCII 码)
add 和 sub
和mov
指令一样有四种形式,不再赘述。
add
是后面的数据与前面的数据相加,结果存放到前面指向的位置。
sub
则是相减。
and 和 or
and
指令:按位与
例如:
mov al, 01100011B
and al, 00111011B
执行后al
中的数据就是00100011B
作用:可以将操作对象的某一位变成0而其他位不变
例如只希望
al
的第6位(从后往前数,最后一位标号为0,下同)变成0,就可以执行:and al, 10111111B
or
指令:按位或
例如:
mov al, 01100011B
or al, 00111011B
执行后al
中的数据就是01111011B
作用:可以将操作对象的某一位变成1而其他位不变
例如只希望
al
的第6位变成1,可以执行:and al, 01000000B
如何进行汇编语言实验
windows10 无法使用dos中的debug,masm 等工具,所以实验均通过 dosbox 来完成。相关教程:https://zhuanlan.zhihu.com/p/370786100
dos中debug工具的基本用法
-
q
:退出debug -
r
:查看各个寄存器的内容r 寄存器名
:修改某一寄存器的内容
-
d 段地址:偏移地址
:查看内存中的内容d 段地址:偏移地址1 偏移地址2
:查看内存中某个范围内的内容
-
e
: 修改内存中的内容 -
u
:查看内存中机器码的含义 -
t
:执行内存中的机器码 -
a
:用汇编指令的形式向内存中写入机器码
第一个汇编程序
一个源程序,从编写到执行需要三步:
- 在文本编辑器中编辑源程序
- 对源程序进行编译连接,生成可直接运行的可执行文件。可执行文件包含两部分:
- 程序和数据
- 相关描述信息(程序的大小、所占内存空间等)
- 执行
assume cs:codesg
codesg segment
start: mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H
codesg ends
end
伪指令 segment, ends
汇编语言的源程序中包含两种指令:汇编指令和伪指令
汇编指令是有对应机器码的指令,最终被CPU所执行
伪指令没有对应的机器指令,由编译器来执行
XXX segment:
XXX ends
这个segment
和ends
就是一个成对使用的伪指令,其作用是定义一个段,而XXX就是这个段的名字。
一个汇编程序可以有多个段组成,这里就照应到了前面所讲的指令段、数据段、栈段等段,它们在伪指令中得到了体现。
一个有意义的汇编程序中至少要有一个代码段
伪指令 end
这是一个汇编程序结束的标记,编译器在编译汇编程序的时候,如果读取到了end
,就立即停止了对源程序的编译。必须在程序末尾添加end
,否则编译器无法知道何时停止编译。
伪指令 assume
将代码段codesg
与段寄存器cs
联系起来
汇编语言中的标号
在汇编程序中,除了汇编指令和伪指令,还有一些标号,例如codesg
,这里的标号就指代了一个地址。
程序返回
一个程序 P 2 P_2 P2 存储在可执行文件中,那么就必须有一个正在运行的程序 P 1 P_1 P1,将 P 2 P_2 P2 载入内存并把CPU的控制权交给 P 2 P_2 P2,并暂停 P 1 P_1 P1 的运行。 P 2 P_2 P2 运行完毕后,就应该把CPU的控制权交回使它得以运行的程序 P 1 P_1 P1,令 P 1 P_1 P1 继续运行。这个过程就是程序返回
以下两条指令可以实现这样的程序返回,其具体原理以后再讨论:
mov ax, 4c00H
int 21H
源程序的编译和链接
使用 masm 指令进行编译,使用 link 指令进行链接
程序运行原理
操作系统的 shell
- 操作系统是一个多功能模块组成的、复杂的软件系统。任何通用的操作系统,都要有一个被称为 shell 的程序来供操作人员来操作计算机的操作。
- dos 系统中的 shell 是一个叫做 command.com 的程序,也叫命令解释器。
- dos 启动并完成其他初始化操作后,运行 command.com,就会在屏幕上显示路径,然后等待用户输入。
- dos执行一次指令后,就会再次显示当前盘符与路径组成的提示符,等待用户输入。
- 用户执行程序时,command 会把对应程序载入内存,将 CS:IP 指向该程序的入口,然后command 暂停运行,CPU运行程序,程序结束后返回到 command,然后就会再次显示提示符。
使用debug追踪程序的运行过程
在提示符后输入
debug t1.exe
即可进入针对程序t1.exe
的调试模式
即:将程序从t1.exe
载入内存,进行相关初始化设定并令CS:IP
指向程序的入口。
使用T命令可以单步执行程序
执行到 int 21时,用P命令执行。
程序的加载过程
- 找到一个起始地址为SA:0000的内存区
- 在这段内存的前256个字节中创建一个称为程序段的前段(PSP)的数据区,DOS使用PSP与被加载程序进行通信。
- 在PSP的后面将程序装入,程序的地址设置为(SA + 10H) : 0
- SA存入DS,并令CS : IP指向程序的入口