/* * wadb, a WebAssembly debugger * Copyright (C) 2025 Xavier Del Campo Romero * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #define _POSIX_C_SOURCE 200809L #include #include #include "types.h" #include #include #include #include #include #include #include #include #include #include enum { DEBUG_EMPTY, DEBUG_ABBREV, DEBUG_INFO, DEBUG_STR, DEBUG_ARANGES, DEBUG_FRAME, DEBUG_LINE, DEBUG_LOC, DEBUG_MACINFO, DEBUG_PUBNAMES, DEBUG_PUBTYPES, DEBUG_RANGES, DEBUG_TYPES, NSECTIONS }; struct dwarf { FILE *f; struct dwsection { long offset; varuint32 len; void *data; } sections[NSECTIONS]; }; struct header { Dwarf_Sig8 type_signature; Dwarf_Unsigned cu_header_length, typeoffset, next_cu_header_offset; Dwarf_Half version_stamp, address_size, length_size, extension_size, header_cu_type; Dwarf_Off abbrev_offset; }; static const char *const names[NSECTIONS] = { [DEBUG_EMPTY] = "", [DEBUG_ABBREV] = ".debug_abbrev", [DEBUG_ARANGES] = ".debug_aranges", [DEBUG_FRAME] = ".debug_frame", [DEBUG_INFO] = ".debug_info", [DEBUG_LINE] = ".debug_line", [DEBUG_LOC] = ".debug_loc", [DEBUG_MACINFO] = ".debug_macinfo", [DEBUG_PUBNAMES] = ".debug_pubnames", [DEBUG_PUBTYPES] = ".debug_pubtypes", [DEBUG_RANGES] = ".debug_ranges", [DEBUG_STR] = ".debug_str", [DEBUG_TYPES] = ".debug_types" }; static void errcb(const Dwarf_Error e, const Dwarf_Ptr args) { } static int get_section_info(void *const obj, const Dwarf_Half section_index, Dwarf_Obj_Access_Section *const return_section, int *const error) { const struct dwarf *const dw = obj; if (section_index >= sizeof dw->sections / sizeof *dw->sections) { fprintf(stderr, "%s: invalid section_index: %hd\n", __func__, section_index); return DW_DLV_ERROR; } const struct dwsection *const s = &dw->sections[section_index]; if (!names[section_index] && !s->len) return DW_DLV_NO_ENTRY; *return_section = (const Dwarf_Obj_Access_Section) { .size = s->len, .addr = s->offset, .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); } static Dwarf_Unsigned get_section_count(void *const obj) { const struct dwarf *const dw = obj; Dwarf_Unsigned ret = 0; for (size_t i = 0; i < sizeof dw->sections / sizeof *dw->sections; i++) if (dw->sections[i].offset) ret++; return ret; } static int load_section(void *const obj, const Dwarf_Half section_index, Dwarf_Small **const return_data, int *const error) { struct dwarf *const dw = obj; FILE *const f = dw->f; Dwarf_Small *p = NULL; if (section_index >= NSECTIONS) { fprintf(stderr, "%s: invalid section_index %hd\n", __func__, section_index); goto failure; } struct dwsection *const s = &dw->sections[section_index]; const char *const name = names[section_index]; if (!s->offset) { fprintf(stderr, "%s: unavailable section: %s\n", __func__, name); goto failure; } else if (!s->len) { fprintf(stderr, "%s: unexpected zero length for section: %s\n", __func__, name); goto failure; } else if (!(p = malloc(s->len))) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } else if (fseek(f, s->offset, SEEK_SET)) { fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno)); goto failure; } else if (!fread(p, s->len, 1, f)) { fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n", __func__, feof(f), ferror(f)); goto failure; } s->data = p; *return_data = p; return DW_DLV_OK; failure: free(p); return DW_DLV_ERROR; } static int check_header(FILE *const f) { 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)) { fprintf(stderr, "%s: fread(3) magic failed, feof=%d, ferror=%d\n", __func__, feof(f), ferror(f)); return -1; } else if (memcmp(magic, "\0asm", sizeof magic)) { fprintf(stderr, "%s: invalid magic number\n", __func__); return -1; } else if (!fread(version, sizeof version, 1, f)) { fprintf(stderr, "%s: fread(3) version failed, feof=%d, ferror=%d\n", __func__, feof(f), ferror(f)); return -1; } else if (memcmp(version, exp, sizeof version)) { fprintf(stderr, "%s: invalid version number\n", __func__); return -1; } return 0; } static char *read_name(FILE *const f) { varuint32 name_len; char *name = malloc(1); if (!name) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } else if (read_varuint32(f, &name_len)) { fprintf(stderr, "%s: read_varuint32 failed\n", __func__); goto failure; } for (varuint32 i = 0; i < name_len; i++) { uint8_t c; const size_t r = fread(&c, sizeof c, 1, f); if (!r) { fprintf(stderr, "%s: fread(3) failed, ferror(3)=%d, feof(3)=%d\n", __func__, ferror(f), feof(f)); goto failure; } char *const newname = realloc(name, i + 2); if (!newname) { fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); goto failure; } name = newname; name[i] = c; } name[name_len] = '\0'; return name; failure: free(name); return NULL; } struct section { varuint7 code; varuint32 len; char *name; }; static int parse_header(FILE *const f, struct section *const s) { varuint7 code; varuint32 payload_len; char *name = NULL; long before, after; if (read_varuint7(f, &code)) { /* Exceptionally, this might not be a fatal error. */ if (!feof(f)) fprintf(stderr, "%s: read_varuint7 failed\n", __func__); goto failure; } else if (read_varuint32(f, &payload_len)) { fprintf(stderr, "%s: read_varuint32 failed\n", __func__); goto failure; } else if ((before = ftell(f)) < 0) { fprintf(stderr, "%s: ftell(3) before: %s\n", __func__, strerror(errno)); goto failure; } else if (!code && !(name = read_name(f))) { fprintf(stderr, "%s: read_name failed\n", __func__); goto failure; } else if ((after = ftell(f)) < 0) { fprintf(stderr, "%s: ftell(3) after: %s\n", __func__, strerror(errno)); goto failure; } const unsigned long sz = after - before; if (payload_len < sz) { fprintf(stderr, "%s: expected payload_len >= %lu, got %lu\n", __func__, sz, (unsigned long)payload_len); goto failure; } payload_len -= sz; *s = (const struct section) { .code = code, .len = payload_len, .name = name }; return 0; failure: free(name); return -1; } static int skip(FILE *const f, const struct section *const s) { const int ret = fseek(f, s->len, SEEK_CUR); if (ret) fprintf(stderr, "%s: fseek(3): %s\n", __func__, strerror(errno)); return ret; } static struct dwsection *getdws(struct dwarf *const dw, const char *const name) { for (size_t i = 0; i < sizeof names / sizeof *names; i++) if (!strcmp(name, names[i])) return &dw->sections[i]; return NULL; } static int read_section(FILE *const f, struct dwarf *const dw) { int ret = -1; struct section s = {0}; struct dwsection *dws; long offset; if (parse_header(f, &s)) { /* Exceptionally, this might not be a fatal error. */ if (!feof(f)) fprintf(stderr, "%s: parse_header failed\n", __func__); goto end; } else if (s.code != SECTION_CUSTOM || !(dws = getdws(dw, s.name))) { if (skip(f, &s)) { fprintf(stderr, "%s: skip failed\n", __func__); goto end; } } else if ((offset = ftell(f)) < 0) { fprintf(stderr, "%s: ftell(3): %s\n", __func__, strerror(errno)); goto end; } else { *dws = (const struct dwsection) { .len = s.len, .offset = offset }; if (skip(f, &s)) { fprintf(stderr, "%s: skip failed\n", __func__); goto end; } } ret = 0; end: free(s.name); return ret; } static int parse(struct dwarf *const dw) { FILE *const f = dw->f; if (check_header(f)) { fprintf(stderr, "%s: check_magic failed\n", __func__); return -1; } for (;;) if (read_section(f, dw)) { if (feof(f)) break; fprintf(stderr, "%s: read_section failed\n", __func__); return -1; } return 0; } static bool 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; } static int read_attr(const Dwarf_Attribute a, const unsigned level, Dwarf_Error *const e) { Dwarf_Half num, form; const char *atname, *fname; char *s = NULL; int res = dwarf_whatattr(a, &num, e); if (res) { fprintf(stderr, "%s: dwarf_whatattr failed with %d\n", __func__, res); return -1; } else if ((res = dwarf_whatform(a, &form, e))) { fprintf(stderr, "%s: dwarf_whatform failed with %d\n", __func__, res); return -1; } else if ((res = dwarf_get_AT_name(num, &atname))) { fprintf(stderr, "%s: dwarf_get_AT_name failed with %d\n", __func__, res); return -1; } else if ((res = dwarf_get_FORM_name(form, &fname))) { fprintf(stderr, "%s: dwarf_get_FORM_name failed with %d\n", __func__, res); return -1; } else if (isformstring(form) && (res = dwarf_formstring(a, &s, e))) { fprintf(stderr, "%s: dwarf_formstring failed with %d\n", __func__, res); return -1; } #if 0 for (unsigned i = 0; i < level; i++) putchar('\t'); printf("atname=%s, fname=%s", atname, fname); if (s) printf(", s=%s", s); putchar('\n'); #endif return 0; } static int print_die(const Dwarf_Debug dbg, const Dwarf_Die die, const unsigned level, Dwarf_Error *const e) { Dwarf_Half tag; Dwarf_Attribute *attrs = NULL; Dwarf_Signed nattr = 0; const char *name; int ret = -1, r = dwarf_tag(die, &tag, e); if (r) { fprintf(stderr, "%s: dwarf_tag failed with %d\n", __func__, r); goto end; } else if ((r = dwarf_get_TAG_name(tag, &name))) name = ""; if ((r = dwarf_attrlist(die, &attrs, &nattr, e))) { fprintf(stderr, "%s: dwarf_attrlist failed with %d\n", __func__, r); goto end; } for (Dwarf_Signed i = 0; i < nattr; i++) if (read_attr(attrs[i], level, e)) { fprintf(stderr, "%s: failed to read attribute %lld\n", __func__, i); goto end; } 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; } static int read_children(const Dwarf_Debug dbg, const Dwarf_Die die, const unsigned level, Dwarf_Error *const e) { int ret = -1; Dwarf_Die child; int r; if (print_die(dbg, die, level, e)) { fprintf(stderr, "%s: failed to print die\n", __func__); return -1; } else if (!(r = dwarf_child(die, &child, e))) { ret = read_children(dbg, child, level + 1, e); dwarf_dealloc_die(child); return ret; } else if (r == DW_DLV_ERROR) { fprintf(stderr, "%s: dwarf_child failed\n", __func__); return -1; } Dwarf_Die d = die; for (;;) { Dwarf_Die sibling; if (!(r = dwarf_siblingof(dbg, d, &sibling, e))) { ret = read_children(dbg, sibling, level, e); if (ret) return ret; } else if (r == DW_DLV_ERROR) { fprintf(stderr, "%s: dwarf_siblingof failed\n", __func__); return -1; } else if (r == DW_DLV_NO_ENTRY) break; d = sibling; } return 0; } static int read_header(const Dwarf_Debug dbg, struct header *const h, Dwarf_Error *const e) { Dwarf_Die die = 0; int ret = -1, 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); if (r != DW_DLV_OK) { fprintf(stderr, "%s: dwarf_next_cu_header_d failed with %d\n", __func__, r); return -1; } else if ((r = dwarf_siblingof(dbg, NULL, &die, e))) { fprintf(stderr, "%s: dwarf_siblingof failed with %d\n", __func__, r); goto end; } else if (read_children(dbg, die, 0, e)) { fprintf(stderr, "%s: failed to read children\n", __func__); goto end; } ret = 0; end: dwarf_dealloc_die(die); return ret; } static int init_client(const char *const tgt) { int ret = -1, fd = -1; char *hostdup = NULL, *portdup = NULL; const char *sep = strchr(tgt, ':'), *host, *port; struct addrinfo *pai = NULL; unsigned long portv; if (!sep) { host = tgt; port = "3333"; } else { if (!*(sep + 1)) { fprintf(stderr, "%s: missing port number\n", __func__); goto end; } else if (sep == tgt) host = "localhost"; else if (!(hostdup = strndup(tgt, sep - tgt))) { perror("strndup(3)"); goto end; } else host = hostdup; if (!(portdup = strdup(sep + 1))) { fprintf(stderr, "%s: failed to duplicate string: %s\n", __func__, strerror(errno)); goto end; } else port = portdup; } char *end; int error; const struct addrinfo ai = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM }; errno = 0; portv = strtoul(port, &end, 10); if (errno || *end || portv > USHRT_MAX) { fprintf(stderr, "%s: invalid port number: %s\n", __func__, port); goto end; } else if ((error = getaddrinfo(host, port, &ai, &pai))) { fprintf(stderr, "%s: getaddrinfo(3) %s:%s: %s\n", __func__, host, port, gai_strerror(error)); goto end; } for (const struct addrinfo *a = pai; a; a = a->ai_next) { if ((fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol)) < 0) { perror("socket(2)"); continue; } else if (connect(fd, a->ai_addr, a->ai_addrlen)) { fprintf(stderr, "%s: connect(2) %s:%s: %s\n", __func__, host, port, strerror(errno)); goto end; } break; } ret = fd; end: if (ret < 0 && fd >= 0 && close(fd)) perror("close(2)"); if (pai) freeaddrinfo(pai); free(hostdup); free(portdup); return ret; } struct verb { const char *verb; int (*f)(void); }; static int help(void); static const struct verb verbs[] = { {"?", help}, {"help", help} }; static int help(void) { return -1; } static int process(const char *const l) { return 0; } static int run(const int fd) { int ret = -1; char *l; size_t n = 0; ssize_t r; printf("(wadb) "); fflush(stdout); if ((r = getline(&l, &n, stdin) < 0)) { if (feof(stdin)) { putchar('\n'); return 1; } else perror("getline(3)"); goto end; } *strchr(l, '\n') = '\0'; if (process(l)) goto end; ret = 0; end: free(l); return ret; } int main(int argc, char *argv[]) { static const Dwarf_Obj_Access_Methods methods = { .get_section_info = get_section_info, .get_byte_order = get_byte_order, .get_length_size = get_length_size, .get_pointer_size = get_pointer_size, .get_section_count = get_section_count, .load_section = load_section }; int ret = EXIT_FAILURE, result, fd = -1; struct dwarf dw = {0}; Dwarf_Obj_Access_Interface aitf = {.object = &dw, .methods = &methods}; Dwarf_Debug dbg = 0; Dwarf_Error error = 0; struct header h; if (argc < 2) { fprintf(stderr, "%s [-h ] [args ...]\n", *argv); goto end; } const char *hostport = "localhost:3333"; int opt; while ((opt = getopt(argc, argv, "h:")) != -1) switch (opt) { case 'h': hostport = optarg; break; } const char *const path = argv[optind]; if (!(dw.f = fopen(path, "rb"))) { fprintf(stderr, "fopen(3) %s: %s\n", path, strerror(errno)); goto end; } else if (parse(&dw)) { fprintf(stderr, "failed to parse %s\n", path); goto end; } else if ((result = dwarf_object_init(&aitf, errcb, &dw, &dbg, &error)) != DW_DLV_OK) { fprintf(stderr, "dwarf_object_init failed with %d: %s\n", result, dwarf_errmsg(error)); goto end; } else if (read_header(dbg, &h, &error)) { fprintf(stderr, "failed to read header\n"); goto end; } else if ((fd = init_client(hostport)) < 0) { fprintf(stderr, "failed to init client\n"); goto end; } while (!(result = run(fd))) ; if (result < 0) goto end; ret = EXIT_SUCCESS; end: if (fd >= 0 && close(fd)) { perror("close(2)"); ret = EXIT_FAILURE; } for (size_t i = 0; i < sizeof dw.sections / sizeof *dw.sections; i++) free(dw.sections[i].data); if (dbg && dwarf_object_finish(dbg, &error)) { fprintf(stderr, "dwarf_object_finish: %s\n", dwarf_errmsg(error)); ret = EXIT_FAILURE; } if (dw.f && fclose(dw.f)) { fprintf(stderr, "fclose(3) %s: %s\n", path, strerror(errno)); ret = EXIT_FAILURE; } return ret; }