Decompiler Construction: Chapter 19 - Practical Decompilation Results and Pipeline Evaluation
To stress-test the process, I have included this program, which includes:
- State-dependent control flow with continuously mutating variables
- Mixed arithmetic and bitwise operations (XOR, shifts, rotation patterns)
- Early loop terminations
- Pointers
- Redundant computations that should be simplified Note. The following implementation used is closed source as of 2026.
The following input program is compiled and passed through the decompilation process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdint.h>
volatile int32_t sink;
typedef struct {
int32_t a; int32_t b; int32_t c;
} Node;
int32_t func(int32_t *arr, const Node *n, const int32_t x) {
int32_t acc = 0;
int32_t i = 0;
int32_t state = (x ^ 0x5A) + (n->a << 1);
while (i < 10) {
state = state ^ (i * 3);
if ((state & 1) == 0) { acc += arr[i] + n->b; } else { acc += arr[i] ^ n->c; }
if (acc > 100) { break; }
++i;
if (!(i % 3)) { continue; }
state = (state << 1) | (state >> 31);
}
int32_t tmp = acc * 2;
tmp = tmp - acc;
if (x < 0) { tmp = tmp ^ 0xDEADBEEF; }
sink = tmp;
return acc + state;
}
int32_t main(void) {
int32_t arr = 9;
for (int32_t t = 0; t < 1000; ++t) {
Node n = { .a = t, .b = t ^ 0xAA, .c = t + 3}; func(&arr, &n, 19); }
return 0;
}
To verify our output is right the original code was ran 5 times with printf after func call
1
printf("OUTPUT arr(%d), n->a(%d), n->b(%d), n->c(%d)\n", arr, n.a, n.b, n.c);
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
OUTPUT arr(9), n->a(0), n->b(170), n->c(3)
OUTPUT arr(9), n->a(1), n->b(171), n->c(4)
OUTPUT arr(9), n->a(2), n->b(168), n->c(5)
OUTPUT arr(9), n->a(3), n->b(169), n->c(6)
OUTPUT arr(9), n->a(4), n->b(174), n->c(7)
OUTPUT arr(9), n->a(5), n->b(175), n->c(8)
OUTPUT arr(9), n->a(6), n->b(172), n->c(9)
OUTPUT arr(9), n->a(7), n->b(173), n->c(10)
OUTPUT arr(9), n->a(8), n->b(162), n->c(11)
OUTPUT arr(9), n->a(9), n->b(163), n->c(12)
OUTPUT arr(9), n->a(10), n->b(160), n->c(13)
OUTPUT arr(9), n->a(11), n->b(161), n->c(14)
OUTPUT arr(9), n->a(12), n->b(166), n->c(15)
OUTPUT arr(9), n->a(13), n->b(167), n->c(16)
....
Typically this step you would use DBI but for for this example we have already compiled and extracted machine code.
Assembly, this was compiled with CLANG.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x0: push rbp
0x1: mov rbp, rsp
0x4: sub rsp, 0x20
0x8: mov dword ptr [rbp - 4], 0
0xf: mov dword ptr [rbp - 8], 9
0x16: mov dword ptr [rbp - 0xc], 0
0x1d: cmp dword ptr [rbp - 0xc], 0x3e8
0x24: jge 0x5d
0x26: mov eax, dword ptr [rbp - 0xc]
0x29: mov dword ptr [rbp - 0x18], eax
0x2c: mov eax, dword ptr [rbp - 0xc]
0x2f: xor eax, 0xaa
0x34: mov dword ptr [rbp - 0x14], eax
0x37: mov eax, dword ptr [rbp - 0xc]
0x3a: add eax, 3
0x3d: mov dword ptr [rbp - 0x10], eax
0x40: lea rdi, [rbp - 8]
0x44: lea rsi, [rbp - 0x18]
0x48: mov edx, 0x13
0x4d: call 0x65
....
We will lift it to IL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
107 SHRN r264, r263, 32
108 XOR r265, r263, r264
109 MOVE r263, r265
110 SHRN r266, r263, 16
111 XOR r267, r263, r266
112 MOVE r263, r267
113 SHRN r268, r263, 8
114 XOR r269, r263, r268
115 MOVE r263, r269
116 SHRN r270, r263, 4
117 XOR r271, r263, r270
118 MOVE r263, r271
119 SHRN r272, r263, 2
120 XOR r273, r263, r272
121 MOVE r263, r273
122 SHRN r274, r263, 1
123 XOR r275, r263, r274
124 MOVE r263, r275
125 ANDN r276, r263, 1
126 BITNOT r277, r276
127 ANDN r278, r277, 1
128 FLAGSET 16, r278
129 BITCAST r250, r250, 64, 0, false
130 MOVE r44, r250
131 NOP
132 BITCAST r248, r248, 32, 0, false
133 LOADINT r249, 0
134 MOVE r248, r249
135 BITCAST r250, r250, 32, 0, false
....
We will then lift the IL to IR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
r260 = memread<int32_t>(r250)
r259 = (r260 - r255)
r259 = int32_t(r259)
r262 = memread<int32_t>(r250)
r261 = (r262 & 15)
r261 = int32_t(r261)
r263 = (r253 & 15)
r263 = int32_t(r263)
r264 = (r261 + r263)
r264 = int32_t(r264)
r265 = (r264 > 15)
r265 = int32_t(r265)
r266 = (r259 & 15)
r266 = int32_t(r266)
r268 = memread<int32_t>(r250)
r267 = (r268 & 15)
r267 = int32_t(r267)
r269 = (r266 < r267)
r269 = int32_t(r269)
r270 = (r265 | r269)
r270 = int32_t(r270)
r270 = int32_t(r270)
....
The IR currently is unsimplified. We will use various passes to simplify it, here are the most common ones:
- Constant folding
- Page constructions
- Dead code elimination
- Jump threading
- Loop reconstruction
- Expression canonicalization
- Loop simplification
- Constant Propagation
- Dead store elimination Note. Full list as of 2026 is proprietary
Completely Safe IR:
1
2
3
C Code Geneation
Prev Chapter: Chapter 18 - Lowering IR to Readable and Executable Code
This post is licensed under CC BY 4.0 by the author.