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=%s(%llu) offset=", n->path ? n->path : n->name,
262 n->str ? n->str : "unknown", (unsigned long long)n->value);
263 expr(sb, n->child_len > 0 ? n->child[0] : NULL);
264 tobi_sb_add(sb, " length=");
265 expr(sb, n->child_len > 1 ? n->child[1] : NULL);
266 tobi_sb_add(sb, ";\n");
267 break;
268 case TOBI_IR_FIELD:
269 indent(sb, ind);
270 tobi_sb_printf(sb, "%s %s", n->str ? n->str : "field", n->name ? n->name : "<field>");
271 if (n->target) {
272 tobi_sb_printf(sb, " backing=%s", n->target);
273 }
274 tobi_sb_printf(sb, " flags=0x%llx access=%u lock=%u update=%u {\n",
275 (unsigned long long)n->value, n->access_type, n->lock_rule, n->update_rule);
276 for (size_t i = 0; i < n->child_len; i++) {
277 indent(sb, ind + 1);
278 if (n->child[i]->kind == TOBI_IR_FIELD_ELEM) {
279 tobi_sb_printf(sb, "%s @%llu:%llu", n->child[i]->name ? n->child[i]->name : "<field>",
280 (unsigned long long)n->child[i]->bit_off,
281 (unsigned long long)n->child[i]->bit_len);
282 if (n->child[i]->str) {
283 tobi_sb_printf(sb, " /* %s */", n->child[i]->str);
284 }
285 tobi_sb_add(sb, ";\n");
286 } else {
287 tobi_sb_add(sb, "bank_value = ");
288 expr(sb, n->child[i]);
289 tobi_sb_add(sb, ";\n");
290 }
291 }
292 indent(sb, ind);
293 tobi_sb_add(sb, "}\n");
294 break;
295 case TOBI_IR_STORE:
296 indent(sb, ind);
297 expr(sb, n);
298 tobi_sb_add(sb, ";\n");
299 break;
300 case TOBI_IR_EXPR:
301 case TOBI_IR_CALL:
302 indent(sb, ind);
303 if (expr_has_target(n)) {
304 expr_target_stmt(sb, n);
305 } else {
306 expr(sb, n);
307 }
308 tobi_sb_add(sb, ";\n");
309 break;
310 case TOBI_IR_RETURN:
311 indent(sb, ind);
312 tobi_sb_add(sb, "return");
313 if (n->child_len) {
314 tobi_sb_ch(sb, ' ');
315 expr(sb, n->child[0]);
316 }
317 tobi_sb_add(sb, ";\n");
318 break;
319 case TOBI_IR_IF:
320 indent(sb, ind);
321 tobi_sb_add(sb, "if (");
322 expr(sb, n->child_len ? n->child[0] : NULL);
323 tobi_sb_add(sb, ") {\n");
324 block(sb, n->child_len > 1 ? n->child[1] : NULL, ind + 1);
325 indent(sb, ind);
326 if (n->child_len > 2) {
327 tobi_sb_add(sb, "} else {\n");
328 block(sb, n->child[2], ind + 1);
329 indent(sb, ind);
330 }
331 tobi_sb_add(sb, "}\n");
332 break;
333 case TOBI_IR_WHILE:
334 indent(sb, ind);
335 tobi_sb_add(sb, "while (");
336 expr(sb, n->child_len ? n->child[0] : NULL);
337 tobi_sb_add(sb, ") {\n");
338 block(sb, n->child_len > 1 ? n->child[1] : NULL, ind + 1);
339 indent(sb, ind);
340 tobi_sb_add(sb, "}\n");
341 break;
342 case TOBI_IR_BREAK:
343 indent(sb, ind);
344 tobi_sb_add(sb, "break;\n");
345 break;
346 case TOBI_IR_CONTINUE:
347 indent(sb, ind);
348 tobi_sb_add(sb, "continue;\n");
349 break;
350 case TOBI_IR_UNKNOWN:
351 indent(sb, ind);
352 tobi_sb_printf(sb, "/* unknown opcode 0x%x at 0x%zx", n->raw_op, n->off);
353 if (n->str) {
354 tobi_sb_printf(sb, ": %s", n->str);
355 }
356 tobi_sb_add(sb, " */\n");
357 break;
358 case TOBI_IR_DIAG:
359 indent(sb, ind);
360 tobi_sb_printf(sb, "/* %s at 0x%zx */\n", n->str ? n->str : "diagnostic", n->off);
361 break;
362 case TOBI_IR_BLOCK:
363 block(sb, n, ind);
364 break;
365 default:
366 indent(sb, ind);
367 tobi_sb_printf(sb, "/* %s at 0x%zx */\n", tobi_ir_kind_name(n->kind), n->off);
368 break;
369 }
370}
371
372char *tobi_dc_emit(const tobi_ir *root, const tobi_diag_list *diag) {
373 tobi_sb sb;
374 tobi_sb_init(&sb);
375 if (diag) {
376 for (size_t i = 0; i < diag->len; i++) {
377 if (diag->items[i].level == TOBI_DIAG_WARN) {
378 tobi_sb_printf(&sb, "/* warning at 0x%zx: %s */\n", diag->items[i].off, diag->items[i].msg);
379 }
380 }
381 }
382 if (root) {
383 for (size_t i = 0; i < root->child_len; i++) {
384 stmt(&sb, root->child[i], 0);
385 }
386 }
387 return tobi_sb_take(&sb);
388}