Detecting DOSBox via the Callback Opcode
- title
- Detecting DOSBox via the Callback Opcode
- type
- summary
- summary
- Snow's trick for proving DOSBox by triggering its custom-invented FE/7 callback opcode and watching whether a
- tags
- retro-computing, x86, emulators, dos
- created
- 2026-04-25
- updated
- 2026-04-25
A December 2025 post on datagirl-xyz by Snow, walking through how to reliably detect that a DOS program is running inside DOSBox โ without relying on something brittle like the BIOS version string at FE00:0061 or the existence of the Z: drive. The approach exploits a custom x86 instruction that DOSBox invented for itself.
The setup
DOSBox is an MS-DOS emulator that doesn't really contain DOS or a BIOS in any conventional sense โ virtual COM files like MOUNT.COM are how DOSBox's host functionality reaches the guest. Disassembling MOUNT.COM shows ordinary x86 (a stack-shrink, an INT 21h) followed by what looks like garbage:
0000000A FE 38 05 00 ; ndisasm: db 0xfe / cmp [di],al / ...
That sequence isn't garbage โ it's a callback into the emulator. In src/cpu/core_normal/prefix_none.h, DOSBox decodes the FE opcode group: /0 is the real INC Eb, /1 is the real DEC Eb, and /7 is the DOSBox-only CallBack, which reads a 16-bit callback ID from the next two bytes and dispatches into the host. So FE 38 00 00 runs callback 0x0005 and falls through as if it were a four-byte NOP.
The detection
On any real x86 from the 80186 onward, an unknown opcode raises #UD (interrupt 06h). So the test is symmetric to DOSBox's behavior:
- Install a custom INT 06h handler.
- Execute
db 0xfe, 0x38, 0x00, 0x00(a callback to ID 0). - If the handler fires, advance IP past the four-byte sequence and zero AX. Restore the previous handler.
- After the sequence, inspect AX โ if it's still the pre-test value, the host swallowed the opcode silently and you're inside DOSBox.
The post includes the full NASM exception handler. The bookkeeping is moderately fiddly because the handler has to walk the saved IP on the stack, peek at the offending opcode bytes (bx == 0x38fe after the little-endian load), and either advance IP / signal "not DOSBox" or restore the original IVT entry and let the real #UD handler retry.
The 86Box bug
Snow's first hardware-style test was on 86Box (a real-PC emulator separate from DOSBox), and the detector falsely reported "DOSBox." Stepping through with DEBUG.COM showed 86Box was happily executing FE 38 00 00 instead of trapping. Root cause: 86Box had inherited a bug from PCem where any FE ModR/M with a non-zero /n field was decoded as FE /1 (DEC). So FE /2, /4, /7 all silently decremented something instead of trapping.
Fix was straightforward and is merged upstream in 86Box PR #6561. Snow credits Linear (linear.network) for cross-checking on real hardware to rule out an Intel documentation issue.
Why this is the right route
The post is explicit that brittle approaches exist and are easier:
- BIOS version string at
FE00:0061starting withDOSBox(a comment in DOSBox-X already says they'd like to make this user-overridable) - The
Z:drive's existence or serial number - DOSBox-X's own custom
MOUNTandVERquirks
All of those can be spoofed by editing a config or rebuilding. The callback opcode is, in contrast, an inherent part of how DOSBox works โ to remove it would be to break MOUNT.COM, IMGMOUNT.COM, and the rest of the virtual command set.
Adjacent emulators
The post closes by noting that detecting other DOS-likes is much easier:
- NTVDM and the Win9x MS-DOS Prompt: a single
INT 2Fhcall - DOSEMU: a surprising number of host-callback APIs implemented as COM files (
UNIX.COMruns arbitrary host commands), so detection is just probing whether those callables exist
None are as hard to spoof as a custom CPU instruction, but all are also more likely to cause side effects than reading a BIOS string.
The test program is on Snow's git forge at git.2ki.xyz/snow/dostests (NASM source).