ACPI AML decompiler w/ CFG recovery and structured pseudocode
1#include "cf.h"
2
3#include "str.h"
4
5#include <stdio.h>
6#include <string.h>
7
8static int has_cfg_note(const tobi_ir *n, size_t off) {
9 for (size_t i = 0; i < n->child_len; i++) {
10 const tobi_ir *c = n->child[i];
11 if (c->kind == TOBI_IR_DIAG && c->off == off && c->str && strstr(c->str, "cfg fallback") != NULL) {
12 return 1;
13 }
14 }
15 return 0;
16}
17
18static int unclear_control(const tobi_ir *n) {
19 if (n->kind != TOBI_IR_UNKNOWN) {
20 return 0;
21 }
22 return n->raw_op == 0xa1u || n->raw_op == 0xccu || n->raw_op == 0x5b31u || n->raw_op == 0x5b32u || n->raw_op == 0u;
23}
24
25static void add_cfg_note(tobi_ir *parent, const tobi_ir *unknown) {
26 if (!parent || !unknown || has_cfg_note(parent, unknown->off)) {
27 return;
28 }
29 char msg[128];
30 (void)snprintf(msg, sizeof(msg), "cfg fallback label L_%04zx for unclear opcode 0x%x", unknown->off, unknown->raw_op);
31 tobi_ir *note = tobi_ir_new(TOBI_IR_DIAG, unknown->off, unknown->len);
32 note->raw_op = unknown->raw_op;
33 tobi_ir_set_name(note, "cfg");
34 tobi_ir_set_str(note, msg);
35 tobi_ir_add(parent, note);
36}
37
38static void walk(tobi_ir *n, tobi_diag_list *diag, unsigned loop_depth) {
39 if (!n) {
40 return;
41 }
42 if (n->kind == TOBI_IR_IF && n->child_len < 2) {
43 tobi_diag_add(diag, TOBI_DIAG_ERROR, n->off, "if node lacks predicate or body");
44 }
45 if (n->kind == TOBI_IR_WHILE && n->child_len < 2) {
46 tobi_diag_add(diag, TOBI_DIAG_ERROR, n->off, "while node lacks predicate or body");
47 }
48 if ((n->kind == TOBI_IR_BREAK || n->kind == TOBI_IR_CONTINUE) && loop_depth == 0) {
49 tobi_diag_add(diag, TOBI_DIAG_WARN, n->off, "%s outside loop kept as explicit statement",
50 n->kind == TOBI_IR_BREAK ? "break" : "continue");
51 }
52 size_t original_children = n->child_len;
53 unsigned child_loop_depth = loop_depth + (n->kind == TOBI_IR_WHILE ? 1u : 0u);
54 for (size_t i = 0; i < original_children; i++) {
55 if ((n->kind == TOBI_IR_BLOCK || n->kind == TOBI_IR_ROOT) && unclear_control(n->child[i])) {
56 add_cfg_note(n, n->child[i]);
57 }
58 walk(n->child[i], diag, child_loop_depth);
59 }
60}
61
62int tobi_cf_recover(tobi_ir *root, tobi_diag_list *diag) {
63 walk(root, diag, 0);
64 return !tobi_diag_has_error(diag);
65}
66
67static void dot_escape(tobi_sb *sb, const char *s) {
68 if (!s) {
69 return;
70 }
71 for (const char *p = s; *p; p++) {
72 if (*p == '"' || *p == '\\') {
73 tobi_sb_ch(sb, '\\');
74 }
75 if (*p == '\n' || *p == '\r') {
76 tobi_sb_ch(sb, ' ');
77 } else {
78 tobi_sb_ch(sb, *p);
79 }
80 }
81}
82
83static size_t dot_stmt(tobi_sb *sb, const tobi_ir *n, size_t *next);
84
85static void dot_edge(tobi_sb *sb, size_t from, size_t to, const char *label) {
86 tobi_sb_printf(sb, " n%zu -> n%zu", from, to);
87 if (label && label[0]) {
88 tobi_sb_add(sb, " [label=\"");
89 dot_escape(sb, label);
90 tobi_sb_add(sb, "\"]");
91 }
92 tobi_sb_add(sb, ";\n");
93}
94
95static void dot_block(tobi_sb *sb, const tobi_ir *b, size_t owner, const char *edge_label, size_t *next) {
96 if (!b) {
97 return;
98 }
99 size_t prev = owner;
100 for (size_t i = 0; i < b->child_len; i++) {
101 size_t id = dot_stmt(sb, b->child[i], next);
102 dot_edge(sb, prev, id, i == 0 ? edge_label : "next");
103 prev = id;
104 }
105}
106
107static size_t dot_stmt(tobi_sb *sb, const tobi_ir *n, size_t *next) {
108 size_t id = (*next)++;
109 tobi_sb_printf(sb, " n%zu [label=\"%s", id, tobi_ir_kind_name(n->kind));
110 if (n->name && n->name[0]) {
111 tobi_sb_add(sb, "\\n");
112 dot_escape(sb, n->name);
113 } else if (n->str && n->str[0]) {
114 tobi_sb_add(sb, "\\n");
115 dot_escape(sb, n->str);
116 }
117 tobi_sb_printf(sb, "\\noff=0x%zx", n->off);
118 if (n->kind == TOBI_IR_UNKNOWN) {
119 tobi_sb_printf(sb, "\\nop=0x%x", n->raw_op);
120 }
121 tobi_sb_add(sb, "\"];\n");
122 if (n->kind == TOBI_IR_METHOD && n->child_len > 0) {
123 dot_block(sb, n->child[0], id, "entry", next);
124 } else if (n->kind == TOBI_IR_SCOPE || n->kind == TOBI_IR_DEVICE || n->kind == TOBI_IR_PROCESSOR) {
125 if (n->child_len > 0) {
126 dot_block(sb, n->child[0], id, "body", next);
127 }
128 } else if (n->kind == TOBI_IR_IF) {
129 if (n->child_len > 1) {
130 dot_block(sb, n->child[1], id, "then", next);
131 }
132 if (n->child_len > 2) {
133 dot_block(sb, n->child[2], id, "else", next);
134 }
135 } else if (n->kind == TOBI_IR_WHILE) {
136 if (n->child_len > 1) {
137 dot_block(sb, n->child[1], id, "while-body", next);
138 tobi_sb_printf(sb, " n%zu -> n%zu [label=\"back\"];\n", (*next) - 1u, id);
139 }
140 }
141 return id;
142}
143
144char *tobi_cf_dot(const tobi_ir *root) {
145 tobi_sb sb;
146 tobi_sb_init(&sb);
147 tobi_sb_add(&sb, "digraph tobi_cfg {\n");
148 tobi_sb_add(&sb, " graph [rankdir=TB];\n");
149 tobi_sb_add(&sb, " node [shape=box,fontname=\"monospace\"];\n");
150 if (root) {
151 size_t next = 0;
152 size_t root_id = dot_stmt(&sb, root, &next);
153 size_t prev = root_id;
154 for (size_t i = 0; i < root->child_len; i++) {
155 size_t id = dot_stmt(&sb, root->child[i], &next);
156 dot_edge(&sb, prev, id, i == 0 ? "entry" : "next");
157 prev = id;
158 }
159 }
160 tobi_sb_add(&sb, "}\n");
161 return tobi_sb_take(&sb);
162}