
📌 汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。 本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
我们在前面文章中讲过,在DOS中,可执行文件中的程序P1若要运行,必须有一个正在运行的程序P2,将P1从可执行文件中加载入内存,将CPU的控制权交给它,P1才能得以运行;当P1运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P2。
按照上面的原理,再来看一下之前的1.exe 的执行过程(思考相关的问题)。
此时,有一个正在运行的程序将1.exe中的程序加载入内存,这个正在运行的程序是什么?它将程序加载入内存后,如何使程序得以运行?
程序运行结束后,返回到哪里?
如果你对 DOS 有比较深入的了解,那么,很容易回答问题1、问题2中所提出的问题。如果没有这种了解,可以先阅读下面的内容。
操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统进行工作。

DOS 中有一个程序 command.com,这个程序在DOS 中称为命令解释器,也就是DOS系统的shell。
DOS 启动时,先完成其他重要的初始化工作,然后运行command.com,command.com运行后,执行完其他的相关任务后,在屏幕上显示出由当前盘符和当前路径组成的提示符,比如:“c:”或“c:\windows”等,然后等待用户的输入。
用户可以输入所要执行的命令,比如,cd、dir、type等,这些命令由command执行,command执行完这些命令后,再次显示由当前盘符和当前路径组成的提示符,等待用户的输入。
如果用户要执行一个程序,则输入该程序的可执行文件的名称,command首先根据文件名找到可执行文件,然后将这个可执行文件中的程序加载入内存,设置CS:IP指向程序的入口。此后,command 暂停运行,CPU 运行程序。程序运行结束后,返回到command中,command再次显示由当前盘符和当前路径组成的提示符,等待用户的输入。
在DOS中,command 处理各种输入:命令或要执行的程序的文件名。我们就是通过command来进行工作的。
现在回答问题1和问题2中所提出的问题。
到此,我们知道了完成一个汇编程序从写出到执行的全部过程应该如下图所示:

可以用 Debug 来跟踪一个程序的运行过程,这通常是必须要做的工作。我们写的程序在逻辑上不一定总是正确,对于简单的错误,仔细检查一下源程序就可以发现;而对于隐藏较深的错误,就必须对程序的执行过程进行跟踪分析才容易发现。
下面以在前面的内容中生成的可执行文件1.exe为例,讲解如何用Debug对程序的执行过程进行跟踪。
现在我们知道,在 DOS中运行一个程序的时候,是由command将程序从可执行文件中加载入内存,并使其得以执行。但是,这样我们不能逐条指令地看到程序的执行过程,因为command 的程序加载,设置CS:IP指向程序的入口的操作是连续完成的,而当CS:IP一指向程序的入口,command 就放弃了CPU的控制权,CPU立即开始运行程序,直至程序结束。
为了观察程序的运行过程,可以使用Debug。Debug可以将程序加载入内存,设置CS:IP指向程序的入口,但Debug并不放弃对CPU的控制,这样,我们就可以使用Debug的相关命令来单步执行程序,查看每一条指令的执行结果。
这里仍然要用到我们之前在【汇编语言】寄存器(CPU工作原理)(七)—— 查看CPU和内存,用机器指令和汇编指令编程这篇文章中所下载的软件和执行程序。
具体方法如下图所示:

在提示符后输入“debug 1.exe”,按 Enter 键,Debug将程序从 1.exe 中加载入内存,进行相关的初始化后设置 CS:IP 指向程序的入口。
接下来可以用R命令看一下各个寄存器的设置情况,如下图所示。

可以看到,Debug将程序从可执行文件加载入内存后,cx中存放的是程序的长度。1.exe 中程序的机器码共有15个字节。则1.exe加载后,cx中的内容为000FH。
现在程序已从1.exe中装入内存,接下来查看一下它的内容,可是我们查看哪里的内容呢?程序被装入内存的什么地方?我们如何得知?
这里,需要讲解一下在DOS系统中.EXE文件中的程序的加载过程。下图针对我们的问题,简要地展示了这个过程。

注意,有一步称为重定位的工作在上图中没有讲解,因为这个问题和操作系统的关系较大,我们不作讨论。
那么,我们的程序被装入内存的什么地方?我们如何得知?从上图中我们知道以
下的信息。
所以,从ds中可以得到PSP的段地址SA,PSP的偏移地址为0,则物理地址为SA*16+0。 因为PSP占256(100H)字节,所以程序的物理地址是:SA*16+0+256=SA*16+16*16+0=(SA+16)*16+0 可用段地址和偏移地址表示为:SA+10H:0。
现在,我们看一下2.2.1图中DS的值,DS=129E,则PSP的地址为129E:0,程序的地址为12AE:0(即 129E+10:0)。
在上面2.2.1图中,CS=12AE,IP=0000,CS:IP指向程序的第一条指令。注意,源程序中的指令是 mov ax,0123H,在 Debug 中记为 mov ax,0123,这是因为 Debug 默认所有数据都用十六进制表示。
可以用U命令看一下其他指令,如下图所示。

可以看到,从076A:0000-076A:000E都是程序的机器码。
现在,我们可以开始跟踪了,用T命令单步执行程序中的每一条指令,并观察每条指令的执行结果,到了int 21,我们要用P命令执行,如下图所示。

上图中,int 21 执行后,显示出“Program terminated normally”,返回到 Debug中。表示程序正常结束。
注意,要使用P命令执行int 21。这里不必考虑是为什么,只要记住这一点就可以了。
需要注意的是,在DOS中运行程序时,是command将程序加载入内存,所以程序运行结束后返回到command中,而在这里是Debug 将程序加载入内存,所以程序运行结束后要返回到 Debug 中。
使用Q命令退出Debug,将返回到command中,因为Debug是由command 加载运行的。

在 DOS 中用“debug 1.exe”运行 Debug对 1.exe 进行跟踪时
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!