经过一些黑客和玩弄之后,我能够得到这个工作。这并不像我希望的那样简单,所以请抓住你的座位。
首先,您需要意识到(听起来很抽象)DOS 是一个单用户、非多任务系统。在这种特殊情况下,这意味着您不能同时运行两个进程。您需要等待一个进程完成执行,然后才能移动到另一个进程。进程并发可以用 TSR(终止并保持驻留)进程在某种程度上模拟,尽管被终止,它们仍保留在内存中,并且可以通过从其代码中挂钩一些中断并稍后从其他一些代码调用它来恢复它们的执行。尽管如此,它与现代操作系统(如 Windows 和 Linux)使用的并发性不同。但这不是重点。
您说您使用 NASM 作为您选择的汇编程序,因此我假设您将代码输出到 COM 文件,这些文件又由 DOS 命令提示符执行。COM 文件由命令提示符在偏移处100h
加载(在加载到该位置的跳转执行后)并且不包含除“精简”代码和数据之外的任何其他内容 - 没有标题,因此它们是最容易生成的。
我将分段解释汇编源代码,以便您(也许)可以更好地了解引擎盖下发生的事情。
该计划以
org 100h
section .data
exename db "C:\hello.com",0
exename2 db "C:\nasm\nasm.exe",0
cmdline db 0,0dh
该org
指令在实际加载到内存时指定文件的来源 - 在我们的例子中,它是100h
. 随后声明了三个标签,它们是要执行的程序的空终止路径,以及exename
指定新创建的进程应该接收的命令行。请注意,它不仅仅是一个普通的字符串:第一个字节是命令行中的字符数,然后是命令行本身,以及一个回车符。在这种情况下,我们没有命令行参数,所以整个事情归结为. 假设我们想作为 params 传递:在这种情况下,我们需要将此标签声明为(注意开头的额外空格!)。继续...exename2
cmdline
db 0,0dh
-h -x 3
db 8," -h -x 3",0dh
dummy times 20 db 0
paramblock dw 0
dw cmdline
dw 0 ; cmdline_seg
dw dummy ; fcb1
dw 0 ; fcb1_seg
dw dummy ; fcb2
dw 0 ; fcb2_seg
标签dummy
只有 20 个字节,其中包含零。接下来是paramblock
标签,它是 Daniel Roethlisberger 提到的 EXEC 结构的表示。第一项是零,这意味着新进程应该具有与其父进程相同的环境。三个地址如下:命令行、第一个 FCB 和第二个 FCB。您应该记住,实模式下的地址由两部分组成:段的地址和段的偏移量。这两个地址都是 16 位长。它们以小端方式写入内存,偏移量在前。因此,我们将命令行指定为 offset cmdline
,并将 FCB 的地址指定为标签的偏移量dummy
,因为 FCB 本身不会被使用,但地址需要指向一个有效的内存位置。这些段需要在运行时填充,因为加载程序会选择加载 COM 文件的段。
section .text
entry:
mov ax, cs
mov [paramblock+4], ax
mov [paramblock+8], ax
mov [paramblock+12],ax
paramblock
我们通过在结构中设置段字段来开始程序。由于对于 COM 文件,CS = DS = ES = SS
即所有段都是相同的,我们只需将这些值设置为cs
寄存器中的值。
mov ax, 4a00h
mov bx, 50
int 21h
这实际上是应用程序中最棘手的问题之一。当 DOS 将 COM 文件加载到内存中时,默认情况下会为其分配所有可用内存(CPU 不知道这一点,因为它处于实模式,但 DOS 内部无论如何都会跟踪它)。因此,调用 EXEC 系统调用会导致它以No memory available
. AH=4Ah
因此,我们需要通过执行“RESIZE MEMORY BLOCK”调用(Ralf Brown)来告诉 DOS,我们并不真正需要所有内存。这bx
register 应该具有以 16 字节为单位(“段落”)的内存块的新大小,因此我们将其设置为 50,我们的程序有 800 个字节。我不得不承认这个值是随机选择的,我尝试将其设置为有意义的值(例如,基于实际文件大小的值),但我一直无处可去。ES
是我们想要“调整大小”的段,在我们的例子中是CS
(或任何其他的,因为在加载 COM 文件时它们都是相同的)。完成此调用后,我们就可以将新程序加载到内存并执行它了。
mov ax, 0100h
int 21h
cmp al, '1'
je .prog1
cmp al, '2'
je .prog2
jmp .end
.prog1:
mov dx, exename
jmp .exec
.prog2:
mov dx, exename2
这段代码应该是不言自明的,它根据标准输入选择插入程序的路径DX
。
.exec:
mov bx, paramblock
mov ax, 4b00h
int 21h
这是调用实际EXEC
系统调用 ( AH=4Bh
) 的地方。AL
包含0,表示程序应该被加载并执行。DS:DX
包含可执行文件的路径地址(由前面的代码段选择),并ES:BX
包含paramblock
标签的地址,其中包含EXEC
结构。
.end:
mov ax, 4c00h
int 21h
在完成调用的程序的执行后exec
,父程序通过执行AH=4Ch
系统调用以退出代码零终止。
感谢vulture-
来自 Freenode 上的##asm 的帮助。我用 DOSBox 和 MS-DOS 6.22 对此进行了测试,希望它也适用于你。