ACPI AML decompiler w/ CFG recovery and structured pseudocode
1#include "dc.h"
2
3#include "mem.h"
4#include "str.h"
5
6#include <stdio.h>
7#include <string.h>
8
9static void indent(tobi_sb *sb, unsigned n) {
10 for (unsigned i = 0; i < n; i++) {
11 tobi_sb_add(sb, " ");
12 }
13}
14
15static void expr(tobi_sb *sb, const tobi_ir *n);
16static void stmt(tobi_sb *sb, const tobi_ir *n, unsigned ind);
17
18static const char *binop(const char *name) {
19 if (!name) {
20 return NULL;
21 }
22 if (strcmp(name, "eq") == 0) return "==";
23 if (strcmp(name, "gt") == 0) return ">";
24 if (strcmp(name, "lt") == 0) return "<";
25 if (strcmp(name, "add") == 0) return "+";
26 if (strcmp(name, "sub") == 0) return "-";
27 if (strcmp(name, "mul") == 0) return "*";
28 if (strcmp(name, "div") == 0) return "/";
29 if (strcmp(name, "mod") == 0) return "%";
30 if (strcmp(name, "shl") == 0) return "<<";
31 if (strcmp(name, "shr") == 0) return ">>";
32 if (strcmp(name, "and") == 0) return "&";
33 if (strcmp(name, "or") == 0) return "|";
34 if (strcmp(name, "xor") == 0) return "^";
35 if (strcmp(name, "land") == 0) return "&&";
36 if (strcmp(name, "lor") == 0) return "||";
37 return NULL;
38}
39
40static const char *unop(const char *name) {
41 if (!name) {
42 return NULL;
43 }
44 if (strcmp(name, "lnot") == 0) return "!";
45 if (strcmp(name, "not") == 0) return "~";
46 return NULL;
47}
48
49static int op_prec(const char *name) {
50 if (!name) {
51 return 0;
52 }
53 if (strcmp(name, "lor") == 0) return 1;
54 if (strcmp(name, "land") == 0) return 2;
55 if (strcmp(name, "or") == 0) return 3;
56 if (strcmp(name, "xor") == 0) return 4;
57 if (strcmp(name, "and") == 0) return 5;
58 if (strcmp(name, "eq") == 0 || strcmp(name, "gt") == 0 || strcmp(name, "lt") == 0) return 6;
59 if (strcmp(name, "shl") == 0 || strcmp(name, "shr") == 0) return 7;
60 if (strcmp(name, "add") == 0 || strcmp(name, "sub") == 0) return 8;
61 if (strcmp(name, "mul") == 0 || strcmp(name, "div") == 0 || strcmp(name, "mod") == 0) return 9;
62 if (unop(name)) return 10;
63 return 0;
64}
65
66static int op_right_needs_group(const char *name) {
67 if (!name) {
68 return 1;
69 }
70 return !(strcmp(name, "add") == 0 || strcmp(name, "mul") == 0 ||
71 strcmp(name, "and") == 0 || strcmp(name, "or") == 0 ||
72 strcmp(name, "xor") == 0 || strcmp(name, "land") == 0 ||
73 strcmp(name, "lor") == 0);
74}
75
76static void expr_prec(tobi_sb *sb, const tobi_ir *n, int parent_prec) {
77 if (!n) {
78 tobi_sb_add(sb, "<missing>");
79 return;
80 }
81 switch (n->kind) {
82 case TOBI_IR_INTEGER:
83 tobi_sb_printf(sb, "0x%llx", (unsigned long long)n->value);
84 return;
85 case TOBI_IR_STRING:
86 tobi_sb_ch(sb, '"');
87 if (n->str) {
88 for (const char *p = n->str; *p; p++) {
89 if (*p == '"' || *p == '\\') {
90 tobi_sb_ch(sb, '\\');
91 }
92 tobi_sb_ch(sb, *p);
93 }
94 }
95 tobi_sb_ch(sb, '"');
96 return;
97 case TOBI_IR_REF:
98 tobi_sb_add(sb, n->name ? n->name : "<ref>");
99 return;
100 case TOBI_IR_CALL:
101 tobi_sb_add(sb, n->name ? n->name : "<call>");
102 tobi_sb_ch(sb, '(');
103 for (size_t i = 0; i < n->child_len; i++) {
104 if (i) {
105 tobi_sb_add(sb, ", ");
106 }
107 expr_prec(sb, n->child[i], 0);
108 }
109 tobi_sb_ch(sb, ')');
110 return;
111 case TOBI_IR_EXPR: {
112 const char *op = binop(n->name);
113 int prec = op_prec(n->name);
114 if (op && n->child_len >= 2 && prec > 0) {
115 int paren = prec < parent_prec;
116 if (paren) {
117 tobi_sb_ch(sb, '(');
118 }
119 expr_prec(sb, n->child[0], prec);
120 tobi_sb_printf(sb, " %s ", op);
121 expr_prec(sb, n->child[1], prec + op_right_needs_group(n->name));
122 if (paren) {
123 tobi_sb_ch(sb, ')');
124 }
125 return;
126 }
127 op = unop(n->name);
128 if (op && n->child_len == 1 && prec > 0) {
129 int paren = prec < parent_prec;
130 if (paren) {
131 tobi_sb_ch(sb, '(');
132 }
133 tobi_sb_add(sb, op);
134 expr_prec(sb, n->child[0], prec);
135 if (paren) {
136 tobi_sb_ch(sb, ')');
137 }
138 return;
139 }
140 tobi_sb_printf(sb, "%s(", n->name ? n->name : "op");
141 for (size_t i = 0; i < n->child_len; i++) {
142 if (i) {
143 tobi_sb_add(sb, ", ");
144 }
145 expr_prec(sb, n->child[i], 0);
146 }
147 tobi_sb_ch(sb, ')');
148 return;
149 }
150 case TOBI_IR_BUFFER:
151 tobi_sb_printf(sb, "Buffer[%s]", n->str ? n->str : "");
152 return;
153 case TOBI_IR_PACKAGE:
154 tobi_sb_ch(sb, '{');
155 for (size_t i = 0; i < n->child_len; i++) {
156 if (i) {
157 tobi_sb_add(sb, ", ");
158 }
159 expr_prec(sb, n->child[i], 0);
160 }
161 tobi_sb_ch(sb, '}');
162 return;
163 case TOBI_IR_STORE:
164 if (n->child_len >= 2) {
165 expr_prec(sb, n->child[1], 0);
166 tobi_sb_add(sb, " = ");
167 expr_prec(sb, n->child[0], 0);
168 } else {
169 tobi_sb_add(sb, "store(?)");
170 }
171 return;
172 case TOBI_IR_UNKNOWN:
173 tobi_sb_printf(sb, "unknown_0x%x", n->raw_op);
174 return;
175 default:
176 tobi_sb_add(sb, n->name ? n->name : tobi_ir_kind_name(n->kind));
177 return;
178 }
179}
180
181static void expr(tobi_sb *sb, const tobi_ir *n) {
182 expr_prec(sb, n, 0);
183}
184
185static int expr_has_target(const tobi_ir *n) {
186 if (!n || n->kind != TOBI_IR_EXPR || !n->name) {
187 return 0;
188 }
189 if (strcmp(n->name, "div") == 0) {
190 return n->child_len >= 4;
191 }
192 return binop(n->name) && n->child_len >= 3;
193}
194
195static void expr_target_stmt(tobi_sb *sb, const tobi_ir *n) {
196 if (strcmp(n->name, "div") == 0 && n->child_len >= 4) {
197 expr(sb, n->child[2]);
198 tobi_sb_add(sb, ", ");
199 expr(sb, n->child[3]);
200 tobi_sb_add(sb, " = divmod(");
201 expr(sb, n->child[0]);
202 tobi_sb_add(sb, ", ");
203 expr(sb, n->child[1]);
204 tobi_sb_ch(sb, ')');
205 return;
206 }
207 expr(sb, n->child[2]);
208 tobi_sb_add(sb, " = ");
209 expr(sb, n);
210}
211
212static void block(tobi_sb *sb, const tobi_ir *b, unsigned ind) {
213 if (!b) {
214 return;
215 }
216 for (size_t i = 0; i < b->child_len; i++) {
217 stmt(sb, b->child[i], ind);
218 }
219}
220
221static void stmt(tobi_sb *sb, const tobi_ir *n, unsigned ind) {
222 if (!n) {
223 return;
224 }
225 switch (n->kind) {
226 case TOBI_IR_SCOPE:
227 indent(sb, ind);
228 tobi_sb_printf(sb, "scope %s {\n", n->path ? n->path : n->name);
229 block(sb, n->child_len ? n->child[0] : NULL, ind + 1);
230 indent(sb, ind);
231 tobi_sb_add(sb, "}\n");
232 break;
233 case TOBI_IR_DEVICE:
234 indent(sb, ind);
235 tobi_sb_printf(sb, "device %s {\n", n->path ? n->path : n->name);
236 block(sb, n->child_len ? n->child[0] : NULL, ind + 1);
237 indent(sb, ind);
238 tobi_sb_add(sb, "}\n");
239 break;
240 case TOBI_IR_METHOD:
241 indent(sb, ind);
242 tobi_sb_printf(sb, "method %s(args=%u, serialized=%s, sync=%u) {\n",
243 n->name ? n->name : "<method>", n->method_args,
244 n->method_serialized ? "true" : "false", n->method_sync);
245 block(sb, n->child_len ? n->child[0] : NULL, ind + 1);
246 indent(sb, ind);
247 tobi_sb_add(sb, "}\n");
248 break;
249 case TOBI_IR_NAME:
250 indent(sb, ind);
251 tobi_sb_printf(sb, "name %s = ", n->path ? n->path : n->name);
252 if (n->child_len) {
253 expr(sb, n->child[0]);
254 } else {
255 tobi_sb_add(sb, "<missing>");
256 }
257 tobi_sb_add(sb, ";\n");
258 break;
259 case TOBI_IR_OPREGION:
260 indent(sb, ind);
261 tobi_sb_printf(sb, "opregion %s space=%llu offset=", n->path ? n->path : n->name, (unsigned long long)n->value);
262 expr(sb, n->child_len > 0 ? n->child[0] : NULL);
263 tobi_sb_add(sb, " length=");
264 expr(sb, n->child_len > 1 ? n->child[1] : NULL);
265 tobi_sb_add(sb, ";\n");
266 break;
267 case TOBI_IR_FIELD:
268 indent(sb, ind);
269 tobi_sb_printf(sb, "%s %s", n->str ? n->str : "field", n->name ? n->name : "<field>");
270 if (n->target) {
271 tobi_sb_printf(sb, " backing=%s", n->target);
272 }
273 tobi_sb_printf(sb, " flags=0x%llx access=%u lock=%u update=%u {\n",
274 (unsigned long long)n->value, n->access_type, n->lock_rule, n->update_rule);
275 for (size_t i = 0; i < n->child_len; i++) {
276 indent(sb, ind + 1);
277 if (n->child[i]->kind == TOBI_IR_FIELD_ELEM) {
278 tobi_sb_printf(sb, "%s @%llu:%llu", n->child[i]->name ? n->child[i]->name : "<field>",
279 (unsigned long long)n->child[i]->bit_off,
280 (unsigned long long)n->child[i]->bit_len);
281 if (n->child[i]->str) {
282 tobi_sb_printf(sb, " /* %s */", n->child[i]->str);
283 }
284 tobi_sb_add(sb, ";\n");
285 } else {
286 tobi_sb_add(sb, "bank_value = ");
287 expr(sb, n->child[i]);
288 tobi_sb_add(sb, ";\n");
289 }
290 }
291 indent(sb, ind);
292 tobi_sb_add(sb, "}\n");
293 break;
294 case TOBI_IR_STORE:
295 indent(sb, ind);
296 expr(sb, n);
297 tobi_sb_add(sb, ";\n");
298 break;
299 case TOBI_IR_EXPR:
300 case TOBI_IR_CALL:
301 indent(sb, ind);
302 if (expr_has_target(n)) {
303 expr_target_stmt(sb, n);
304 } else {
305 expr(sb, n);
306 }
307 tobi_sb_add(sb, ";\n");
308 break;
309 case TOBI_IR_RETURN:
310 indent(sb, ind);
311 tobi_sb_add(sb, "return");
312 if (n->child_len) {
313 tobi_sb_ch(sb, ' ');
314 expr(sb, n->child[0]);
315 }
316 tobi_sb_add(sb, ";\n");
317 break;
318 case TOBI_IR_IF:
319 indent(sb, ind);
320 tobi_sb_add(sb, "if (");
321 expr(sb, n->child_len ? n->child[0] : NULL);
322 tobi_sb_add(sb, ") {\n");
323 block(sb, n->child_len > 1 ? n->child[1] : NULL, ind + 1);
324 indent(sb, ind);
325 if (n->child_len > 2) {
326 tobi_sb_add(sb, "} else {\n");
327 block(sb, n->child[2], ind + 1);
328 indent(sb, ind);
329 }
330 tobi_sb_add(sb, "}\n");
331 break;
332 case TOBI_IR_WHILE:
333 indent(sb, ind);
334 tobi_sb_add(sb, "while (");
335 expr(sb, n->child_len ? n->child[0] : NULL);
336 tobi_sb_add(sb, ") {\n");
337 block(sb, n->child_len > 1 ? n->child[1] : NULL, ind + 1);
338 indent(sb, ind);
339 tobi_sb_add(sb, "}\n");
340 break;
341 case TOBI_IR_BREAK:
342 indent(sb, ind);
343 tobi_sb_add(sb, "break;\n");
344 break;
345 case TOBI_IR_CONTINUE:
346 indent(sb, ind);
347 tobi_sb_add(sb, "continue;\n");
348 break;
349 case TOBI_IR_UNKNOWN:
350 indent(sb, ind);
351 tobi_sb_printf(sb, "/* unknown opcode 0x%x at 0x%zx", n->raw_op, n->off);
352 if (n->str) {
353 tobi_sb_printf(sb, ": %s", n->str);
354 }
355 tobi_sb_add(sb, " */\n");
356 break;
357 case TOBI_IR_DIAG:
358 indent(sb, ind);
359 tobi_sb_printf(sb, "/* %s at 0x%zx */\n", n->str ? n->str : "diagnostic", n->off);
360 break;
361 case TOBI_IR_BLOCK:
362 block(sb, n, ind);
363 break;
364 default:
365 indent(sb, ind);
366 tobi_sb_printf(sb, "/* %s at 0x%zx */\n", tobi_ir_kind_name(n->kind), n->off);
367 break;
368 }
369}
370
371char *tobi_dc_emit(const tobi_ir *root, const tobi_diag_list *diag) {
372 tobi_sb sb;
373 tobi_sb_init(&sb);
374 if (diag) {
375 for (size_t i = 0; i < diag->len; i++) {
376 if (diag->items[i].level == TOBI_DIAG_WARN) {
377 tobi_sb_printf(&sb, "/* warning at 0x%zx: %s */\n", diag->items[i].off, diag->items[i].msg);
378 }
379 }
380 }
381 if (root) {
382 for (size_t i = 0; i < root->child_len; i++) {
383 stmt(&sb, root->child[i], 0);
384 }
385 }
386 return tobi_sb_take(&sb);
387}