diff options
Diffstat (limited to 'main.c')
| -rw-r--r-- | main.c | 895 |
1 files changed, 895 insertions, 0 deletions
@@ -0,0 +1,895 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#define _POSIX_C_SOURCE 200809L + +#include <dwarf.h> +#include <libdwarf/libdwarf.h> +#include "types.h" +#include <sys/socket.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +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 = "<bogus tag>"; + + 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 <host:port>] <wasm-file> [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; +} |
