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