ACPI AML decompiler w/ CFG recovery and structured pseudocode
1#include "cf.h"
2#include "da.h"
3#include "dc.h"
4#include "js.h"
5#include "mem.h"
6#include "p.h"
7
8#include <errno.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13
14#ifndef TOBI_VERSION
15#define TOBI_VERSION "0.0.0"
16#endif
17
18static void usage(FILE *f) {
19 fputs("usage: tobi [opts] <file> [file...]\n"
20 "\n"
21 "options:\n"
22 " --dis print opcode-level disassembly\n"
23 " --json print parsed ir as json\n"
24 " --dot print recovered control flow as Graphviz DOT\n"
25 " --graph K print DOT graph: cfg, ast, namespace\n"
26 " --graph-labels K\n"
27 " set graph labels: short, full, none\n"
28 " --raw print input/table metadata and aml offsets\n"
29 " -o FILE write primary output to FILE instead of stdout\n"
30 " --strict treat malformed or unsupported constructs as hard errors\n"
31 " --plain disable coloured diagnostics\n"
32 " --help print usage\n"
33 " --version print version\n"
34 "\n"
35 "Use '-' as an input path to read AML from stdin.\n", f);
36}
37
38static int append_file(const char ***files, size_t *file_len, size_t *file_cap, const char *path) {
39 if (*file_len == *file_cap) {
40 *file_cap = tobi_xgrow_cap(*file_cap, *file_len + 1u, 4u);
41 *files = tobi_xrealloc((void *)*files, tobi_xmul_size(*file_cap, sizeof((*files)[0])));
42 }
43 (*files)[(*file_len)++] = path;
44 return 1;
45}
46
47static const char *source_label(const char *path) {
48 return path && strcmp(path, "-") == 0 ? "<stdin>" : path;
49}
50
51static int read_stream(FILE *fp, const char *label, int close_stream, uint8_t **data, size_t *len) {
52 size_t cap = 4096;
53 size_t used = 0;
54 uint8_t *buf = tobi_xmalloc(cap);
55 for (;;) {
56 if (used == cap) {
57 cap = tobi_xgrow_cap(cap, tobi_xadd_size(used, 1u), 4096u);
58 buf = tobi_xrealloc(buf, cap);
59 }
60 size_t room = cap - used;
61 size_t got = fread(buf + used, 1, room, fp);
62 if (got > room) {
63 fprintf(stderr, "tobi: %s: read exceeded buffer\n", label);
64 free(buf);
65 if (close_stream) {
66 fclose(fp);
67 }
68 return 0;
69 }
70 used += got;
71 if (got < room) {
72 if (ferror(fp)) {
73 fprintf(stderr, "tobi: %s: read failed: %s\n", label, strerror(errno));
74 free(buf);
75 if (close_stream) {
76 fclose(fp);
77 }
78 return 0;
79 }
80 break;
81 }
82 }
83 if (close_stream && fclose(fp) != 0) {
84 fprintf(stderr, "tobi: %s: close failed: %s\n", label, strerror(errno));
85 free(buf);
86 return 0;
87 }
88 *data = buf;
89 *len = used;
90 return 1;
91}
92
93static int read_file(const char *path, uint8_t **data, size_t *len) {
94 if (strcmp(path, "-") == 0) {
95 return read_stream(stdin, "<stdin>", 0, data, len);
96 }
97 FILE *fp = fopen(path, "rb");
98 if (!fp) {
99 fprintf(stderr, "tobi: %s: %s\n", path, strerror(errno));
100 return 0;
101 }
102 if (fseek(fp, 0, SEEK_END) != 0) {
103 fprintf(stderr, "tobi: %s: seek failed: %s\n", path, strerror(errno));
104 fclose(fp);
105 return 0;
106 }
107 long n = ftell(fp);
108 if (n < 0) {
109 fprintf(stderr, "tobi: %s: size failed: %s\n", path, strerror(errno));
110 fclose(fp);
111 return 0;
112 }
113 rewind(fp);
114 *data = tobi_xmalloc((size_t)n ? (size_t)n : 1);
115 *len = (size_t)n;
116 if (*len && fread(*data, 1, *len, fp) != *len) {
117 fprintf(stderr, "tobi: %s: read failed%s%s\n", path, ferror(fp) ? ": " : "",
118 ferror(fp) ? strerror(errno) : "");
119 fclose(fp);
120 free(*data);
121 *data = NULL;
122 *len = 0;
123 return 0;
124 }
125 if (fclose(fp) != 0) {
126 fprintf(stderr, "tobi: %s: close failed: %s\n", path, strerror(errno));
127 free(*data);
128 *data = NULL;
129 *len = 0;
130 return 0;
131 }
132 return 1;
133}
134
135static FILE *open_output(const char *path) {
136 if (!path || strcmp(path, "-") == 0) {
137 return stdout;
138 }
139 FILE *fp = fopen(path, "wb");
140 if (!fp) {
141 fprintf(stderr, "tobi: %s: %s\n", path, strerror(errno));
142 return NULL;
143 }
144 return fp;
145}
146
147static int close_output(FILE *fp, const char *path) {
148 if (!fp || fp == stdout) {
149 return 1;
150 }
151 if (fclose(fp) != 0) {
152 fprintf(stderr, "tobi: %s: close failed: %s\n", path, strerror(errno));
153 return 0;
154 }
155 return 1;
156}
157
158static int parse_graph_kind(const char *s, tobi_graph_kind *kind) {
159 if (strcmp(s, "cfg") == 0) {
160 *kind = TOBI_GRAPH_CFG;
161 return 1;
162 }
163 if (strcmp(s, "ast") == 0) {
164 *kind = TOBI_GRAPH_AST;
165 return 1;
166 }
167 if (strcmp(s, "namespace") == 0 || strcmp(s, "ns") == 0) {
168 *kind = TOBI_GRAPH_NAMESPACE;
169 return 1;
170 }
171 return 0;
172}
173
174static int parse_graph_labels(const char *s, tobi_graph_labels *labels) {
175 if (strcmp(s, "short") == 0) {
176 *labels = TOBI_GRAPH_LABEL_SHORT;
177 return 1;
178 }
179 if (strcmp(s, "full") == 0) {
180 *labels = TOBI_GRAPH_LABEL_FULL;
181 return 1;
182 }
183 if (strcmp(s, "none") == 0) {
184 *labels = TOBI_GRAPH_LABEL_NONE;
185 return 1;
186 }
187 return 0;
188}
189
190static void print_diags(const tobi_diag_list *dl, int plain) {
191 int colour = !plain && isatty(fileno(stderr));
192 for (size_t i = 0; i < dl->len; i++) {
193 const char *lvl = tobi_diag_level_name(dl->items[i].level);
194 const char *c = dl->items[i].level == TOBI_DIAG_ERROR ? "\033[31m" : "\033[33m";
195 if (dl->items[i].has_source && dl->items[i].source) {
196 fprintf(stderr, "%s:", dl->items[i].source);
197 }
198 if (colour) {
199 fprintf(stderr, "0x%zx: %s%s\033[0m: %s\n", dl->items[i].off, c, lvl, dl->items[i].msg);
200 } else {
201 fprintf(stderr, "0x%zx: %s: %s\n", dl->items[i].off, lvl, dl->items[i].msg);
202 }
203 }
204}
205
206static void set_source(tobi_input_meta *meta, const char *path) {
207 if (!path) {
208 return;
209 }
210 free(meta->source);
211 meta->source = tobi_xstrdup(path);
212}
213
214static void apply_source(tobi_parse_result *res, const char *path, size_t input_index) {
215 set_source(&res->meta, path);
216 if (input_index < res->input_len) {
217 set_source(&res->inputs[input_index], path);
218 }
219 tobi_diag_set_source(&res->diag, path, input_index);
220 tobi_ns_set_source(&res->ns, path, input_index);
221 if (res->root) {
222 tobi_ir_set_source_recursive(res->root, path, input_index);
223 }
224}
225
226static void print_raw_meta(FILE *out, const char *input, const tobi_parse_result *res) {
227 fprintf(out, "input=%s\nsource=%s\nformat=%s\nis_table=%s\ntable_length=%zu\naml_offset=%zu\naml_length=%zu\nrevision=%u\nchecksum_byte=0x%02x\nchecksum_sum=0x%02x\nchecksum_valid=%s\noem_id=%s\noem_table_id=%s\noem_revision=%u\ncreator_id=%s\ncreator_revision=%u\nheader_hash=0x%08x\naml_hash=0x%08x\nnamespace_objects=%zu\n",
228 input, res->meta.source ? res->meta.source : "raw", res->meta.signature,
229 res->meta.is_table ? "true" : "false", res->meta.table_len,
230 res->meta.aml_off, res->meta.aml_len, (unsigned)res->meta.revision,
231 (unsigned)res->meta.checksum_byte, (unsigned)res->meta.checksum_sum,
232 res->meta.checksum_valid ? "true" : "false", res->meta.oem_id,
233 res->meta.oem_table_id, res->meta.oem_revision, res->meta.creator_id,
234 res->meta.creator_revision, res->meta.header_hash, res->meta.aml_hash, res->ns.len);
235}
236
237static void free_inputs(uint8_t **data, size_t *lens, size_t file_len, const char **files) {
238 (void)lens;
239 for (size_t i = 0; i < file_len; i++) {
240 free(data[i]);
241 }
242 free(lens);
243 free(data);
244 free(files);
245}
246
247int main(int argc, char **argv) {
248 int mode_dis = 0;
249 int mode_json = 0;
250 int mode_graph = 0;
251 int mode_raw = 0;
252 int strict = 0;
253 int plain = 0;
254 int stdin_count = 0;
255 const char *output_path = NULL;
256 tobi_graph_kind graph_kind = TOBI_GRAPH_CFG;
257 tobi_graph_labels graph_labels = TOBI_GRAPH_LABEL_SHORT;
258 const char **files = NULL;
259 size_t file_len = 0;
260 size_t file_cap = 0;
261 for (int i = 1; i < argc; i++) {
262 if (strcmp(argv[i], "--help") == 0) {
263 usage(stdout);
264 return 0;
265 }
266 if (strcmp(argv[i], "--version") == 0) {
267 puts("tobi " TOBI_VERSION);
268 return 0;
269 }
270 if (strcmp(argv[i], "--dis") == 0) {
271 mode_dis = 1;
272 } else if (strcmp(argv[i], "--json") == 0) {
273 mode_json = 1;
274 } else if (strcmp(argv[i], "--dot") == 0) {
275 mode_graph = 1;
276 graph_kind = TOBI_GRAPH_CFG;
277 } else if (strcmp(argv[i], "--graph") == 0) {
278 if (i + 1 >= argc) {
279 fprintf(stderr, "tobi: --graph requires cfg, ast, or namespace\n");
280 free(files);
281 return 2;
282 }
283 if (!parse_graph_kind(argv[++i], &graph_kind)) {
284 fprintf(stderr, "tobi: unknown graph kind %s\n", argv[i]);
285 free(files);
286 return 2;
287 }
288 mode_graph = 1;
289 } else if (strcmp(argv[i], "--graph-labels") == 0) {
290 if (i + 1 >= argc) {
291 fprintf(stderr, "tobi: --graph-labels requires short, full, or none\n");
292 free(files);
293 return 2;
294 }
295 if (!parse_graph_labels(argv[++i], &graph_labels)) {
296 fprintf(stderr, "tobi: unknown graph label mode %s\n", argv[i]);
297 free(files);
298 return 2;
299 }
300 } else if (strcmp(argv[i], "--raw") == 0) {
301 mode_raw = 1;
302 } else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) {
303 if (i + 1 >= argc) {
304 fprintf(stderr, "tobi: %s requires a file\n", argv[i]);
305 free(files);
306 return 2;
307 }
308 output_path = argv[++i];
309 } else if (strcmp(argv[i], "--strict") == 0) {
310 strict = 1;
311 } else if (strcmp(argv[i], "--plain") == 0) {
312 plain = 1;
313 } else if (strcmp(argv[i], "-") == 0) {
314 stdin_count++;
315 (void)append_file(&files, &file_len, &file_cap, argv[i]);
316 } else if (argv[i][0] == '-') {
317 fprintf(stderr, "tobi: unknown option %s\n", argv[i]);
318 usage(stderr);
319 return 2;
320 } else {
321 (void)append_file(&files, &file_len, &file_cap, argv[i]);
322 }
323 }
324 if (file_len == 0) {
325 usage(stderr);
326 free(files);
327 return 2;
328 }
329 if (stdin_count > 1) {
330 fprintf(stderr, "tobi: stdin can only be used once\n");
331 free(files);
332 return 2;
333 }
334 if ((mode_dis + mode_json + mode_graph + mode_raw) > 1) {
335 fprintf(stderr, "tobi: choose only one output mode\n");
336 free(files);
337 return 2;
338 }
339 FILE *outfp = open_output(output_path);
340 if (!outfp) {
341 free(files);
342 return 1;
343 }
344 uint8_t **data = tobi_xcalloc(file_len, sizeof(data[0]));
345 size_t *lens = tobi_xcalloc(file_len, sizeof(lens[0]));
346 for (size_t i = 0; i < file_len; i++) {
347 if (!read_file(files[i], &data[i], &lens[i])) {
348 (void)close_output(outfp, output_path);
349 free_inputs(data, lens, file_len, files);
350 return 1;
351 }
352 }
353 if (mode_dis || mode_raw) {
354 int fail_any = 0;
355 for (size_t i = 0; i < file_len; i++) {
356 const char *label = source_label(files[i]);
357 tobi_parse_result one;
358 int ok = tobi_parse(data[i], lens[i], strict, &one);
359 apply_source(&one, label, 0);
360 if (mode_raw) {
361 print_raw_meta(outfp, label, &one);
362 } else {
363 if (file_len > 1) {
364 fprintf(outfp, "== %s ==\n", label);
365 }
366 char *s = tobi_da_emit(data[i], lens[i], &one.meta);
367 fputs(s, outfp);
368 free(s);
369 }
370 print_diags(&one.diag, plain);
371 if ((strict && tobi_diag_has_error(&one.diag)) || !ok) {
372 fail_any = 1;
373 }
374 tobi_parse_result_free(&one);
375 }
376 int output_ok = close_output(outfp, output_path);
377 free_inputs(data, lens, file_len, files);
378 if (!output_ok) {
379 return 1;
380 }
381 return fail_any ? 1 : 0;
382 }
383 tobi_parse_result res;
384 int ok = 0;
385 if (file_len == 1) {
386 ok = tobi_parse(data[0], lens[0], strict, &res);
387 apply_source(&res, source_label(files[0]), 0);
388 } else {
389 tobi_parse_input *inputs = tobi_xcalloc(file_len, sizeof(inputs[0]));
390 for (size_t i = 0; i < file_len; i++) {
391 inputs[i].data = data[i];
392 inputs[i].len = lens[i];
393 inputs[i].name = source_label(files[i]);
394 }
395 ok = tobi_parse_multi(inputs, file_len, strict, &res);
396 free(inputs);
397 }
398 if (!res.root) {
399 print_diags(&res.diag, plain);
400 tobi_parse_result_free(&res);
401 (void)close_output(outfp, output_path);
402 free_inputs(data, lens, file_len, files);
403 return 1;
404 }
405 (void)tobi_cf_recover(res.root, &res.diag);
406 if (mode_json) {
407 char *s = tobi_js_emit(&res);
408 fputs(s, outfp);
409 free(s);
410 } else if (mode_graph) {
411 char *s = tobi_graph_dot_labels(res.root, &res.ns, graph_kind, graph_labels);
412 fputs(s, outfp);
413 free(s);
414 } else {
415 char *s = tobi_dc_emit(res.root, &res.diag);
416 fputs(s, outfp);
417 free(s);
418 }
419 print_diags(&res.diag, plain);
420 int fail = (strict && tobi_diag_has_error(&res.diag)) || !ok;
421 tobi_parse_result_free(&res);
422 int output_ok = close_output(outfp, output_path);
423 free_inputs(data, lens, file_len, files);
424 if (!output_ok) {
425 return 1;
426 }
427 return fail ? 1 : 0;
428}