Windbg Decompile Extension via LLM
June 20, 2026 ยท View on GitHub


This project is a Windows x64 WinDbg extension skeleton that resolves a function by name or address, reconstructs a deterministic control-flow view, and asks an LLM directly from the extension to produce pseudocode.
Layout
src/extension: WinDbg extension DLL and!decompcommand.src/shared: JSON, analyzer, protocol, and verifier code shared by the extension.scripts: build and vendor-copy helpers.third_party/dbgeng: optional vendoreddbgeng.handdbgeng.libcopy.third_party/zydis: vendored stable Zydis source tree used by default when present.
Current Scope
- x64-only assumptions
- live-memory analysis through DbgEng
- Zydis-backed structured disassembly for stable mnemonic/operand recovery
- symbol-region, unwind, and heuristic function-range recovery
- SSA-lite style recovery for incoming register arguments, stack-slot locals, merge candidates, and normalized branch conditions
- low-level IR value facts with def-use hints, canonicalized copy/constant expressions, and dead-definition markers
- block-level value state facts for converged live-in/live-out reaching definitions across registers and stack locals
- dominator-backed control-flow region facts for natural loops, if/else candidates, switch candidates, loop induction metadata, and switch range/default metadata
- x64 ABI facts for shadow/home slots, stack pointer deltas, prolog/epilog recognition, no-return calls, tail calls, thunks, import-wrapper candidates, and recovered register/stack call arguments
- SIMD/FP-aware Microsoft x64 argument recovery for
xmm0throughxmm3, with vector zero-idiom guards to avoid false incoming arguments - type recovery hints for pointer-like values, stack locals, field offsets, scaled-index arrays, enum-like compares, bitflag tests, and vtable candidates
- idiom and library-pattern facts for memory/string helpers, security cookies, stack probes, allocators, aggregate initializers, and RIP-relative global/import loads
- call-target facts for direct calls, register/memory indirect calls, virtual-call/vtable-offset candidates, return type, parameter model, side effects, memory effects, ownership hints, and confidence
- OLLVM-style obfuscation facts for control-flow flattening dispatchers, recovered semantic edges, opaque-predicate dead edges, and scalar instruction-substitution idioms
- deobfuscation readiness facts plus
/deobf:on|offcontrol over whether recovered obfuscation facts may guide pseudo-C rewrite - evidence graph facts that link high-signal analyzer, PDB, and observed behavior facts back to instruction/block grounding
- refine-first prompting with an analyzer-generated pseudocode skeleton, graph-aware summaries for CFG regions, conditions, and important blocks, ranked high-signal fact selection, and spread sampling for large fact sets
- WinDbg DML links for entry/basic-block/evidence/call-target navigation when the output callback supports DML
- separated result modes for brief, evidence explanation, facts-only, debug prompt, JSON, and data-model style output
- user correction switches for no-return, type, field, and rename hints
- session-aware analysis policy facts for live, dump, kernel, and TTD-like sessions
- observed behavior facts from the current debugger context, including register argument samples, memory hotspots, and TTD query suggestions when available
- RIP-relative string/global/IAT classification and call-target signature hints for LLM prompting
- loaded-PDB aware prototype, scoped parameter/local, field, enum, and source-line hints for LLM prompting
- direct in-process LLM calls from the extension
- OpenAI-compatible HTTP adapter or deterministic mock fallback
- verifier pass over LLM output
WinDbg Usage
Load the extension from the build output, then run !decomp against a symbol or an address:
.load C:\path\to\decomp.dll
!decomp /doctor
!decomp module!FunctionName
!decomp 0x7ffb`12345678
Use /doctor when setup looks wrong or before enabling an LLM provider:
!decomp /doctor
!decomp /doctor:net
/doctordoes not require a target and does not call the provider. It reports config path/load status, provider/model/endpoint summary, auth presence without secrets, timeout/token/chunking settings, DML support, session class/qualifier, processor type, and PDB caveats./doctor:netis accepted as an explicit network-check request, but currently reports that provider ping is skipped. The extension does not perform a network probe from doctor mode.- Secret values such as API keys, bearer tokens, refresh tokens, and URL query strings are not printed.
Targets can be public/private symbols, exported function names, or addresses. If the target resolves to an address inside a function, the extension tries to recover the containing function range from symbols, unwind data, and control-flow heuristics. Put quotes around targets that contain spaces:
!decomp "my module!Function With Spaces"
The normal command path performs local analysis, builds analyzer facts, optionally calls the configured LLM endpoint, verifies the response against recovered evidence, and prints pseudo-C plus confidence, warnings, and uncertainty notes:
!decomp ntdll!RtlAllocateHeap
!decomp kernel32!Sleep
!decomp game.exe!CheckIntegrity
Normal, brief, and explain output include a compact progress stream even without /verbose. Long LLM runs show local-analysis completion, chunk progress, retry notices, merge start, verification, and the Ctrl+Break cancellation hint. Machine-readable modes such as /view:json, /view:facts, /view:prompt, and /view:data suppress progress lines and DML helper links so scripts receive only the requested payload.
Use /view:* to choose what you want to see. This keeps the command surface small: one option controls all output modes.
!decomp /view:brief module!HotPath
!decomp /view:explain module!BranchyFunction
!decomp /view:json module!FunctionName
!decomp /view:facts module!FunctionName
!decomp /view:prompt module!FunctionName
!decomp /view:data module!FunctionName
!decomp /view:analyzer module!FunctionName
!decomp /view:plan module!FunctionName
briefprints target, confidence, summary, and the first uncertainty or verifier warning.explainadds evidence, control-flow, type-hint, observed-behavior, and call-target sections.jsonprints machine-readable request and response JSON.factsprints only analyzer facts and disables the LLM path.promptprints the exact system prompt, user prompt, and prompt facts. It disables the LLM call.dataprints a stable JSON snapshot intended for WinDbg JavaScript/NatVis-style automation.analyzerrenders the deterministic analyzer-only pseudo-code path without calling the LLM.planperforms local analysis and prints a preflight plan without calling the LLM or updating the result cache. It includes target/module/range counts, PDB availability, session policy, estimated chunking, prompt-size-relevant counts, and practical recommendations.
Use /verbose when a command appears stuck or when you want to see the full progress stream:
!decomp /verbose module!SlowFunction
!decomp /verbose /view:json module!SlowFunction
/verboseprints local stages such as target resolution, function range recovery, byte reads, disassembly, analyzer fact construction, PDB/session enrichment, pseudo-code tokenization, and verifier results.- In LLM mode,
/verbosealso prints prompt sizes, request token budgets, HTTP connection/send/receive stages, response chunk sizes, finish reason, extracted model JSON preview, retry attempts, and verifier-feedback retry decisions. - The API key is not printed. Request/response logs show sizes and short previews rather than full headers or full prompt bodies.
/verbosereplaces the compact progress stream with the full trace. Use it when the compact progress lines are not enough to diagnose where time is going.- During a long-running
!decompcommand, press Ctrl+Break in WinDbg to request cancellation. The extension checks for interrupts between local analysis stages and while waiting for the LLM worker, then asks the active synchronous HTTP I/O to stop.
Legacy aliases such as /brief, /explain, /json, /facts-only, /debug-prompt, /data-model, /dx, and /no-llm still work for old scripts, but new examples use /view:*.
Window viewer:
!decomp /view:window module!FunctionName
!decomp /view:window /view:explain module!FunctionName
/view:windowruns the normal!decompresult path for the target and opens the full rendered result in a separate viewer.- The viewer uses the same response renderer as the console path, then opens a native Win32 modeless tool window owned by the debugger window when one can be found.
- The debugger output reports the native viewer window handle. If the viewer window cannot be created, the command prints a warning and falls back to the normal console result.
- DML-only links are rendered as text labels with their command strings in the viewer. When RichEdit is available, the window uses a GitHub-style RTF layout with section headings, metadata styling, and pseudo-code highlighting; otherwise it falls back to plain text.
- When the current session has previous cached results, the viewer shows a left-side history list so you can switch between the current output and earlier decompilation results without rerunning analysis.
/view:json,/view:facts,/view:prompt, and/view:dataremain machine-readable console outputs and are not redirected to the viewer.
Large functions:
!decomp /limit:deep module!LargeFunction
!decomp /limit:huge module!VeryLargeFunction
!decomp /limit:12000 module!VeryLargeFunction
!decomp /timeout:120000 module!SlowFunction
/limit:deepraises the instruction cap to8192./limit:hugeraises the instruction cap to16384./limit:Nsets an explicit instruction cap./timeout:MSoverrides the request timeout for this invocation.- LLM chunking is controlled by
decomp.llm.json; the command-line instruction cap controls how much local code the extension attempts to recover before prompting. - Legacy
/deep,/huge, and/maxinsn:Nremain supported.
Obfuscation-aware decompilation:
!decomp /deobf:on module!FlattenedFunction
!decomp /deobf:off module!FlattenedFunction
!decomp /view:facts /deobf:off module!FlattenedFunction
/deobf:onis the default. The analyzer still emits raw facts, but high-confidence OLLVM-style dispatcher recovery, opaque dead-edge proof, substitution idioms, and semantic CFG overlays may guide prompt facts, merge policy, verifier conflict policy, and structured pseudo-C recovery./deobf:offkeepsobfuscation,semantic_control_flow, anddeobfuscation_readinessfacts visible, but disables rewrite safe actions, keeps control-flow structuring on the raw CFG, and tells prompt/merge/verifier paths to preserve the raw obfuscated shape.- Use
/deobf:offwhen you want to inspect the dispatcher, bogus branch, or substitution surface directly instead of asking the extension to recover a deobfuscated structure. /deobfuscation:on|offis accepted as a longer alias.
Cache and replay helpers:
!decomp /view:json module!FunctionName
!decomp /last:json
!decomp /view:explain module!FunctionName
!decomp /last:explain
!decomp /view:facts module!FunctionName
!decomp /last:facts
!decomp /view:data module!FunctionName
!decomp /last:data
!decomp /view:prompt module!FunctionName
!decomp /last:prompt
!decomp /history
!decomp /refresh module!FunctionName
!decomp /last:2:explain
!decomp /last:2:json
/last:jsonprints the previous request/response JSON without re-running analysis./last:explainre-renders the previous full result with the explain section without re-running analysis or calling the LLM./last:factsprints the analyzer facts from the previous result without re-running analysis./last:dataprints the previous data-model snapshot without re-running analysis./last:promptprints the previous prompt dump without re-running analysis./historylists the in-memory result ring buffer. Index1is the newest result./refresh <target>bypasses persistent artifact replay for that target, runs fresh local analysis and LLM analysis, and replaces the saved artifact after a successful LLM-backed result./last:N:explain,/last:N:json,/last:N:facts,/last:N:data, and/last:N:promptreplay an older cached result by history index without re-running local analysis or calling the LLM./last:*modes are terminal replay commands. If a target is present in the same command, the cached artifact is replayed and no local analysis or LLM request is started for that target.- In-memory cached artifacts live in the loaded extension instance only. The result history keeps the newest 8 results and disappears when WinDbg unloads the extension or the process exits.
- Successful LLM-backed results are also saved automatically under an
artifactfolder beside the loadeddecomp.dll. The operator does not need a separate save command. - Persistent artifacts include
request,response,data_model,debug_prompt, and akernel_buildobject with Win32/KD version values, build string, optionalNtBuildLab, and a build fingerprint. - On a later session, running the same
!decomp <target>command automatically checks theartifact\<kernel_build>\...path after target resolution and function RVA recovery. If the savedkernel_buildmatches the current OS build, the extension replays the artifact without reading function bytes, running local analyzer passes, or calling the LLM. - Missing, unreadable, or mismatched persistent artifacts are treated as cache misses. The command falls back to a fresh analysis and overwrites the artifact only after a successful LLM-backed result.
- DML action links in normal output use these cached
/last:*views, so clickingexplain,json,facts,prompt, ordata-modeldoes not start a new decompile run. - Legacy
/last-json,/last-explain,/last-facts,/last-data-model,/last-dx, and/last-promptremain supported.
DML navigation:
- When WinDbg reports that the current output callback is DML-aware, pseudo-code is syntax-highlighted with configured DML color slots.
- Normal output includes an
actionsrow with clickableexplain,json,facts,prompt,data-model, andhistorylinks for the same target. - Normal output also includes a
navrow with entry disassembly, entry breakpoint, and last-artifact replay links. - Entry addresses, basic blocks, evidence blocks, control-flow regions, type-hint sites, observed memory-hotspot sites, TTD query suggestions, and direct call targets become clickable links where the extension has enough address information.
- Uncertainties and verifier warnings are linked to the best recovered evidence location when the cause can be mapped to a branch, loop, switch, no-return call, return instruction, or function entry.
- If the current output path is not DML-aware, the extension automatically falls back to plain text. The analysis result is the same; only the presentation changes.
Session-aware and observed-behavior details:
/view:json,/view:facts,/view:prompt, and normal LLM mode includesession_policy.session_policyrecords the debug class, qualifier, execution kind, analysis strategy, dump/live/kernel flags, and whether TTD support appears loaded.observed_behaviorrecords the currentrip,rsp, return address when readable, Microsoft x64 register argument samples (rcx,rdx,r8,r9), repeated memory-access hotspots, and suggested TTD commands.- Current-frame argument samples are high confidence only when the current instruction pointer is inside the analyzed function. Otherwise they are kept as contextual hints.
- If
ttdext.dllorTTDReplay.dllis loaded in the debugger process, the extension adds suggesteddx @$cursession.TTD.Calls(...)queries instead of silently pretending trace data was already collected.
User correction switches let you patch analyzer facts from the command line when the debugger lacks enough semantic information:
!decomp /fix:noreturn:FatalError module!FunctionName
!decomp /fix:type:rcx=MY_TYPE* module!FunctionName
!decomp /fix:field:[rcx+18h]=uint32_t module!FunctionName
!decomp /fix:rename:v3=request module!FunctionName
!decomp /fix:clear
/fix:noreturn:nametreats matching calls as no-return for fallback disassembly, CFG recovery, ABI facts, and verifier checks./fix:type:expr=TYPEadds a high-confidence user type hint./fix:field:expr=TYPEadds a high-confidence user field hint./fix:rename:old=newadds a rename hint and applies the rename to the final pseudocode identifiers./fix:clearclears all session-persistent correction overrides.
The environment variable DECOMP_NORETURN_OVERRIDES remains supported. Command-line /fix:noreturn: values are layered on top of the original environment value for the current WinDbg session.
Correction switches are session-persistent:
/fix:noreturn:,/fix:type:,/fix:field:, and/fix:rename:are remembered by the loaded extension and reused by later!decompruns./fix:clearclears all session-persistent corrections and restores the no-return environment override to its original value from extension load time.- Legacy
/noreturn:,/type:,/field:,/rename:, and/clear-overridesremain supported.
Malformed correction values are ignored and reported in uncertainties rather than being cached. For example, /fix:type:rcx is ignored because it does not contain an expr=TYPE pair.
Recommended investigation workflow:
- Start with
!decomp /view:facts targetto confirm the function range, blocks, calls, imports, PDB data, and session facts look reasonable. - Use
!decomp /view:plan targetto estimate chunking, prompt size, timeout risk, and symbol quality before spending an LLM request. - Use
!decomp /view:prompt targetwhen prompt size, language, or evidence selection looks wrong. - Run
!decomp targetfor the full verified pseudo-C result. - Use
!decomp /refresh targetwhen an existing persistent artifact is being replayed but you need a fresh analysis. - If the result looks wrong, run
!decomp /view:explain targetand inspect verifier warnings, evidence coverage, and suggested fixes. - Add focused corrections such as
/fix:noreturn:,/fix:type:,/fix:field:, or/fix:rename:and re-run the same target. - Use
/historyand indexed/last:N:*replay when comparing several recent results. - Capture
/view:jsonor/last:jsonwhen filing bugs or comparing behavior across builds.
Analyzer Fact Surface
Recent analyzer facts are intentionally carried through /view:json, /view:facts, /view:prompt, and normal LLM mode. High-value fields to inspect first:
stack_pointerrecords per-instruction stack deltas, frame-relative aliases, and confidence.call_argumentsrecords recovered register and stack arguments at call sites, including nearby cross-block stack stores when evidence is strong enough.pdb.prototype_parametersrecords structured prototype parameter names, types, ordinals, ABI locations, and source confidence.control_flowincludes loop induction variables, initial values, steps, bounds, direction, switch table address, case targets, default target, range bounds, signedness, and index expression when recovered.callee_summariesand call-target facts include direct, indirect, and virtual-call/vtable candidates plus known Win32/NT/Rtl memory, allocation, release, and status semantics.obfuscationexposes OLLVM-style flattening dispatcher candidates, state variables, recovered semantic edges, opaque predicates, and scalar substitution idioms.semantic_control_flowexposes recovered live/dead edges that are derived from obfuscation facts and remain available for inspection even when/deobf:offis used.deobfuscation_readinessexposesenabled, safe rewrite actions, blocked assumptions, priority fact paths, counts, and confidence. When disabled, it records the policy decision and blocks deobfuscated control-flow rewrite.- Prompt fact selection ranks high-signal entries first, then preserves distribution with spread sampling so large functions do not lose all low-frequency evidence.
Recommended dbgeng Setup
Fastest path is to vendor the header and import library into the project.
Expected vendor layout:
third_party\dbgeng\inc\dbgeng.h
third_party\dbgeng\lib\dbgeng.lib
You can copy them manually, or use the helper script.
Prepare vendor copy from a debugger root
powershell -ExecutionPolicy Bypass -File .\scripts\Prepare-DbgengVendor.ps1 `
-SourceRoot 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64'
Prepare vendor copy from explicit file paths
powershell -ExecutionPolicy Bypass -File .\scripts\Prepare-DbgengVendor.ps1 `
-HeaderPath 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\sdk\inc\dbgeng.h' `
-LibraryPath 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbgeng.lib'
Once third_party\dbgeng exists, Build.ps1 will prefer it automatically and you usually do not need DEBUGGERS_ROOT.
Recommended Zydis Setup
The repository can use either:
- vendored
third_party\zydissource - CMake
FetchContent
Default behavior is auto, which prefers third_party\zydis when present and falls back to fetching Zydis during CMake configure.
Expected vendor layout:
third_party\zydis\CMakeLists.txt
third_party\zydis\include\Zydis\Zydis.h
third_party\zydis\dependencies\zycore\CMakeLists.txt
Refresh or create the vendor copy:
powershell -ExecutionPolicy Bypass -File .\scripts\Prepare-ZydisVendor.ps1
You can also vendor from an already-downloaded local source tree:
powershell -ExecutionPolicy Bypass -File .\scripts\Prepare-ZydisVendor.ps1 `
-SourcePath 'C:\path\to\zydis'
Build
Recommended path is a Visual Studio Developer PowerShell or Developer Command Prompt.
The built decomp.dll now embeds a Windows file version taken from version.txt.
Normal build
powershell -ExecutionPolicy Bypass -File .\scripts\Build.ps1 -Reconfigure
Regression tests
cmake --build build --config Debug
ctest --test-dir build -C Debug --output-on-failure
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure
decomp_snapshot_tests covers the analyzer/protocol/verifier contracts for recovered stack arguments, SIMD/FP ABI inputs, vector zero-idiom suppression, loop induction preference, switch metadata, virtual-call metadata, OLLVM-style obfuscation facts, /deobf:off policy, known API summaries, prompt fact selection, and verifier grounding checks.
Legacy dbgeng build
powershell -ExecutionPolicy Bypass -File .\scripts\Build-Legacy.ps1 -Reconfigure
Release build with auto-incremented DLL file version
powershell -ExecutionPolicy Bypass -File .\scripts\Invoke-ReleaseBuild.ps1
This script increments the last component in version.txt by 1, forces a reconfigure, and then builds the Release DLL. For example, 1.0.0.7 becomes 1.0.0.8.
Common options
-Configuration Release|Debug-Clean-Reconfigure-ConfigureOnly-Verbose-ZydisSource Auto|Vendor|Fetch-ZydisVendorDir 'C:\path\to\zydis'-DebuggersRoot 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64'-DbgengIncludeDir 'E:\works\windbg_llm_decomp_2\windbg_llm_decomp\third_party\dbgeng\inc'-DbgengLibrary 'E:\works\windbg_llm_decomp_2\windbg_llm_decomp\third_party\dbgeng\lib\dbgeng.lib'
Vendor-first example
powershell -ExecutionPolicy Bypass -File .\scripts\Build.ps1 `
-Configuration Release `
-ZydisSource Vendor `
-Reconfigure `
-Verbose
Explicit include and library example
powershell -ExecutionPolicy Bypass -File .\scripts\Build.ps1 `
-Configuration Release `
-DbgengIncludeDir 'E:\works\windbg_llm_decomp_2\windbg_llm_decomp\third_party\dbgeng\inc' `
-DbgengLibrary 'E:\works\windbg_llm_decomp_2\windbg_llm_decomp\third_party\dbgeng\lib\dbgeng.lib' `
-Reconfigure
The build script automatically tries to locate:
cmake.exefrom PATH, standalone CMake, or Visual Studio bundled CMakethird_party\dbgengunder the project rootDEBUGGERS_ROOTfrom environment variables or common Windows Kits locations
Zydis source selection works like this:
Auto: preferthird_party\zydis, otherwise fetchZydisduring configureVendor: require a usablethird_party\zydistree or the path passed by-ZydisVendorDirFetch: ignore the vendor tree and always let CMake downloadZydis
DEBUGGERS_ROOT may point to a debugger root that uses one of these layouts:
sdk\inc\dbgeng.handsdk\lib\dbgeng.libsdk\inc\dbgeng.handsdk\lib\amd64\dbgeng.libsdk\inc\dbgeng.handsdk\lib\x64\dbgeng.libsdk\inc\dbgeng.handdbgeng.libinc\dbgeng.handlib\dbgeng.libinc\dbgeng.handlib\amd64\dbgeng.libinc\dbgeng.handlib\x64\dbgeng.libdbgeng.handdbgeng.lib
If your installation does not match those layouts, pass the CMake paths directly:
cmake -S . -B build-manual -G "Visual Studio 17 2022" -A x64 `
-DDBGENG_INCLUDE_DIR='E:\works\windbg_llm_decomp_2\windbg_llm_decomp\third_party\dbgeng\inc' `
-DDBGENG_LIBRARY='E:\works\windbg_llm_decomp_2\windbg_llm_decomp\third_party\dbgeng\lib\dbgeng.lib'
cmake --build build-manual --config Release
Legacy dbgeng Compatibility
If your dbgeng.h is too old and the build fails on GetSymbolEntryOffsetRegions or GetSymbolEntryString, use Build-Legacy.ps1 or pass the CMake option manually.
With DECOMP_USE_SYMBOL_ENTRY_APIS=OFF, the extension falls back to:
GetFunctionEntryByOffsetfor x64 unwind-based range recoveryGetNameByOffsetplus heuristic disassembly if unwind metadata is missing
PDB Usage
The extension automatically consumes symbols and type information that WinDbg has already loaded for the target modules.
There are two practical levels of PDB enrichment:
- module-level typed facts: function name, prototype, return type, global symbol names, field offsets, enum constant names, and source-line hints
- scope-level facts: parameter and local names/types from the active debugger scope when the target function matches the current scope or when the extension can switch scope to the function entry
How this affects pseudocode generation:
- recovered register arguments can be renamed from heuristic names like
arg1to PDB names such asctx - stack locals can be upgraded from generic slot names to scoped local names and types when available
- pointer-based memory accesses can gain field hints such as
ctx->State - enum-like comparisons can gain symbolic names such as
state == StateRunning - direct callee summaries can reuse PDB-derived prototypes and return types
Important limitations:
- public PDBs may provide function names and some type data but often do not include scoped locals
- optimized builds can make scoped local values and locations incomplete or ambiguous
- the extension treats PDB data as semantic hints, not as permission to override control flow that is contradicted by disassembly
Current behavior is automatic. There is no separate config switch for PDB usage; the quality depends on what WinDbg has already loaded and whether the current scope can be matched to the target function.
Configuration
Place decomp.llm.json beside decomp.dll.
This file is not only for network LLM settings.
provider,endpoint,model, token budgets, and chunking settings affect the LLM path.display_languageaffects the natural language used in summaries and uncertainties.syntax_highlightingaffects pseudo-code rendering in WinDbg when DML-aware output is available.display_languageandsyntax_highlightingare still used for/view:analyzerand mock-provider output.
Example:
{
"provider": "openai-compatible",
"endpoint": "https://api.openai.com/v1/chat/completions",
"model": "gpt-5.4-2026-03-05",
"api_key_env": "OPENAI_API_KEY",
"timeout_ms": 120000,
"max_completion_tokens": 12000,
"force_chunked": false,
"chunk_trigger_instructions": 900,
"chunk_trigger_blocks": 36,
"chunk_block_limit": 24,
"chunk_count_limit": 16,
"chunk_completion_tokens": 6000,
"merge_completion_tokens": 12000,
"display_language": {
"mode": "auto",
"tag": "en-US",
"name": "English"
},
"syntax_highlighting": {
"keyword_color": "warnfg",
"type_color": "emphfg",
"function_name_color": "srcid",
"identifier_color": "wfg",
"number_color": "changed",
"string_color": "srcstr",
"char_color": "srcchar",
"comment_color": "subfg",
"preprocessor_color": "verbfg",
"operator_color": "srcannot",
"punctuation_color": "srcpair"
}
}
ChatGPT subscription example:
{
"provider": "chatgpt",
"model": "gpt-5.5",
"chatgpt_auth_file": "%USERPROFILE%\\.codex\\auth.json",
"timeout_ms": 120000,
"max_completion_tokens": 12000,
"force_chunked": false,
"chunk_trigger_instructions": 900,
"chunk_trigger_blocks": 36,
"chunk_block_limit": 24,
"chunk_count_limit": 16,
"chunk_completion_tokens": 6000,
"merge_completion_tokens": 12000,
"reasoning_effort": "medium"
}
For provider: "chatgpt", endpoint is optional and defaults to https://chatgpt.com/backend-api/codex/responses. A base URL such as https://chatgpt.com/backend-api/codex is also accepted and normalized to /responses. The extension reads tokens.access_token and tokens.refresh_token from the configured auth file, refreshes expired JWT access tokens through OpenAI OAuth, and writes the refreshed token set back to that file. The default auth file is %USERPROFILE%\.codex\auth.json, so a Codex CLI ChatGPT login can be reused directly. The extension does not launch a browser or start an OAuth login flow from inside WinDbg; if the auth file is missing, invalid, or no longer refreshable, run codex login outside WinDbg and retry !decomp. For one-off tests, use access_token or access_token_env instead of an auth file. api_key, api_key_env, DECOMP_LLM_API_KEY, and OPENAI_API_KEY are reserved for OpenAI-compatible API-key providers and are ignored by the ChatGPT provider.
Supported keys:
providerendpointmodelapi_keyapi_key_envaccess_tokenaccess_token_envchatgpt_auth_filereasoning_efforttimeout_msmax_completion_tokensforce_chunkedchunk_trigger_instructionschunk_trigger_blockschunk_block_limitchunk_count_limitchunk_completion_tokensmerge_completion_tokensdisplay_languagesyntax_highlighting
Supported display_language keys:
modetagname
display_language.mode accepts:
autofixed
Supported syntax_highlighting keys:
keyword_colortype_colorfunction_name_coloridentifier_colornumber_colorstring_colorchar_colorcomment_colorpreprocessor_coloroperator_colorpunctuation_color
How syntax_highlighting color values work:
- These values are WinDbg DML color slot names, not fixed RGB or CSS color names.
- The extension passes them through to WinDbg DML as
<col fg="...">. - WinDbg resolves each slot name against its current theme and command window color settings.
- Because of that,
verbfg,warnfg,emphfg,srcid, and similar names do not map to one universal color across every machine. - There is currently no extension-side config for arbitrary RGB values such as
#FF8800. The effective color comes from WinDbg, not fromdecomp.llm.json.
Practical consequence:
- If a symbol color looks too dark on one dark theme, the same slot may look acceptable on another machine or another WinDbg theme.
- If two slots look almost identical in your current theme, change the slot names in
syntax_highlightingrather than assuming the extension is ignoring your setting. - If you need a truly different final color, change WinDbg's theme or command window color settings so that the slot itself resolves differently.
When highlighting is visible:
- The extension emits DML-colored pseudo-code when WinDbg reports that the current output callbacks are DML-aware.
- If the current debugger output path is not DML-aware, the extension falls back to plain text pseudo-code automatically.
/view:jsonoutput is not DML-rendered. Instead, it carriespseudo_c_tokensso external tools can apply their own syntax highlighting.
Common DML foreground slots:
wfgDefault window foreground text.normfgNormal command window text.emphfgEmphasized text. Microsoft documents this as light blue by default, but the exact appearance still depends on theme.warnfgWarning text.errfgError text.verbfgVerbose text.changedChanged data. Microsoft documents this as red by default.
Common source-oriented DML foreground slots:
srcnumNumeric constants.srccharCharacter constants.srcstrString constants.srcidIdentifiers.srckwKeywords.srcpairBrace or matching-symbol pairs.srccmntComments.srcdrctDirectives.srcspidSpecial identifiers.srcannotSource annotations or annotation-like elements.
Examples:
verbfgmeans "Verbose foreground slot", not "a specific named blue".warnfgmeans "Warning foreground slot", not "always yellow or orange".function_name_color: "srcid"means "render function names using WinDbg's identifier slot".
If you are tuning colors on a dark theme:
- Start with
function_name_color: "emphfg"orfunction_name_color: "verbfg"if function names look too dim withsrcid. - Use
identifier_color: "normfg"oridentifier_color: "wfg"for general symbols that should stay readable but not overpower keywords. - Keep
comment_color: "subfg"if you want comments to recede without disappearing entirely.
Official reference:
- DML color slot behavior and examples: Customizing Debugger Output Using DML
- Command-window message classes such as normal, warning, error, and verbose: .printf (WinDbg)
The checked-in decomp.llm.json.example contains only valid top-level settings that the extension actually reads.
Reference-only examples:
Follow the PC UI language:
{
"display_language": {
"mode": "auto"
}
}
Force English:
{
"display_language": {
"mode": "fixed",
"tag": "en-US",
"name": "English"
}
}
Force Korean:
{
"display_language": {
"mode": "fixed",
"tag": "ko-KR",
"name": "Korean"
}
}
Dark syntax-highlighting preset:
{
"syntax_highlighting": {
"keyword_color": "warnfg",
"type_color": "emphfg",
"function_name_color": "srcid",
"identifier_color": "wfg",
"number_color": "changed",
"string_color": "verbfg",
"char_color": "srcchar",
"comment_color": "subfg",
"preprocessor_color": "normfg",
"operator_color": "srcannot",
"punctuation_color": "srcpair"
}
}
Light syntax-highlighting preset:
{
"syntax_highlighting": {
"keyword_color": "emphfg",
"type_color": "warnfg",
"function_name_color": "srcid",
"identifier_color": "normfg",
"number_color": "changed",
"string_color": "verbfg",
"char_color": "srcchar",
"comment_color": "subfg",
"preprocessor_color": "srcannot",
"operator_color": "wfg",
"punctuation_color": "subfg"
}
}
Example /view:json response details:
- The JSON response includes
pseudo_candpseudo_c_tokens. pseudo_c_tokensis a deterministic token stream suitable for external syntax highlighting.- The serialized request includes
preferred_natural_language_tagandpreferred_natural_language_name, which reflect the resolved display language after applyingdisplay_language.mode. - Analyzer facts now include P0 quality fields:
ir_values,block_value_states,control_flow, andabi. ir_valuesexposes SSA-like value ids, definition sites, targets, canonical expressions, use links, constant/copy flags, and dead-definition hints.block_value_statesexposes per-basic-block live-in/live-out reaching definitions, canonical values, storage class, convergence state, and confidence.stack_pointerexposes per-instruction stack deltas, frame-relative aliases, raw base/offsets, and confidence.control_flowexposes structured region candidates such asnatural_loop,if_else_candidate, andswitch_candidatewith block evidence, loop induction metadata, switch table/default/range metadata, signedness, index expressions, and confidence.abiexposes Microsoft x64 shadow-space assumptions, home-slot evidence, frame/prolog/epilog recognition, no-return call evidence, tail-call candidates, thunk candidates, import-wrapper candidates, and recovered call arguments from registers and stack stores.- Analyzer facts now also include P1 semantic fields:
type_hints,idioms, andcallee_summaries. type_hintsexposes pointer, local, field-offset, array-like, enum-like, bitflag-like, and vtable-candidate evidence with source and confidence. When PDB data is available, scoped params/locals, field hints, and enum constants are also promoted into this unified type-hint stream.idiomsexposes higher-level replacements for recognized helper calls and compiler patterns such as memory copy/fill, string copy, security cookie checks, stack probes, allocation/free helpers, aggregate initializers, and RIP-relative global/import loads.callee_summariesexposes direct and indirect callee return-type, parameter-model, side-effect, memory-effect, ownership, source, and confidence hints; symbol/type-enriched call targets replace the initial heuristic summaries when WinDbg can resolve them, and virtual-call candidates include target expressions plus vtable offsets when recovered.- Known Win32/NT/Rtl API summaries describe memory copy/fill/zero, allocation, release, status, and error behavior when symbol names are available.
- Prompt facts include
analyzer_skeletonandgraph_summaryso the model refines an evidence-backed draft instead of starting from a blank page. graph_summaryprovides entry block, control-flow regions, normalized conditions, and representative high-signal blocks with an explicit truncation policy. Prompt fact selection now ranks high-signal entries and uses spread sampling to keep large fact sets representative.evidence_graphexposes high-signal fact nodes and provenance edges so IR values, block value states, memory accesses, call targets, type hints, PDB hints, and observed behavior can be traced back to instruction and block evidence.obfuscation,semantic_control_flow, anddeobfuscation_readinessexpose OLLVM-style recovery facts and whether deobfuscation rewrite guidance is enabled for the current command.- The verifier response includes legacy
warningsplus structuredissuesentries. Each issue carriesseverity,code,message, and optionalevidenceso tools can filter errors such asbranch.true_target_not_successorseparately from lower-risk warnings. - Verifier checks now compare normalized branch true/false targets against CFG successors, compare pseudo-code branch density against recovered conditional branches, cross-check direct callee summaries against pseudo-code call effects, validate evidence-graph node/edge grounding, and check block value state references back to recovered blocks and IR values.
- Normal and explain output may include a concise
suggested fixessection. These are conservative/fix:*commands derived from verifier issues, PDB-backed rename opportunities, or repeated observed memory hotspots. DML-aware output renders immediately applicable suggestions as clickable rerun links for the same target; placeholder field-type suggestions remain plain text untilTYPEis replaced. - In LLM mode, the extension automatically feeds verifier issues back into one retry prompt. The retry is kept when it preserves or improves verifier quality; otherwise the original response is retained with an added uncertainty note.
session_policyandobserved_behaviorexpose WinDbg-specific context such as live/dump/kernel/TTD-like policy, current-frame register argument samples, memory hotspots, and suggested trace queries.- The serialized request now also includes a
pdbobject when symbol/type data is available. pdb.availabilityreports the enrichment level such asnone,symbols,typed, orscoped.pdb.params,pdb.locals,pdb.field_hints,pdb.enum_hints, andpdb.source_locationsare intended as machine-readable semantic hints for external tooling or offline analysis.
Optional environment overrides:
DECOMP_LLM_PROVIDERDECOMP_LLM_ENDPOINTDECOMP_LLM_MODELDECOMP_LLM_API_KEYOPENAI_API_KEYDECOMP_LLM_CHATGPT_ACCESS_TOKENDECOMP_LLM_CODEX_ACCESS_TOKENKERNFORGE_CODEX_ACCESS_TOKENDECOMP_LLM_CHATGPT_AUTH_FILEDECOMP_LLM_CODEX_AUTH_FILEKERNFORGE_CODEX_AUTH_FILEDECOMP_LLM_REASONING_EFFORTDECOMP_LLM_TIMEOUT_MSDECOMP_LLM_MAX_COMPLETION_TOKENSDECOMP_LLM_FORCE_CHUNKEDDECOMP_LLM_CHUNK_TRIGGER_INSTRUCTIONSDECOMP_LLM_CHUNK_TRIGGER_BLOCKSDECOMP_LLM_CHUNK_BLOCK_LIMITDECOMP_LLM_CHUNK_COUNT_LIMITDECOMP_LLM_CHUNK_COMPLETION_TOKENSDECOMP_LLM_MERGE_COMPLETION_TOKENSDECOMP_NORETURN_OVERRIDESComma- or semicolon-separated function-name fragments treated as no-return targets during fallback disassembly, CFG successor recovery, ABI facts, and verifier checks. Example:DECOMP_NORETURN_OVERRIDES=MyAbort;PanicAndExit.
Quality-first note:
- The extension now supports chunked multi-pass analysis for large functions.
- The analyzer sends IR value facts, block value states, control-flow regions, evidence graph facts, and x64 ABI/no-return evidence to the LLM before refinement, so
/view:analyzer,/view:json, and normal LLM mode all share the same P0 evidence base. - The verifier cross-checks loop, switch, no-return, branch targets, return behavior, callee call effects, evidence graph grounding, block value state consistency, evidence coverage, and suspicious identifier claims against analyzer evidence. It lowers trust when confident prose outruns recovered facts and labels each issue with a stable severity/code pair.
- When verifier feedback finds schema errors, fact conflicts, or very low adjusted confidence, the LLM path performs one automatic retry with the verifier issues appended to the prompt.
- A good starting point for cloud models is
max_completion_tokens=12000,chunk_completion_tokens=6000, andmerge_completion_tokens=12000, withforce_chunked=falseand chunk triggers around900 instructionsor36 blocks. - Keep
force_chunked=truefor chunk-pipeline stress tests only. Quality-focused decompilation of flattened or dispatcher-heavy functions usually needs a single prompt until the function is large enough to exceed the configured chunk triggers. - Keep
timeout_mshigh for cloud models.120000is a safer starting point than15000. - If quality is still weak on huge functions, raise
chunk_count_limitbefore shrinking/limit:N. - If no endpoint is configured, the extension falls back to the deterministic mock provider.
- Even when the extension is using
/view:analyzeror the mock provider,display_languageandsyntax_highlightingstill affect what the user sees.
WinDbg Smoke Test
- Build with
Build.ps1orBuild-Legacy.ps1. - Place
decomp.llm.jsonbeside the builtdecomp.dll. - Start WinDbg. Environment variables are optional overrides only.
- Load the extension.
- Validate analyzer-only mode before enabling the LLM path.
.load C:\path\to\decomp.dll
!decomp /view:analyzer ntdll!RtlAllocateHeap
!decomp /view:facts kernel32!Sleep
Then validate LLM mode:
!decomp ntdll!RtlAllocateHeap
!decomp /view:json ntdll!RtlAllocateHeap
!decomp 0x7ffb`12345678
Expected checks:
target,entry, andmoduleshould resolve consistentlyregionsshould be non-zero for normal functions/view:analyzershould still print analyzer confidence and pseudocode stub- LLM mode should fill
summary,pseudo_c,pseudo_c_tokens, andverified /view:jsonoutput should includepreferred_natural_language_tagandpreferred_natural_language_namein the serialized request- when private or rich PDBs are loaded,
/view:jsonshould also includepdb.prototype,pdb.params, and possiblypdb.locals - for typed structs and enums,
/view:jsonmay includepdb.field_hintsandpdb.enum_hints
ChatGPT Subscription Example
$env:DECOMP_LLM_PROVIDER = "chatgpt"
$env:DECOMP_LLM_MODEL = "gpt-5.5"
$env:DECOMP_LLM_CHATGPT_AUTH_FILE = "$env:USERPROFILE\.codex\auth.json"
$env:DECOMP_LLM_TIMEOUT_MS = "120000"
If the auth file contains a refresh token, the extension refreshes an expired access token before sending the request. DECOMP_LLM_CHATGPT_ACCESS_TOKEN can be used for a temporary bearer token, but the auth-file path is better for normal WinDbg sessions because it survives token expiry. The extension never opens a browser during !decomp; run codex login outside WinDbg when an interactive ChatGPT login is needed.
Local LLM Endpoint Examples
Ollama
$env:DECOMP_LLM_ENDPOINT = "http://127.0.0.1:11434/v1/chat/completions"
$env:DECOMP_LLM_MODEL = "qwen2.5-coder:14b"
$env:DECOMP_LLM_API_KEY = "ollama"
LM Studio
$env:DECOMP_LLM_ENDPOINT = "http://127.0.0.1:1234/v1/chat/completions"
$env:DECOMP_LLM_MODEL = "local-model"
$env:DECOMP_LLM_API_KEY = "lm-studio"
vLLM or OpenAI-compatible local server
$env:DECOMP_LLM_ENDPOINT = "http://127.0.0.1:8000/v1/chat/completions"
$env:DECOMP_LLM_MODEL = "Qwen/Qwen2.5-Coder-14B-Instruct"
$env:DECOMP_LLM_API_KEY = "local"