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