ACPI AML decompiler w/ CFG recovery and structured pseudocode
1#include "t.h"
2
3#include "cf.h"
4#include "da.h"
5#include "dc.h"
6#include "js.h"
7#include "p.h"
8
9#include <dirent.h>
10#include <errno.h>
11#include <sys/stat.h>
12#include <unistd.h>
13
14#ifndef TOBI_SRC_DIR
15#define TOBI_SRC_DIR "."
16#endif
17
18static int read_blob(const char *path, uint8_t **data, size_t *len) {
19 FILE *fp = fopen(path, "rb");
20 if (!fp) {
21 fprintf(stderr, "open %s: %s\n", path, strerror(errno));
22 return 0;
23 }
24 if (fseek(fp, 0, SEEK_END) != 0) {
25 fclose(fp);
26 return 0;
27 }
28 long n = ftell(fp);
29 if (n < 0) {
30 fclose(fp);
31 return 0;
32 }
33 rewind(fp);
34 *len = (size_t)n;
35 *data = malloc(*len ? *len : 1u);
36 if (!*data) {
37 fclose(fp);
38 return 0;
39 }
40 if (*len && fread(*data, 1, *len, fp) != *len) {
41 free(*data);
42 fclose(fp);
43 return 0;
44 }
45 fclose(fp);
46 return 1;
47}
48
49static int write_blob(const char *path, const uint8_t *data, size_t len) {
50 FILE *fp = fopen(path, "wb");
51 if (!fp) {
52 fprintf(stderr, "open %s: %s\n", path, strerror(errno));
53 return 0;
54 }
55 if (len && fwrite(data, 1, len, fp) != len) {
56 fclose(fp);
57 return 0;
58 }
59 return fclose(fp) == 0;
60}
61
62static int stable_emit_blob(const char *label, const uint8_t *data, size_t len, int strict_should_pass) {
63 int ok = 0;
64 tobi_parse_result a;
65 tobi_parse_result b;
66 tobi_parse_result strict;
67 (void)tobi_parse(data, len, 0, &a);
68 (void)tobi_parse(data, len, 0, &b);
69 int strict_ok = tobi_parse(data, len, 1, &strict);
70 int expected_strict_ok = strict_should_pass && a.diag.len == 0;
71 (void)tobi_cf_recover(a.root, &a.diag);
72 (void)tobi_cf_recover(b.root, &b.diag);
73 char *aj = tobi_js_emit(&a);
74 char *bj = tobi_js_emit(&b);
75 char *ad = tobi_dc_emit(a.root, &a.diag);
76 char *bd = tobi_dc_emit(b.root, &b.diag);
77 char *aa = tobi_da_emit(data, len, &a.meta);
78 char *ba = tobi_da_emit(data, len, &b.meta);
79 ok = aj != NULL && bj != NULL && ad != NULL && bd != NULL && aa != NULL && ba != NULL &&
80 strcmp(aj, bj) == 0 && aj[0] == '{' && strstr(aj, "\"meta\"") != NULL &&
81 strstr(aj, "\"ir\"") != NULL && strstr(aj, ",}") == NULL && strstr(aj, ",]") == NULL &&
82 ad[0] != '\0' && aa[0] != '\0' && strcmp(ad, bd) == 0 && strcmp(aa, ba) == 0;
83 if (expected_strict_ok) {
84 ok = ok && strict_ok && !tobi_diag_has_error(&strict.diag);
85 } else {
86 ok = ok && (!strict_ok || tobi_diag_has_error(&strict.diag));
87 }
88 if (!ok) {
89 fprintf(stderr, "corpus regression failed for %s\n", label);
90 }
91 free(aa);
92 free(ba);
93 free(ad);
94 free(bd);
95 free(aj);
96 free(bj);
97 tobi_parse_result_free(&a);
98 tobi_parse_result_free(&b);
99 tobi_parse_result_free(&strict);
100 return ok ? 0 : 1;
101}
102
103static int stable_emit(const char *path, int strict_should_pass) {
104 uint8_t *data = NULL;
105 size_t len = 0;
106 int rc = 1;
107 if (!read_blob(path, &data, &len)) {
108 return 1;
109 }
110 rc = stable_emit_blob(path, data, len, strict_should_pass);
111 if (rc == 0 && len > 1) {
112 size_t cuts[] = {1u, len / 2u, len - 1u};
113 for (size_t i = 0; i < sizeof(cuts) / sizeof(cuts[0]); i++) {
114 if (cuts[i] == 0 || cuts[i] >= len) {
115 continue;
116 }
117 char label[900];
118 snprintf(label, sizeof(label), "%s truncated:%zu", path, cuts[i]);
119 if (stable_emit_blob(label, data, cuts[i], 0) != 0) {
120 rc = 1;
121 break;
122 }
123 }
124 }
125 free(data);
126 return rc;
127}
128
129static int has_aml_suffix(const char *name) {
130 size_t len = strlen(name);
131 return len >= 4u && strcmp(name + len - 4u, ".aml") == 0;
132}
133
134static int walk_dir_path(const char *dir_path, size_t *count, size_t *bad_count) {
135 DIR *dir = opendir(dir_path);
136 T_CHECK(dir != NULL);
137 struct dirent *de = NULL;
138 while ((de = readdir(dir)) != NULL) {
139 if (de->d_name[0] == '.') {
140 continue;
141 }
142 char path[1024];
143 int n = snprintf(path, sizeof(path), "%s/%s", dir_path, de->d_name);
144 T_CHECK(n > 0 && (size_t)n < sizeof(path));
145 struct stat st;
146 T_CHECK(stat(path, &st) == 0);
147 if (S_ISDIR(st.st_mode)) {
148 T_CHECK(walk_dir_path(path, count, bad_count) == 0);
149 } else if (S_ISREG(st.st_mode) && has_aml_suffix(de->d_name)) {
150 int bad = strstr(de->d_name, "bad") != NULL;
151 T_CHECK(stable_emit(path, !bad) == 0);
152 (*count)++;
153 if (bad) {
154 (*bad_count)++;
155 }
156 }
157 }
158 T_CHECK(closedir(dir) == 0);
159 return 0;
160}
161
162static int walk_dir(const char *rel, size_t *count, size_t *bad_count) {
163 char dir_path[512];
164 int n = snprintf(dir_path, sizeof(dir_path), "%s/%s", TOBI_SRC_DIR, rel);
165 T_CHECK(n > 0 && (size_t)n < sizeof(dir_path));
166 return walk_dir_path(dir_path, count, bad_count);
167}
168
169static int walk_optional_external_dir(size_t *count, size_t *bad_count) {
170 const char *dir = getenv("TOBI_AML_CORPUS");
171 if (!dir || !dir[0]) {
172 dir = getenv("TOBI_CORPUS");
173 }
174 if (!dir || !dir[0]) {
175 return 0;
176 }
177 size_t before = *count;
178 T_CHECK(walk_dir_path(dir, count, bad_count) == 0);
179 T_CHECK(*count > before);
180 return 0;
181}
182
183static int exercise_external_discovery(void) {
184 char dir[] = "/tmp/tobi-corpus-XXXXXX";
185 T_CHECK(mkdtemp(dir) != NULL);
186 char nested[256];
187 int n = snprintf(nested, sizeof(nested), "%s/nested", dir);
188 T_CHECK(n > 0 && (size_t)n < sizeof(nested));
189 T_CHECK(mkdir(nested, 0700) == 0);
190 char path[256];
191 n = snprintf(path, sizeof(path), "%s/m0.aml", nested);
192 T_CHECK(n > 0 && (size_t)n < sizeof(path));
193 uint8_t m0[] = {0x14,0x0c,'M','T','H','0',0x01,0x70,0x0a,0x2a,0x60,0xa4,0x60};
194 T_CHECK(write_blob(path, m0, sizeof(m0)));
195 size_t count = 0;
196 size_t bad_count = 0;
197 T_CHECK(walk_dir_path(dir, &count, &bad_count) == 0);
198 T_CHECK(count == 1);
199 T_CHECK(bad_count == 0);
200 T_CHECK(unlink(path) == 0);
201 T_CHECK(rmdir(nested) == 0);
202 T_CHECK(rmdir(dir) == 0);
203 return 0;
204}
205
206int t_corpus(void) {
207 size_t count = 0;
208 size_t bad_count = 0;
209 T_CHECK(exercise_external_discovery() == 0);
210 T_CHECK(walk_dir("sam", &count, &bad_count) == 0);
211 T_CHECK(walk_dir("fuzz/seed", &count, &bad_count) == 0);
212 T_CHECK(walk_optional_external_dir(&count, &bad_count) == 0);
213 T_CHECK(count >= 9);
214 T_CHECK(bad_count >= 2);
215 return 0;
216}