汇编语言学习 (1~4章)
一、基础知识
1. 汇编语言简介
从机器语言到汇编语言:
机器语言:由“0”“1”数字编成,以二进制为基础表示电子器件的高低电平,从而执行机器指令,进行运算。
明显弊端:难以书写和阅读,复杂难懂。
于是,程序员写出了编译器来翻译机器语言,汇编语言诞生了。
汇编语言:主体为汇编指令。如“mov ax,bx”。操作对象为CPU、寄存器和内存。
2. CPU基本工作原理
CPU 是计算机的核心部件,它控制整个计算机的运作并进行运算。要想让一个CPU工作,就必须向它提供指令和数据。指令和数据在存储器中存放,也就是我们平时所说的内存。
2.1 存储单元、指令和数据
指令和数据:
指令和数据是应用上的概念。在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。CPU 在工作的时候把有的信息看作指令,有的信息看作数据,为同样的信息赋予了不同的意义。()
存储单元:
存储器被划分成若干个存储单元,每个存储单元从0开始顺序编号。一个存储器能存储128个Byte。
内存计量单位:
8bit=1B 1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB
2.2 CPU对存储器的读写
存储器被划分成多个存储单元,存储单元从零开始顺序编号。这些编号可以看作存储单元在存储器中的地址。
CPU 要从内存中读数据,首先要指定存储单元的地址
CPU 在读写数据时还要指明,它要对哪一个器件进行操作,进行哪种操作,是从中读出数据,还是向里面写入数据。
可见,CPU想进行数据的读写需要和外部器件进行以下三类信息交互:
- 存储单元的地址(地址信息);
- 器件的选择,读或写的命令(控制信息);
- 读或写的数据(数据信息)。
2.3 三大总线
CPU通过导线将电信号传到存储器芯片中,从而实现信息的交互。
在计算机中专门有连接 CPU 和其他芯片的导线,通常称为总线。总线从物理上来讲,就是一根根导线的集合。 根据传送信息的不同,总线从逻辑上又分为3类,地址总线、控制总线和数据总线。
示例:
(1) CPU通过地址线将地址信息了发出。
(2) CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据。
(3) 存储器将3号单元中的数据8通过数据线送入CPU。
写操作与读操作的步骤相似。如向3号单元写入数据26:
(1) CPU通过地址线将地址信息了发出。
(2) CPU通过控制线发出内存写命令,选中存储器芯片,并通知它,要向其中写入数据。
(3) CPU通过数据线將数据26送入内存的3号单元中。
要让一个计算机或微处理器工作,应向它输入能够驱动它进行工作的电平信息(机器码)。CPU 按收这条机器码后将完成我们上面所述的读写工作。 机器码难于记忆,用汇编指令来表示,情况如下。
机器码: 10100001 00000011 00000000
对应的汇编指令:MOV AX,[3]
含义: 传送3号单元的内容入AX
由此可见,汇编指令可以方便地直接对CPU进行操作。
地址总线: CPU是通过地址总线来指定存储器单元的,地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU可以寻找2ⁿ个内存单元。
数据总线: CPU与内存或其他器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了CPU和外界的数据传送速度。
8根数据总线一次可传送一个8位二进制数据(即一个字节)。16根数据总线一次可传送两个字节。
控制总线: CPU对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。有多少根控制总线,就意味着 CPU 提供了对外部器件的多少种控制。所以,控制总线的宽度决定了CPU 对外部器件的控制能力。
- 地址总线的宽度决定了CPU 的寻址能力;
- 数据总线的宽度决定了CPU 与其他器件进行数据传送时的一次数据传送量:
- 控制总线的宽度决定了CPU 对系统中其他器件的控制能力。
3. 内存地址空间
CPU在操控存储器的时候,把它们都当作内存来对待,把它们总的看作一个由若干存储单元组成的逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间。
在图1. 8中,所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
内存地址空间的大小受CPU地址总线宽度的限制。8086CPU的地址总线宽度为20,可以传送2²⁰个不同的地址信息(大小从0至2²⁰−1)。即可定位2²⁰个内存单元,则8086PC的内存地址空间大小为1MB。同理,80386CPU的地址总线宽度为32,则内存地址空间最大为4GB。
二、寄存器与简单汇编指令
1.* 简单汇编指令
1.1 mov,add和sub指令
mov指令(传输指令)
用C语言可理解为赋值语句,如"mov ax,bx"
可理解成"ax=bx"。
使用形式如下:
mov 寄存器,数据 比如: mov ax,8
mov 寄存器,寄存器 比如: mov ax,bx
mov 寄存器,内存单元 比如: mov ax,[0]
mov 内存单元,寄存器 比如: mov [0],ax
mov 段寄存器,寄存器 比如: mov ds,ax
mov 寄存器,段寄存器 比如: mov ax,ds
mov 内存单元,段寄存器 比如:mov [0],ds
mov 段寄存器,内存单元 比如:mov ds,[0]
add和sub指令
类似C语言中"+=“和”-="这两个运算符号,如"add ax,8"和"sub ax,8"可分别翻译成"ax+=8"和"ax-=8"这两种运算。
使用形式可参照上述方式用debug自行实验,不再赘述。
1.2 jmp指令(转移指令)
用于修改CS和IP的值来控制CPU执行目标指令。
若想同时修改CS、IP的值,可用
“jmp 段地址:偏移地址”的指令完成。如:
jmp 2AE3:3,执行后,CS=2AE3H,IP=0003H,CPU将从2AE33H处读取指令。
jmp 3:0B16,执行后,CS=0003H,IP=0B16H,CPU将从00B46H处读取指令。
若只想修改IP的值,可用“jmp 某一合法寄存器”完成。如:
jmp ax,在含义上好似“mov IP,ax”意思是用寄存器的值修改IP。
1.3 push和pop指令(入栈、出栈指令)
push和pop指令是可以在寄存器和内存(栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。)之间传送数据的。
push和pop指令的格式可以是如下形式:
push 寄存器;将一个奇存器中的数据入栈
pop 寄存器;出栈,用一个寄存器接收出栈的数据
(PS:用什么类型数据入栈,就用什么类型数据出栈。)
指令执行时,CPU要知道内存单元的地址,可以在push、pop 指令中只给出内存单元的偏移地址,段地址在指令执行时,CPU 从ds 中取得。
- 用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先出栈。
- 将寄存器清零,可以用
mov ax,0
sub ax,ax
xor ax,ax
2. 寄存器
一个典型的CPU由运算器、控制器、寄存器(CPU工作原理)等器件构成,这些器件靠内部总线相连。其中寄存器进行信息存储。程序员正是通过改变各种寄存器中的内容来实现对CPU的控制。
每一代CPU都是向下兼容的。
8086CPU 的上一代CPU中的寄存器都是8位的,为了保证兼容,使原来基于上代CPU编写的程序稍加修改就可以运行在8086之上,8086CPU的AX、BX、CX、DX这4个寄存器都可分为两个可独立使用的8位寄存器来用
- AX可分为AH和AL;
- BX 可分为BH 和BL;
- CX 可分为CH 和CL;
- DX可分为DH和DL。
以 AX 为例,8086CPU的16位寄存器分为两个8位寄存器的情况如图2. 3所示。
2.1 字在寄存器中的存储
8086CPU可以一次性处理以下两种尺寸的数据。
- 字节: 记为byte,一个字节由8个bit组成,可以存在8位寄存器中。
- 宇:记为word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节,一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。一个字型数据20000,存在AX寄存器中,在AH中存储它的高8位,在AL中存储了它的低8位。AH和AL中的数据,既可以看成是一个字型数据的高8位和低8位,这个字型数据的大小是20000:又可以看成是两个独立的字节型数据,它们的大小分别是78和32。
- 高位存储高地址,低位存储低地址。
- 一个字要用两个地址连续的内存单元来存放
- 任何两个地址连续的内存单元,N号单元和 N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。
字节型数据:该地址存放数据,是一个字节。
字型数据:整体所指数据,是一个字。
2.2 物理地址与偏移地址
物理地址: CPU访问内存单元时,要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,我们将这个唯一的地址称为物理地址。
CPU 通过地址总线送入存储器的,必须是一个内存单元的物理地址。在 CPU 向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址。不同的CPU可以有不同的形成物理地址的方式。
16位CPU(8086CPU):
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度为16位
- 寄存器和运算器之间的通路为16位
8086CPU有20位地址总线,可以传送20位地址,达到IMB寻址能力。8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。从8086CPU的内部结构来看,如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出的寻址能力只有64KB。8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
(1)CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
(2)段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
(3)地址加法器将两个16位地址合成为一个20位的物理地址;
(4)地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
(5)输入输出控制电路将20位物理地址送上地址总线;
(6)20位物理地址被地址总线传送到存储器。
地址加法器采用**“物理地址=段地址x16+偏移地址”**的方法用段地址和偏移地址合成物理地址。
“段地址x16”的含义:
(1)一个数据的二进制形式左移1位,相当于该数据乘以2:
(2)一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
(3)地址加法器完成段地址×16的运算就是将以二进制形式存放的段地址左移4位。
(4)进一步可知一个X进制的数据左移1位,相当于乘以X。
“物理地址=段地址x16+偏移地址”的本质含义
CPU在访问内存时,用一个基础地址(段地址×16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。
更一般地说,8086CPU的这种寻址功能是 “基础地址+偏移地址=物理地址”寻址模式的一种具体实现方案。8086CPU中,段地址×16可看作是基础地址。
2.3 段
(1) 基本概念
其实,内存并没有分段,段的划分来自于CPU我们可以用分段的方式来管理内存。(人为规定的)
将若干地址连续的内存单元看作一个段,用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。
段地址x16必然是16的倍数,所以一个段的起始地址也一定是16的倍数:偏移地址为16位,16位地址的寻址能力为64KB,所以给定一个段地址,仅通过变化偏移地址来进行寻址,偏移地址16位,变化范围为0- FFFFH,仅用偏移地址来寻址最多可寻64KB个内存单元。
PS:
- CPU 可以用不同的段地址和偏移地址形成同一个物理地址
- “数据在21F60H内存单元中。”有两种类似的说法:
1.数据存在内存2000:1F60单元中;
2.数据存在内存的2000H段中的1F60H单元中。
(2) 段寄存器
8086CPU有4个段寄存器:CS、DS、SS、ES。当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。
(3) CS和IP
CS和IP是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址。CS 为代码段寄存器,IP 为指令指针寄存器,从名称上我们可以看出它们和指令的关系。
在8086PC机中,任意时刻,设CS中的内容为M,IP 中的内容为N,8086CPU将从内存Mx16+N 单元开始,读取一条指令并执行。
也可以这样表述:8086机中,任意时刻,CPU将CS:IP 指向的内容当作指令执行。
如图可知,将数据存放到寄存器中需要三个字节,对两个寄存器进行操作需要两个字节。
8086CPU的工作过程可简要描述如下:
(1)从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
(2)IP=IP+所读取指令的长度,从而指向下一条指令;
(3)执行指令。转到步骤(1),重复这个过程。
CPU将CS: IP指向的内存单元中的内容看作指令,因为,在任何时候,CPU将CS、IP 中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。
(4)代码段
可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N≤64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中,我们可以认为,这段内存是用来存放代码的,从而定义了 一个代码段。(同样是人为规定的,CPU不知道,它只负责读写指令,执行)
(5)数据段
我们可以将一组长度为 N( ≤ 64KB) 、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段用DS存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。(同上)
(6) DS和[address]
内存地址由段地址和偏移地址组成。8086CPU 中有DS寄存器,通常用来存放要访问数据的段地址。
“[…]”表示一个内存单元,“[…]”中的0表示内存单元的偏移地址。
"mov al,[0]"指令执行时,8086CPU自动取DS中的数据为内存单元的段地址。
10000H 用段地址和偏移地址表示为1000:0,我们先将段地址1000H放入ds,然后用"mov al,[0]"完成传送。mov指令中的[0]说明操作对象是一个内存单元,[0]中的"0"说明这个内存单元的偏移地址是0,它的段地址默认放在DS中,指令执行时,8086CPU会自动从DS中取出。
“数据——>通用寄存器——>段寄存器”
mov bx, 1000H
mov ds,bx
是给段寄存器赋值的常用操作。
下面是字传送的详细操作:
三、*栈
1. 栈的概念
栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。
2. CPU栈机制
入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出一个元素。栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)。
从程序化的⻆度来讲,应该有一个标记,这个标记一直指示着栈顶。(SS:SP)
- 栈从栈底(高位)开始存入数据,从栈顶(低位)开始复制数据转移出寄存器。
3. SS和SP
CPU 如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU 从SS 和SP 中得到栈顶的地址。
push ax 的执行,由以下两步完成。
(1)SP=SP-2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
(2)将ax中的内容送入SS:SP 指向的内存单元处,SS:SP此时指向新栈顶。
pop ax的执行过程和push ax刚好相反,由以下两步完成。
(1)将SS:SP 指向的内存单元处的数据送入ax 中;
(2)SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
- push指令先指定空位置,再存入数据
- pop指令先存入数据到寄存器中,再改变SS:SP位置
- pop 操作前的栈顶元素依然存在,但是,它己不在栈中。当再次执行 push 等入栈指令后,SS:SP移至上一个内存单元,并在里面写入新的数据,它将被覆盖。
故磁盘格式化并没有清除数据,只是把“指针”复位了而已,下次写入只是覆盖原有数据,可以复原。
4. 栈溢出
将 10010H~1001FH当作栈空间,该栈空间容量为16字节(8字),初始状态为空,SS=1000H、SP=0020H,SS:SP 指向10020H;
在执行8次push ax 后,向栈中压入8个字,栈满,SS: SP 指向 10010H;
再次执行 push ax: sp=sp- 2,SS:SP 指向 1000EH,栈顶超出了栈空间,ax 中的数据送入1000 EH 单元处,将栈空间外的数据覆盖
上面描述了执行push、pop 指令时,发生的栈顶超界问题。可以看到,当栈满的时候再使用push 指令入栈,或栈空的时候再使用pop 指令出栈,都将发生栈顶超界问题(栈溢出)。
栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。
PS: 如果是故意造成栈溢出的,就可以利用此特性将一些程序中的受保护数据取出(例如用户登录密码等信息),所以栈溢出也是PWN攻击的一种方式。
8086CPU只知道栈项在何处(由SS:SP指示) ,而不知道我们安排的栈空间有多大。这点就好像CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道要执行的指令有多少。从这两点上我们可以看出8086CPU 的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令是哪一条。
5. 栈段
我们可以将长度为N( ≤ 64KB) 的一组地址连续、起始地址为16的倍数的内存单元,当作栈空间来用,从而定义了一个栈段。比如,我们将 10010H~1001FH 这段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。这段空间就可以称为一个栈段。
如果将 10000H~1FFFFH 这段空间当作栈段,SS=1000H,栈空间为64KB,栈最底部的字单元地址为1000:FFFE。任意时刻,SS:SP 指向栈顶单元,当栈中只有一个元素的时候,S S=1000H,SP=FFFEH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2.
SP原 来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。
换一个⻆度看,任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以sS:SP 只能指向栈的最底部单元下面的单元,该单元的地址为栈最底部的字单元的地址+2。栈最底部字单元的地址为1000:FFFE ,所以栈空时,SP=0000H.
段的总结
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。
不管我们如何安排,CPU将内存中的某段内容当作代码,是因CSIP 指向了那里:CPU将某段内存当作栈,是因为SS;SP 指向了那里。我们一定要清楚,什么是我们的安排,以及如何让CPU 按我们的安排行事。要非常清楚 CPU 的工作机理,才能在控制 CPU 按照我们的安排运行的时候做到游刃有余。
四、使用Debug进行简单汇编指令编程
1.DOSBox和Debug安装及使用
由于目前的Windows系统不再支持Debug的直接使用,所以我们需要先安装DOSBox和Debug来进行汇编指令编程。
安装
安装地址网上一般都能搜到,这里只贴出:DOSBox的地址:https://www.dosbox.com
使用
点开安装后一直Next,选个你需要的磁盘位置安装(一般是D盘)就可以,安装Debug的时候需要注意Debug的文件路径。
- 打开DOSBox后,使用“mount c Debug位置”来让DOSBox进入Debug程序运行;然后输入“C:”
;最后输入“debug”就能运行了。
但是每次进入DOSBox程序时都要重复输入该指令,所以我们可以打开DOSBox文件位置,找到“…Option.bat”文件,然后如图把指令挪过来:
以后每次打开就能直接运行Debug程序了。 - 打开DOSCBox后可以发现窗口无法放大缩小,可以打开“…Option.bat”文件后找到
“windowresolution=original
output=surface”代码,将它改为
“windowresolution=你需要的分辨率(如1200x800)
output=opengl”就能正常放大程序窗口了。
2.Debug命令
可以参照这篇文章进行了解和学习:
一般是先用r命令查看当前寄存器内部的值,再用a命令进行编程。
汇编语言DEBUG命令详解||汇编命令||DEBUG的常用命令:A,U,R,T,D,E,Q 等等
五、程序的编写与执行
汇编语言程序写出到执行的过程: 写出源程序—>对源程序进行编译连接—>生成可执行文件—>操作系统将其加载入内存—>CPU执行程序
1.源程序
源程序包括汇编语言和伪指令。
示例:
伪指令
伪指令是给编译器看的指令,编译器根据伪指令来进行相关编译工作。
(1) segment与ends
作用:定义一个段。
segment 表示一个段的开始,ends 表示一个段的结束。
用法:
段名 segment
:
段名 ends
(2) end
作用:结束编译。
PS: 注意区分end与ends的含义。
用法:在程序结尾处使用。
(3) assume
作用:将段与指定的段寄存器关联起来。(即声明该段在CPU中的执行位置)
用法:在一个段的开头使用。
assume 段寄存器:段名
(4) 标号
一个标号指代了一个段地址。
它被编译后最终处理为一个段地址。
程序与程序结构
程序返回
(1)DOS中的程序运行
DOS是一个单任务的操作系统。
当要运行XXX.exe程序时,DOS程序会终止运行,CPU将控制权移交给此exe程序,程序运行完成后,会返回DOS程序继续运行,等待下一条指令的输入。
我们称这个过程为程序返回
原理详见CSAPP中对于进程与线程的阐述。
(2) 实现程序返回(中断机制)
在程序末尾添加返回的程序段。
mov ax,4c00H
int 21H
PS: 这是汇编指令,由CPU负责执行。
小结:
逻辑错误与语法错误
被编译器发现的错误是语法错误;编译后,在运行时发生的错误是逻辑错误。
(一个是语句错误书写,一个是缺少必要语句或者本身的逻辑问题)
2.环境配置
(1)下载Notepad++(用于编写源程序,用其他文本软件也可以)
(2)下载MASM,并将这几个程序与源程序文件放在一起
(3)在Notepad++上编写源程序并保存到上述的文件夹中
3.源程序的编译、连接与执行
(1)如源程序的示例那样编写好源程序后,我们就可以将其保存为一个XXX.asm的文件,然后进入DOSBox程序,输入以下命令以打开源程序文件所在位置:
mount c D:\StudyAssembler\MASM\ (文件所在位置)
C:
如图所示:
(2)输入以下命令以编译源程序:
masm 1.asm
如下图所示:
(3)编译完成后会生成一个1.obj对象文件,输入以下命令以连接程序:
link 1.obj
由于书本中的1.asm文件只有一个代码段,所以无需作其他配置,接下来一直回车直到出现命令行的出现。
如下图所示:
(4)输入“1.exe”运行程序,会看到一个短暂的黑屏,然后回到DOS命令行,程序运行完毕。
至此,我们完成了1.asm文件的编译、连接和运行,但是,我们并没有看到运行程序的过程。
PS:
- XXX.com文件大小为64KB,只能有一个段。
XXX.exe文件可以很大,其中一般有多个段。
所以当你编写的程序中只有一个段,可以考虑使用XXX.com文件执行,可以用于伪装。 - 可以用“ML 1.asm”指令来编译并连接程序,并且文件后缀和文件名有时可省略,因为已默认文件位置和类型。
- 可以指定文件生成的位置,只需在执行文件前加上路径
- Link的作用:可以将源程序分开编译,然后将它们连在一起
- 在命令行输入指令后加分号,可以使编译器忽略中间文件的生成,更简捷
4.对于DOS程序运行原理的解释
(1)任何通用系统都要提供shell(外壳)程序,用户使用此程序来操作计算机系统进行工作。
(2)DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS系统的shell。
(3)DOS启动时,先完成其他重要的初始化工作,然后运行command.com,command.com运行后,执行完其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符,比如:“c:\”或“c:\windows”等,然后等待用户的输入。
(4)command首先根据文件名找到可执行文件,然后将这个可执行文件中的程序加载入内存,设置CS:IP指向程序的入口。此后,command暂停运行,CPU运行程序。程序运行结束后,返回到command中,command再次显示由当前盘符和当前路径组成的提示符,等待用户的输入。在DOS中,command处理各种输入:命令或要执行的程序的文件名。我们就是通过command来进行工作的。
故汇编程序的完整执行过程为:
编程 (Edit) 一>l.asm一>编译(masm)一>1.obj一>连接(link)一>1.exe一>加载 (command) 一>内存中的程序一>运行(CPU)
5.跟踪程序的执行
在源程序的汇编指令前加标号(对段命名),并在end后加上这个标号,就给程序设置了一个入口。
Debug利用中断指令实现对程序的跟踪。
CX中存放程序的长度。
上图说明内存中程序存放在DS地址+10H(即256字节)处,这256字节里存放着PSP,DOS用来和程序通信。
故程序的物理地址为:(X+16)x16+0 (左移一格)
可用段地址和偏移地址:X+10H:0
Debug的执行原理:command加载Debug,Debug加载1.exe。程序结束后从1.exe中的程序返回到Debug,从Debug返回到command。
参考文献:
《汇编语言》(第三版)王爽著