#include "instr.h" #include "wasm.h" #include "types.h" #include #include #include #include #include #include #include #include #include const char *const Wasm::names[NSECTIONS] = { "", ".debug_abbrev", ".debug_info", ".debug_str", ".debug_aranges", ".debug_frame", ".debug_line", ".debug_loc", ".debug_macinfo", ".debug_pubnames", ".debug_pubtypes", ".debug_ranges", ".debug_types" }; static void errcb(const Dwarf_Error e, const Dwarf_Ptr args) { fprintf(stderr, "%s\n", __func__); } int Wasm::get_section_info(void *const obj, const Dwarf_Half section_index, Dwarf_Obj_Access_Section *const return_section, int *const error) { const Wasm *const w = static_cast(obj); if (section_index >= sizeof w->sections / sizeof *w->sections) { fprintf(stderr, "%s: invalid section_index: %hd\n", __func__, section_index); return DW_DLV_ERROR; } const struct dwsection *const s = &w->sections[section_index]; if (!names[section_index] && !s->len) return DW_DLV_NO_ENTRY; return_section->size = s->len; return_section->addr = s->offset; return_section->name = names[section_index]; return 0; } static Dwarf_Endianness get_byte_order(void *const obj) { return DW_OBJECT_LSB; } static Dwarf_Small get_length_size(void *const obj) { return sizeof (uint32_t); } static Dwarf_Small get_pointer_size(void *const obj) { return sizeof (uint32_t); } Dwarf_Unsigned Wasm::get_section_count(void *const obj) { const Wasm *const w = static_cast(obj); Dwarf_Unsigned ret = 0; for (size_t i = 0; i < sizeof w->sections / sizeof *w->sections; i++) if (w->sections[i].offset) ret++; return ret; } int Wasm::load_section(void *const obj, const Dwarf_Half section_index, Dwarf_Small **const return_data, int *const error) { Wasm *const w = static_cast(obj); FILE *const f = w->f; struct dwsection *s; const char *name; if (section_index >= NSECTIONS) { fprintf(stderr, "%s: invalid section_index %hd\n", __func__, section_index); return DW_DLV_ERROR; } s = &w->sections[section_index]; name = names[section_index]; QVector &p = s->data; if (!s->offset) { fprintf(stderr, "%s: unavailable section: %s\n", __func__, name); return DW_DLV_ERROR; } else if (!s->len) { fprintf(stderr, "%s: unexpected zero length for section: %s\n", __func__, name); return DW_DLV_ERROR; } p.reserve(s->len); if (fseek(f, s->offset, SEEK_SET)) { fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno)); return DW_DLV_ERROR; } else if (!fread(p.data(), s->len, 1, f)) { fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n", __func__, feof(f), ferror(f)); return DW_DLV_ERROR; } *return_data = p.data(); return DW_DLV_OK; } int Wasm::check_header(QString &error) const { uint8_t magic[sizeof "asm"], version[sizeof (uint32_t)]; static const uint8_t exp[sizeof version] = {1, 0, 0, 0}; if (!fread(magic, sizeof magic, 1, f)) { error = QString("fread(3) magic failed, feof=") + QString::number(feof(f)) + ", ferror=" + QString::number(ferror(f)); return -1; } else if (memcmp(magic, "\0asm", sizeof magic)) { error = "invalid magic number"; return -1; } else if (!fread(version, sizeof version, 1, f)) { error = QString("fread(3) version failed, feof=") + QString::number(feof(f)) + ", ferror=" + QString::number(ferror(f)); return -1; } else if (memcmp(version, exp, sizeof version)) { error = "invalid version number"; return -1; } return 0; } int Wasm::read_name(QString &name, QString &error) const { varuint32 name_len; if (read_varuint32(f, &name_len)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < name_len; i++) { uint8_t c; const size_t r = fread(&c, sizeof c, 1, f); if (!r) { error = "fread(3) failed, ferror(3)=" + QString::number(ferror(f)) + "feof(3)=" + QString::number(feof(f)); return -1; } name += c; } return 0; } int Wasm::parse_header(section &s, QString &error) const { varuint7 code; varuint32 payload_len; QString name; long before, after; if (read_varuint7(f, &code)) { /* Exceptionally, this might not be a fatal error. */ if (!feof(f)) error = "read_varuint7 failed"; return -1; } else if (read_varuint32(f, &payload_len)) { error = "read_varuint32 failed"; return -1; } else if ((before = ftell(f)) < 0) { error = QString("ftell(3) before: ") + strerror(errno); return -1; } else if (!code && read_name(name, error)) return -1; else if ((after = ftell(f)) < 0) { error = QString("ftell(3) after: ") + strerror(errno); return -1; } const unsigned long sz = after - before; if (payload_len < sz) { error = "section " + QString::number(code) + ": expected payload_len >= " + QString::number(sz) + ", got " + QString::number(payload_len); return -1; } s.code = code; s.len = payload_len - sz; s.name = name; return 0; } int Wasm::skip(const section &s, QString &error) const { const int ret = fseek(f, s.len, SEEK_CUR); if (ret) error = QString("fseek(3): ") + strerror(errno); return ret; } Wasm::dwsection *Wasm::getdws(const QString &name) { for (size_t i = 0; i < sizeof names / sizeof *names; i++) if (name == names[i]) return §ions[i]; return NULL; } int Wasm::parse_global(QString &error) { varuint32 count; if (read_varuint32(f, &count)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < count; i++) { WasmGlobal gl; WasmType wtype; value_type content_type; varuint1 mutability; if (read_value_type(f, &content_type)) { error = "read_value_type failed"; return -1; } else if (get_type(content_type, wtype)) { error = "Invalid type for global " + QString::number(i) + ": 0x" + QString::number(content_type, 16); return -1; } else if (read_varuint1(f, &mutability)) { error = "read_varuint1 failed"; return -1; } int ret; bool end; WasmInstr instr; while (!(ret = WasmInstr::parse(f, instr, error, &end)) && !end) ; if (ret) return ret; gl.mutability = mutability; gl.type = wtype; vglobals.push_back(gl); } return 0; } int Wasm::parse_import(QString &error) { varuint32 fncnt = 0, count; if (read_varuint32(f, &count)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < count; i++) { varuint32 module_len, field_len; quint8 external_kind; enum {FUNCTION, TABLE, MEMORY, GLOBAL}; if (read_varuint32(f, &module_len)) { error = "read_varuint32 failed"; return -1; } else if (fseek(f, module_len, SEEK_CUR)) { error = QString("fseek(3): ") + strerror(errno); return -1; } else if (read_varuint32(f, &field_len)) { error = "read_varuint32 failed"; return -1; } else if (fseek(f, field_len, SEEK_CUR)) { error = QString("fseek(3): ") + strerror(errno); return -1; } else if (!fread(&external_kind, sizeof external_kind, 1, f)) { error = "fread(3) failed, ferror(3)=" + QString::number(ferror(f)) + ", feof(3)=" + QString::number(feof(f)); return -1; } else if (external_kind == FUNCTION) { varuint32 type; if (read_varuint32(f, &type)) { error = "read_varuint32 failed"; return -1; } fncnt++; } } import_fncnt = fncnt; return 0; } int Wasm::get_type(const value_type type, WasmType &wtype) const { static const struct { value_type v; WasmType wtype; } types[] = { {0x7f, WasmType::i32}, {0x7e, WasmType::i64}, {0x7d, WasmType::f32}, {0x7c, WasmType::f64} }; for (const auto &t : types) if (type == t.v) { wtype = t.wtype; return 0; } return -1; } int Wasm::parse_body(const varuint32 index, QString &error) { varuint32 body_size, local_count; QVector locals; long start; if (read_varuint32(f, &body_size)) { error = "read_varuint32 failed"; return -1; } else if ((start = ftell(f)) < 0) { error = QString("ftell(3)") + strerror(errno); return -1; } else if (read_varuint32(f, &local_count)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < local_count; i++) { varuint32 count; value_type type; WasmType wtype; if (read_varuint32(f, &count)) { error = "read_varuint32 failed"; return -1; } else if (read_value_type(f, &type)) { error = "read_value_type failed"; return -1; } else if (get_type(type, wtype)) { error = "Invalid value_type for function " + QString::number(index) + ": 0x" + QString::number(type, 16); return -1; } for (varuint32 i = 0; i < count; i++) { WasmLocal l; l.type = wtype; locals.push_back(l); } } const long end = ftell(f); if (end < 0) { error = QString("ftell(3)") + strerror(errno); return -1; } const unsigned long delta = end - start; if (delta >= body_size) { error = "Function " + QString::number(index) + " exceeds maximum body preamble size (" + QString::number(delta) + "/" + QString::number(body_size) + ")"; return -1; } const unsigned long len = body_size - delta; QVector instr; if (WasmInstr::parse(f, len, instr, error)) return -1; WasmRoutine r; r.lopc = end; r.hipc = r.lopc + len; r.instructions = instr; r.locals = locals; fmap[index + import_fncnt] = r; return 0; } int Wasm::parse_code(QString &error) { varuint32 count; if (read_varuint32(f, &count)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < count; i++) if (parse_body(i, error)) return -1; return 0; } int Wasm::parse_fname(QString &error, const varuint32 maxlen) { varuint32 count; const long start = ftell(f); if (start < 0) { error = QString("ftell(3): ") + strerror(errno); return -1; } else if (read_varuint32(f, &count)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < count; i++) { varuint32 index, name_len; QString name_payload_data; if (read_varuint32(f, &index) || read_varuint32(f, &name_len)) { error = "read_varuint32 failed"; return -1; } for (varuint32 i = 0; i < name_len; i++) { quint8 b; if (!fread(&b, sizeof b, 1, f)) { error = "fread(3) failed, ferror(3)=" + QString::number(ferror(f)) + ", feof(3)=" + QString::number(feof(f)); return -1; } name_payload_data += b; } if (index >= import_fncnt) { if (!fmap.contains(index)) { error = "Could not find function index " + QString::number(index) + " from code section (" + name_payload_data + ")"; return -1; } auto r = fmap[index]; r.name = name_payload_data; fmap[index] = r; } } const long end = ftell(f); if (end < 0) { error = QString("ftell(3): ") + strerror(errno); return -1; } const unsigned long delta = end - start; if (delta != maxlen) { error = "Name entry mismatches expected length (" + QString::number(delta) + "/" + QString::number(maxlen) + ")"; return -1; } return 0; } int Wasm::parse_name(QString &error, varuint32 len) { varuint7 name_type; varuint32 name_payload_len; enum {MODULE, FUNCTION, LOCAL}; while (len) { if (read_varuint7(f, &name_type)) { error = "read_varuint7 failed"; return -1; } else if (read_varuint32(f, &name_payload_len)) { error = "read_varuint32 failed"; return -1; } else if (!name_payload_len) { error = "Invalid zero name_payload_len"; return -1; } else if (name_payload_len > len) { error = "name_payload_len exceeds maximum allowed length (" + QString::number(name_payload_len) + "/" + QString::number(len) + ")"; return -1; } else if (name_type != FUNCTION) { if (name_type != LOCAL && name_type != MODULE) { error = "Invalid name_type: 0x" + QString::number(name_type, 16); return -1; } else if (fseek(f, name_payload_len, SEEK_CUR)) { error = QString("fseek(3): ") + strerror(errno); return -1; } } else return parse_fname(error, name_payload_len); len -= name_payload_len; } return 0; } int Wasm::read_section(QString &error) { struct section s; struct dwsection *dws; long start, offset; if ((start = ftell(f)) < 0) { error = QString("ftell(3) start") + strerror(errno); return -1; } else if (parse_header(s, error)) return -1; else if (s.code == SECTION_GLOBAL) return parse_global(error); else if (s.code == SECTION_IMPORT) return parse_import(error); else if (s.code == SECTION_CODE) { code_offset = start; return parse_code(error); } else if (s.code != SECTION_CUSTOM) return skip(s, error); else if (!(dws = getdws(s.name))) { if (s.name == "name") return parse_name(error, s.len); else return skip(s, error); } else if ((offset = ftell(f)) < 0) { error = QString("ftell(3) offset") + strerror(errno); return -1; } else { dws->len = s.len; dws->offset = offset; return skip(s, error); } return 0; } int Wasm::parse(QString &error) { if (check_header(error)) return -1; for (;;) if (read_section(error)) { if (feof(f)) break; return -1; } return 0; } bool Wasm::isformstring(const Dwarf_Half form) { switch (form) { case DW_FORM_string: case DW_FORM_GNU_strp_alt: case DW_FORM_GNU_str_index: case DW_FORM_strx: case DW_FORM_strx1: case DW_FORM_strx2: case DW_FORM_strx3: case DW_FORM_strx4: case DW_FORM_strp: return true; default: break; } return false; } bool Wasm::isformudata(const Dwarf_Half form) { switch (form) { case DW_FORM_data1: case DW_FORM_data2: case DW_FORM_data4: case DW_FORM_data8: case DW_FORM_udata: return true; default: break; } return false; } bool Wasm::isformsdata(const Dwarf_Half form) { switch (form) { case DW_FORM_data1: case DW_FORM_data2: case DW_FORM_data4: case DW_FORM_data8: case DW_FORM_sdata: return true; default: break; } return false; } int Wasm::read_attr(const Dwarf_Attribute a, Dwarf_Error &e, QString &error, attr &attr) { Dwarf_Half num, form; const char *atname; int res = dwarf_whatattr(a, &num, &e); if (res != DW_DLV_OK) { error = "dwarf_whatattr failed with " + QString::number(res); return -1; } else if ((res = dwarf_whatform(a, &form, &e)) != DW_DLV_OK) { error = "dwarf_whatform failed with " + QString::number(res); return -1; } else if ((res = dwarf_get_AT_name(num, &atname)) != DW_DLV_OK) { error = "dwarf_get_AT_name failed with " + QString::number(res); return -1; } attr.name = atname; /* TODO: extract type */ if (isformstring(form)) { char *s; if ((res = dwarf_formstring(a, &s, &e)) != DW_DLV_OK) { error = "dwarf_formstring failed with " + QString::number(res); return -1; } attr.value = QVariant(s); } else if (form == DW_FORM_addr) { Dwarf_Addr addr; if ((res = dwarf_formaddr(a, &addr, &e)) != DW_DLV_OK) { error = "dwarf_formaddr failed with " + QString::number(res); return -1; } attr.value = QVariant(addr); } else if (isformudata(form)) { Dwarf_Unsigned v; if ((res = dwarf_formudata(a, &v, &e)) != DW_DLV_OK) { error = "dwarf_formudata failed with " + QString::number(res); return -1; } attr.value = QVariant(v); } else if (isformudata(form)) { Dwarf_Signed v; if ((res = dwarf_formsdata(a, &v, &e)) != DW_DLV_OK) { error = "dwarf_formsdata failed with " + QString::number(res); return -1; } attr.value = QVariant(v); } return 0; } int Wasm::process_die(const Dwarf_Debug dbg, const Dwarf_Die die, Dwarf_Error &e, QString &error) { Dwarf_Half tag; Dwarf_Attribute *attrs = NULL; Dwarf_Signed nattr = 0; const char *name; QHash attrmap; int ret = -1, r = dwarf_tag(die, &tag, &e); if (r) { error = "dwarf_tag failed with" + QString::number(r); goto end; } else if ((r = dwarf_get_TAG_name(tag, &name))) name = ""; if ((r = dwarf_attrlist(die, &attrs, &nattr, &e)) == DW_DLV_ERROR) { error = "dwarf_attrlist failed"; goto end; } else if (r == DW_DLV_OK) for (Dwarf_Signed i = 0; i < nattr; i++) { attr attr; if (read_attr(attrs[i], e, error, attr)) goto end; attrmap[attr.name] = attr.value; } if (!strcmp(name, "DW_TAG_subprogram")) { if (code_offset == -1) { error = "Code section expected"; goto end; } else if (attrmap.contains("DW_AT_low_pc") && attrmap["DW_AT_low_pc"].toUInt() != -1u && attrmap.contains("DW_AT_high_pc")) { WasmRoutine wr; auto name = attrmap["DW_AT_name"].toString(); auto lopc = attrmap["DW_AT_low_pc"].toUInt() + code_offset, hipc = attrmap["DW_AT_high_pc"].toUInt() + lopc; auto r = routine(name); if (r.isValid() || (r = routine(lopc)).isValid() || (r = routine(hipc)).isValid()) lastwr = QVariant::fromValue(fmap.key(r.value())); else lastwr.clear(); } else lastwr.clear(); } else if (!strcmp(name, "DW_TAG_formal_parameter") && attrmap.contains("DW_AT_name") && !lastwr.isNull()) { const auto index = lastwr.toUInt(); WasmRoutine r = fmap[index]; WasmParam wp; wp.name = attrmap["DW_AT_name"].toString(); wp.dwtype = attrmap["DW_AT_type"].toString(); r.params.push_back(wp); fmap[index] = r; } ret = 0; end: for (Dwarf_Signed i = 0; i < nattr; i++) dwarf_dealloc_attribute(attrs[i]); dwarf_dealloc(dbg, attrs, DW_DLA_LIST); return ret; } int Wasm::read_child(const Dwarf_Debug dbg, Dwarf_Die die, Dwarf_Error &e, QString &error) { for (;;) { int r; Dwarf_Die child, sibling; if (process_die(dbg, die, e, error)) return -1; else if ((r = dwarf_child(die, &child, &e)) == DW_DLV_OK) { const int ret = read_child(dbg, child, e, error); dwarf_dealloc_die(child); if (ret) return ret; } else if (r == DW_DLV_ERROR) { error = "dwarf_child failed"; return -1; } if ((r = dwarf_siblingof(dbg, die, &sibling, &e)) == DW_DLV_ERROR) { error = "dwarf_siblingof failed"; return -1; } else if (r == DW_DLV_NO_ENTRY) break; die = sibling; } return 0; } int Wasm::read_dwarf(const Dwarf_Debug dbg, header &h, Dwarf_Error &e, QString &error) { Dwarf_Die die = 0; int ret = -1; for (;;) { int r = dwarf_next_cu_header_d(dbg, 1, &h.cu_header_length, &h.version_stamp, &h.abbrev_offset, &h.address_size, &h.length_size, &h.extension_size, &h.type_signature, &h.typeoffset, &h.next_cu_header_offset, &h.header_cu_type, &e); die = 0; if (r == DW_DLV_ERROR) { error = "dwarf_next_cu_header_d failed"; goto end; } else if (r == DW_DLV_NO_ENTRY) break; else if ((r = dwarf_siblingof(dbg, NULL, &die, &e)) == DW_DLV_OK) { if (read_child(dbg, die, e, error)) goto end; } else if (r == DW_DLV_ERROR) { error = "dwarf_siblingof failed with " + QString::number(r); goto end; } else if (DW_DLV_NO_ENTRY) break; dwarf_dealloc_die(die); } ret = 0; end: dwarf_dealloc_die(die); return ret; } int Wasm::open(const QString &path, QString &error) { int ret = -1, result; header h; Dwarf_Obj_Access_Methods m = {0}; Dwarf_Obj_Access_Interface aitf = {0}; Dwarf_Debug dbg = 0; Dwarf_Error dwerr = 0; const std::string spath = path.toStdString(); const char *const cpath = spath.c_str(); m.get_section_info = get_section_info; m.get_byte_order = get_byte_order; m.get_length_size = get_length_size; m.get_pointer_size = get_pointer_size; m.get_section_count = get_section_count; m.load_section = load_section; aitf.object = this; aitf.methods = &m; if (!(f = fopen(cpath, "rb"))) { error = "Could not open " + path; goto end; } else if (parse(error)) { error = "Failed to parse " + path + ":\n" + error; goto end; } else if ((result = dwarf_object_init(&aitf, errcb, this, &dbg, &dwerr)) != DW_DLV_OK) { error = "dwarf_object_init failed with " + QString::number(result) + ": " + dwarf_errmsg(dwerr); goto end; } else if (read_dwarf(dbg, h, dwerr, error)) goto end; ret = 0; end: if (f && fclose(f)) { error = "fclose(3) " + path + ": " + strerror(errno); ret = -1; } else f = nullptr; if (dbg && dwarf_object_finish(dbg, &dwerr)) { error = QString("dwarf_object_finish failed with: ") + dwarf_errmsg(dwerr); ret = -1; } return ret; } QVariant Wasm::routine(const QString &name) const { for (const auto &r : fmap.values()) if (r.name == name) return QVariant::fromValue(r); return QVariant(); } QVariant Wasm::routine(const quint32 addr) const { for (const auto &r : fmap.values()) if (addr >= r.lopc && addr <= r.hipc) return QVariant::fromValue(r); return QVariant(); } const QVector Wasm::globals() const { return vglobals; } Wasm::Wasm() : f(nullptr), code_offset(-1), import_fncnt(0) { } Wasm::~Wasm() { if (f && fclose(f)) std::runtime_error(std::string("fopen(3): ") + strerror(errno)); }