Current version

v1.10.4 (stable)


Main page
Archived news
Plugin SDK
Knowledge base
Contact info
Other projects


Blog Archive

Why Ctrl+Break doesn't work with QBasic in DOSBox

One of the problems with switching to a 64-bit version of Windows is that you can't run 16-bit DOS programs, due to the removal of NTVDM. While this is usually attributed to Microsoft cleaning legacy house, it's also due to a hardware limitation: the original x86-64 specification prohibited VM86 mode under long mode. Therefore, I've been making greater use of DOSBox in the few cases where I need to run such programs, since it integrates a little better than virtualization.

Unfortunately, one of the old programs I occasionally run is QBasic, and there's a long standing problem where Ctrl+Break doesn't work in QBasic under DOSBox.

I finally got annoyed enough about this to try to diagnose it myself. I'd had luck doing this previously, when I tracked down the bug where the keyboard would suddenly stop working if you pressed Ctrl+Esc. (I'm actually not a fan of the "fix it yourself it's open source" mentality, and I don't have any good memories of 16-bit real mode x86, but scuttle four Colonization games in a row and I will launch the debugger.) Now, I already had a suspicion as to what was causing this: there's an anomaly in the PC keyboard where certain scan codes change depending on modifier keys. One of them is Pause, which magically turns to Break when Ctrl is also pressed. This anomaly is often reflected in higher level APIs, and in Win32, this manifests as your window handler getting a WM_KEYDOWN message with VK_CANCEL instead of VK_PAUSE. Therefore, somewhere along the line, it's likely that this difference wasn't being emulated.

Sure enough, when I dug into the keyboard emulation code, it was always sending the scan codes for the pause key. That was easily fixed, but I noticed that Ctrl+Break was never arriving from the UI, either. Okay, we probably just need to check for the analogous SDL constant for VK_CANCEL, which appears to be SDLK_BREAK. Easily done. Except... that doesn't seem to be arriving either. In fact, no keyboard events are arriving at all when I press Ctrl+Break. Luckily, I built the SDL library in Debug configuration from source, so I can debug into it. Let's see, no WM_KEYDOWN handler, which means it's probably using DirectInput, so let's set a breakpoint on the DirectInput code... uh oh.

DirectX, like most Windows APIs, has a lot of unwritten lore that still isn't really documented after a decade, basic stuff that you need to know but end up learning the hard way. An example is dynamic vertex/index buffers in Direct3D9, which seem easy, until you find out that occasionally on mode switches Lock() hands back an invalid pointer while still returning D3D_OK. Apparently, one of the undocumented gotchas for DirectInput is that you might not get Ctrl+Break events, ever. I'd never encountered this because I use window messages for keyboard input in my own programs. There wasn't a straightforward fix for this, and I didn't really have the intention to rewrite the input path in both DOSBox and SDL, so I decided to just bypass it and try to get the Pause key to send Ctrl+Break into the emulation.

The next step, then, was to figure out how to push Ctrl+Break through. As I said earlier, it was easy enough to send down the right set of scancodes, but that wasn't working. That meant the next likely missing part was the BIOS handling, which involves setting a break flag, pushing a break code into the keyboard buffer, and calling a break interrupt vector. None of that was working either, so I had to figure out how QBasic handles Ctrl+Break. Surprisingly, WinDbg has a limited ability to debug 16-bit code -- disassembly's a bit wonky, but breakpoints do work -- so I was able to debug the IRQ1 handler under NTVDM on a machine running 32-bit XP. It turns out that while QBasic hooks the BIOS Ctrl+Break callback to block it in edit mode, it run mode it takes over the IRQ1 interrupt vector and monitors the raw scan codes for Ctrl+Break. Not only that, but it won't accept it as such unless it also sees the Ctrl key down, which is why I couldn't get it to work with Pause as the host key. I hacked the emulator's keyboard hardware code to also send Ctrl down/up codes, and sure enough, QBasic finally broke out of run mode. Victory!

Of course, soon after that, I found a post saying that you can just bind Ctrl+ScrollLock instead, since QBasic also accepts that. Figures. On my current laptop, that ends up being the unintuitive and very discoverable Ctrl+Fn+F5.

Anyway, in conclusion, the reason why Ctrl+Break hasn't been fixed yet in DOSBox is because there's half a dozen things in the way.


This blog was originally open for comments when this entry was first posted, but was later closed and then removed due to spam and after a migration away from the original blog software. Unfortunately, it would have been a lot of work to reformat the comments to republish them. The author thanks everyone who posted comments and added to the discussion.