经过一番黑客攻击和摆弄之后,我终于能够让它工作了。这并不像我希望的那么简单,所以请坐在座位上。
首先,您需要认识到(尽管听起来很抽象)DOS 是一个单用户、非多任务系统。在这种特殊情况下,这意味着您不能同时运行两个进程。你need等待一个进程完成执行,然后再转移到另一进程。进程并发性可以在某种程度上用 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
the org
指令,它指定实际加载到内存时文件的来源 - 在我们的例子中,这是100h
。以下是三个标签的声明,exename
and 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
label,这是 Daniel Roethlisberger 提到的 EXEC 结构的表示。第一项为零,这意味着新进程应与其父进程具有相同的环境。接下来是三个地址:命令行、第一个 FCB 和第二个 FCB。您应该记住,实模式下的地址由两部分组成:段的地址和段中的偏移量。这两个地址都是 16 位长。它们以小端方式写入内存,偏移量在前。因此,我们将命令行指定为offsetcmdline
,以及 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
。因此,我们需要通过执行“RESIZE MEMORY BLOCK”来告诉DOS我们并不真正需要所有内存AH=4Ah
call (拉尔夫·布朗). The bx
寄存器应该具有以 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 对此进行了测试,希望它也适合您。