Search code examples
assemblyx86dosreal-modewindows-3.1

Why isn't INT 31H set up properly even with a DPMI host active?


I've been doing a lot of experimenting with assembly programming in MS-DOS. I've read that Windows 3.1 acts as a DPMI host for DOS programs, and that DPMI uses interrupt 31h for function calls.

So let's give that a try. I open a DOS prompt in Windows 3.1...

C:\WINDOWS>debug
-a100
23A4:0100 mov ax,0400
23A4:0103 int 31
23A4:0105
-r
AX=0000  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=23A4  ES=23A4  SS=23A4  CS=23A4  IP=0100   NV UP EI PL NZ NA PO NC
23A4:0100 B80004        MOV     AX,0400
-p

AX=0400  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=23A4  ES=23A4  SS=23A4  CS=23A4  IP=0103   NV UP EI PL NZ NA PO NC
23A4:0103 CD31          INT     31
-p

And suddenly I'm back in Windows with an error box: "This application has violated system integrity due to execution of an invalid instruction and will be terminated. Quit all applications, quit Windows, and then restart your computer.

So something clearly isn't working.

I try again, using the Trace command instead of Proceed. This way it will actually step inside of the interrupt handler instead of skipping over it.

AX=0400  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=23A4  ES=23A4  SS=23A4  CS=23A4  IP=0103   NV UP EI PL NZ NA PO NC
23A4:0103 CD31          INT     31
-t

AX=0400  BX=0000  CX=0000  DX=0000  SP=FFE8  BP=0000  SI=0000  DI=0000
DS=23A4  ES=23A4  SS=23A4  CS=F000  IP=FF01   NV UP DI PL NZ NA PO NC
F000:FF01 7261          JB      FF64
-

Let's see what's about to be executed...

-u
F000:FF01 7261          JB      FF64
F000:FF03 63            DB      63
F000:FF04 6C            DB      6C
F000:FF05 65            DB      65
F000:FF06 20564D        AND     [BP+4D],DL
F000:FF09 205669        AND     [BP+69],DL
⋮
F000:FF18 53            PUSH    BX
F000:FF19 0000          ADD     [BX+SI],AL
F000:FF1B 0000          ADD     [BX+SI],AL
F000:FF1D 0000          ADD     [BX+SI],AL
F000:FF1F 0000          ADD     [BX+SI],AL
-

...Yeah, that doesn't look like anything that's supposed to be executed. In fact, the byte code looks suspiciously like ASCII text. Sure enough...

-df000:ff01
F000:FF00     72 61 63 6C 65 20 56-4D 20 56 69 72 74 75 61    racle VM Virtua
F000:FF10  6C 42 6F 78 20 42 49 4F-53 00 00 00 00 00 00 00   lBox BIOS.......
F000:FF20  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
F000:FF30  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
F000:FF40  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ................
F000:FF50  00 58 4D CF CF 89 C0 89-C0 89 C0 89 C0 89 C0 FC   .XM.............
F000:FF60  5F 53 4D 5F 7D 1F 02 05-FF 00 00 00 00 00 00 00   _SM_}...........
F000:FF70  5F 44 4D 49 5F 58 C2 01-00 10 0E 00 0A 00 25 00   _DMI_X........%.
F000:FF80  00                                                .
-

That's clearly not a DPMI handler. Anyway, if I step past the JB FF64, it'll proceed to the DB 63 (the 'c' in "Oracle") and that's what causes the invalid instruction error. Alternatively, if the Carry flag is set, it'll take the jump and encounter what it interprets as a JGE FF85 instruction. If the flags are such that it doesn't take the jump, it'll eventually reach another invalid instruction. Otherwise, it'll increase the byte at [BX+SI] by the value in AL 105 times, increase the byte 77 bytes after that by the value in BL a single time, and finally reboot the VM, because the next instruction is the real-mode reset vector. (In v86 mode, but I guess Windows 3.1 allows it.)

Long story short, it's clear there's no DPMI handler installed at INT 31H like there's apparently supposed to be. A few additional bits of information:

  • You may have noticed I only typed int 31, not int 31h. This is not a mistake; debug uses and expects hexadecimal everywhere. And as you saw, it didn't try to execute the graphics mode bitmap font as code. :P

  • I tried it with Qualitas MAX instead of Windows 3.1, as well as with no DPMI host at all, and got the same result in both cases. (Minus the Windows 3.1 error dialog, of course.)

With that in mind, can anyone tell me what I'm doing wrong?


Solution

  • The Real/Virtual 86 Mode interface for DPMI is only interrupt 2Fh service 1687h. That returns an entrypoint which you can use to enter Protected Mode. Interrupt 31h services are only available in Protected Mode. Here's an example of a small DPMI client. Relevant code to enter Protected Mode:

            mov ax, 1687h
            int 2Fh
            test ax, ax             ; DPMI host installed?
            jnz nohost
            push es                 ; save DPMI entry address
            push di
            test si, si             ; host requires client-specific DOS memory?
            jz .nomemneeded         ; no -->
            mov bx, si
            mov ah, 48h
            int 21h                 ; allocate memory
            jc nomemory
            mov es, ax
    .nomemneeded:
            ; (message and breakpoint omitted)
            mov bp, sp
            mov ax, 0001h           ; start a 32-bit client
            call far [bp]           ; initial switch to protected-mode
            jnc initsuccessful
    initfailed:
    

    Protected Mode is generally exited by executing PM interrupt 21h service 4Ch (which subsequently is also passed to the DOS 86M handler and thus terminates the DOS process).

    By the way, to debug a DPMI client you may want to use FreeDOS DebugX or my fork lDebugX.