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