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