ACPI AML decompiler w/ CFG recovery and structured pseudocode
29

Configure Feed

Select the types of activity you want to include in your feed.

tobi / src / main.c
13 kB 398 lines
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}