diff --git a/sys/ddb/db_command.c b/sys/ddb/db_command.c --- a/sys/ddb/db_command.c +++ b/sys/ddb/db_command.c @@ -166,6 +166,7 @@ DB_CMD("capture", db_capture_cmd, CS_OWN|DB_CMD_MEMSAFE), DB_CMD("textdump", db_textdump_cmd, CS_OWN|DB_CMD_MEMSAFE), DB_CMD("findstack", db_findstack_cmd, 0), + DB_CMD("pprint", db_pprint_cmd, CS_OWN|DB_CMD_MEMSAFE), }; struct db_command_table db_cmd_table = LIST_HEAD_INITIALIZER(db_cmd_table); diff --git a/sys/ddb/db_ctf.h b/sys/ddb/db_ctf.h new file mode 100644 --- /dev/null +++ b/sys/ddb/db_ctf.h @@ -0,0 +1,16 @@ +#ifndef _DDB_DB_CTF_H_ +#define _DDB_DB_CTF_H_ + +#include +#include + +#define DB_CTF_OBJTOFF_INVALID 0xffffffff + +bool db_ctf_loaded(void); + +const ctf_header_t *db_ctf_fetch_cth(void); +struct ctf_type_v3 *db_ctf_sym_to_type(const Elf_Sym *sym); +struct ctf_type_v3 *db_ctf_typeid_to_type(uint32_t typeid); +const char *db_ctf_stroff_to_str(uint32_t off); + +#endif /* !_DDB_DB_CTF_H_ */ diff --git a/sys/ddb/db_ctf.c b/sys/ddb/db_ctf.c new file mode 100644 --- /dev/null +++ b/sys/ddb/db_ctf.c @@ -0,0 +1,245 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 1983, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +static struct ddb_ctf { + linker_ctf_t kernel_ctf; + bool loaded; +} db_ctf; + +static void +db_ctf_init(void *arg) +{ + int err; + + memset((void *)&db_ctf, 0, sizeof(db_ctf)); + + err = linker_ctf_get_ddb(linker_kernel_file, &db_ctf.kernel_ctf); + if (err) { + printf("%s: linker_ctf_get_ddb error: %d\n", __func__, err); + return; + } + + printf("%s: loaded kernel CTF info\n", __func__); + + db_ctf.loaded = true; +} + +SYSINIT(db_ctf, SI_SUB_KLD, SI_ORDER_FOURTH, db_ctf_init, NULL); + +bool +db_ctf_loaded(void) +{ + return db_ctf.loaded; +} + +const ctf_header_t * +db_ctf_fetch_cth(void) +{ + return (const ctf_header_t *)db_ctf.kernel_ctf.ctftab; +} + +static uint32_t +sym_to_objtoff(const Elf_Sym *sym, const Elf_Sym *symtab, + const Elf_Sym *symtab_end) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(); + uint32_t objtoff = hp->cth_objtoff; + const size_t idwidth = 4; + + /* Ignore non-object symbols */ + if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT) { + return DB_CTF_OBJTOFF_INVALID; + } + + /* Sanity check */ + if (!(sym >= symtab && sym <= symtab_end)) { + return DB_CTF_OBJTOFF_INVALID; + } + + for (const Elf_Sym *symp = symtab; symp < symtab_end; symp++) { + /* Make sure we do not go beyond the objtoff section */ + if (objtoff >= hp->cth_funcoff) { + objtoff = DB_CTF_OBJTOFF_INVALID; + break; + } + + if (symp->st_name == 0 || symp->st_shndx == SHN_UNDEF) { + continue; + } + + if ((symp->st_shndx == SHN_ABS && symp->st_value == 0)) { + continue; + } + + /* Skip non-object symbols */ + if (ELF_ST_TYPE(symp->st_info) != STT_OBJECT) { + continue; + } + + if (symp == sym) { + break; + } + + objtoff += idwidth; + } + + return objtoff; +} + +struct ctf_type_v3 * +db_ctf_typeid_to_type(uint32_t typeid) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(); + const uint8_t *ctfstart = (const uint8_t *)hp + sizeof(ctf_header_t); + + uint32_t typeoff = hp->cth_typeoff; + uint32_t stroff = hp->cth_stroff; + /* CTF typeids start at 0x1 */ + size_t cur_typeid = 1; + + /* Find corresponding type */ + while (typeoff < stroff) { + u_int vlen, kind, size; + size_t skiplen, type_struct_size; + struct ctf_type_v3 *t = + (struct ctf_type_v3 *)(__DECONST(uint8_t *, ctfstart) + + typeoff); + + vlen = CTF_V3_INFO_VLEN(t->ctt_info); + kind = CTF_V3_INFO_KIND(t->ctt_info); + size = ((t->ctt_size == CTF_V3_LSIZE_SENT) ? CTF_TYPE_LSIZE(t) : + t->ctt_size); + type_struct_size = ((t->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3)); + + switch (kind) { + case CTF_K_INTEGER: + case CTF_K_FLOAT: + skiplen = sizeof(uint32_t); + break; + case CTF_K_ARRAY: + skiplen = sizeof(struct ctf_array_v3); + break; + case CTF_K_UNION: + case CTF_K_STRUCT: + skiplen = vlen * + ((size < CTF_V3_LSTRUCT_THRESH) ? + sizeof(struct ctf_member_v3) : + sizeof(struct ctf_lmember_v3)); + break; + case CTF_K_ENUM: + skiplen = vlen * sizeof(struct ctf_enum); + break; + case CTF_K_FUNCTION: + skiplen = vlen * sizeof(uint32_t); + break; + case CTF_K_UNKNOWN: + case CTF_K_FORWARD: + case CTF_K_POINTER: + case CTF_K_TYPEDEF: + case CTF_K_VOLATILE: + case CTF_K_CONST: + case CTF_K_RESTRICT: + skiplen = 0; + break; + default: + db_printf("Error: invalid CTF type kind encountered\n"); + return (NULL); + } + + /* We found the type struct */ + if (cur_typeid == typeid) { + break; + } + + cur_typeid++; + typeoff += type_struct_size + skiplen; + } + + if (typeoff < stroff) { + return (struct ctf_type_v3 *)(__DECONST(uint8_t *, ctfstart) + + typeoff); + } else { /* A type struct was not found */ + return (NULL); + } +} + +const char * +db_ctf_stroff_to_str(uint32_t off) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(); + uint32_t stroff = hp->cth_stroff + off; + + if (stroff >= (hp->cth_stroff + hp->cth_strlen)) { + return "invalid"; + } + + const char *ret = ((const char *)hp + sizeof(ctf_header_t)) + stroff; + if (*ret == '\0') { + return NULL; + } + + return ret; +} + +struct ctf_type_v3 * +db_ctf_sym_to_type(const Elf_Sym *sym) +{ + uint32_t objtoff, typeid; + const Elf_Sym *symtab, *symtab_end; + + if (sym == NULL) { + return (NULL); + } + + symtab = db_ctf.kernel_ctf.symtab; + symtab_end = symtab + db_ctf.kernel_ctf.nsym; + + objtoff = sym_to_objtoff(sym, symtab, symtab_end); + /* Sanity check - should not happen */ + if (objtoff == DB_CTF_OBJTOFF_INVALID) { + db_printf("Could not find CTF object offset."); + return (NULL); + } + + typeid = *(const uint32_t *)(db_ctf.kernel_ctf.ctftab + + sizeof(ctf_header_t) + objtoff); + + return db_ctf_typeid_to_type(typeid); +} diff --git a/sys/ddb/db_pprint.c b/sys/ddb/db_pprint.c new file mode 100644 --- /dev/null +++ b/sys/ddb/db_pprint.c @@ -0,0 +1,398 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 1983, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DB_PPRINT_DEFAULT_DEPTH 1 + +static void db_pprint_type(db_addr_t addr, struct ctf_type_v3 *type, + u_int depth); + +static u_int max_depth = DB_PPRINT_DEFAULT_DEPTH; + +static inline void +db_pprint_int(db_addr_t addr, struct ctf_type_v3 *type) +{ + if (db_pager_quit) { + return; + } + + size_t type_struct_size = ((type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3)); + uint32_t data = db_get_value((db_expr_t)type + type_struct_size, + sizeof(uint32_t), 0); + + u_int bits = CTF_INT_BITS(data); + boolean_t sign = !!(CTF_INT_ENCODING(data) & CTF_INT_SIGNED); + + if (bits > 64) { + db_printf("Invalid size '%d' found for integer type\n", bits); + return; + } + + int nbytes = (bits / 8) ? (bits / 8) : 1; + db_printf("0x%lx", db_get_value(addr, nbytes, sign)); +} + +static inline void +db_pprint_struct(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + const char *mname; + + size_t type_struct_size = ((type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3)); + size_t struct_size = ((type->ctt_size == CTF_V3_LSIZE_SENT) ? + CTF_TYPE_LSIZE(type) : + type->ctt_size); + u_int vlen = CTF_V3_INFO_VLEN(type->ctt_info); + + if (db_pager_quit) { + return; + } + + if (depth > max_depth) { + db_printf("{ ... }, "); + return; + } + + db_printf("{\n"); + + if (struct_size < CTF_V3_LSTRUCT_THRESH) { + struct ctf_member_v3 *mp, *endp; + + mp = (struct ctf_member_v3 *)((db_addr_t)type + + type_struct_size); + endp = mp + vlen; + + for (; mp < endp; mp++) { + if (db_pager_quit) { + return; + } + + struct ctf_type_v3 *mtype = db_ctf_typeid_to_type( + mp->ctm_type); + db_addr_t maddr = addr + mp->ctm_offset; + + mname = db_ctf_stroff_to_str(mp->ctm_name); + if (mname) { + db_printf("%s = ", mname); + } + + db_pprint_type(maddr, mtype, depth + 1); + db_printf(", "); + } + } else { + struct ctf_lmember_v3 *mp, *endp; + mp = (struct ctf_lmember_v3 *)((db_addr_t)type + + type_struct_size); + endp = mp + vlen; + + for (; mp < endp; mp++) { + if (db_pager_quit) { + return; + } + + struct ctf_type_v3 *mtype = db_ctf_typeid_to_type( + mp->ctlm_type); + db_addr_t maddr = addr + CTF_LMEM_OFFSET(mp); + + mname = db_ctf_stroff_to_str(mp->ctlm_name); + if (mname) { + db_printf("%s = ", mname); + } + + db_pprint_type(maddr, mtype, depth + 1); + db_printf(", "); + } + } + + db_printf("\n}"); +} + +static inline void +db_pprint_arr(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + struct ctf_array_v3 *arr; + struct ctf_type_v3 *elem_type; + size_t elem_size; + size_t type_struct_size = ((type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3)); + + arr = (struct ctf_array_v3 *)((db_addr_t)type + type_struct_size); + elem_type = db_ctf_typeid_to_type(arr->cta_contents); + elem_size = ((elem_type->ctt_size == CTF_V3_LSIZE_SENT) ? + CTF_TYPE_LSIZE(elem_type) : + elem_type->ctt_size); + + db_addr_t elem_addr = addr; + db_addr_t end = addr + (arr->cta_nelems * elem_size); + + db_printf("["); + for (; elem_addr < end; elem_addr += elem_size) { + if (db_pager_quit) { + return; + } + + db_pprint_type(elem_addr, elem_type, depth); + + if ((elem_addr + elem_size) < end) { + db_printf(", "); + } + } + db_printf("]\n"); +} + +static inline void +db_pprint_enum(db_addr_t addr, struct ctf_type_v3 *type) +{ + struct ctf_enum *ep, *endp; + const char *valname; + u_int vlen = CTF_V3_INFO_VLEN(type->ctt_info); + db_expr_t val = db_get_value(addr, sizeof(int), 0); + size_t type_struct_size = ((type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3)); + + if (db_pager_quit) { + return; + } + + ep = (struct ctf_enum *)((db_addr_t)type + type_struct_size); + endp = ep + vlen; + + for (; ep < endp; ep++) { + if (val == ep->cte_value) { + valname = db_ctf_stroff_to_str(ep->cte_name); + if (valname) { + db_printf("%s ", valname); + } + + db_printf("(0x%lx)", val); + break; + } + } +} + +static inline void +db_pprint_ptr(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + const char *qual = ""; + const char *name; + struct ctf_type_v3 *ref_type; + u_int kind; + db_addr_t val; + + ref_type = db_ctf_typeid_to_type(type->ctt_type); + kind = CTF_V3_INFO_KIND(ref_type->ctt_info); + + switch (kind) { + case CTF_K_STRUCT: + qual = "struct "; + break; + case CTF_K_VOLATILE: + qual = "volatile "; + break; + case CTF_K_CONST: + qual = "const "; + break; + default: + break; + } + + val = db_get_value(addr, sizeof(db_addr_t), false); + + if (depth < max_depth) { + db_pprint_type(addr, ref_type, depth + 1); + } else { + + name = db_ctf_stroff_to_str(ref_type->ctt_name); + if (name) { + db_printf("(%s%s *)", qual, name); + } + + db_printf("0x%lx", val); + } +} + +static void +db_pprint_type(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + + if (db_pager_quit) { + return; + } + + if (type == NULL) { + db_printf("unknown type"); + return; + } + + switch (CTF_V3_INFO_KIND(type->ctt_info)) { + case CTF_K_INTEGER: + db_pprint_int(addr, type); + break; + case CTF_K_UNION: + case CTF_K_STRUCT: + db_pprint_struct(addr, type, depth); + break; + case CTF_K_FUNCTION: + case CTF_K_FLOAT: + db_printf("0x%lx", addr); + break; + case CTF_K_POINTER: + db_pprint_ptr(addr, type, depth); + break; + case CTF_K_TYPEDEF: + case CTF_K_VOLATILE: + case CTF_K_RESTRICT: + case CTF_K_CONST: { + struct ctf_type_v3 *ref_type = db_ctf_typeid_to_type( + type->ctt_type); + db_pprint_type(addr, ref_type, depth); + break; + } + case CTF_K_ENUM: + db_pprint_enum(addr, type); + break; + case CTF_K_ARRAY: + db_pprint_arr(addr, type, depth); + break; + case CTF_K_UNKNOWN: + case CTF_K_FORWARD: + default: + break; + } +} + +static int +db_pprint_symbol(const Elf_Sym *sym) +{ + db_addr_t addr = sym->st_value; + struct ctf_type_v3 *type = NULL; + db_expr_t _val; + + const char *sym_name = NULL; + const char *type_name = NULL; + + if (db_pager_quit) { + return -1; + } + + type = db_ctf_sym_to_type(sym); + if (!type) { + db_printf("Cant find CTF type info\n"); + return -1; + } + + db_symbol_values((c_db_sym_t)sym, &sym_name, &_val); + type_name = db_ctf_stroff_to_str(type->ctt_name); + + if (type_name) { + db_printf("%s ", type_name); + } + if (sym_name) { + db_printf("%s = ", sym_name); + } + + db_pprint_type(addr, type, 0); + + return 0; +} + +/* + * Pretty print an address. + * Syntax: pprint [/d depth] addr + */ +void +db_pprint_cmd(db_expr_t addr, bool have_addr, db_expr_t count, char *modif) +{ + int t = 0; + Elf_Sym *sym; + db_expr_t off; + + if (!db_ctf_loaded()) { + db_error("Kernel CTF data not present\n"); + } + + /* Set default depth */ + max_depth = DB_PPRINT_DEFAULT_DEPTH; + + /* Parse print modifiers */ + t = db_read_token(); + if (t == tSLASH) { + t = db_read_token(); + if (t != tIDENT) { + db_error("Invalid flag passed\n"); + } + /* Fetch desired depth level */ + if (!strcmp(db_tok_string, "d")) { + t = db_read_token(); + if (t != tNUMBER) { + db_error("Invalid depth provided\n"); + } + max_depth = db_tok_number; + } else { + db_error("Invalid flag passed\n"); + } + /* Fetch next token */ + t = db_read_token(); + } + + if (t != tNUMBER) { + db_error("No address supplied\n"); + } + + addr = db_tok_number; + + sym = __DECONST(Elf_Sym *, db_search_symbol(addr, DB_STGY_ANY, &off)); + if (sym == NULL) { + db_error("Symbol not found\n"); + } + + if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT) { + db_error("Symbol is not a variable\n"); + } + + if (db_pprint_symbol(sym)) { + db_error(""); + } +} diff --git a/sys/ddb/ddb.h b/sys/ddb/ddb.h --- a/sys/ddb/ddb.h +++ b/sys/ddb/ddb.h @@ -275,6 +275,7 @@ db_cmdfcn_t db_unscript_cmd; db_cmdfcn_t db_watchpoint_cmd; db_cmdfcn_t db_write_cmd; +db_cmdfcn_t db_pprint_cmd; /* * Interface between DDB and the DDB output capture facility.