Skip to content
FreeDOSDeep Dive Published Updated 7 min readViews unavailable

DOS INT 29h: Fast Console Output and the Redirection Trap

Understand INT 29h's one-character console contract, device-driver opt-in, redirection bypass, and why portable DOS applications should prefer handles.

INT 29h is a historical DOS fast-console output entry point that accepts a character in AL and returns no useful success result. It is not a general file-writing API and does not provide the same redirection contract as a DOS output handle. Its purpose is narrow: a console device or replacement handler can accept a single character through a fast path, traditionally avoiding some of the overhead of normal DOS character output.

Ralf Brown’s Interrupt List records that DOS character devices with bit 4 set in their device-driver attribute word may invoke INT 29h while writing to the device. It also documents a simple default behavior for certain DOS versions: pass the character to BIOS video service INT 10h/AH=0Eh. These are compatibility observations across DOS implementations, not a modern guarantee that all kernels, drivers, BIOSes, or emulators use the same code path.

One byte, no status, and a narrow contract

The call is intentionally small. The caller places the byte to display in AL and executes INT 29h. There is no documented filename, handle, buffer length, encoding argument, destination selector, or standard error code. Some DOS versions may destroy BX. Code that depends on register preservation must follow the documented target ABI and save what it needs.

The contrast is concrete in the DOS handle interface: INT 21h/AH=40h accepts a handle in BX, a byte count in CX, and a buffer address in DS:DX, then reports completion or failure through the normal DOS result convention. A caller can target standard output or another handle and inspect how many bytes were written. That call has different performance and device-routing costs, but it is a better contract for data that must be redirected, counted, or diagnosed. INT 29h optimizes a narrower interaction at the cost of those guarantees.

That interface is poorly suited to general application output. It cannot report whether the output was redirected, whether a device accepted the byte, or whether the screen displayed the intended glyph. It also provides no write count and cannot distinguish a successful display from an ignored character. For ordinary program output, the handle API allows a caller to write an explicit byte count to a chosen handle and receive an error result. Use the narrow interrupt only when the application deliberately targets the installed console path and has verified the target’s handler contract.

The device attribute opt-in matters

DOS character-device drivers publish attribute bits in their device header. The historical device-driver interface uses a bit to indicate that the driver supports fast console output through INT 29h. When the relevant bit is set, DOS may use that route for writes to the console device. A replacement CON driver that advertises the fast path must provide a compatible handler; claiming a capability without implementing its behavior can break output for programs that rely on it.

This is why installing a console driver can change behavior beyond what a shell prompt suggests. ANSI-capable drivers, remote console replacements, and emulators may hook the interrupt or implement a different fast path. Do not assume that a device driver that can process normal DOS writes can also process INT 29h. Check the driver’s own documentation and test the exact kernel/driver combination.

Why > may not capture it

Shell redirection changes a DOS handle or the stream path used by the child process. INT 29h directly invokes a console-oriented interrupt. Its purpose and traditional implementation are specifically tied to displaying a character, so software using it can bypass a redirected STDOUT file. A message may therefore appear on screen even when the program’s ordinary output is redirected.

This is a diagnostic clue, not a reason to redirect INT 29h globally. An application may intentionally send progress/status text to the physical console while its data stream goes to a file. A resident hook can change this behavior, but global hooks create compatibility and ownership concerns: another TSR may already own the vector, a later driver may replace it, and cleanup must restore the prior handler only when the vector still points to the TSR being removed.

If output appears on the local display despite CTTY or redirection, compare the program’s I/O route. Test a known program that writes through a DOS handle, one that calls console functions, and the failing program. The difference can reveal a BIOS or direct-video path. INT 29h is one possible path, but direct writes to video memory are another. Do not assert that every unredirectable message uses this interrupt.

The desired behavior also depends on which stream is redirected. A program may send data through handle 1 and errors through handle 2, while a direct console call ignores both. To capture a diagnostic, redirect both streams only if the program uses those handles. If an error still appears, determine whether the program deliberately writes to CON, invokes a BIOS service, calls INT 29h, or draws directly. The remedy may be an application option or device-aware build, not a global interrupt hook.

Character encoding and terminal behavior are not guaranteed

The interface transports one byte. It does not define a Unicode character, code page, terminal escape protocol, line ending, or color attribute. The handler may pass the byte to BIOS teletype output, an ANSI-aware TSR, or another device-specific implementation. High-bit bytes can render differently under another code page, and an escape sequence requires a handler that interprets the sequence rather than the default BIOS routine.

A test should send only known printable characters and separately test carriage return/line feed behavior, high-bit bytes, and any control sequences used by the application. Capture both the configured display code page and active console driver. A successful interrupt return cannot confirm that a glyph rendered correctly.

Safe use and a focused test

For an application that intentionally uses INT 29h, document the target kernel and required console driver. A small assembly test may call the vector with a single known byte, but it should be run in a disposable VM first. Preserve registers according to the application’s ABI and never invoke the interrupt from an unsafe asynchronous context merely because it appears fast; the routine may call BIOS or a TSR handler with its own reentrancy constraints.

Test these cases:

  1. Call the routine with no console replacement and record the visible result.
  2. Run with the expected ANSI or console driver and test only sequences that its manual documents.
  3. Redirect ordinary standard output to a file and compare it with the INT 29h result.
  4. Repeat after switching the console with CTTY only if the full configuration supports that path.
  5. Remove the test handler and confirm the old vector remains intact.

For output intended to be captured in automated tests, compare exact bytes rather than visual appearance. Run the same workload with ordinary output directed to a file, inspect the file length and contents on the host, and separately record console output. A missing byte in the file plus a visible glyph suggests two paths were used; it does not imply that redirection itself is broken. Record the program’s chosen API if source or a debugger is available.

If an interrupt hook is unavoidable, chain to the previous handler for bytes the hook does not own, preserve the register contract, and do not issue DOS file calls from an interrupt path unless the specific context is documented as safe. A small fast output path is not a reentrancy license. Keep an installation check, record vector ownership, and ensure unload restores the prior vector only when the current vector is still yours.

The practical rule is to reserve INT 29h for compatibility code that needs the historical fast console path. Prefer DOS handles for portable output, diagnostics that must be captured, and programs that need explicit error reporting. When a message escapes redirection, identify the actual low-level route instead of changing shell syntax blindly. The interrupt’s small contract is useful precisely because it is narrow, and it remains reliable only when the installed console handler and device attributes agree.

Related:

Sources:

Comments