Skip to content
FreeDOSDeep Dive Published Updated 5 min readViews unavailable

FreeDOS INT 21h/AH=33h: Querying and Restoring Break-Check State

Read or change FreeDOS break checking through INT 21h/AH=33h, distinguish kernel policy from the INT 23h handler, and test the shared state safely.

DOS exposes two related but different controls for Control-C and Control-Break. The INT 23h vector is the handler that receives control after DOS detects a break condition. INT 21h/AH=33h queries or changes the kernel’s break-checking flag. Changing the flag does not install a new handler, save the previous vector, or make a program’s cancellation path safe.

This article describes the FreeDOS kernel implementation at immutable source commit d6791add2043c9d7b584d840a8ffaf8829fd2bdc. The register contract below is verified against that source snapshot. It should not be generalized to every DOS version, DOS clone, protected-mode host, or compatibility layer without testing.

The two subfunctions used for the flag

Scope note: This table covers only AL=00h and AL=01h, the query/set path for break_ena. FreeDOS also implements AL=02h as a separate extended Control-Break subfunction; its behavior is outside this table’s scope.

Call INT 21h with function 33h in AH; the subfunction is in AL.

Input FreeDOS behavior in the pinned source Result
AX=3300h Query the current break flag. The low bit of DL reports the current value: zero or one.
AX=3301h, DL=0 or DL=1 Set the flag from bit zero of DL. The handler falls through to the query case and returns the stored value in DL.

In the FreeDOS dispatcher, AL=01h stores DL & 1 in the kernel’s break_ena variable and then shares the AL=00h result path. This is why the set call also leaves the current state in DL. The source code, rather than an assumed cross-DOS convention, is the evidence for that detail.

The flag controls when the kernel checks for break conditions. FreeDOS’s kernel configuration documentation describes BREAK=ON|OFF: ON is the documented default and enables extended checks before most INT 21h calls; with OFF, checks remain on I/O calls using standard streams. So “off” is not a universal promise that no break can be noticed. The specific function, standard-stream path, device driver, and shell remain part of the behavior.

A query-only diagnostic

Start by reading the current value without changing the environment:

; Query only. This diagnostic leaves the kernel setting unchanged.
mov  ax, 3300h
int  21h
mov  [observed_break], dl
; DL is the FreeDOS value returned by the pinned INT 21h implementation.

For a controlled test in a disposable VM, an application can preserve the original byte, change the flag for a short interval, and restore it through one cleanup path:

; Save the existing state.
mov  ax, 3300h
int  21h
mov  [saved_break], dl

; Deliberate test only: request BREAK OFF for a bounded operation.
mov  ax, 3301h
xor  dl, dl
int  21h

; Every ordinary success or error branch must converge here.
mov  ax, 3301h
mov  dl, [saved_break]
int  21h

This is an ABI sketch, not a complete .COM program. It omits segment setup, storage declarations, the application’s error handling, and any compiler-specific calling conventions. Do not put blocking input, arbitrary file I/O, or an unbounded operation inside the test interval. With break checking changed, cancellation may be delivered at a different DOS call than expected.

Treat the value as shared kernel state

In the pinned FreeDOS implementation, the flag is held in kernel state rather than in an application’s data segment. That is a practical warning: a utility that changes it must not assume the change is private to itself. Restore the exact value returned by the query instead of guessing that the user’s setting was 1. Route every ordinary exit path through cleanup, and restore before launching a child program or returning control to an interactive shell.

The kernel source does not make this a transaction with ownership or locking. A resident program or another component can also affect DOS behavior. Querying before and after a test is useful, but it cannot prevent another owner from changing shared state in between. If the application does not own the environment, the safest behavior is usually to leave the flag unchanged and let the existing policy handle cancellation.

Do not use this function as a substitute for INT 23h lifecycle management. If a program installs a handler, it still needs a valid code lifetime, a deliberate return path, and correct ownership cleanup. Conversely, installing a handler does not mean every keypress will arrive at an arbitrary point in a CPU loop. DOS decides when to check; delivery depends on its service path and configuration.

Verify the actual kernel policy

For a reproducible diagnostic, record the FreeDOS kernel build, shell, device drivers, VM or hardware, and whether standard input and output are redirected. Query the flag at startup, exercise one harmless console I/O path and one non-I/O DOS service, then query again. Repeat with the boot-time BREAK directive set to ON and OFF in a disposable configuration. Do not infer behavior from one function call or from a different DOS clone.

The useful test matrix is narrow:

Condition Evidence to record
BREAK=ON Whether the target service checks before dispatch and when the handler runs.
BREAK=OFF with standard-stream I/O Whether the documented remaining I/O check is observable.
Redirected streams or custom device drivers Whether that path follows the same policy as interactive CON.
Flag changed by a test program The saved value, returned value after setting, and restored value.
Normal exit and recoverable error exit Whether cleanup restored the original state.

Never validate the behavior with a destructive command or a production process that cannot be safely stopped. A disposable VM snapshot makes the test repeatable and gives you a recovery path if a faulty test program exits before restoring state.

The key distinction is ownership: AH=33h changes a kernel checking policy, while INT 23h is a handler endpoint. Keeping those mechanisms separate prevents a local-looking register write from silently changing how later DOS work responds to cancellation.

Related:

Sources:

Comments