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
14 kB 428 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 " --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}