Hex-Rays pseudocode expression simplification hints — a non-invasive annotation plugin for IDA Pro.
Version 1.1.0 · IDA Pro 9.x + Hex-Rays · IDAPython 3.x
DecHint walks the Hex-Rays ctree (expression AST) of a decompiled function, matches every sub-expression against a library of algebraic simplification rules, and surfaces the results as pseudocode comments and an interactive report dialog — without ever modifying the decompilation itself.
- ❌ Does not alter the IDA database or the decompiled output
- ✅ All suggestions are displayed as removable
[DH]-prefixed comments - ✅ Conservative by design — false negatives over false positives
Copy the entire dechint/ folder into IDA's plugins directory:
<IDA_INSTALL>/plugins/dechint/
IDA 9.x automatically discovers ida-plugin.json inside sub-directories and loads the plugin on startup. No manual registration required.
dechint/
├── ida-plugin.json # IDA 9.x plugin descriptor
├── dechint_entry.py # Entry point (PLUGIN_ENTRY)
├── __init__.py
├── LICENSE # MIT License
├── README.md # This file (English)
├── README_CN.md # 中文文档
├── dh_core/
│ ├── plugin.py # plugin_t + action registration + UI hooks
│ ├── analyzer.py # 4-layer analysis pipeline (Match → Normalize → Conflict Resolution → Render)
│ ├── rules.py # Rule engine — 35 simplification rules
│ ├── expr_utils.py # Expression helpers (type checks, structural matching, pretty-print, side-effect analysis)
│ ├── models.py # Data models (RewriteHint, confidence levels)
│ ├── settings.py # Runtime settings
│ └── comment_writer.py # Write hints as pseudocode comments via treeloc_t
└── dh_ui/
├── report_window.py # PyQt5 report dialog with checkboxes
└── settings_dialog.py # PyQt5 settings dialog
- Open a binary in IDA and press F5 to decompile a function.
- Press Ctrl+Shift+H (or Edit → Plugins → DecHint) to analyze.
- An interactive report table pops up with all detected simplifications.
- Check the hints you want, then click "Write Selected" to insert them as pseudocode comments.
In any pseudocode view, right-click to find the DecHint/ submenu:
| Action | Description |
|---|---|
| Analyze Current Function | Run analysis & open the report (Ctrl+Shift+H) |
| Settings… | Open the settings dialog |
- Checkbox per row — select which hints to write as comments
- Filter by confidence — toggle Safe / Conditional visibility
- Text search — filter by rule name, expression text, etc.
- Write Selected / Write All / Clear Comments buttons
- Double-click any row to jump to that address in IDA
- Copy results to clipboard
- Refresh to re-analyze without reopening
| Option | Default | Description |
|---|---|---|
| Show safe rules | ✅ | Display results from safe (always-valid) rules |
| Show conditional rules | ✅ | Display results from conditional rules |
| Show candidate rules | ❌ | Display low-confidence candidates |
| Max hints per line | 2 | Cap hints written to a single pseudocode line |
The analyzer uses a 4-layer pipeline:
| Layer | Purpose |
|---|---|
| 1. Match | Full scan — every rule runs on every expression node. No early break. |
| 2. Normalize | Deduplicate exact copies (same address + rule + rewrite text). |
| 3. Conflict Resolution | Per-address: larger expression span wins → safe beats conditional → higher priority wins → substring subsumption across all addresses. |
| 4. Render | Two outputs: report (near-full list for the dialog) and comments (strict conflict-resolved subset for writing). |
| Rule | Pattern | Simplifies To |
|---|---|---|
div-mul-mod-identity |
a*(x/a) + x%a |
x |
sub-div-mul-to-mod |
x - (x/a)*a |
x % a |
base-plus-div-mul-mod |
base + a*(x/a) + x%a |
base + x |
nested-mod-identity |
((x%a)/b)*b + (x%a)%b |
x % a |
redundant-cast |
(T)(T)x |
(T)x |
linear-index-normalize |
N*((x%M)/N) + (x%M)%N |
x % M |
array-index-simplify |
arr[complex_index] |
arr[simplified] |
add-zero |
x + 0 |
x |
mul-one |
x * 1 |
x |
xor-self |
x ^ x |
0 |
sub-self |
x - x |
0 |
double-neg |
-(-x) |
x |
double-bnot |
~(~x) |
x |
or-self |
x | x |
x |
and-self |
x & x |
x |
shift-by-zero |
x << 0 / x >> 0 |
x |
and-all-ones |
x & 0xFFFFFFFF |
x |
xor-zero |
x ^ 0 |
x |
or-zero |
x | 0 |
x |
xor-all-ones |
x ^ 0xFF…F |
~x |
| Rule | Pattern | Simplifies To | Assumption |
|---|---|---|---|
mul-zero |
x * 0 |
0 |
No side effects |
and-zero |
x & 0 |
0 |
No side effects |
or-all-ones |
x | 0xFF…F |
0xFF…F |
No side effects |
mul-pow2-to-shift |
x * 2^k |
x << k |
Integer equivalence |
div-pow2-to-shift |
x / 2^k |
x >> k |
Unsigned / non-negative |
mod-pow2-to-and |
x % 2^k |
x & (2^k-1) |
Unsigned / non-negative |
and-to-mod-pow2 |
x & (2^k-1) |
x % 2^k |
Readability |
twos-complement-add-normalize |
x + 0x61C88647 |
x - 0x9E3779B9 |
uint32 two's complement |
twos-complement-sub-normalize |
x - 0xFFFFFFFF |
x + 1 |
uint32 two's complement |
shift-mask-explain |
(x >> k) << k |
x & ~mask |
Shift-mask equivalence |
sub-shift-mask-to-mod |
x - ((x>>k)<<k) |
x % 2^k |
Unsigned / non-negative |
mul-neg-one |
x * 0xFF…F |
-x |
Two's complement |
add-neg-const |
x + 0xFFFFFFFC |
x - 4 |
Two's complement |
add-self |
x + x |
x << 1 |
Equivalence |
shl-one-to-pow2 |
1 << k |
2^k |
Readability |
Comments injected into pseudocode are prefixed with [DH] for easy identification and bulk removal:
// [DH] dword_51A0[((8 * ((v3 % 64) / 8)) + ((v3 % 64) % 8))]
// [DH] => dword_51A0[v3 % 64]
// [DH] [safe] rule: array-index-simplify
// [DH] (x + 0x61C88647)
// [DH] => (x - 0x9E3779B9)
// [DH] assume: uint32 two's complement
// [DH] [conditional] rule: twos-complement-add-normalize
The Clear Comments button removes all [DH]-prefixed lines without touching user-written comments.
Hex-Rays pseudocode comments are placed via cfunc_t.set_user_cmt(treeloc_t, text).
- Normal statement lines (ending with
;): positioned usingget_line_item() → ptail.loc. - Control-structure heads (
if/while/for/switch): a parent-awareCV_PARENTSvisitor builds anexpr_ea → enclosing control cinsn_tmap, then usestreeloc_t { insn.ea, ITP_BLOCK1 }— the same coordinate IDA uses when users press/to comment.
exprs_equal() performs structural comparison with commutative awareness — for operators like +, *, &, |, ^, it also checks the swapped operand order.
is_side_effect_free() recursively checks whether an expression contains calls, assignments, or pre/post-increment operations. Rules like mul-zero and and-zero use this to decide between SAFE and CONDITIONAL confidence.
- Single-function, single-expression scope — no inter-procedural analysis
- Type inference depends on Hex-Rays — missing types may prevent some conditional rules from firing
- Settings are session-only (not persisted across IDA restarts)
MIT License — Copyright © 2026 Euarno