SearchA-ZJ › js-dos

js-dos

2015 Open source · GPL-2.0 Online

js-dos is a WebAssembly build of DOSBox with a small JavaScript API, used to embed playable DOS games and utilities directly in a web page. It is the technology behind the Internet Archive’s in-browser MS-DOS collection.

Visit the official site ↗

Runs on: Web browser

js-dos Online Emulator

Play js-dos using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
DOS promptjs-dosIBM PC/ATDOS (MS-DOS / DR-DOS)openOpen ⛶

Machines emulated

Operating systems

Chips

Notes

Embedding

js-dos is DOSBox compiled to WebAssembly with Emscripten. It is one ordinary Emscripten Module: you give it a <canvas>, optional command-line arguments, and it fetches dosbox.js + dosbox.wasm and boots. We self-host everything (no CDN); there is no external ROM because DOSBox ships its own DOS kernel and BIOS, so the whole thing is GPL-2 and freely redistributable.

var Module = {
  canvas:    document.getElementById('canvas'),
  arguments: ['-c', 'mount c /c', '-c', 'c:'],   // land at C:\>
  preRun:    [ function(){ Module.FS.mkdir('/c'); } ],
  onRuntimeInitialized: boot                                // wasm ready -> publish EMU_BOOT
};
// <script src="/emulator/js-dos/src/dosbox.js"></script> instantiates it.
MemberKindWhat it does
Module._emudbg_pause(1|0)hookPause / resume by swapping DOSBox's frame loop (see the debugger note). Our transport's pause / resume.
Module._emudbg_step()hookQueue exactly one CPU instruction; the paused frame loop runs it. Our step / stepInsn.
Module._emudbg_reg(i) / _emudbg_set_reg(i,v)hookRead / write one x86 register by index (added to DOSBox - see below).
Module._emudbg_read(addr)hookOne side-effect-free guest byte. Drives the memory + disassembly views.
Module.canvaselementThe SDL2 render target. The debugger re-homes it between the play view and its Screen window.

Physical keys work through SDL2's own window key handling once the canvas has focus. The on-screen keyboard dispatches synthetic KeyboardEvents (keyed on event.code) that SDL2 receives the same way.

Debugger integration

This is the Tier-4 point: giving a COMPILED wasm core the same debugger as the pure-JS emulators by rebuilding it with hooks. em-dosbox keeps its whole CPU state inside the wasm sandbox (no views leak out) so we added ONE source file, src/emudbg.cpp, whose functions SAMPLE that state on demand. They are called ~10×/second by the debugger refresh and once per Step; nothing is added to DOSBox's per-instruction hot path (the golden performance rule).

What the debugger needsThe hook, and the DOSBox internal it reads
EAX ECX EDX EBX ESP EBP ESI EDI, EIPemudbg_reg(i) / emudbg_set_reg(i,v) read/write reg_eaxreg_edi, reg_eip - the cpu_regs struct in include/regs.h. Freely writable.
CS DS SS ES FS GS, EFLAGSemudbg_reg(i) reads SegValue(cs..gs) (the Segs struct) and reg_flags. Shown read-only.
Program counter for disasmemudbg_pc() returns the real-mode linear address (SegValue(cs)<<4)+reg_ip.
Physical RAMemudbg_read(addr) / emudbg_read_block(addr,len) call phys_readb() (from include/mem.h) - the raw MemBase backing store, so a read never touches an I/O device.

Pause / resume. DOSBox drives emulation through a swappable loop pointer (DOSBOX_SetLoop). emudbg_pause(1) installs our own Emudbg_Pause_Loop; emudbg_pause(0) restores Normal_Loop. The pause loop holds the CPU still and yields a frame to the browser, so while paused the whole page, and the debugger, stays fully responsive.

Single-step, a real one-instruction step. DOSBox's own debugger steps by running the core with a one-instruction budget: CPU_Cycles = 1; (*cpudecoder)();. emudbg_step() queues that, and the paused frame loop executes exactly one instruction, then stops again. So unlike v86 (whose JIT-in-wasm core only exposes a whole time-slice), Step here advances the CPU by a single x86 instruction and you watch EIP and the registers move one op at a time.

Breakpoints / watchpoints are surfaced but best-effort: normal execution runs inside DOSBox's own loop and DOSBox's native execution-breakpoint facility is a separate DEBUG build, so a host-side PC check would mean a per-instruction hook - which the golden rule forbids. They are honestly labelled coarse; true PC breakpoints would need DOSBox's C_DEBUG core wired to the hooks.

The honest headless caveat. DOSBox waits for keyboard input with a host-side idle loop that (by design, so its cooperative Asyncify threading stays valid) does not yield mid-instruction. At an idle DOS prompt that keeps the wasm thread busy. It is irrelevant in a real browser (compositing is off-thread) and it disappears the moment you PAUSE, which is exactly when the debugger is used, because the pause loop yields every frame.

Architecture

DOSBox is a full-system PC emulator: an x86 real-mode CPU (8086/286-class), plus PIC, PIT, DMA, VGA/CGA/EGA, SB16, and its own DOS kernel and BIOS. em-dosbox compiles the whole thing to WebAssembly with Emscripten Asyncify, so DOSBox's blocking, cooperative code runs unchanged in the browser.

  • dosbox.js - the Emscripten runtime + glue that instantiates the wasm, owns the canvas, and routes SDL2 input/audio.
  • dosbox.wasm - DOSBox itself: the CPU cores, the device models, the DOS/BIOS, and our emudbg.cpp hooks.

The default machine is a real-mode DOS PC, so the shared 16-bit x86 disassembler drives the disasm view correctly. DOS software that flips into 32-bit protected mode will disassemble approximately (the decoder is an 8086/16-bit model).