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#define TOBI_VERSION "0.1.0"
15
16static void usage(FILE *f) {
17 fputs("usage: tobi [opts] <file> [file...]\n"
18 "\n"
19 "options:\n"
20 " --dis print opcode-level disassembly\n"
21 " --json print parsed ir as json\n"
22 " --dot print recovered control flow as Graphviz DOT\n"
23 " --raw print input/table metadata and aml offsets\n"
24 " --strict treat malformed or unsupported constructs as hard errors\n"
25 " --plain disable coloured diagnostics\n"
26 " --help print usage\n"
27 " --version print version\n", f);
28}
29
30static int read_file(const char *path, uint8_t **data, size_t *len) {
31 FILE *fp = fopen(path, "rb");
32 if (!fp) {
33 fprintf(stderr, "tobi: %s: %s\n", path, strerror(errno));
34 return 0;
35 }
36 if (fseek(fp, 0, SEEK_END) != 0) {
37 fprintf(stderr, "tobi: %s: seek failed: %s\n", path, strerror(errno));
38 fclose(fp);
39 return 0;
40 }
41 long n = ftell(fp);
42 if (n < 0) {
43 fprintf(stderr, "tobi: %s: size failed: %s\n", path, strerror(errno));
44 fclose(fp);
45 return 0;
46 }
47 rewind(fp);
48 *data = tobi_xmalloc((size_t)n ? (size_t)n : 1);
49 *len = (size_t)n;
50 if (*len && fread(*data, 1, *len, fp) != *len) {
51 fprintf(stderr, "tobi: %s: read failed%s%s\n", path, ferror(fp) ? ": " : "",
52 ferror(fp) ? strerror(errno) : "");
53 fclose(fp);
54 free(*data);
55 *data = NULL;
56 *len = 0;
57 return 0;
58 }
59 if (fclose(fp) != 0) {
60 fprintf(stderr, "tobi: %s: close failed: %s\n", path, strerror(errno));
61 free(*data);
62 *data = NULL;
63 *len = 0;
64 return 0;
65 }
66 return 1;
67}
68
69static void print_diags(const tobi_diag_list *dl, int plain) {
70 int colour = !plain && isatty(fileno(stderr));
71 for (size_t i = 0; i < dl->len; i++) {
72 const char *lvl = tobi_diag_level_name(dl->items[i].level);
73 const char *c = dl->items[i].level == TOBI_DIAG_ERROR ? "\033[31m" : "\033[33m";
74 if (dl->items[i].has_source && dl->items[i].source) {
75 fprintf(stderr, "%s:", dl->items[i].source);
76 }
77 if (colour) {
78 fprintf(stderr, "0x%zx: %s%s\033[0m: %s\n", dl->items[i].off, c, lvl, dl->items[i].msg);
79 } else {
80 fprintf(stderr, "0x%zx: %s: %s\n", dl->items[i].off, lvl, dl->items[i].msg);
81 }
82 }
83}
84
85static void set_source(tobi_input_meta *meta, const char *path) {
86 if (!path) {
87 return;
88 }
89 (void)snprintf(meta->source, sizeof(meta->source), "%s", path);
90}
91
92static void apply_source(tobi_parse_result *res, const char *path, size_t input_index) {
93 set_source(&res->meta, path);
94 if (input_index < res->input_len) {
95 set_source(&res->inputs[input_index], path);
96 }
97 tobi_diag_set_source(&res->diag, path, input_index);
98 tobi_ns_set_source(&res->ns, path, input_index);
99 if (res->root) {
100 tobi_ir_set_source_recursive(res->root, path, input_index);
101 }
102}
103
104static void print_raw_meta(const char *input, const tobi_parse_result *res) {
105 printf("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",
106 input, res->meta.source, res->meta.signature, res->meta.is_table ? "true" : "false",
107 res->meta.table_len, res->meta.aml_off, res->meta.aml_len,
108 (unsigned)res->meta.revision, (unsigned)res->meta.checksum_byte,
109 (unsigned)res->meta.checksum_sum, res->meta.checksum_valid ? "true" : "false",
110 res->meta.oem_id, res->meta.oem_table_id, res->meta.oem_revision,
111 res->meta.creator_id, res->meta.creator_revision, res->meta.header_hash,
112 res->meta.aml_hash, res->ns.len);
113}
114
115int main(int argc, char **argv) {
116 int mode_dis = 0;
117 int mode_json = 0;
118 int mode_dot = 0;
119 int mode_raw = 0;
120 int strict = 0;
121 int plain = 0;
122 const char **files = NULL;
123 size_t file_len = 0;
124 size_t file_cap = 0;
125 for (int i = 1; i < argc; i++) {
126 if (strcmp(argv[i], "--help") == 0) {
127 usage(stdout);
128 return 0;
129 }
130 if (strcmp(argv[i], "--version") == 0) {
131 puts("tobi " TOBI_VERSION);
132 return 0;
133 }
134 if (strcmp(argv[i], "--dis") == 0) {
135 mode_dis = 1;
136 } else if (strcmp(argv[i], "--json") == 0) {
137 mode_json = 1;
138 } else if (strcmp(argv[i], "--dot") == 0) {
139 mode_dot = 1;
140 } else if (strcmp(argv[i], "--raw") == 0) {
141 mode_raw = 1;
142 } else if (strcmp(argv[i], "--strict") == 0) {
143 strict = 1;
144 } else if (strcmp(argv[i], "--plain") == 0) {
145 plain = 1;
146 } else if (argv[i][0] == '-') {
147 fprintf(stderr, "tobi: unknown option %s\n", argv[i]);
148 usage(stderr);
149 return 2;
150 } else {
151 if (file_len == file_cap) {
152 file_cap = tobi_xgrow_cap(file_cap, file_len + 1u, 4u);
153 files = tobi_xrealloc(files, tobi_xmul_size(file_cap, sizeof(files[0])));
154 }
155 files[file_len++] = argv[i];
156 }
157 }
158 if (file_len == 0) {
159 usage(stderr);
160 free(files);
161 return 2;
162 }
163 if ((mode_dis + mode_json + mode_dot + mode_raw) > 1) {
164 fprintf(stderr, "tobi: choose only one output mode\n");
165 free(files);
166 return 2;
167 }
168 uint8_t **data = tobi_xcalloc(file_len, sizeof(data[0]));
169 size_t *lens = tobi_xcalloc(file_len, sizeof(lens[0]));
170 for (size_t i = 0; i < file_len; i++) {
171 if (!read_file(files[i], &data[i], &lens[i])) {
172 for (size_t j = 0; j < file_len; j++) {
173 free(data[j]);
174 }
175 free(lens);
176 free(data);
177 free(files);
178 return 1;
179 }
180 }
181 if (mode_dis || mode_raw) {
182 int fail_any = 0;
183 for (size_t i = 0; i < file_len; i++) {
184 tobi_parse_result one;
185 int ok = tobi_parse(data[i], lens[i], strict, &one);
186 apply_source(&one, files[i], 0);
187 if (mode_raw) {
188 print_raw_meta(files[i], &one);
189 } else {
190 if (file_len > 1) {
191 printf("== %s ==\n", files[i]);
192 }
193 char *s = tobi_da_emit(data[i], lens[i], &one.meta);
194 fputs(s, stdout);
195 free(s);
196 }
197 print_diags(&one.diag, plain);
198 if ((strict && tobi_diag_has_error(&one.diag)) || !ok) {
199 fail_any = 1;
200 }
201 tobi_parse_result_free(&one);
202 }
203 for (size_t i = 0; i < file_len; i++) {
204 free(data[i]);
205 }
206 free(lens);
207 free(data);
208 free(files);
209 return fail_any ? 1 : 0;
210 }
211 tobi_parse_result res;
212 int ok = 0;
213 if (file_len == 1) {
214 ok = tobi_parse(data[0], lens[0], strict, &res);
215 apply_source(&res, files[0], 0);
216 } else {
217 tobi_parse_input *inputs = tobi_xcalloc(file_len, sizeof(inputs[0]));
218 for (size_t i = 0; i < file_len; i++) {
219 inputs[i].data = data[i];
220 inputs[i].len = lens[i];
221 inputs[i].name = files[i];
222 }
223 ok = tobi_parse_multi(inputs, file_len, strict, &res);
224 free(inputs);
225 }
226 if (!res.root) {
227 print_diags(&res.diag, plain);
228 tobi_parse_result_free(&res);
229 for (size_t i = 0; i < file_len; i++) {
230 free(data[i]);
231 }
232 free(lens);
233 free(data);
234 free(files);
235 return 1;
236 }
237 (void)tobi_cf_recover(res.root, &res.diag);
238 if (mode_json) {
239 char *s = tobi_js_emit(&res);
240 fputs(s, stdout);
241 free(s);
242 } else if (mode_dot) {
243 char *s = tobi_cf_dot(res.root);
244 fputs(s, stdout);
245 free(s);
246 } else {
247 char *s = tobi_dc_emit(res.root, &res.diag);
248 fputs(s, stdout);
249 free(s);
250 }
251 print_diags(&res.diag, plain);
252 int fail = (strict && tobi_diag_has_error(&res.diag)) || !ok;
253 tobi_parse_result_free(&res);
254 for (size_t i = 0; i < file_len; i++) {
255 free(data[i]);
256 }
257 free(lens);
258 free(data);
259 free(files);
260 return fail ? 1 : 0;
261}