可执行文件:
源程序: 源程序文件中的所有程序,皆称为源程序
程序: 源程序中最终 由计算机执行,处理的指令或数据。
汇编源程序由伪指令 和汇编指令 构成
汇编指令: 用来翻译成机器码
伪指令: 给编译器执行的,让编译器执行相关编译工作。
;
在汇编中表示注释
段名 segment
和段名 ends
是一对,定义一个段,这个段用来存放代码。(ends
后面的s
表示的是segment而不是复数的意思)标号:
XXX segment
里的XXX就是标号。它是一个段的名称 ,最终会被编译,连接成为一个段的段地址(就类似C中的指针)
end
用来标识程序的结束assume
假设寄存器和程序中的某一个XXX segment ... XXX ends
段相关联,很像给寄存器取别名注意: 至少要有一个段(代码段)
DOS是一个单任务系统:
比如仿DOS程序CMD,执行p2.exe时,CMD.exe停止执行。等到p2.exe执行完毕后,CMD.exe再次执行。
一个程序结束,然后返回调用的程序的行为,叫做程序的返回 ret
要让程序返回,需要以下两行代码
int 21H
是中断的意思,中断机制是DOS的发明。在Win中,变成了消息机制,从而单任务系统变为多任务系统。
注意: 上面两行代码是固定的,原理后面再讲。
程序错误分为:语法错误 和逻辑错误
很类似
JAVA
中的运行期异常 和编译期异常 。但是 错误 和 异常 一定要区分开来。错误是不可避免的,异常是可以避免的。所以,语法错误叫成 语法异常 更准确。逻辑错误,可能是由于代码不规范,那就应该列为 逻辑异常 ;如果是由于溢出之类导致内存出错之类的,应该列为 逻辑错误
源文件得不到目标文件的两类错误:
severe errors
有亿些错误.asm
为扩展名1.asm
生成目标文件1.obj
。直接输入masm,然后如图操作1.obj
生成可执行文件1.exe
。直接输入link,然后如图操作链接时报了个错误:
no stack segment
没有栈段
原因是: 在连接过程中,并未因为有“stacksg segment”,和assume了“ss:stacksg”就认为设置了堆栈段。
解决办法: 在代码段开头添加stack
关键字。但是! 这样处理后,文件不能停止了,所以这个错误直接忽略吧
有简化方式:
masm 文件名.asm;
可以直接默认编译,不用输入参数。link
也同样有这个功能。关键在于;
。ml 文件名.asm
,编译同时链接
Shell壳:
Shell
程序,让用户能操作计算机系统DOS
中的Shell
,叫做command.com
命令解释器。DOS
启动先初始化环境,然后运行command.com
,等待用户输入(命令也是一个可执行文件)command.com
会将这个程序加载入内存,设置CS/IP
指向程序入口。然后command.com
暂停,把CPU
控制权交出。程序结束后,将控制权交给command.com
,等待用户输入下面这个过程,要烂熟于心:
之前的程序是没有入口的,我们编写一个有入口的(不一定用start,只要和end后面的标号相同即可,不要忘记冒号)
用Debug单步执行: 用t单步执行,到了程序返回的代码int 21
用p
补充: 用
Debug
程序运行时,cx
默认记录的是程序的长度(2.EXE为c,表示12字节大小)
程序返回是返回上一级,2.asm
结束返回到debug.exe
,debug.exe
使用q
命令返回到command.com
程序被加载后,CS
应该和DS
指向同一个地址。看上图,会发现 C S − D S = 10 H CS - DS = 10H CS−DS=10H,中间那一段是什么?
PSP
,来进行程序和DOS的通信(和操作系统通信的一个接口)。这个区域占256字节,也就是10H。PSP
的头两个字节确实是CD
,通过查看汇编,我们可以知道,PSP
的第一条语句是int 20
,又是中断机制,以后再学
问题: Debug中有R命令,可以直接修改段寄存器的值。而mov只能通过通用寄存器间接修改。这两者修改有什么区别?
答: debug是程序,而mov是汇编语句,两者没关系!汇编中,只能通过jmp来跳转程序。
要完整描述一个内存单元,需要两种信息:
DS:[偏移地址]
- 补充:
mov ax, [0]
,传递的是字。mov al, [0]
,传递的是字节。- 重要: 如
五.1
所示,[0]
这个不会被识别为内存单元,需要使用:
mov bx, 0
mov ax, [BX]
也就是拿bx中转
描述性符号1: ()
用来描述一个地址存放的内容。段寄存器:偏移地址
就相当于一个指针,它指向的内存地址的值也可以用()
描述。
注意: ax之类通用寄存器,本身保存的内容可以说是一个值,也可以说是一个地址。用
()
描述会得到这个寄存器本身保存的内容。寄存器更像是一个普通变量。
段寄存器虽然名义上是保存地址的,实际上保存的还是一个值。DS:0000
可以描述一个内存单元,(DS):0000
也可以描述一个内存单元。
上面的地址描述不规范,应当为(DS)*16 + 0000
- 可以简单粗暴的理解为
()
就是取值的意思
描述性符号2: idata
表示常量
格式: loop 标号
执行loop会进行两步操作:
循环结构:
mov cx, 循环次数
s: 循环程序段
loop s
注意: 写程序前,要小心数据溢出,要事先估算数据范围
注意: 汇编程序中,程序不能以字母开头
一个问题: 一次t
只能走一步,如果循环的次数多,那是不是要一直搞t
?有没有简便方法?有的,g
命令(会将指定偏移地址前的代码执行完毕)
用p
命令,可以后台静默执行循环:
段前缀: 利用DS:[0]来取内存单元的值,这个DS叫做段前缀。也可以使用
es
/ss
/es
等其他方式指定段前缀。不写的话,默认段前缀为DS。
内存空间不止我们写的程序在用,还有包括操作系统在内的程序在使用。所以,我们在操作一个空间时,需要先知道它是安全的
补充: 在纯DOS方式(实模式)下,可以不理会DOS,直接用汇编去操作硬件
一般情况下,0:200~0:2ff
这个空间不被其他程序使用,是安全的。
安全的空间不够了咋办: 只要是合法的请求,系统会给的。具体怎么给,日后再说。
实验4答案: 0020H, 40H
看寄存器是多少字节的。
为什么要使用多个段: 为了日后的封装
dw
即define word
,定义字型数据(db
定义字节数据)
段地址: 这八个数据存在哪儿呢?因为是放在cs代码段中,所以数据存在cs代码段中
? 程序如何知道从16开始执行呢?可以注意到我们把程序的入口定义在了第一条程序语句前,就是这个入口。
最关键的不是读错行,而是读错指令。可以试下,当没有指定程序入口时,大概率也不会读错。8086很聪明,它知道从第16条开始执行。但是!因为实际读取的是机器码,从而导致语句错误(直观就是,用u查看我们所写代码对应的内存,会发现这些指令和我们写的不一样。原因就是被CPU把机器码错误组合导致的。当然,有概率不会出错。因为本人写的没出错,所以就没截图了。)总结:不一定报错,但是结果一定错
程序怎么知道程序的入口: 直接去找end
,end
后面的标号是一个地址,程序直接去找这个地址执行
需求: 把数据存入栈,然后逆序排列
补充: 可以看出,汇编语言并没有区域注释。这一点上和C如出一辙。
补充: 我们可以说,定义了八个字形数据。也可以说,开辟了八个字的内存空间,然后在里面存放了数据。两种角度,表达不同,理解不同,效果一样
前面我们在程序中放入了数据、栈、代码,我们需要时刻注意内存空间是数据、栈、还是段
代码: 注意阅读注释
补充1: 可以看出,每个部分占用一个段。(默认是连续的三个段)
原因: 如果一个数据 占用N个字节,程序加载后,该段实际占有的空间是16*(N/16 + 1)
补充2: 一个段的时候,DS比CS小10H,大的部分用来放
PSP
了。多个段的时候,DS只比CS小1H,那么PSP在哪呢?经过本人试验,发现PSP
在DS - 10H
处。
注意: CPU默认把机器码当指令而不是数据(数据就直接读写,指令需要CPU运算)
补充3: CPU是从上往下读的。先定义数据段,DS就小;先定义代码段,CS就小。
补充4: 如果不指定程序入口,CPU会从上到下,把碰到的机器码都当成指令