#include "p.h" #include "mem.h" #include "nm.h" #include "op.h" #include "rd.h" #include "str.h" #include #include #include #include #define MAX_DEPTH 64 #define MAX_TERMS 200000u typedef struct parser { tobi_diag_list *diag; int strict; char *scope; tobi_ns *ns; const char *source; size_t terms; size_t input_index; int has_source; } parser; static void diag(parser *p, tobi_diag_level lvl, size_t off, const char *fmt, ...) { va_list ap; va_start(ap, fmt); va_list cp; va_copy(cp, ap); int n = vsnprintf(NULL, 0, fmt, cp); va_end(cp); if (n < 0) { n = 0; } char *msg = tobi_xmalloc((size_t)n + 1); (void)vsnprintf(msg, (size_t)n + 1, fmt, ap); va_end(ap); tobi_diag_add_source(p->diag, lvl, off, p->source, p->input_index, p->has_source, "%s", msg); free(msg); } static uint32_t le32_at(const uint8_t *d) { return (uint32_t)d[0] | ((uint32_t)d[1] << 8) | ((uint32_t)d[2] << 16) | ((uint32_t)d[3] << 24); } static void copy_trim(char *dst, size_t dst_len, const uint8_t *src, size_t src_len) { size_t n = src_len; while (n > 0 && src[n - 1u] == ' ') { n--; } if (n >= dst_len) { n = dst_len - 1u; } memcpy(dst, src, n); dst[n] = '\0'; } static uint32_t hash32(const uint8_t *data, size_t len) { uint32_t h = 2166136261u; for (size_t i = 0; i < len; i++) { h ^= data[i]; h *= 16777619u; } return h; } static int is_table_sig(const uint8_t *d) { return memcmp(d, "DSDT", 4) == 0 || memcmp(d, "SSDT", 4) == 0; } static void input_meta_set_source(tobi_input_meta *meta, const char *source) { free(meta->source); meta->source = tobi_xstrdup(source && source[0] ? source : "raw"); } static void input_meta_copy(tobi_input_meta *dst, const tobi_input_meta *src) { *dst = *src; dst->source = src->source ? tobi_xstrdup(src->source) : NULL; } static void input_meta_free(tobi_input_meta *meta) { free(meta->source); meta->source = NULL; } static void parser_free(parser *p) { free(p->scope); } static tobi_ns_kind ns_kind_from_ir(tobi_ir_kind kind) { switch (kind) { case TOBI_IR_SCOPE: return TOBI_NS_SCOPE; case TOBI_IR_DEVICE: return TOBI_NS_DEVICE; case TOBI_IR_METHOD: return TOBI_NS_METHOD; case TOBI_IR_PROCESSOR: return TOBI_NS_PROCESSOR; case TOBI_IR_NAME: return TOBI_NS_NAME; case TOBI_IR_ALIAS: return TOBI_NS_ALIAS; case TOBI_IR_OPREGION: return TOBI_NS_OPREGION; case TOBI_IR_FIELD: case TOBI_IR_FIELD_ELEM: return TOBI_NS_FIELD; case TOBI_IR_MUTEX: return TOBI_NS_MUTEX; case TOBI_IR_EVENT: return TOBI_NS_EVENT; default: return TOBI_NS_UNKNOWN; } } static const char *region_space_name(unsigned space) { switch (space) { case 0x00: return "SystemMemory"; case 0x01: return "SystemIO"; case 0x02: return "PCI_Config"; case 0x03: return "EmbeddedControl"; case 0x04: return "SMBus"; case 0x05: return "SystemCMOS"; case 0x06: return "PCIBarTarget"; case 0x07: return "IPMI"; case 0x08: return "GPIO"; case 0x09: return "GenericSerialBus"; case 0x0a: return "PCC"; default: return "OEM"; } } static void ns_declare_path(parser *p, tobi_ns_kind kind, const char *path, size_t off, unsigned args, unsigned flags, int external, const char *target) { if (!p->ns || !path || path[0] == '\0') { return; } int duplicate = 0; tobi_ns_ent *ent = tobi_ns_add(p->ns, kind, path, p->scope, off, args, flags, external, target, &duplicate); if (ent && p->has_source && (!duplicate || !ent->has_source)) { tobi_ns_ent_set_source(ent, p->source, p->input_index); } if (duplicate) { diag(p, p->strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, off, "duplicate namespace object %s at %s", tobi_ns_kind_name(kind), path); } } static void ns_set_region_range(parser *p, const char *path, uint64_t off, uint64_t len) { tobi_ns_ent *e = tobi_ns_find(p->ns, path); if (!e) { return; } e->region_offset = off; e->region_len = len; e->has_region_range = 1; } static void ns_declare_name(parser *p, tobi_ns_kind kind, const char *name, size_t off, unsigned args, unsigned flags, int external, const char *target) { char *path = tobi_nm_resolve(p->scope, name); ns_declare_path(p, kind, path, off, args, flags, external, target); free(path); } static uint8_t table_sum8(const uint8_t *data, size_t len) { uint8_t sum = 0; for (size_t i = 0; i < len; i++) { sum = (uint8_t)(sum + data[i]); } return sum; } static int detect_input(const uint8_t *data, size_t len, int strict, tobi_diag_list *diag_list, tobi_input_meta *meta, const char *source, size_t input_index, int has_source) { memset(meta, 0, sizeof(*meta)); meta->aml_off = 0; meta->aml_len = len; memcpy(meta->signature, "RAW", 4); meta->signature[4] = '\0'; input_meta_set_source(meta, has_source && source ? source : "raw"); if (len >= 4 && is_table_sig(data)) { memcpy(meta->signature, data, 4); meta->signature[4] = '\0'; meta->is_table = 1; if (len < 36) { tobi_diag_add_source(diag_list, TOBI_DIAG_ERROR, 0, source, input_index, has_source, "ACPI table shorter than 36-byte header"); return !strict; } uint32_t table_len = le32_at(data + 4); meta->table_len = table_len; if (table_len < 36 || table_len > len) { tobi_diag_add_source(diag_list, TOBI_DIAG_ERROR, 4, source, input_index, has_source, "invalid ACPI table length %u for file size %zu", table_len, len); return !strict; } meta->revision = data[8]; meta->checksum_byte = data[9]; copy_trim(meta->oem_id, sizeof(meta->oem_id), data + 10, 6); copy_trim(meta->oem_table_id, sizeof(meta->oem_table_id), data + 16, 8); meta->oem_revision = le32_at(data + 24); copy_trim(meta->creator_id, sizeof(meta->creator_id), data + 28, 4); meta->creator_revision = le32_at(data + 32); meta->header_hash = hash32(data, 36); uint8_t sum = table_sum8(data, table_len); meta->checksum_sum = sum; meta->checksum_valid = sum == 0; if (sum != 0) { tobi_diag_add_source(diag_list, strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, 9, source, input_index, has_source, "ACPI table checksum mismatch: byte sum is 0x%02x", sum); if (strict) { return 0; } } meta->aml_off = 36; meta->aml_len = (size_t)table_len - 36; } if (meta->aml_off <= len && meta->aml_len <= len - meta->aml_off) { meta->aml_hash = hash32(data + meta->aml_off, meta->aml_len); } return 1; } static int pkg_window(parser *p, tobi_rd *rd, size_t op_start, size_t *body_start, size_t *body_len, size_t *whole_len) { size_t len = 0; size_t enc = 0; size_t pkg_start = rd->pos; if (!tobi_rd_pkg_len(rd, &len, &enc)) { diag(p, TOBI_DIAG_ERROR, rd->base + pkg_start, "truncated AML package length"); return 0; } if (len < enc) { diag(p, TOBI_DIAG_ERROR, rd->base + pkg_start, "invalid AML package length %zu smaller than encoded length %zu", len, enc); return 0; } size_t end = pkg_start + len; if (end > rd->len) { diag(p, TOBI_DIAG_ERROR, rd->base + pkg_start, "AML package body extends past input"); end = rd->len; } *body_start = rd->pos; *body_len = end >= rd->pos ? end - rd->pos : 0; *whole_len = (rd->base + end) - op_start; return 1; } static tobi_ir *unknown_node(parser *p, size_t off, uint32_t raw, size_t len, const char *why) { tobi_ir *n = tobi_ir_new(TOBI_IR_UNKNOWN, off, len); n->raw_op = raw; tobi_ir_set_str(n, why); diag(p, p->strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, off, "unsupported or malformed opcode 0x%x: %s", raw, why); return n; } static int parse_term_list(parser *p, tobi_rd *rd, tobi_ir *parent, unsigned depth); static tobi_ir *parse_expr(parser *p, tobi_rd *rd, unsigned depth); static tobi_ir *parse_term(parser *p, tobi_rd *rd, unsigned depth); static int parse_namestring_diag(parser *p, tobi_rd *rd, char **name) { size_t off = tobi_rd_off(rd); if (!tobi_nm_namestring(rd, name)) { diag(p, TOBI_DIAG_ERROR, off, "invalid or truncated AML namestring"); return 0; } return 1; } static tobi_ir *parse_block_from_reader(parser *p, tobi_rd *sub, unsigned depth, size_t off) { tobi_ir *block = tobi_ir_new(TOBI_IR_BLOCK, off, tobi_rd_left(sub)); tobi_ir_set_scope(block, p->scope); (void)parse_term_list(p, sub, block, depth + 1); return block; } static tobi_ir *parse_pkg_named(parser *p, tobi_rd *rd, unsigned depth, tobi_ir_kind kind, const char *kind_name, size_t op_start) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, rd->data[op_start - rd->base], 1, "bad package length"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *name = NULL; if (!parse_namestring_diag(p, &body, &name)) { rd->pos = body_start + body_len; return unknown_node(p, op_start, rd->data[op_start - rd->base], whole_len, kind_name); } char *path = tobi_nm_resolve(p->scope, name); tobi_ir *n = tobi_ir_new(kind, op_start, whole_len); tobi_ir_set_name(n, name); tobi_ir_set_path(n, path); ns_declare_path(p, ns_kind_from_ir(kind), path, op_start, 0, 0, 0, NULL); char *old_scope = p->scope; p->scope = tobi_xstrdup(path); tobi_ir *block = parse_block_from_reader(p, &body, depth, tobi_rd_off(&body)); free(p->scope); p->scope = old_scope; tobi_ir_add(n, block); rd->pos = body_start + body_len; free(name); free(path); return n; } static tobi_ir *parse_method(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, 0x14, 1, "bad method package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *name = NULL; if (!parse_namestring_diag(p, &body, &name)) { rd->pos = body_start + body_len; return unknown_node(p, op_start, 0x14, whole_len, "bad method name"); } uint8_t flags = 0; if (!tobi_rd_u8(&body, &flags)) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated method flags"); rd->pos = body_start + body_len; free(name); return unknown_node(p, op_start, 0x14, whole_len, "bad method flags"); } char *path = tobi_nm_resolve(p->scope, name); tobi_ir *n = tobi_ir_new(TOBI_IR_METHOD, op_start, whole_len); tobi_ir_set_name(n, name); tobi_ir_set_path(n, path); n->method_args = flags & 0x07u; n->method_serialized = (flags >> 3) & 0x01u; n->method_sync = (flags >> 4) & 0x0fu; ns_declare_path(p, TOBI_NS_METHOD, path, op_start, n->method_args, flags, 0, NULL); char *old_scope = p->scope; p->scope = tobi_xstrdup(path); tobi_ir *block = parse_block_from_reader(p, &body, depth, tobi_rd_off(&body)); free(p->scope); p->scope = old_scope; tobi_ir_add(n, block); rd->pos = body_start + body_len; free(name); free(path); return n; } static tobi_ir *parse_name(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, op_start, 0x08, 1, "bad NameOp name"); } char *path = tobi_nm_resolve(p->scope, name); tobi_ir *n = tobi_ir_new(TOBI_IR_NAME, op_start, 0); tobi_ir_set_name(n, name); tobi_ir_set_path(n, path); ns_declare_path(p, TOBI_NS_NAME, path, op_start, 0, 0, 0, NULL); tobi_ir *value = parse_expr(p, rd, depth + 1); if (value) { tobi_ir_add(n, value); } n->len = tobi_rd_off(rd) - op_start; free(name); free(path); return n; } static tobi_ir *parse_alias(parser *p, tobi_rd *rd, size_t op_start) { char *src = NULL; char *dst = NULL; if (!parse_namestring_diag(p, rd, &src) || !parse_namestring_diag(p, rd, &dst)) { free(src); free(dst); return unknown_node(p, op_start, 0x06, 1, "bad AliasOp names"); } tobi_ir *n = tobi_ir_new(TOBI_IR_ALIAS, op_start, tobi_rd_off(rd) - op_start); tobi_ir_set_name(n, dst); tobi_ir_set_str(n, src); char *path = tobi_nm_resolve(p->scope, dst); char *target = tobi_nm_resolve(p->scope, src); tobi_ir_set_path(n, path); ns_declare_path(p, TOBI_NS_ALIAS, path, op_start, 0, 0, 0, target); free(target); free(path); free(src); free(dst); return n; } static tobi_ir *parse_external(parser *p, tobi_rd *rd, size_t op_start) { char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, op_start, 0x15, 1, "bad ExternalOp name"); } uint8_t obj_type = 0; uint8_t argc = 0; if (!tobi_rd_u8(rd, &obj_type) || !tobi_rd_u8(rd, &argc)) { free(name); diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "truncated ExternalOp metadata"); return unknown_node(p, op_start, 0x15, tobi_rd_off(rd) - op_start, "bad ExternalOp metadata"); } tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, tobi_rd_off(rd) - op_start); n->raw_op = 0x15; n->value = argc; tobi_ir_set_name(n, "external"); char *path = tobi_nm_resolve(p->scope, name); tobi_ir_set_path(n, path); tobi_ir *typ = tobi_ir_new(TOBI_IR_INTEGER, op_start + n->len - 2u, 1); typ->value = obj_type; tobi_ir_add(n, typ); ns_declare_path(p, obj_type == 0x08u ? TOBI_NS_METHOD : TOBI_NS_EXTERNAL, path, op_start, argc & 0x07u, obj_type, 1, NULL); free(path); free(name); return n; } static void apply_field_meta(tobi_ir *e, uint64_t bit_off, uint64_t bit_len, uint8_t flags) { e->bit_off = bit_off; e->bit_len = bit_len; e->access_type = flags & 0x0fu; e->lock_rule = (flags >> 4) & 0x01u; e->update_rule = (flags >> 5) & 0x03u; } static char *field_target_meta(parser *p, const char *prefix, const char *path) { const tobi_ns_ent *e = tobi_ns_find_const(p->ns, path); tobi_sb sb; tobi_sb_init(&sb); tobi_sb_add(&sb, prefix); tobi_sb_add(&sb, path); if (e && e->kind == TOBI_NS_OPREGION) { tobi_sb_printf(&sb, " space=%s", region_space_name(e->flags)); if (e->has_region_range) { tobi_sb_printf(&sb, " byte_offset=0x%llx byte_length=0x%llx", (unsigned long long)e->region_offset, (unsigned long long)e->region_len); } } return tobi_sb_take(&sb); } static void parse_field_list(parser *p, tobi_rd *body, tobi_ir *n, uint8_t flags, const tobi_ns_ent *region) { uint64_t bit_pos = 0; while (tobi_rd_left(body) > 0) { size_t foff = tobi_rd_off(body); uint8_t b = 0; if (!tobi_rd_peek(body, &b)) { break; } if (b == 0x00) { (void)tobi_rd_u8(body, &b); size_t bits = 0; size_t enc = 0; if (!tobi_rd_pkg_len(body, &bits, &enc)) { diag(p, TOBI_DIAG_ERROR, foff, "truncated reserved field length"); break; } tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff); tobi_ir_set_name(e, ""); e->value = bits; apply_field_meta(e, bit_pos, bits, flags); tobi_ir_add(n, e); bit_pos += bits; continue; } if (b == 0x01) { if (!tobi_rd_skip(body, 3)) { diag(p, TOBI_DIAG_ERROR, foff, "truncated AccessField"); break; } tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff); tobi_ir_set_name(e, ""); tobi_ir_add(n, e); continue; } if (b == 0x02) { (void)tobi_rd_u8(body, &b); char *conn = NULL; if (!tobi_nm_namestring(body, &conn)) { diag(p, TOBI_DIAG_ERROR, foff, "bad ConnectField name"); break; } tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff); tobi_ir_set_name(e, ""); tobi_ir_set_str(e, conn); tobi_ir_add(n, e); free(conn); continue; } if (b == 0x03) { if (!tobi_rd_skip(body, 4)) { diag(p, TOBI_DIAG_ERROR, foff, "truncated ExtendedAccessField"); break; } tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff); tobi_ir_set_name(e, ""); tobi_ir_add(n, e); continue; } char *fname = NULL; if (!tobi_nm_nameseg(body, &fname)) { diag(p, TOBI_DIAG_ERROR, foff, "bad field element"); (void)tobi_rd_skip(body, 1); continue; } size_t bits = 0; size_t enc = 0; if (!tobi_rd_pkg_len(body, &bits, &enc)) { diag(p, TOBI_DIAG_ERROR, foff, "truncated field element length"); free(fname); break; } tobi_ir *e = tobi_ir_new(TOBI_IR_FIELD_ELEM, foff, tobi_rd_off(body) - foff); tobi_ir_set_name(e, fname); e->value = bits; apply_field_meta(e, bit_pos, bits, flags); if (n->target) { tobi_sb target; tobi_sb_init(&target); tobi_sb_add(&target, n->target); tobi_sb_printf(&target, " field_bit=%llu field_bytes=%llu", (unsigned long long)bit_pos, (unsigned long long)((bits + 7u) / 8u)); if (region && region->has_region_range && region->region_offset <= UINT64_MAX / 8u && bit_pos <= UINT64_MAX - (region->region_offset * 8u)) { uint64_t abs_bit = (region->region_offset * 8u) + bit_pos; tobi_sb_printf(&target, " absolute_bit=0x%llx absolute_byte=0x%llx absolute_bytes=0x%llx", (unsigned long long)abs_bit, (unsigned long long)(abs_bit / 8u), (unsigned long long)((bits + 7u) / 8u)); } tobi_ir_set_target(e, target.buf); tobi_sb_free(&target); } ns_declare_name(p, TOBI_NS_FIELD, fname, foff, 0, 0, 0, NULL); tobi_ir_add(n, e); bit_pos += bits; free(fname); } } static tobi_ir *parse_field_like(parser *p, tobi_rd *rd, unsigned depth, size_t op_start, uint32_t raw, const char *label) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, raw, 2, "bad field package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *first = NULL; if (!parse_namestring_diag(p, &body, &first)) { rd->pos = body_start + body_len; return unknown_node(p, op_start, raw, whole_len, "bad field name"); } tobi_ir *n = tobi_ir_new(TOBI_IR_FIELD, op_start, whole_len); tobi_ir_set_str(n, label); const tobi_ns_ent *backing_region = NULL; if (raw == 0x5b81u) { tobi_ir_set_name(n, first); char *path = tobi_ns_resolve_path(p->ns, p->scope, first); char *target = field_target_meta(p, "region=", path); tobi_ir_set_target(n, target); backing_region = tobi_ns_find_const(p->ns, path); free(target); free(path); } else if (raw == 0x5b86u) { char *data = NULL; if (!parse_namestring_diag(p, &body, &data)) { free(first); rd->pos = body_start + body_len; return unknown_node(p, op_start, raw, whole_len, "bad IndexField data name"); } tobi_sb nm; tobi_sb_init(&nm); tobi_sb_add(&nm, first); tobi_sb_add(&nm, ","); tobi_sb_add(&nm, data); tobi_ir_set_name(n, nm.buf); tobi_sb target; tobi_sb_init(&target); char *idx_path = tobi_ns_resolve_path(p->ns, p->scope, first); char *data_path = tobi_ns_resolve_path(p->ns, p->scope, data); char *idx_meta = field_target_meta(p, "index=", idx_path); tobi_sb_add(&target, idx_meta); tobi_sb_add(&target, " data="); tobi_sb_add(&target, data_path); tobi_ir_set_target(n, target.buf); free(idx_meta); free(idx_path); free(data_path); tobi_sb_free(&target); tobi_sb_free(&nm); free(data); } else { char *bank = NULL; if (!parse_namestring_diag(p, &body, &bank)) { free(first); rd->pos = body_start + body_len; return unknown_node(p, op_start, raw, whole_len, "bad BankField bank name"); } tobi_sb nm; tobi_sb_init(&nm); tobi_sb_add(&nm, first); tobi_sb_add(&nm, ","); tobi_sb_add(&nm, bank); tobi_ir_set_name(n, nm.buf); tobi_sb target; tobi_sb_init(&target); char *region_path = tobi_ns_resolve_path(p->ns, p->scope, first); char *bank_path = tobi_ns_resolve_path(p->ns, p->scope, bank); char *region_meta = field_target_meta(p, "region=", region_path); backing_region = tobi_ns_find_const(p->ns, region_path); tobi_sb_add(&target, region_meta); tobi_sb_add(&target, " bank="); tobi_sb_add(&target, bank_path); tobi_ir_set_target(n, target.buf); free(region_meta); free(region_path); free(bank_path); tobi_sb_free(&target); tobi_sb_free(&nm); free(bank); tobi_ir *bank_value = parse_expr(p, &body, depth + 1); if (bank_value) { tobi_ir_add(n, bank_value); } } uint8_t flags = 0; if (!tobi_rd_u8(&body, &flags)) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated field flags"); } n->value = flags; n->access_type = flags & 0x0fu; n->lock_rule = (flags >> 4) & 0x01u; n->update_rule = (flags >> 5) & 0x03u; parse_field_list(p, &body, n, flags, backing_region); rd->pos = body_start + body_len; free(first); return n; } static tobi_ir *ref_from_name(parser *p, tobi_rd *rd, size_t owner_start, const char *why) { size_t off = tobi_rd_off(rd); char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, owner_start, 0, tobi_rd_off(rd) - owner_start, why); } tobi_ir *n = tobi_ir_new(TOBI_IR_REF, off, tobi_rd_off(rd) - off); tobi_ir_set_name(n, name); char *path = tobi_ns_resolve_path(p->ns, p->scope, name); tobi_ir_set_path(n, path); free(path); free(name); return n; } static void add_expr_children(parser *p, tobi_rd *rd, unsigned depth, tobi_ir *n, unsigned count) { for (unsigned i = 0; i < count && tobi_rd_left(rd) > 0; i++) { tobi_ir *arg = parse_expr(p, rd, depth + 1); if (arg) { tobi_ir_add(n, arg); } } } static tobi_ir *parse_op_expr(parser *p, tobi_rd *rd, unsigned depth, size_t op_start, uint32_t raw, const tobi_op_expr *info) { tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, 0); n->raw_op = raw; tobi_ir_set_name(n, info->ir_name); add_expr_children(p, rd, depth, n, info->arg_count); add_expr_children(p, rd, depth, n, info->target_count); for (unsigned i = 0; i < info->name_count && tobi_rd_left(rd) > 0; i++) { tobi_ir *ref = ref_from_name(p, rd, op_start, "bad expression namestring"); if (ref) { if (ref->path && ref->path[0] == '\\' && strncmp(info->ir_name, "create_", 7) == 0) { ns_declare_path(p, TOBI_NS_FIELD, ref->path, ref->off, 0, raw, 0, NULL); } tobi_ir_add(n, ref); } } n->len = tobi_rd_off(rd) - op_start; return n; } static tobi_ir *parse_ext_load(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, 0); n->raw_op = 0x5b20u; tobi_ir_set_name(n, "load"); tobi_ir *src = ref_from_name(p, rd, op_start, "bad LoadOp source name"); if (src) { tobi_ir_add(n, src); } tobi_ir *target = parse_expr(p, rd, depth + 1); if (target) { tobi_ir_add(n, target); } n->len = tobi_rd_off(rd) - op_start; return n; } static tobi_ir *parse_ext_pkg_scope(parser *p, tobi_rd *rd, unsigned depth, size_t op_start, uint32_t raw, tobi_ir_kind kind, const char *kind_name, unsigned prefix_bytes) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, raw, 2, kind_name); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *name = NULL; if (!parse_namestring_diag(p, &body, &name)) { rd->pos = body_start + body_len; return unknown_node(p, op_start, raw, whole_len, kind_name); } if (!tobi_rd_skip(&body, prefix_bytes)) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated %s metadata", kind_name); rd->pos = body_start + body_len; free(name); return unknown_node(p, op_start, raw, whole_len, kind_name); } char *path = tobi_nm_resolve(p->scope, name); tobi_ir *n = tobi_ir_new(kind, op_start, whole_len); tobi_ir_set_name(n, name); tobi_ir_set_path(n, path); tobi_ir_set_str(n, kind_name); ns_declare_path(p, ns_kind_from_ir(kind), path, op_start, 0, raw, 0, NULL); char *old_scope = p->scope; p->scope = tobi_xstrdup(path); tobi_ir *block = parse_block_from_reader(p, &body, depth, tobi_rd_off(&body)); free(p->scope); p->scope = old_scope; tobi_ir_add(n, block); rd->pos = body_start + body_len; free(path); free(name); return n; } static tobi_ir *parse_ext(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { uint8_t ext = 0; if (!tobi_rd_u8(rd, &ext)) { return unknown_node(p, op_start, 0x5b, 1, "truncated extended opcode"); } uint32_t raw = 0x5b00u | ext; switch (ext) { case 0x82: return parse_pkg_named(p, rd, depth, TOBI_IR_DEVICE, "DeviceOp", op_start); case 0x83: { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, raw, 2, "bad ProcessorOp package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *name = NULL; if (!parse_namestring_diag(p, &body, &name)) { rd->pos = body_start + body_len; return unknown_node(p, op_start, raw, whole_len, "bad processor name"); } uint8_t proc_id = 0; uint32_t pblk = 0; uint8_t pblk_len = 0; if (!tobi_rd_u8(&body, &proc_id) || !tobi_rd_u32(&body, &pblk) || !tobi_rd_u8(&body, &pblk_len)) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated ProcessorOp metadata"); rd->pos = body_start + body_len; free(name); return unknown_node(p, op_start, raw, whole_len, "bad ProcessorOp metadata"); } char *path = tobi_nm_resolve(p->scope, name); tobi_ir *n = tobi_ir_new(TOBI_IR_PROCESSOR, op_start, whole_len); tobi_ir_set_name(n, name); tobi_ir_set_path(n, path); ns_declare_path(p, TOBI_NS_PROCESSOR, path, op_start, 0, 0, 0, NULL); n->value = proc_id; char *old_scope = p->scope; p->scope = tobi_xstrdup(path); tobi_ir_add(n, parse_block_from_reader(p, &body, depth, tobi_rd_off(&body))); free(p->scope); p->scope = old_scope; rd->pos = body_start + body_len; free(path); free(name); return n; } case 0x80: { char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, op_start, raw, 2, "bad OpRegion name"); } uint8_t space = 0; if (!tobi_rd_u8(rd, &space)) { free(name); diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "truncated OpRegion space"); return unknown_node(p, op_start, raw, 2, "bad OpRegion"); } tobi_ir *n = tobi_ir_new(TOBI_IR_OPREGION, op_start, 0); tobi_ir_set_name(n, name); char *path = tobi_nm_resolve(p->scope, name); tobi_ir_set_path(n, path); ns_declare_path(p, TOBI_NS_OPREGION, path, op_start, 0, space, 0, NULL); n->value = space; tobi_ir_set_str(n, region_space_name(space)); tobi_ir *off = parse_expr(p, rd, depth + 1); tobi_ir *len = parse_expr(p, rd, depth + 1); if (off) { tobi_ir_add(n, off); } if (len) { tobi_ir_add(n, len); } if (n->child_len >= 2 && n->child[0]->kind == TOBI_IR_INTEGER && n->child[1]->kind == TOBI_IR_INTEGER) { ns_set_region_range(p, path, n->child[0]->value, n->child[1]->value); } n->len = tobi_rd_off(rd) - op_start; free(path); free(name); return n; } case 0x81: return parse_field_like(p, rd, depth, op_start, raw, "Field"); case 0x86: return parse_field_like(p, rd, depth, op_start, raw, "IndexField"); case 0x87: return parse_field_like(p, rd, depth, op_start, raw, "BankField"); case 0x84: return parse_ext_pkg_scope(p, rd, depth, op_start, raw, TOBI_IR_SCOPE, "PowerResOp", 2); case 0x85: return parse_ext_pkg_scope(p, rd, depth, op_start, raw, TOBI_IR_SCOPE, "ThermalZoneOp", 0); case 0x20: return parse_ext_load(p, rd, depth, op_start); case 0x30: { tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, op_start, tobi_rd_off(rd) - op_start); n->value = 2; return n; } case 0x31: { tobi_ir *n = tobi_ir_new(TOBI_IR_REF, op_start, tobi_rd_off(rd) - op_start); n->raw_op = raw; tobi_ir_set_name(n, "Debug"); return n; } case 0x33: { tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, op_start, tobi_rd_off(rd) - op_start); n->raw_op = raw; tobi_ir_set_name(n, "timer"); return n; } case 0x88: { char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, op_start, raw, 2, "bad DataRegion name"); } tobi_ir *n = tobi_ir_new(TOBI_IR_OPREGION, op_start, 0); n->raw_op = raw; tobi_ir_set_name(n, name); char *path = tobi_nm_resolve(p->scope, name); tobi_ir_set_path(n, path); tobi_ir_set_str(n, "DataRegion"); ns_declare_path(p, TOBI_NS_OPREGION, path, op_start, 0, 0, 0, NULL); add_expr_children(p, rd, depth, n, 3); n->len = tobi_rd_off(rd) - op_start; free(path); free(name); return n; } case 0x01: case 0x02: { char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, op_start, raw, 2, ext == 0x01 ? "bad MutexOp" : "bad EventOp"); } tobi_ir *n = tobi_ir_new(ext == 0x01 ? TOBI_IR_MUTEX : TOBI_IR_EVENT, op_start, 0); tobi_ir_set_name(n, name); char *path = tobi_nm_resolve(p->scope, name); tobi_ir_set_path(n, path); ns_declare_path(p, ext == 0x01 ? TOBI_NS_MUTEX : TOBI_NS_EVENT, path, op_start, 0, 0, 0, NULL); if (ext == 0x01) { uint8_t sync = 0; if (!tobi_rd_u8(rd, &sync)) { free(path); free(name); diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "truncated MutexOp sync flags"); return unknown_node(p, op_start, raw, tobi_rd_off(rd) - op_start, "bad MutexOp sync flags"); } n->value = sync; } n->len = tobi_rd_off(rd) - op_start; free(path); free(name); return n; } default: break; } const tobi_op_expr *info = tobi_op_expr_find((uint16_t)raw); if (info) { return parse_op_expr(p, rd, depth, op_start, raw, info); } return unknown_node(p, op_start, raw, 2, "unsupported extended opcode"); } static tobi_ir *parse_if(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, 0xa0, 1, "bad IfOp package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); tobi_ir *n = tobi_ir_new(TOBI_IR_IF, op_start, whole_len); tobi_ir *pred = parse_expr(p, &body, depth + 1); if (pred) { tobi_ir_add(n, pred); } tobi_ir_add(n, parse_block_from_reader(p, &body, depth, tobi_rd_off(&body))); rd->pos = body_start + body_len; uint8_t next = 0; if (tobi_rd_peek(rd, &next) && next == 0xa1) { size_t else_start = tobi_rd_off(rd); (void)tobi_rd_u8(rd, &next); size_t eb = 0; size_t el = 0; size_t ew = 0; if (pkg_window(p, rd, else_start, &eb, &el, &ew)) { tobi_rd er; tobi_rd_init(&er, rd->data + eb, el, rd->base + eb); tobi_ir_add(n, parse_block_from_reader(p, &er, depth, tobi_rd_off(&er))); rd->pos = eb + el; n->len = (rd->base + rd->pos) - op_start; } else { tobi_ir_add(n, unknown_node(p, else_start, 0xa1, 1, "bad ElseOp package")); } } return n; } static tobi_ir *parse_while(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, 0xa2, 1, "bad WhileOp package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); tobi_ir *n = tobi_ir_new(TOBI_IR_WHILE, op_start, whole_len); tobi_ir *pred = parse_expr(p, &body, depth + 1); if (pred) { tobi_ir_add(n, pred); } tobi_ir_add(n, parse_block_from_reader(p, &body, depth, tobi_rd_off(&body))); rd->pos = body_start + body_len; return n; } static tobi_ir *parse_string(parser *p, tobi_rd *rd, size_t op_start) { size_t s = rd->pos; while (rd->pos < rd->len && rd->data[rd->pos] != 0) { rd->pos++; } size_t n = rd->pos - s; if (rd->pos >= rd->len) { diag(p, TOBI_DIAG_ERROR, rd->base + s, "unterminated AML string"); } else { rd->pos++; } char *tmp = tobi_xstrndup((const char *)rd->data + s, n); tobi_ir *node = tobi_ir_new(TOBI_IR_STRING, op_start, tobi_rd_off(rd) - op_start); tobi_ir_set_str(node, tmp); free(tmp); return node; } static const char *small_res_name(unsigned name) { switch (name) { case 0x04: return "IRQ"; case 0x05: return "DMA"; case 0x06: return "StartDependentFn"; case 0x07: return "EndDependentFn"; case 0x08: return "IO"; case 0x09: return "FixedIO"; case 0x0e: return "VendorSmall"; case 0x0f: return "EndTag"; default: return NULL; } } static const char *large_res_name(unsigned name) { switch (name) { case 0x01: return "Memory24"; case 0x02: return "GenericRegister"; case 0x04: return "VendorLarge"; case 0x05: return "Memory32"; case 0x06: return "FixedMemory32"; case 0x07: return "DWordAddressSpace"; case 0x08: return "WordAddressSpace"; case 0x09: return "ExtendedIRQ"; case 0x0a: return "QWordAddressSpace"; case 0x0b: return "ExtendedAddressSpace"; case 0x0c: return "GPIO"; case 0x0e: return "SerialBus"; default: return NULL; } } static uint16_t res_u16(const uint8_t *p) { return (uint16_t)p[0] | ((uint16_t)p[1] << 8); } static uint32_t res_u32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } static uint64_t res_u64(const uint8_t *p) { return (uint64_t)res_u32(p) | ((uint64_t)res_u32(p + 4) << 32); } static void add_res_node(tobi_ir *parent, const char *label, size_t off, size_t len, uint32_t tag, size_t payload_len, const char *detail) { tobi_ir *r = tobi_ir_new(TOBI_IR_RESOURCE, off, len); r->raw_op = tag; r->value = payload_len; tobi_ir_set_name(r, label); if (detail) { tobi_ir_set_str(r, detail); } tobi_ir_add(parent, r); } static char *small_res_detail(unsigned name, const uint8_t *p, size_t len) { tobi_sb sb; tobi_sb_init(&sb); if (name == 0x04 && len >= 2) { tobi_sb_printf(&sb, "irq_mask=0x%x", (unsigned)res_u16(p)); if (len >= 3) { tobi_sb_printf(&sb, " flags=0x%x", (unsigned)p[2]); } } else if (name == 0x05 && len >= 2) { tobi_sb_printf(&sb, "dma_mask=0x%x flags=0x%x", (unsigned)p[0], (unsigned)p[1]); } else if (name == 0x08 && len >= 7) { tobi_sb_printf(&sb, "decode=0x%x min=0x%x max=0x%x align=%u length=%u", (unsigned)p[0], (unsigned)res_u16(p + 1), (unsigned)res_u16(p + 3), (unsigned)p[5], (unsigned)p[6]); } else if (name == 0x09 && len >= 3) { tobi_sb_printf(&sb, "base=0x%x length=%u", (unsigned)res_u16(p), (unsigned)p[2]); } else if (name == 0x0f && len >= 1) { tobi_sb_printf(&sb, "checksum=0x%x", (unsigned)p[0]); } else { tobi_sb_printf(&sb, "bytes=%zu", len); } return tobi_sb_take(&sb); } static char *large_res_detail(unsigned name, const uint8_t *p, size_t len) { tobi_sb sb; tobi_sb_init(&sb); if (name == 0x02 && len >= 12) { tobi_sb_printf(&sb, "space=0x%x width=%u offset=%u access=%u address=0x%x", (unsigned)p[0], (unsigned)p[1], (unsigned)p[2], (unsigned)p[3], (unsigned)res_u64(p + 4)); } else if ((name == 0x05 || name == 0x06) && len >= 9) { tobi_sb_printf(&sb, "write=0x%x min=0x%x max=0x%x align=0x%x length=0x%x", (unsigned)p[0], (unsigned)res_u32(p + 1), (unsigned)res_u32(p + 5), len >= 13 ? (unsigned)res_u32(p + 9) : 0u, len >= 17 ? (unsigned)res_u32(p + 13) : 0u); } else if (name == 0x07 && len >= 23) { tobi_sb_printf(&sb, "rtype=0x%x flags=0x%x gran=0x%x min=0x%x max=0x%x trans=0x%x length=0x%x", (unsigned)p[0], (unsigned)p[1], (unsigned)res_u32(p + 3), (unsigned)res_u32(p + 7), (unsigned)res_u32(p + 11), (unsigned)res_u32(p + 15), (unsigned)res_u32(p + 19)); } else if (name == 0x08 && len >= 13) { tobi_sb_printf(&sb, "rtype=0x%x flags=0x%x gran=0x%x min=0x%x max=0x%x", (unsigned)p[0], (unsigned)p[1], (unsigned)res_u16(p + 3), (unsigned)res_u16(p + 5), (unsigned)res_u16(p + 7)); } else if (name == 0x09 && len >= 2) { unsigned count = len >= 2 ? (unsigned)p[1] : 0u; tobi_sb_printf(&sb, "flags=0x%x interrupts=%u", (unsigned)p[0], count); } else if (name == 0x0a && len >= 43) { tobi_sb_printf(&sb, "rtype=0x%x flags=0x%x gran=0x%llx min=0x%llx max=0x%llx length=0x%llx", (unsigned)p[0], (unsigned)p[1], (unsigned long long)res_u64(p + 3), (unsigned long long)res_u64(p + 11), (unsigned long long)res_u64(p + 19), (unsigned long long)res_u64(p + 35)); } else if (name == 0x0c && len >= 2) { tobi_sb_printf(&sb, "revision=%u type=0x%x", (unsigned)p[0], (unsigned)p[1]); } else if (name == 0x0e && len >= 2) { tobi_sb_printf(&sb, "revision=%u type=0x%x", (unsigned)p[0], (unsigned)p[1]); } else { tobi_sb_printf(&sb, "bytes=%zu", len); } return tobi_sb_take(&sb); } static char *decode_resource_template(tobi_ir *parent, const uint8_t *data, size_t len, size_t base) { if (!data || len == 0) { return NULL; } tobi_sb sb; tobi_sb_init(&sb); size_t pos = 0; int any = 0; int ended = 0; while (pos < len) { uint8_t tag = data[pos++]; if ((tag & 0x80u) != 0) { if (pos + 2 > len) { break; } unsigned name = tag & 0x7fu; size_t item_len = (size_t)data[pos] | ((size_t)data[pos + 1] << 8); pos += 2; if (item_len > len - pos) { break; } const char *label = large_res_name(name); if (!label) { label = "LargeItem"; } char *detail = large_res_detail(name, data + pos, item_len); add_res_node(parent, label, base + pos - 3u, item_len + 3u, tag, item_len, detail); free(detail); if (any) { tobi_sb_add(&sb, "; "); } tobi_sb_printf(&sb, "%s(len=%zu)", label, item_len); any = 1; pos += item_len; } else { unsigned name = (tag >> 3) & 0x0fu; size_t item_len = tag & 0x07u; if (item_len > len - pos) { break; } const char *label = small_res_name(name); if (!label) { label = "SmallItem"; } char *detail = small_res_detail(name, data + pos, item_len); add_res_node(parent, label, base + pos - 1u, item_len + 1u, tag, item_len, detail); free(detail); if (any) { tobi_sb_add(&sb, "; "); } tobi_sb_printf(&sb, "%s(len=%zu)", label, item_len); any = 1; pos += item_len; if (name == 0x0f) { ended = 1; break; } } } if (!any || !ended || pos != len) { tobi_sb_free(&sb); return NULL; } return tobi_sb_take(&sb); } static tobi_ir *parse_buffer(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, 0x11, 1, "bad Buffer package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); tobi_ir *n = tobi_ir_new(TOBI_IR_BUFFER, op_start, whole_len); tobi_ir *sz = parse_expr(p, &body, depth + 1); if (sz) { tobi_ir_add(n, sz); } tobi_sb hex; tobi_sb_init(&hex); const uint8_t *bytes = body.data + body.pos; size_t byte_len = tobi_rd_left(&body); tobi_hex(&hex, bytes, byte_len); char *res = decode_resource_template(n, bytes, byte_len, body.base + body.pos); if (res) { tobi_sb_add(&hex, " resources="); tobi_sb_add(&hex, res); free(res); } tobi_ir_set_str(n, hex.buf); tobi_sb_free(&hex); rd->pos = body_start + body_len; return n; } static tobi_ir *parse_package(parser *p, tobi_rd *rd, unsigned depth, size_t op_start, int variable) { size_t body_start = 0; size_t body_len = 0; size_t whole_len = 0; if (!pkg_window(p, rd, op_start, &body_start, &body_len, &whole_len)) { return unknown_node(p, op_start, variable ? 0x13 : 0x12, 1, "bad Package"); } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); tobi_ir *n = tobi_ir_new(TOBI_IR_PACKAGE, op_start, whole_len); if (variable) { tobi_ir *cnt = parse_expr(p, &body, depth + 1); if (cnt) { tobi_ir_add(n, cnt); } } else { uint8_t count = 0; if (tobi_rd_u8(&body, &count)) { n->value = count; } else { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(&body), "truncated package element count"); } } while (tobi_rd_left(&body) > 0) { size_t before = body.pos; tobi_ir *e = parse_expr(p, &body, depth + 1); if (e) { tobi_ir_add(n, e); } if (body.pos == before) { (void)tobi_rd_skip(&body, 1); } } rd->pos = body_start + body_len; return n; } static tobi_ir *parse_name_or_call(parser *p, tobi_rd *rd, unsigned depth) { size_t off = tobi_rd_off(rd); char *name = NULL; if (!parse_namestring_diag(p, rd, &name)) { return unknown_node(p, off, 0, 1, "bad namestring expression"); } const tobi_ns_ent *ent = tobi_ns_lookup(p->ns, p->scope, name); int argc = ent && ent->kind == TOBI_NS_METHOD ? (int)ent->args : -1; char *path = ent ? tobi_xstrdup(ent->path) : tobi_nm_resolve(p->scope, name); tobi_ir *n = tobi_ir_new(argc >= 0 ? TOBI_IR_CALL : TOBI_IR_REF, off, 0); tobi_ir_set_name(n, name); tobi_ir_set_path(n, path); if (argc >= 0) { n->value = (uint64_t)argc; for (int i = 0; i < argc && tobi_rd_left(rd) > 0; i++) { tobi_ir *arg = parse_expr(p, rd, depth + 1); if (arg) { tobi_ir_add(n, arg); } } } n->len = tobi_rd_off(rd) - off; free(path); free(name); return n; } static tobi_ir *parse_expr(parser *p, tobi_rd *rd, unsigned depth) { if (depth > MAX_DEPTH) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "AML nesting too deep"); return NULL; } size_t off = tobi_rd_off(rd); uint8_t op = 0; if (!tobi_rd_u8(rd, &op)) { diag(p, TOBI_DIAG_ERROR, off, "truncated AML expression"); return NULL; } if (op == 0x00 || op == 0x01 || op == 0xff) { tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, 1); n->value = op == 0x00 ? 0 : (op == 0x01 ? 1 : UINT64_MAX); return n; } if (op == 0x0a) { uint8_t v = 0; if (!tobi_rd_u8(rd, &v)) { diag(p, TOBI_DIAG_ERROR, off, "truncated ByteConst"); } tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off); n->value = v; return n; } if (op == 0x0b) { uint16_t v = 0; if (!tobi_rd_u16(rd, &v)) { diag(p, TOBI_DIAG_ERROR, off, "truncated WordConst"); } tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off); n->value = v; return n; } if (op == 0x0c) { uint32_t v = 0; if (!tobi_rd_u32(rd, &v)) { diag(p, TOBI_DIAG_ERROR, off, "truncated DWordConst"); } tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off); n->value = v; return n; } if (op == 0x0e) { uint64_t v = 0; if (!tobi_rd_u64(rd, &v)) { diag(p, TOBI_DIAG_ERROR, off, "truncated QWordConst"); } tobi_ir *n = tobi_ir_new(TOBI_IR_INTEGER, off, tobi_rd_off(rd) - off); n->value = v; return n; } if (op == 0x0d) { return parse_string(p, rd, off); } if (op == 0x5b) { return parse_ext(p, rd, depth, off); } if (op == 0x11) { return parse_buffer(p, rd, depth, off); } if (op == 0x12 || op == 0x13) { return parse_package(p, rd, depth, off, op == 0x13); } if (op >= 0x60 && op <= 0x67) { tobi_ir *n = tobi_ir_new(TOBI_IR_REF, off, 1); char name[8]; (void)snprintf(name, sizeof(name), "Local%u", (unsigned)(op - 0x60)); tobi_ir_set_name(n, name); return n; } if (op >= 0x68 && op <= 0x6e) { tobi_ir *n = tobi_ir_new(TOBI_IR_REF, off, 1); char name[8]; (void)snprintf(name, sizeof(name), "Arg%u", (unsigned)(op - 0x68)); tobi_ir_set_name(n, name); return n; } if (op == 0x70) { tobi_ir *n = tobi_ir_new(TOBI_IR_STORE, off, 0); tobi_ir *src = parse_expr(p, rd, depth + 1); tobi_ir *dst = parse_expr(p, rd, depth + 1); if (src) { tobi_ir_add(n, src); } if (dst) { tobi_ir_add(n, dst); } n->len = tobi_rd_off(rd) - off; return n; } const tobi_op_expr *info = tobi_op_expr_find(op); if (info) { return parse_op_expr(p, rd, depth, off, op, info); } rd->pos--; uint8_t b = 0; (void)tobi_rd_peek(rd, &b); if (b == '\\' || b == '^' || b == 0x2e || b == 0x2f || tobi_nm_is_lead(b)) { return parse_name_or_call(p, rd, depth); } (void)tobi_rd_u8(rd, &op); return unknown_node(p, off, op, 1, "unknown expression opcode"); } static tobi_ir *parse_return(parser *p, tobi_rd *rd, unsigned depth, size_t op_start) { tobi_ir *n = tobi_ir_new(TOBI_IR_RETURN, op_start, 0); if (tobi_rd_left(rd) > 0) { tobi_ir *v = parse_expr(p, rd, depth + 1); if (v) { tobi_ir_add(n, v); } } n->len = tobi_rd_off(rd) - op_start; return n; } static tobi_ir *parse_term(parser *p, tobi_rd *rd, unsigned depth) { if (depth > MAX_DEPTH) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "AML nesting too deep"); return NULL; } if (++p->terms > MAX_TERMS) { diag(p, TOBI_DIAG_ERROR, tobi_rd_off(rd), "too many AML terms"); rd->pos = rd->len; return NULL; } size_t off = tobi_rd_off(rd); uint8_t op = 0; if (!tobi_rd_u8(rd, &op)) { return NULL; } switch (op) { case 0x06: return parse_alias(p, rd, off); case 0x08: return parse_name(p, rd, depth, off); case 0x10: return parse_pkg_named(p, rd, depth, TOBI_IR_SCOPE, "ScopeOp", off); case 0x14: return parse_method(p, rd, depth, off); case 0x15: return parse_external(p, rd, off); case 0x5b: return parse_ext(p, rd, depth, off); case 0x70: rd->pos--; return parse_expr(p, rd, depth); case 0xa0: return parse_if(p, rd, depth, off); case 0xa2: return parse_while(p, rd, depth, off); case 0xa3: { tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, off, 1); tobi_ir_set_name(n, "noop"); return n; } case 0xa4: return parse_return(p, rd, depth, off); case 0xa5: return tobi_ir_new(TOBI_IR_BREAK, off, 1); case 0x9f: return tobi_ir_new(TOBI_IR_CONTINUE, off, 1); case 0xcc: { tobi_ir *n = tobi_ir_new(TOBI_IR_EXPR, off, 1); n->raw_op = 0xcc; tobi_ir_set_name(n, "breakpoint"); return n; } default: rd->pos--; return parse_expr(p, rd, depth); } } static int parse_term_list(parser *p, tobi_rd *rd, tobi_ir *parent, unsigned depth) { while (tobi_rd_left(rd) > 0) { size_t before = rd->pos; tobi_ir *n = parse_term(p, rd, depth); if (n) { tobi_ir_add(parent, n); } if (rd->pos == before) { uint8_t b = 0; if (tobi_rd_u8(rd, &b)) { tobi_ir_add(parent, unknown_node(p, rd->base + before, b, 1, "parser made no progress")); } else { break; } } } return 1; } static int scan_pkg_window(tobi_rd *rd, size_t *body_start, size_t *body_len) { size_t pkg_start = rd->pos; size_t pkg_len = 0; size_t enc = 0; if (!tobi_rd_pkg_len(rd, &pkg_len, &enc) || pkg_len < enc) { return 0; } size_t end = pkg_start + pkg_len; if (end > rd->len || end < rd->pos) { return 0; } *body_start = rd->pos; *body_len = end - rd->pos; return 1; } static void scan_methods(parser *p, tobi_rd *rd, unsigned depth) { if (depth > MAX_DEPTH) { return; } while (tobi_rd_left(rd) > 0) { size_t start = rd->pos; size_t abs_start = rd->base + start; uint8_t op = 0; if (!tobi_rd_u8(rd, &op)) { return; } if (op == 0x14 || op == 0x10) { size_t body_start = 0; size_t body_len = 0; if (!scan_pkg_window(rd, &body_start, &body_len)) { rd->pos = rd->len; return; } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *name = NULL; if (!tobi_nm_namestring(&body, &name)) { rd->pos = body_start + body_len; continue; } char *path = tobi_nm_resolve(p->scope, name); if (op == 0x14) { uint8_t flags = 0; if (tobi_rd_u8(&body, &flags)) { ns_declare_path(p, TOBI_NS_METHOD, path, abs_start, flags & 0x07u, flags, 0, NULL); } } else { char *old_scope = p->scope; p->scope = tobi_xstrdup(path); scan_methods(p, &body, depth + 1); free(p->scope); p->scope = old_scope; } free(path); free(name); rd->pos = body_start + body_len; continue; } if (op == 0x15) { char *name = NULL; if (tobi_nm_namestring(rd, &name)) { uint8_t obj_type = 0; uint8_t argc = 0; if (tobi_rd_u8(rd, &obj_type) && tobi_rd_u8(rd, &argc) && obj_type == 0x08u) { char *path = tobi_nm_resolve(p->scope, name); ns_declare_path(p, TOBI_NS_METHOD, path, abs_start, argc & 0x07u, obj_type, 1, NULL); free(path); } free(name); } continue; } if (op == 0x5b) { uint8_t ext = 0; if (!tobi_rd_u8(rd, &ext)) { return; } if (ext == 0x82 || ext == 0x83 || ext == 0x84 || ext == 0x85) { size_t body_start = 0; size_t body_len = 0; if (!scan_pkg_window(rd, &body_start, &body_len)) { rd->pos = rd->len; return; } tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); char *name = NULL; if (tobi_nm_namestring(&body, &name)) { char *path = tobi_nm_resolve(p->scope, name); if (ext == 0x83) { (void)tobi_rd_skip(&body, 6); } else if (ext == 0x84) { (void)tobi_rd_skip(&body, 2); } char *old_scope = p->scope; p->scope = tobi_xstrdup(path); scan_methods(p, &body, depth + 1); free(p->scope); p->scope = old_scope; free(path); free(name); } rd->pos = body_start + body_len; continue; } if (ext == 0x81 || ext == 0x86 || ext == 0x87) { size_t body_start = 0; size_t body_len = 0; if (scan_pkg_window(rd, &body_start, &body_len)) { rd->pos = body_start + body_len; } continue; } } if (op == 0x11 || op == 0x12 || op == 0x13 || op == 0xa0 || op == 0xa1 || op == 0xa2) { size_t body_start = 0; size_t body_len = 0; if (scan_pkg_window(rd, &body_start, &body_len)) { if (op == 0xa0 || op == 0xa1 || op == 0xa2) { tobi_rd body; tobi_rd_init(&body, rd->data + body_start, body_len, rd->base + body_start); scan_methods(p, &body, depth + 1); } rd->pos = body_start + body_len; continue; } } if (rd->pos == start) { rd->pos++; } } } static void sem_diag_source(parser *p, size_t off, const char *source, size_t input_index, int has_source, const char *fmt, ...) { va_list ap; va_start(ap, fmt); va_list cp; va_copy(cp, ap); int n = vsnprintf(NULL, 0, fmt, cp); va_end(cp); if (n < 0) { n = 0; } char *msg = tobi_xmalloc((size_t)n + 1); (void)vsnprintf(msg, (size_t)n + 1, fmt, ap); va_end(ap); tobi_diag_add_source(p->diag, p->strict ? TOBI_DIAG_ERROR : TOBI_DIAG_WARN, off, source, input_index, has_source, "%s", msg); free(msg); } static void sem_diag_node(parser *p, const tobi_ir *n, const char *fmt, ...) { va_list ap; va_start(ap, fmt); va_list cp; va_copy(cp, ap); int sz = vsnprintf(NULL, 0, fmt, cp); va_end(cp); if (sz < 0) { sz = 0; } char *msg = tobi_xmalloc((size_t)sz + 1); (void)vsnprintf(msg, (size_t)sz + 1, fmt, ap); va_end(ap); sem_diag_source(p, n ? n->off : 0, n ? n->source : p->source, n ? n->input_index : p->input_index, n ? n->has_source : p->has_source, "%s", msg); free(msg); } static int semantic_alias_cycle_from(const tobi_ns *ns, const tobi_ns_ent *start) { const tobi_ns_ent *cur = start; for (unsigned depth = 0; cur && cur->kind == TOBI_NS_ALIAS && cur->target; depth++) { if (depth > ns->len) { return 1; } const tobi_ns_ent *next = tobi_ns_find_const(ns, cur->target); if (!next) { return 0; } if (next == start) { return 1; } cur = next; } return 0; } static int semantic_ref_is_value(const tobi_ir *parent) { if (!parent) { return 1; } if (parent->kind == TOBI_IR_EXPR && parent->name) { if (strncmp(parent->name, "create_", 7) == 0 || strcmp(parent->name, "cond_ref_of") == 0 || strcmp(parent->name, "load") == 0 || strcmp(parent->name, "ref_of") == 0 || strcmp(parent->name, "acquire") == 0 || strcmp(parent->name, "wait") == 0 || strcmp(parent->name, "signal") == 0 || strcmp(parent->name, "reset") == 0 || strcmp(parent->name, "release") == 0) { return 0; } } return 1; } static void semantic_walk(parser *p, const tobi_ir *n, const tobi_ir *parent) { if (!n) { return; } if (n->kind == TOBI_IR_CALL && n->path && n->path[0] == '\\') { const tobi_ns_ent *ent = tobi_ns_find_const(p->ns, n->path); if (!ent || ent->kind != TOBI_NS_METHOD) { sem_diag_node(p, n, "unresolved AML method call %s", n->path); } else if (n->child_len != (size_t)ent->args) { sem_diag_node(p, n, "AML method call %s has %zu args but namespace declares %u", n->path, n->child_len, ent->args); } } else if (n->kind == TOBI_IR_REF && n->path && n->path[0] == '\\' && semantic_ref_is_value(parent)) { const tobi_ns_ent *ent = tobi_ns_find_const(p->ns, n->path); if (!ent) { sem_diag_node(p, n, "unresolved AML namespace reference %s", n->path); } } for (size_t i = 0; i < n->child_len; i++) { semantic_walk(p, n->child[i], n); } } typedef struct method_state { parser *p; tobi_ir *method; uint16_t locals; uint16_t local_reads; uint16_t local_writes; uint16_t arg_reads; uint16_t arg_writes; unsigned warnings; } method_state; static int ref_index(const tobi_ir *n, const char *prefix, unsigned max, unsigned *idx) { size_t plen = strlen(prefix); if (!n || n->kind != TOBI_IR_REF || !n->name || strncmp(n->name, prefix, plen) != 0) { return 0; } char c = n->name[plen]; if (n->name[plen + 1u] != '\0' || c < '0' || c > (char)('0' + max)) { return 0; } *idx = (unsigned)(c - '0'); return 1; } static void method_mark_ref(method_state *st, tobi_ir *n, int write_target) { unsigned idx = 0; if (ref_index(n, "Local", 7, &idx)) { if (write_target) { st->locals |= (uint16_t)(1u << idx); st->local_writes |= (uint16_t)(1u << idx); tobi_ir_set_str(n, "method_symbol=local write=initialised"); } else if ((st->locals & (uint16_t)(1u << idx)) == 0) { st->local_reads |= (uint16_t)(1u << idx); st->warnings++; sem_diag_node(st->p, n, "Local%u may be read before being initialised in method %s", idx, st->method->path ? st->method->path : st->method->name); tobi_ir_set_str(n, "method_symbol=local read=uninitialised"); } else { st->local_reads |= (uint16_t)(1u << idx); tobi_ir_set_str(n, "method_symbol=local read=initialised"); } } else if (ref_index(n, "Arg", 6, &idx)) { if (write_target) { st->arg_writes |= (uint16_t)(1u << idx); } else { st->arg_reads |= (uint16_t)(1u << idx); } if (idx >= st->method->method_args) { st->warnings++; sem_diag_node(st->p, n, "Arg%u %s exceeds declared argument count %u in method %s", idx, write_target ? "write" : "read", st->method->method_args, st->method->path ? st->method->path : st->method->name); tobi_ir_set_str(n, write_target ? "method_symbol=arg write=out_of_range" : "method_symbol=arg read=out_of_range"); } else if (write_target) { tobi_ir_set_str(n, "method_symbol=arg write=declared"); } else { tobi_ir_set_str(n, "method_symbol=arg read=declared"); } } } static void method_symbols_node(method_state *st, tobi_ir *n); static void method_symbols_block(method_state *st, tobi_ir *b) { if (!b) { return; } for (size_t i = 0; i < b->child_len; i++) { method_symbols_node(st, b->child[i]); } } static void method_symbols_node(method_state *st, tobi_ir *n) { if (!n) { return; } if (n->kind == TOBI_IR_STORE) { if (n->child_len > 0) { method_symbols_node(st, n->child[0]); } if (n->child_len > 1) { unsigned idx = 0; if (ref_index(n->child[1], "Local", 7, &idx) || ref_index(n->child[1], "Arg", 6, &idx)) { method_mark_ref(st, n->child[1], 1); } else { method_symbols_node(st, n->child[1]); } } return; } if (n->kind == TOBI_IR_REF) { method_mark_ref(st, n, 0); return; } if (n->kind == TOBI_IR_EXPR && n->name) { size_t target_start = n->child_len; const tobi_op_expr *info = tobi_op_expr_find_name(n->name); if (info && info->target_count > 0 && n->child_len >= (size_t)info->arg_count + info->target_count) { target_start = info->arg_count; } for (size_t i = 0; i < target_start; i++) { method_symbols_node(st, n->child[i]); } for (size_t i = target_start; i < n->child_len; i++) { unsigned idx = 0; if (ref_index(n->child[i], "Local", 7, &idx) || ref_index(n->child[i], "Arg", 6, &idx)) { method_mark_ref(st, n->child[i], 1); } else { method_symbols_node(st, n->child[i]); } } return; } if (n->kind == TOBI_IR_IF) { if (n->child_len > 0) { method_symbols_node(st, n->child[0]); } uint16_t base = st->locals; uint16_t then_locals = base; uint16_t else_locals = base; if (n->child_len > 1) { st->locals = base; method_symbols_block(st, n->child[1]); then_locals = st->locals; } if (n->child_len > 2) { st->locals = base; method_symbols_block(st, n->child[2]); else_locals = st->locals; st->locals = then_locals & else_locals; } else { st->locals = base; } return; } if (n->kind == TOBI_IR_WHILE) { if (n->child_len > 0) { method_symbols_node(st, n->child[0]); } uint16_t base = st->locals; if (n->child_len > 1) { st->locals = base; method_symbols_block(st, n->child[1]); } st->locals = base; return; } for (size_t i = 0; i < n->child_len; i++) { method_symbols_node(st, n->child[i]); } } static void semantic_methods(parser *p, tobi_ir *n) { if (!n) { return; } if (n->kind == TOBI_IR_METHOD) { method_state st; memset(&st, 0, sizeof(st)); st.p = p; st.method = n; if (n->child_len > 0) { method_symbols_block(&st, n->child[0]); } char summary[160]; (void)snprintf(summary, sizeof(summary), "method_symbols args=%u local_reads=0x%x local_writes=0x%x final_locals=0x%x arg_reads=0x%x arg_writes=0x%x warnings=%u", n->method_args, st.local_reads, st.local_writes, st.locals, st.arg_reads, st.arg_writes, st.warnings); tobi_ir_set_target(n, summary); } for (size_t i = 0; i < n->child_len; i++) { semantic_methods(p, n->child[i]); } } static void semantic_validate(parser *p, tobi_ir *root) { for (size_t i = 0; i < p->ns->len; i++) { const tobi_ns_ent *e = &p->ns->items[i]; if (e->external && e->kind == TOBI_NS_METHOD) { sem_diag_source(p, e->off, e->source, e->input_index, e->has_source, "external AML method %s remains unresolved", e->path); } if (e->kind == TOBI_NS_ALIAS) { if (e->target && !tobi_ns_find_const(p->ns, e->target)) { sem_diag_source(p, e->off, e->source, e->input_index, e->has_source, "AML alias %s targets unresolved object %s", e->path, e->target); } else if (semantic_alias_cycle_from(p->ns, e)) { sem_diag_source(p, e->off, e->source, e->input_index, e->has_source, "AML alias cycle includes %s", e->path); } } } semantic_walk(p, root, NULL); semantic_methods(p, root); } int tobi_parse(const uint8_t *data, size_t len, int strict, tobi_parse_result *out) { memset(out, 0, sizeof(*out)); tobi_diag_init(&out->diag); tobi_ns_init(&out->ns); if (!detect_input(data, len, strict, &out->diag, &out->meta, "raw", 0, 1)) { return 0; } out->inputs = tobi_xcalloc(1u, sizeof(out->inputs[0])); input_meta_copy(&out->inputs[0], &out->meta); out->input_len = 1; out->root = tobi_ir_new(TOBI_IR_ROOT, out->meta.aml_off, out->meta.aml_len); tobi_ir_set_scope(out->root, "\\"); tobi_ir_set_source(out->root, out->meta.source, 0); parser p; memset(&p, 0, sizeof(p)); p.diag = &out->diag; p.strict = strict; p.ns = &out->ns; p.scope = tobi_xstrdup("\\"); p.source = out->meta.source; p.input_index = 0; p.has_source = 1; tobi_rd rd; if (out->meta.aml_off <= len && out->meta.aml_len <= len - out->meta.aml_off) { tobi_rd scan; tobi_rd_init(&scan, data + out->meta.aml_off, out->meta.aml_len, out->meta.aml_off); scan_methods(&p, &scan, 0); tobi_rd_init(&rd, data + out->meta.aml_off, out->meta.aml_len, out->meta.aml_off); (void)parse_term_list(&p, &rd, out->root, 0); } semantic_validate(&p, out->root); tobi_ir_assign_ids(out->root); parser_free(&p); return !(strict && tobi_diag_has_error(&out->diag)); } int tobi_parse_multi(const tobi_parse_input *inputs, size_t count, int strict, tobi_parse_result *out) { memset(out, 0, sizeof(*out)); tobi_diag_init(&out->diag); tobi_ns_init(&out->ns); memcpy(out->meta.signature, "MULT", 4); out->meta.signature[4] = '\0'; input_meta_set_source(&out->meta, "multi"); out->root = tobi_ir_new(TOBI_IR_ROOT, 0, 0); tobi_ir_set_scope(out->root, "\\"); tobi_ir_set_source(out->root, "multi", 0); if (!inputs || count == 0) { tobi_diag_add(&out->diag, TOBI_DIAG_ERROR, 0, "no AML inputs"); return 0; } tobi_input_meta *metas = tobi_xcalloc(count, sizeof(metas[0])); out->inputs = metas; out->input_len = count; int ok = 1; parser p; memset(&p, 0, sizeof(p)); p.diag = &out->diag; p.strict = strict; p.ns = &out->ns; p.scope = tobi_xstrdup("\\"); for (size_t i = 0; i < count; i++) { p.source = inputs[i].name; p.input_index = i; p.has_source = inputs[i].name != NULL; if (!detect_input(inputs[i].data, inputs[i].len, strict, &out->diag, &metas[i], inputs[i].name, i, inputs[i].name != NULL)) { ok = 0; continue; } out->meta.aml_len = tobi_xadd_size(out->meta.aml_len, metas[i].aml_len); if (metas[i].aml_off <= inputs[i].len && metas[i].aml_len <= inputs[i].len - metas[i].aml_off) { tobi_rd scan; tobi_rd_init(&scan, inputs[i].data + metas[i].aml_off, metas[i].aml_len, metas[i].aml_off); scan_methods(&p, &scan, 0); } } if (!strict || ok) { for (size_t i = 0; i < count; i++) { if (metas[i].aml_off > inputs[i].len || metas[i].aml_len > inputs[i].len - metas[i].aml_off) { continue; } p.source = inputs[i].name; p.input_index = i; p.has_source = inputs[i].name != NULL; tobi_ir *file = tobi_ir_new(TOBI_IR_BLOCK, metas[i].aml_off, metas[i].aml_len); tobi_ir_set_name(file, inputs[i].name ? inputs[i].name : ""); tobi_ir_set_scope(file, "\\"); if (inputs[i].name) { tobi_ir_set_source(file, inputs[i].name, i); } tobi_rd rd; tobi_rd_init(&rd, inputs[i].data + metas[i].aml_off, metas[i].aml_len, metas[i].aml_off); (void)parse_term_list(&p, &rd, file, 0); tobi_ir_add(out->root, file); } } semantic_validate(&p, out->root); tobi_ir_assign_ids(out->root); parser_free(&p); return ok && !(strict && tobi_diag_has_error(&out->diag)); } void tobi_parse_result_free(tobi_parse_result *res) { tobi_ir_free(res->root); res->root = NULL; input_meta_free(&res->meta); for (size_t i = 0; i < res->input_len; i++) { input_meta_free(&res->inputs[i]); } free(res->inputs); res->inputs = NULL; res->input_len = 0; tobi_ns_free(&res->ns); tobi_diag_free(&res->diag); }