深入分析FreeDos -- COM文件加载与执行


现在已经走到了执行第一个任务的地方了,如下

main()
      |-->init_kernel()
      |
      |-->kernel() -> p_0() -> DosExec(..."COMMAND.COM"...)

官方给出的COMMAND.COM是FreeCom,FreeCom有些复杂,既然我们研究的是COM文件加载执行,何不选择一个较小的COM文件,岂不更方便?

很多编程语言的第一个示例都是”Hello World”,这里我们也不脱俗,也用个只显示Hello World的COM文件作为研究对象。

;HelloWrold.asm
	org	0100h		; COM文件必须从偏移 0x100开始

	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	call	ShowHelloWorld	; 调用显示Hello World
	jmp	$		; 死循环
	;mov ah, 4ch		; 回退到DOS系统,在此不需要
	;int 21h
	
ShowHelloWorld:
	mov	ax, HelloStr
	mov	bp, ax		; ES:BP = 串地址
	mov	cx, 12		; CX = 串长度
	mov	ax, 01301h	; AH = 13,  AL = 01h
	mov	bx, 000ch	; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
	mov	dl, 0
	int	10h		; int 10h
	ret
HelloStr:	db	"Hello World!"

编译命令: nasm HelloWrold.asm -o COMMAND.COM

将COMMAND.COM放到Image(虚拟软盘)上,有很多种方法,也可以参考第二节,将这个COMMAND.COM放到build目录下,执行build.bat即可。

运行截图如下,左上角显示Hello World:

pic


先说下COM文件吧,上面的汇编代码设置 org 0100h, 为什么是0x100呢

在COM执行前,DOS需要给COM分配256个字节的PSP段,用于保存程序状态。详见http://en.wikipedia.org/wiki/Program_Segment_Prefix
将COM载入内存后,设置IP为0x100,即COM起始处。

下面就分析COM的加载执行过程:

因为是COM文件,所以流程为DosExec() -> DosComloader(),Kernel先设置好环境变量,然后将COMMAND.COM加载进内存,如下图

pic

加载完后,在COMMAND.COM内存前0x100处设置PSP,然后设置新任务的寄存器及栈空间,最后执行跳转:

pic

上面的代码,先在当前段的末尾处划分块空间,保存新任务的寄存器。以当前版本为例,此时mem段为0x13EB,为新任务选择的栈地址为0x13EB:0xFFFE。 切换任务后,CS:IP为 13EB:100,即COMMAND.COM所在内存地址,开始执行COMMAND代码。 下面是任务切换前后CPU的寄存器对比:

寄存器   任务切换前      任务切换后,执行COMMAND.COM
      _exec_user执行前   _exec_user执行后
AX       13EB            FFFF       
BX       0000            0000
CX       0004            0000
DX       0080            0000
SP       283E            FFFE
BP       286A            0000
SI       0005            0000
DI       0000            0000
CS       0060            13EB
DS       0F40            13EB
SS       0F40            13EB
ES       13EB            13EB
IP       E421            0100
Flags    0246            0202

最后,是COMMAND.COM执行时内存分配图:

pic


上篇: 深入分析FreeDos -- Kernel初始化 下篇: 深入分析FreeDos -- FreeCom编译