IBM DOS 5.0 QBASIC hangs on non-IBM systems

Much like its predecessors, IBM DOS 5.0 has no trouble running on clone systems, as long as they’re sufficiently compatible. That includes virtual machines. However, the QBASIC.EXE (and consequently EDIT.COM) program dated May 9, 1991 consistently hangs on any non-IBM system. Why is that?

Analysis shows that the key is the presence (or absence) of ROM BASIC, which was built into the ROM of all IBM PC systems (including PS/2) from the very first PCs until approximately 1991, sometime after IBM DOS 5.0 was released. But why does QBASIC care when it’s supposed to replace the ROM BASIC anyway?

Disassembly shows that during startup, the IBM version of QBASIC.EXE calls INT 15h, sub-function 22h to query the ROM BASIC location. If the call fails, as it will on older IBM systems, the traditional ROM BASIC location of F600:0000 is assumed. That will also happen on clone systems which do not support the INT 15h, sub-function 22h call.

What IBM’s QBASIC.EXE does next is rather unusual. It runs through a few hundred bytes (780 bytes to be exact) at the beginning of the ROM BASIC and uses the data to XOR code within the QBASIC.EXE executable itself. There appears to be no other use for the ROM BASIC.

In other words, the ROM BASIC simply acts as a key to unlock QBASIC.EXE. IBM’s QBASIC does not need it, but requires it and will deliberately crash without it.

This may have been a form of copy protection, to prevent running on non-IBM systems (which IBM DOS 5.0 did not officially support). Or it may have been a licensing requirement from Microsoft’s side; since IBM was already licensing the ROM BASIC from Microsoft, it is possible that the license fee for QBASIC was less than what other OEMs had to pay.

IBM DOS 5.00.1 changed the situation. Since that version, IBM officially supported selected non-IBM systems, and requiring the ROM BASIC even as a licensing check no longer made any sense. The “decryption” code was removed from QBASIC.EXE and IBM’s QBASIC happily ran on non-IBM computers. Around the same time, IBM also removed ROM BASIC from its own systems.

It should be possible to “unlock” the original IBM version of QBASIC, or write a simple TSR which intercepts INT 15h, sub-function 22h and points to the first 800 or so bytes of the IBM ROM BASIC image. However, it’s much easier to simply use a newer QBASIC executable.

This entry was posted in BIOS, DOS, PC history. Bookmark the permalink.

20 Responses to IBM DOS 5.0 QBASIC hangs on non-IBM systems

  1. Yuhong Bao says:

    FYI, seems that this change was introduced in a CSD to PC-DOS 5.0.

  2. michaln says:

    Which CSD? What file date? Do you have a reference?

  3. William Jones says:

    If I may ask, how did you figure this out/what’s your references? I just attempted to figure out the answer to this question by running DEBUG, but I got impatient because I didn’t know what to look for. See, I just replaced my IBM BIOS in my PC/AT with an equivalent AWARDBIOS (I put PCDOS 5.0 on the AT when I got it) so that I could dump the rom of the original BIOS. Ah well, switching back to the IBM BIOS until I can be arsed to write the TSR…

  4. michaln says:

    I disassembled the QBASIC executable with IDA after I realized that it consistently hangs with anything except a real IBM BIOS ROM. What to look for is a call to INT 15h, fn 22h. After calling that, QBASIC does the XOR thing on its own code segment. I’m not sure if this has been analyzed/documented elsewhere. I was comparing the execution with/without IBM BIOS and found that with non-IBM BIOS, QBASIC hung in code that just didn’t look right at all.

    I’d suggest using a newer QBASIC (from IBM DOS 5.02, or even 5.00.1 if you have it — I don’t). A TSR is not worth the effort unless it’s just for fun. I can’t comment on the relative qualities of IBM vs. AWARD BIOS, so I don’t know which is the least painful solution for you.

  5. William Jones says:

    Funny this is, I’m actually writing my first TSR to fix this problem as a just-for-fun exercise. I must be batshit insane to think learning DOS x86 is fun lol.

  6. michaln says:

    Not insane, just mildly masochistic 🙂 Learning DOS is worthwhile, if only to find out how software should not be done.

  7. William Jones says:

    Would you be willing to provide a link or email me your disassembly of QBASIC? I don’t have the money to purchase IDA. The portion of the TSR which handles all BUT subfunction 22h works correctly, but QBASIC hangs when I try to patch in my routine (possibly because I’m not returning from the interrupt correctly). Of course, the subfunction 22h handler does NOT call the BIOS routine after making sure the registers are correctly set, and attempts to return (iret) to where QBASIC calls interrupt 15.

  8. michaln says:

    I can just post the relevant snippet here:

    push ds
    push si
    mov ah, 22h
    int 15h
    mov ax, es
    jnb short have_basseg
    mov ax, 0F600h ; ROM BASIC segment in PC/XT/AT
    mov ds, ax
    assume ds:nothing
    xor si, si
    mov bx, offset loc_14ECC
    mov ax, seg seg005
    mov es, ax
    xor es:[bx], ax
    inc bx
    inc bx
    cmp bx, offset sub_151D9
    jb short next_word
    pop si
    pop ds

    The “encrypted” code is between loc_14ECC and sub_151D9 (i.e. offsets 14ECCh and 151D9h in the image). INT 15h, fn 22h must return the BASIC segment in ES register.

  9. Yuhong Bao says:

    William Jones: FYI, IDA Free include support for DOS EXE/COM formats.

  10. William Jones says:

    Oh thank you, I didn’t know that. I didn’t abandon the project… just caught up with finals right now… stay tuned!

  11. William Jones says:

    Took me long enough, but amazingly, I did create a TSR which fixes the problem with QBASIC (and therefore EDIT). You can download it (as well as the source) here (Public Dropbox link:

    If compiling, you need the OpenWatcom C Compiler and the Netwide Assembler. Though it took me forever (school/real life gets in the way), and I put WAY too much effort into this to be considered sane, it was fun and gave me good practice for writing a TSR. The TSR library/object files that I used can be found on this Vintage Computer thread.

  12. michaln says:

    Nice work! I’m going to have a peek at your source code later… I would have probably written everything in assembly for this particular TSR, but that’s not a great option for anything more involved.

  13. William Jones says:

    Ordering segments is a pain in the ass in C (even more than normally :P), no question about it. But using C for the interrupts and front end stems from really only 3 reasons:

    1. I’m not really comfortable with the video BIOS interrupts/lodsw for string processing and output (MSDOS API is easier, and functions to capture strings as input I can do in raw assembly).

    2. I didn’t feel like incrementing/decrementing the SP to find the correct registers (ES BX) to modify on return from int 15h, 22h. I don’t have the order of how registers are passed in an interrupt memorized; point blank- I was bound to screw it up and left wondering what went wrong :P. Watcom C took care of this for me using the “INTPACK regs” structure.

    3. The code I used as a TSR basis was written using NASM syntax, which I’m not as good with compared to MASM (I know NASM is probably superior, but… old habits die hard).

    I don’t have an issue mixing C and assembly provided that I have a good idea what’s going on in the machine code. 🙂

  14. michaln says:

    You don’t order segments in C – you order them in assembler and let C code follow the order.

    In an assembler interrupt handler, you don’t mess with SP to access registers – you just access the registers 🙂 And spend a lot of time worrying about which registers exactly you can modify and which you can’t.

    Mixing C and assembler indeed isn’t hard, as long as one has a good idea what the calling conventions are. Including the not so obvious bits, such as remembering that C code typically assumes the direction flag is cleared, while assembler does not necessarily stick to that.

  15. ibmpc5150 says:

    I have 4 kinds of IBM DOS 5.0x as 5.25″ and 3.5″


    5.00 (Normal) (12:00PM / 05-09-1991)
    –> 6EA

    5.00.1 (Upgrade) (12:00PM / 02-28-1992)
    –> 6EA
    (*Upgrade version needs preinstalled DOS 2.10 or higher)

    5.02 (Normal) (12:00PM / 02-28-1992)
    –> 6EA

    5.02 (Normal) (12:00PM / 09-01-1992)
    –> 5EA (Quick BASIC is removed.)


    5.00 (Normal) (12:00PM / 05-09-1991)
    –> 3EA

    5.02 (Normal) (12:00PM / 09-01-1992)
    –> 3EA (Quick BASIC is still included.)

    I wonder you have 5.25″ or 3.5″ of IBM DOS 5.0x

    I can’t understand why Quick Basic in 5.25″ version of 5.02 was removed.

    I’ve tested it PCE – IBM PC Emulator with BASIC or DOSBOX.
    On PCE, all versions of QBASIC works well.
    On DOSBOX, QBASIC of 5.00 doesn’t run correctly.

  16. michaln says:

    The DOSBox behavior is predictable… they don’t have the IBM ROM BASIC. Which is obviously copyrighted material and can’t be easily distributed, so no surprise there. I assume PCE lets users “find” and supply the original ROMs.

    I wonder if QBASIC (not Quick BASIC, a different product!) was removed simply to save space. Perhaps the assumption was that only users with old machines would want the 5.25″ floppies and would be more interested in the old BASIC anyway. Just a speculation; I see no mention of BASIC at all in the IBM DOS 5.02 announcement. Whereas the 5.00.1 announcement explicitly mentions that QBASIC now works on clones…

    Anyway, I only have the 3.5″ versions of IBM DOS 5.00 and 5.02. I’d actually be very interested in the 5.00.1 version; should be useful for my DOS 5 article that I’ve been working on (on and off).

  17. William Jones says:

    I’m personally thinking of rewriting my TSR to postpone loading the IBM BASIC decryption key into memory until Interrupt 15h, 22h is actually called. This would save about 800 bytes or so of precious conventional memory, which always bothered me about the original TSR. I’d still like to have the TSR written partially in C, but this may be a bit more difficult because I’m not quite sure if I can coerce a C program to only load part of itself into memory (the decryption key being the remaining portion).

    I’m splitting my spare time for programming between DOS/PC programming and SNES programming, with maybe some microcontroller projects in between. So, I’m still around- and still check this post from time to time- just not as frequent.

  18. Michal Necasek says:

    If you mean an executable (.EXE) file that isn’t fully loaded into memory then yes, that’s quite doable in C. Simply concatenate the .EXE file with whatever binary data you want…

  19. Ever since Dropbox very-conveniently discontinued the Public folder, the above link has been broken. A few people asked me to upload the source out of curiosity, so the source (and binaries for those who need it!) are available on Github now. Hope this is still useful to some.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.