Skip to content

fix: read command output from a UTF-16 log on multibyte code pages - #103

Merged
svnscha merged 1 commit into
developfrom
fix/multibyte-output
Sep 5, 2026
Merged

fix: read command output from a UTF-16 log on multibyte code pages#103
svnscha merged 1 commit into
developfrom
fix/multibyte-output

Conversation

@svnscha

@svnscha svnscha commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Fixes #102.

Problem

On Windows whose system code page is multibyte (Chinese 936, Japanese 932, Korean 949, or the "Use Unicode UTF-8" setting 65001), the debugger truncates its text output over a pipe: each line is written short by its multibyte expansion, dropping the tail (and newline) of any line that contains non-ASCII text. This lost the end of du strings and other Unicode output, and a line cut in the middle of a character left the session unresponsive to every later command.

Reproduced on a Windows 11 VM. It affects every command that prints Unicode, not just du, and no pipe output mode avoids it: interactive, -c batch, and .logopen to an ANSI file all truncate, because the miscount is in the debugger's writer.

Fix (transport level, not per-command)

.logopen /u writes a UTF-16 log that is complete and flushes per command. On a multibyte code page the session opens such a log at startup and reads each command's output from it, keyed by the same .echo markers the pipe already uses to synchronize.

  • The pipe stays the control channel for markers, timeouts, go-commands and break-in; only the returned command output comes from the log. All the existing sync logic is untouched.
  • Only for sessions whose engine is local - a crash dump and a kernel target on the wire, where the debugger is our own subprocess and the log opens on this machine. A user-mode -remote client drives the engine on the server, so it keeps the pipe (see Limitations).
  • Single-byte code pages (Western 1252 and the like), where the pipe is lossless, are detected with GetCPInfo and keep the pipe path, byte-for-byte unchanged.
  • Prompt scaffolding stripped from the log transcript: the bare 0:000>, kernel 0: kd> / local lkd>, WOW64 1:001:x86>, and remote [server (tcp ...)] 0:000> forms.
  • Reader hardened: the pipe is decoded with the debugger's own ANSI code page and errors="replace", so a split multibyte sequence can no longer raise in the reader thread on the paths that still read the pipe.
  • Cleaned up: the temporary log is deleted on session shutdown (with a short retry, since a force-killed debugger may still be releasing the handle).

Research: can the pipe itself be UTF-16?

Checked before going the log route. A Windows pipe is a raw byte stream; the writer picks the encoding, and cdb/kd convert their internal Unicode to the ANSI code page (the truncating WriteFile path) whenever stdout is a redirected handle. There is no cdb.exe switch to emit UTF-16/UTF-8 to the pipe. The only native Unicode sinks the engine offers are the wide output callback IDebugOutputCallbacksWide (which would mean hosting dbgeng.dll ourselves, a full re-architecture) and the Unicode log (.logopen /u, or the command-line -logou/-logau). A pseudo-console (ConPTY) would make cdb call WriteConsoleW, but its output arrives wrapped in VT escape sequences and hard-wrapped at the console width, which is worse to parse than the log. The log is the supported, clean mechanism.

Verification

Reproduced at the 1.2.1 release commit and verified with this branch on a Windows 11 VM, stock SDK cdb/kd 10.0.26100.1742:

System code page before this branch
1252 (Western) works passthrough, byte-for-byte unchanged
936 (Chinese) empty / truncated / wedged full Unicode for every command
65001 (UTF-8) empty / truncated / wedged full Unicode for every command

du, dw, db, .echo, version and the rest return complete text; the temp log leaves nothing behind. Full suite: 106 passed on the 1252 host and 111 on the VM under both 936 and 65001; the hermetic tests drive the log path on any platform (a fake debugger mirrors a UTF-16 log) and are pinned to the single-byte path by default so they behave the same regardless of the host's real code page.

  • Crash dump: full CJK strings recovered for du/du <addr> L<n> under 936 and 65001.
  • Kernel (kd.exe): verified against a live kernel via local kernel debugging on the UTF-8 VM - the log transport engages, and version, lm m nt and a live kernel-memory read return complete output with the lkd> prompt stripped and the temp log cleaned up.
  • Remote process debugging (-remote): end-to-end against a real .server host - attach, process (|), threads (~), modules (lm) and registers all work. The log stays off (engine is on the server), so this is unchanged from before.

Limitations

  • A -remote client on a multibyte code page still truncates Unicode output: the engine runs on the server, so its Unicode log is not on our machine (and for a cross-machine server would not be reachable at all). Remote debugging otherwise works; this is unchanged from before the fix.
  • A live target's asynchronous output (a go/break-in bugcheck banner) still comes from the pipe, so a bugcheck message containing non-ASCII text could still truncate. Ordinary command output, which is what du command returns empty output via run_cdb_command, while direct CDB shows the Unicode string #102 is about, is fully covered.

@svnscha
svnscha marked this pull request as draft September 4, 2026 21:46
@svnscha
svnscha force-pushed the fix/multibyte-output branch from 3c5ac46 to 2edaa04 Compare September 4, 2026 22:05
@svnscha svnscha changed the title fix: keep cdb output that a multibyte code page glues onto the marker line fix: recover du output for Unicode strings on multibyte code pages Sep 4, 2026
@svnscha
svnscha force-pushed the fix/multibyte-output branch from 2edaa04 to 1e8fbb2 Compare September 4, 2026 22:50
@svnscha svnscha changed the title fix: recover du output for Unicode strings on multibyte code pages fix: read command output from a UTF-16 log on multibyte code pages Sep 4, 2026
@svnscha
svnscha force-pushed the fix/multibyte-output branch 4 times, most recently from a223bba to eca732a Compare September 4, 2026 23:52
On a multibyte system code page (Chinese 936, Japanese 932, Korean 949, or the
"Use Unicode UTF-8" setting 65001) the debugger truncates its text output over
a pipe: it writes each line's character count as a byte count, so the tail of
any line with non-ASCII text, its newline included, is dropped. This lost the
end of du strings and other Unicode output, and a line cut in the middle of a
character wedged the session. It affects every command, not just du, and no
debugger output mode over the pipe avoids it.

.logopen /u writes a UTF-16 log that is complete and flushes per command. So on
a multibyte code page the session now opens such a log at startup and reads
each command's output from it, keyed by the same .echo markers the pipe already
uses to synchronize. The pipe stays the control channel; only the returned
content comes from the log. Single-byte code pages (e.g. Western 1252), where
the pipe is lossless, are detected via GetCPInfo and keep the pipe path,
byte-for-byte unchanged.

The pipe is also decoded with the debugger's own ANSI code page and with
errors="replace", so a multibyte sequence split across reads cannot raise in
the reader thread on the paths that still read the pipe (a live target's
asynchronous output).

Verified on a Windows 11 VM with cdb 10.0.26100.1742 under code pages 1252
(passthrough), 936 and 65001 (all commands return complete Unicode). Fixes #102.
@svnscha
svnscha force-pushed the fix/multibyte-output branch from eca732a to 4d3e4d5 Compare September 5, 2026 08:11
@svnscha
svnscha marked this pull request as ready for review September 5, 2026 08:23
@svnscha
svnscha merged commit d8acc17 into develop Sep 5, 2026
6 checks passed
@svnscha
svnscha deleted the fix/multibyte-output branch September 5, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

du command returns empty output via run_cdb_command, while direct CDB shows the Unicode string

1 participant