A hacky mechanism to call forth 32 bits userland DLL's from 16 bits applications
which execute under the NTVDM is supported in Windows in order to let DOS
applications extend their environment, whatever it is (video, sound, serial port
or any other peripheral). It is called BOP(-Instructions). And I guess no one really
knows what it stands for, but who cares. This mechanism let you register a DLL
and then call it and later on unregister the DLL. Luckily, it is easily used,
with an interesting trick. In order to invoke those BOP's you have to use an invalid
instruction (0xc4, 0xc4 - sort of LEA but with register-register operands which is
invalid form). Then an Invalid Instruction exception, interrupt #6, is generated
by the processor and the KiTrap06 is executed. The KiTrap quickly tries to determine
the reason of the exception, whether it was a misuse of the LOCK prefix or maybe just
a BOP instruction. It then checks for two specific BOP's instructions, for fast IO.
The BOP instructions we have to use are for 3rd party services, I guess letting DOS
applications communicate with userland applications or VDD's. There are many kinds of
BOP's, but we will simply use a specific one, 0x58, and passing parameters to it as
well. KiTrap06 transfers control to NTVDM which does the real job for handling the
BOP's instructions. The DLL file should be in the DOS application directory, or in
the default paths. When invoking the dispatcher NTVDM guarantees to not change the
values of the registers, thus using the DDK with the VDD functions you could be able
to read and write memory to and fro userland DLL. But in the example I use NTVDM.EXE
directly, in order to spare the library and header files.
Register Module: Register a third party DLL with the BOP manager.
Input:
DS:SI - Null terminated string with the userland DLL name.
ES:DI - Null terminated string with the initialization routine (Optional, ES=DI=0).
DS:BX - Null terminated string with the dispatch routine (for later use by dispatcher).
Return:
If Carry is set:
AX = 1 - DLL file was not found.
AX = 2 - Dispatch routine wasn't found (GetProcAddress failed).
AX = 3 - Initialization routine wasn't found (GetProcAddress faild).
AX = 4 - Insufficient memory.
Otherwise
AX contains the handle of the registered DLL.
BOP Instruction: 0xc4, 0xc4, 0x58, 0x00
DispatchCall: Call the registered DLL function.
Input: AX - Handle to a registered DLL.
[It depends on you how to pass info to the DLL]
Return: Depends on dispatch routine.
BOP Instruction: 0xc4, 0xc4, 0x58, 0x02
Unregister Module: Unregister a module from the BOP manager.
Input: AX - Handle to a registered DLL.
Return: None
BOP Instruction: 0xc4, 0xc4, 0x58, 0x01
The userland DLL side (using VS):
File galaxy.dll:
__declspec(dllexport) void MyRoutine()
{
MessageBox(NULL, "Hey there", "BOPs", MB_OK);
}
The DOS side (using NASM):
bits 16
mov si, DLL_NAME
;mov di, INIT_PROC
xor di, di
push di
pop es
mov bx, DISPATCHER
db 0xc4, 0xc4, 0x58, 0x0
jc error:
; Store the DLL's handle
mov [DLL_HANDLE], ax
;...
;... In the sample, a string is being passed to the userland DLL!
;...
error:
ret
DLL_HANDLE dw 0
DLL_NAME: db "galaxy.dll", 0
INIT_PROC: db "InitProc", 0
DISPATCHER: db "MyRoutine", 0
Here's a full sample code: galaxy.zip
That took a few hours of researching, I think it's really kewl,
not sure if it's handy though. Oh well. ;)