diff options
author | Bojan Novković <bnovkov@FreeBSD.org> | 2024-03-22 03:01:34 +0000 |
---|---|---|
committer | Bojan Novković <bnovkov@FreeBSD.org> | 2024-03-22 03:03:33 +0000 |
commit | c21bc6f3c2425de74141bfee07b609bf65b5a6b3 (patch) | |
tree | 06b3412042dd10e0edb1f6f29140856aeaf65a2a | |
parent | 312a05c39e5fd79b37ee6f922462232797f56a2a (diff) | |
download | src-c21bc6f3c2425de74141bfee07b609bf65b5a6b3.tar.gz src-c21bc6f3c2425de74141bfee07b609bf65b5a6b3.zip |
ddb: Add CTF-based pretty printing
Add basic CTF support and a CTF-powered pretty-printer to ddb.
The db_ctf.* files expose a basic interface for fetching type
data for ELF symbols, interacting with the CTF string table,
and translating type identifiers to type data.
The db_pprint.c file uses those interfaces to implement
a pretty-printer for all kernel ELF symbols.
The pretty-printer works with symbol names and arbitrary addresses:
pprint struct thread 0xffffffff8194ad90
Pretty-printing currently only works after the root filesystem
gets mounted because the CTF info is not available during
early boot.
Differential Revision: https://reviews.freebsd.org/D37899
Approved by: markj (mentor)
-rw-r--r-- | share/man/man4/ddb.4 | 26 | ||||
-rw-r--r-- | sys/conf/files | 2 | ||||
-rw-r--r-- | sys/ddb/db_command.c | 1 | ||||
-rw-r--r-- | sys/ddb/db_ctf.c | 326 | ||||
-rw-r--r-- | sys/ddb/db_ctf.h | 64 | ||||
-rw-r--r-- | sys/ddb/db_pprint.c | 450 | ||||
-rw-r--r-- | sys/ddb/ddb.h | 1 | ||||
-rw-r--r-- | sys/kern/kern_ctf.c | 40 | ||||
-rw-r--r-- | sys/kern/kern_linker.c | 68 | ||||
-rw-r--r-- | sys/kern/link_elf.c | 37 | ||||
-rw-r--r-- | sys/kern/link_elf_obj.c | 14 | ||||
-rw-r--r-- | sys/kern/linker_if.m | 23 | ||||
-rw-r--r-- | sys/sys/linker.h | 3 |
13 files changed, 1054 insertions, 1 deletions
diff --git a/share/man/man4/ddb.4 b/share/man/man4/ddb.4 index 3648c9ca58cb..f3443cbac127 100644 --- a/share/man/man4/ddb.4 +++ b/share/man/man4/ddb.4 @@ -289,6 +289,32 @@ eax = xxxxxx ecx = yyyyyy .Ed .Pp +.It Ic pprint Ns Oo Li / Ns Cm d depth Oc Oo Ar name Oc +Pretty-print symbol specified by +.Ar name +using CTF debugging data. Works for all symbols exported by the kernel and loaded kernel modules. +.Pp +If the +.Cm d +modifier has been specified, contents of structs nested up to +.Ar depth +levels deep will also be included in the output. +.Ed +.Pp +.It Ic pprint struct Ns Oo Li / Ns Cm d depth Ic Oc Oo Ar name Oc Ns Op Ns Ar addr +Print memory at +.Ar addr +as struct +.Ar name Ns . +Works for all structs defined by the kernel and loaded kernel modules. +.Pp +If the +.Cm d +modifier has been specified, contents of structs nested up to +.Ar depth +levels deep will also be included in the output. +.Ed +.Pp .It Xo .Ic write Ns Op Li / Ns Cm bhl .Ar addr expr1 Op Ar expr2 ... diff --git a/sys/conf/files b/sys/conf/files index c902bcfdbd52..021829408c0f 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -718,12 +718,14 @@ ddb/db_access.c optional ddb ddb/db_break.c optional ddb ddb/db_capture.c optional ddb ddb/db_command.c optional ddb +ddb/db_ctf.c optional ddb ddb/db_examine.c optional ddb ddb/db_expr.c optional ddb ddb/db_input.c optional ddb ddb/db_lex.c optional ddb ddb/db_main.c optional ddb ddb/db_output.c optional ddb +ddb/db_pprint.c optional ddb ddb/db_print.c optional ddb ddb/db_ps.c optional ddb ddb/db_run.c optional ddb diff --git a/sys/ddb/db_command.c b/sys/ddb/db_command.c index 9d79e3b2a6d3..0c88d496f6b8 100644 --- a/sys/ddb/db_command.c +++ b/sys/ddb/db_command.c @@ -163,6 +163,7 @@ static struct db_command db_cmds[] = { 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), }; struct db_command_table db_cmd_table = LIST_HEAD_INITIALIZER(db_cmd_table); diff --git a/sys/ddb/db_ctf.c b/sys/ddb/db_ctf.c new file mode 100644 index 000000000000..03145064885c --- /dev/null +++ b/sys/ddb/db_ctf.c @@ -0,0 +1,326 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2023 Bojan Novković <bnovkov@freebsd.org> + * + * 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 <sys/cdefs.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ctype.h> +#include <sys/linker.h> +#include <sys/malloc.h> +#include <sys/mutex.h> + +#include <ddb/ddb.h> +#include <ddb/db_ctf.h> + +static const ctf_header_t * +db_ctf_fetch_cth(linker_ctf_t *lc) +{ + return (const ctf_header_t *)lc->ctftab; +} + +/* + * Tries to look up the ELF symbol -> CTF type identifier mapping by scanning + * the CTF object section. + */ +static uint32_t +sym_to_objtoff(linker_ctf_t *lc, const Elf_Sym *sym, const Elf_Sym *symtab, + const Elf_Sym *symtab_end) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(lc); + 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_INVALID_OFF); + } + /* Sanity check */ + if (!(sym >= symtab && sym <= symtab_end)) { + return (DB_CTF_INVALID_OFF); + } + + 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_INVALID_OFF; + 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); +} + +/* + * Returns the size of CTF type 't'. + */ +static u_int +db_ctf_type_size(struct ctf_type_v3 *t) +{ + u_int vlen, kind, ssize; + u_int type_struct_size, kind_size; + + vlen = CTF_V3_INFO_VLEN(t->ctt_info); + kind = CTF_V3_INFO_KIND(t->ctt_info); + ssize = ((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: + kind_size = sizeof(uint32_t); + break; + case CTF_K_ARRAY: + kind_size = sizeof(struct ctf_array_v3); + break; + case CTF_K_UNION: + case CTF_K_STRUCT: + kind_size = vlen * + ((ssize < CTF_V3_LSTRUCT_THRESH) ? + sizeof(struct ctf_member_v3) : + sizeof(struct ctf_lmember_v3)); + break; + case CTF_K_ENUM: + kind_size = vlen * sizeof(struct ctf_enum); + break; + case CTF_K_FUNCTION: + kind_size = 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: + kind_size = 0; + break; + default: + db_printf("Error: invalid CTF type kind encountered\n"); + return (-1); + } + + return (type_struct_size + kind_size); +} + +/* + * Looks up type name 'name' in the CTF string table and returns the + * corresponding CTF type struct, if any. + */ +struct ctf_type_v3 * +db_ctf_typename_to_type(linker_ctf_t *lc, const char *name) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(lc); + char *start, *cur, *end; + uint32_t stroff = hp->cth_stroff; + uint32_t typeoff = hp->cth_typeoff; + uint32_t name_stroff; + const uint8_t *ctfstart = (const uint8_t *)hp + sizeof(ctf_header_t); + + u_int skiplen; + + /* Scan ctf strtab for typename. */ + start = cur = __DECONST(char *, hp) + sizeof(ctf_header_t) + + hp->cth_stroff; + end = cur + hp->cth_strlen; + while (cur < end) { + if (strcmp(cur, name) == 0) + break; + cur += strlen(cur) + 1; + } + if (cur >= end) + return (NULL); + name_stroff = (uint32_t)(cur - start); + + /* Scan for type containing the found stroff. */ + while (typeoff < stroff) { + struct ctf_type_v3 *t = + (struct ctf_type_v3 *)(__DECONST(uint8_t *, ctfstart) + + typeoff); + /* We found the type struct */ + if (t->ctt_name == name_stroff) { + break; + } + if ((skiplen = db_ctf_type_size(t)) == -1) { + return (NULL); + } + typeoff += skiplen; + } + if (typeoff < stroff) { + return (struct ctf_type_v3 *)(__DECONST(uint8_t *, ctfstart) + + typeoff); + } else { /* A type struct was not found */ + return (NULL); + } +} + +/* + * Wrapper used by the kernel linker CTF routines. + * Currently used to implement lookup of CTF types accross all loaded kernel + * modules. + */ +bool +db_ctf_lookup_typename(linker_ctf_t *lc, const char *typename) +{ + return (db_ctf_typename_to_type(lc, typename) != NULL); +} + +/* + * Returns the type corresponding to the 'typeid' parameter from the CTF type + * section. + */ +struct ctf_type_v3 * +db_ctf_typeid_to_type(db_ctf_sym_data_t sd, uint32_t typeid) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(&sd->lc); + 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; + u_int skiplen; + + /* Find corresponding type */ + while (typeoff < stroff) { + struct ctf_type_v3 *t = + (struct ctf_type_v3 *)(__DECONST(uint8_t *, ctfstart) + + typeoff); + + /* We found the type struct */ + if (cur_typeid == typeid) { + break; + } + cur_typeid++; + if ((skiplen = db_ctf_type_size(t)) == -1) { + return (NULL); + } + typeoff += 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(db_ctf_sym_data_t sd, uint32_t off) +{ + const ctf_header_t *hp = db_ctf_fetch_cth(&sd->lc); + uint32_t stroff = hp->cth_stroff + off; + const char *ret; + + if (stroff >= (hp->cth_stroff + hp->cth_strlen)) { + return ("invalid"); + } + ret = ((const char *)hp + sizeof(ctf_header_t)) + stroff; + if (*ret == '\0') { + return (NULL); + } + + return (ret); +} + +/* + * Tries to find the type of the symbol specified in 'sd->sym'. + */ +struct ctf_type_v3 * +db_ctf_sym_to_type(db_ctf_sym_data_t sd) +{ + uint32_t objtoff, typeid; + const Elf_Sym *symtab, *symtab_end; + + if (sd->sym == NULL) { + return (NULL); + } + symtab = sd->lc.symtab; + symtab_end = symtab + sd->lc.nsym; + + objtoff = sym_to_objtoff(&sd->lc, sd->sym, symtab, symtab_end); + /* Sanity check - should not happen */ + if (objtoff == DB_CTF_INVALID_OFF) { + db_printf("Could not find CTF object offset.\n"); + return (NULL); + } + + typeid = *( + const uint32_t *)(sd->lc.ctftab + sizeof(ctf_header_t) + objtoff); + + return (db_ctf_typeid_to_type(sd, typeid)); +} + +/* + * Scans the kernel file and all loaded module for symbol 'name'. + */ +int +db_ctf_find_symbol(const char *name, db_ctf_sym_data_t sd) +{ + int error; + c_linker_sym_t lsym = NULL; + + error = linker_ctf_lookup_sym_ddb(name, &lsym, &sd->lc); + if (error != 0) { + db_printf( + "failed to look up symbol and CTF info for %s: error %d\n", + name, error); + return (error); + } + sd->sym = __DECONST(Elf_Sym *, lsym); + + return (0); +} + +/* + * Scans the kernel file and all loaded module for type specified by 'typename'. + */ +struct ctf_type_v3 * +db_ctf_find_typename(db_ctf_sym_data_t sd, const char *typename) +{ + if (linker_ctf_lookup_typename_ddb(&sd->lc, typename) != 0) { + return (NULL); + } + return (db_ctf_typename_to_type(&sd->lc, typename)); +} diff --git a/sys/ddb/db_ctf.h b/sys/ddb/db_ctf.h new file mode 100644 index 000000000000..6da5f76b6cf6 --- /dev/null +++ b/sys/ddb/db_ctf.h @@ -0,0 +1,64 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2023 Bojan Novković <bnovkov@freebsd.org> + * + * 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. + */ + +#ifndef _DDB_DB_CTF_H_ +#define _DDB_DB_CTF_H_ + +#include <sys/types.h> +#include <sys/ctf.h> +#include <sys/linker.h> + +#include <ddb/ddb.h> +#include <ddb/db_sym.h> + +#define DB_CTF_INVALID_OFF 0xffffffff + +struct db_ctf_sym_data { + linker_ctf_t lc; + Elf_Sym *sym; +}; + +typedef struct db_ctf_sym_data *db_ctf_sym_data_t; + +/* + * Routines for finding symbols and CTF info accross all loaded linker files. + */ +int db_ctf_find_symbol(const char *name, db_ctf_sym_data_t sd); +struct ctf_type_v3 *db_ctf_find_typename(db_ctf_sym_data_t sd, + const char *typename); +bool db_ctf_lookup_typename(linker_ctf_t *lc, const char *typename); + +/* + * Routines for working with CTF data. + */ +struct ctf_type_v3 *db_ctf_sym_to_type(db_ctf_sym_data_t sd); +const char *db_ctf_stroff_to_str(db_ctf_sym_data_t sd, uint32_t off); +struct ctf_type_v3 *db_ctf_typename_to_type(linker_ctf_t *lc, const char *name); +struct ctf_type_v3 *db_ctf_typeid_to_type(db_ctf_sym_data_t sd, + uint32_t typeid); + +#endif /* !_DDB_DB_CTF_H_ */ diff --git a/sys/ddb/db_pprint.c b/sys/ddb/db_pprint.c new file mode 100644 index 000000000000..dc7582864957 --- /dev/null +++ b/sys/ddb/db_pprint.c @@ -0,0 +1,450 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Bojan Novković <bnovkov@freebsd.org> + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/ctype.h> +#include <sys/linker.h> + +#include <machine/stdarg.h> + +#include <ddb/ddb.h> +#include <ddb/db_ctf.h> +#include <ddb/db_lex.h> +#include <ddb/db_sym.h> +#include <ddb/db_access.h> + +#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 struct db_ctf_sym_data sym_data; + +/* + * Pretty-prints a CTF_INT type. + */ +static inline void +db_pprint_int(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + uint32_t data; + size_t type_struct_size; + + type_struct_size = (type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3); + + 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 (db_pager_quit) { + return; + } + if (bits > 64) { + db_printf("Invalid size '%d' found for integer type\n", bits); + return; + } + db_printf("0x%lx", + db_get_value(addr, (bits / 8) ? (bits / 8) : 1, sign)); +} + +/* + * Pretty-prints a struct. Nested structs are pretty-printed up 'depth' nested + * levels. + */ +static inline void +db_pprint_struct(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + size_t type_struct_size; + size_t struct_size; + struct ctf_type_v3 *mtype; + const char *mname; + db_addr_t maddr; + u_int vlen; + + type_struct_size = (type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3); + struct_size = ((type->ctt_size == CTF_V3_LSIZE_SENT) ? + CTF_TYPE_LSIZE(type) : + type->ctt_size); + 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; + } + mtype = db_ctf_typeid_to_type(&sym_data, mp->ctm_type); + maddr = addr + mp->ctm_offset; + mname = db_ctf_stroff_to_str(&sym_data, mp->ctm_name); + db_indent = depth; + if (mname != NULL) { + db_iprintf("%s = ", mname); + } else { + db_iprintf(""); + } + + db_pprint_type(maddr, mtype, depth + 1); + db_printf(",\n"); + } + } 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; + } + mtype = db_ctf_typeid_to_type(&sym_data, mp->ctlm_type); + maddr = addr + CTF_LMEM_OFFSET(mp); + mname = db_ctf_stroff_to_str(&sym_data, mp->ctlm_name); + db_indent = depth; + if (mname != NULL) { + db_iprintf("%s = ", mname); + } else { + db_iprintf(""); + } + + db_pprint_type(maddr, mtype, depth + 1); + db_printf(","); + } + } + db_indent = depth - 1; + db_iprintf("}"); +} + +/* + * Pretty-prints an array. Each array member is printed out in a separate line + * indented with 'depth' spaces. + */ +static inline void +db_pprint_arr(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + struct ctf_type_v3 *elem_type; + struct ctf_array_v3 *arr; + db_addr_t elem_addr, end; + size_t type_struct_size; + size_t elem_size; + + 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(&sym_data, arr->cta_contents); + elem_size = ((elem_type->ctt_size == CTF_V3_LSIZE_SENT) ? + CTF_TYPE_LSIZE(elem_type) : + elem_type->ctt_size); + elem_addr = addr; + end = addr + (arr->cta_nelems * elem_size); + + db_indent = depth; + db_printf("[\n"); + /* Loop through and print individual elements. */ + for (; elem_addr < end; elem_addr += elem_size) { + if (db_pager_quit) { + return; + } + db_iprintf(""); + db_pprint_type(elem_addr, elem_type, depth); + if ((elem_addr + elem_size) < end) { + db_printf(",\n"); + } + } + db_printf("\n"); + db_indent = depth - 1; + db_iprintf("]"); +} + +/* + * Pretty-prints an enum value. Also prints out symbolic name of value, if any. + */ +static inline void +db_pprint_enum(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + struct ctf_enum *ep, *endp; + size_t type_struct_size; + const char *valname; + db_expr_t val; + u_int vlen; + + type_struct_size = (type->ctt_size == CTF_V3_LSIZE_SENT) ? + sizeof(struct ctf_type_v3) : + sizeof(struct ctf_stype_v3); + vlen = CTF_V3_INFO_VLEN(type->ctt_info); + val = db_get_value(addr, sizeof(int), 0); + + 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(&sym_data, ep->cte_name); + if (valname != NULL) + db_printf("%s (0x%lx)", valname, val); + else + db_printf("(0x%lx)", val); + break; + } + } +} + +/* + * Pretty-prints a pointer. If the 'depth' parameter is less than the + * 'max_depth' global var, the pointer is "dereference", i.e. the contents of + * the memory it points to are also printed. The value of the pointer is printed + * otherwise. + */ +static inline void +db_pprint_ptr(db_addr_t addr, struct ctf_type_v3 *type, u_int depth) +{ + struct ctf_type_v3 *ref_type; + const char *qual = ""; + const char *name; + db_addr_t val; + u_int kind; + + ref_type = db_ctf_typeid_to_type(&sym_data, 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) { + /* Print contents of memory pointed to by this pointer. */ + db_pprint_type(addr, ref_type, depth + 1); + } else { + name = db_ctf_stroff_to_str(&sym_data, ref_type->ctt_name); + db_indent = depth; + if (name != NULL) + db_printf("(%s%s *) 0x%lx", qual, name, val); + else + db_printf("0x%lx", val); + } +} + +/* + * Pretty-print dispatching function. + */ +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, depth); + 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_indent = depth; + db_iprintf("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(&sym_data, + type->ctt_type); + db_pprint_type(addr, ref_type, depth); + break; + } + case CTF_K_ENUM: + db_pprint_enum(addr, type, depth); + break; + case CTF_K_ARRAY: + db_pprint_arr(addr, type, depth); + break; + case CTF_K_UNKNOWN: + case CTF_K_FORWARD: + default: + break; + } +} + +/* + * Symbol pretty-printing command. + * Syntax: pprint [/d depth] <sym_name> + */ +static void +db_pprint_symbol_cmd(const char *name) +{ + db_addr_t addr; + int db_indent_old; + const char *type_name = NULL; + struct ctf_type_v3 *type = NULL; + + if (db_pager_quit) { + return; + } + /* Clear symbol and CTF info */ + memset(&sym_data, 0, sizeof(struct db_ctf_sym_data)); + if (db_ctf_find_symbol(name, &sym_data) != 0) { + db_error("Symbol not found\n"); + } + if (ELF_ST_TYPE(sym_data.sym->st_info) != STT_OBJECT) { + db_error("Symbol is not a variable\n"); + } + addr = sym_data.sym->st_value; + type = db_ctf_sym_to_type(&sym_data); + if (type == NULL) { + db_error("Can't find CTF type info\n"); + } + type_name = db_ctf_stroff_to_str(&sym_data, type->ctt_name); + if (type_name != NULL) + db_printf("%s ", type_name); + db_printf("%s = ", name); + + db_indent_old = db_indent; + db_pprint_type(addr, type, 0); + db_indent = db_indent_old; +} + +/* + * Command for pretty-printing arbitrary addresses. + * Syntax: pprint [/d depth] struct <struct_name> <addr> + */ +static void +db_pprint_struct_cmd(db_expr_t addr, const char *struct_name) +{ + int db_indent_old; + struct ctf_type_v3 *type = NULL; + + type = db_ctf_find_typename(&sym_data, struct_name); + if (type == NULL) { + db_error("Can't find CTF type info\n"); + return; + } + + db_printf("struct %s ", struct_name); + db_printf("%p = ", (void *)addr); + + db_indent_old = db_indent; + db_pprint_type(addr, type, 0); + db_indent = db_indent_old; +} + +/* + * Pretty print an address or a symbol. + */ +void +db_pprint_cmd(db_expr_t addr, bool have_addr, db_expr_t count, char *modif) +{ + int t = 0; + const char *name; + + /* 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"); + } + /* Parse desired depth level */ + if (strcmp(db_tok_string, "d") == 0) { + 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(); + } + /* Parse subcomannd */ + if (t == tIDENT) { + if (strcmp(db_tok_string, "struct") == 0) { + t = db_read_token(); + + if (t != tIDENT) { + db_error("Invalid struct type name provided\n"); + } + name = db_tok_string; + + if (db_expression(&addr) == 0) { + db_error("Address not provided\n"); + } + db_pprint_struct_cmd(addr, name); + } else { + name = db_tok_string; + db_pprint_symbol_cmd(name); + } + } else { + db_error("Invalid subcommand\n"); + } + db_skip_to_eol(); +} diff --git a/sys/ddb/ddb.h b/sys/ddb/ddb.h index 1f388efcb389..bb92fef63e94 100644 --- a/sys/ddb/ddb.h +++ b/sys/ddb/ddb.h @@ -297,6 +297,7 @@ db_cmdfcn_t db_trace_until_matching_cmd; 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. diff --git a/sys/kern/kern_ctf.c b/sys/kern/kern_ctf.c index 748622653eb3..8a2106a15308 100644 --- a/sys/kern/kern_ctf.c +++ b/sys/kern/kern_ctf.c @@ -27,6 +27,10 @@ */ #include <sys/ctf.h> +#include <sys/kdb.h> +#include <sys/linker.h> + +#include <ddb/db_ctf.h> /* * Note this file is included by both link_elf.c and link_elf_obj.c. @@ -86,6 +90,9 @@ link_elf_ctf_get(linker_file_t lf, linker_ctf_t *lc) return (0); } + if (panicstr != NULL || kdb_active) + return (ENXIO); + /* * We need to try reading the CTF data. Flag no CTF data present * by default and if we actually succeed in reading it, we'll @@ -288,3 +295,36 @@ out: return (error); } + +static int +link_elf_ctf_get_ddb(linker_file_t lf, linker_ctf_t *lc) +{ + elf_file_t ef = (elf_file_t)lf; + + /* + * Check whether CTF data was loaded or if a + * previous loading attempt failed (ctfcnt == -1). + */ + if (ef->ctfcnt <= 0) { + return (ENOENT); + } + + lc->ctftab = ef->ctftab; + lc->ctfcnt = ef->ctfcnt; + lc->symtab = ef->ddbsymtab; + lc->strtab = ef->ddbstrtab; + lc->strcnt = ef->ddbstrcnt; + lc->nsym = ef->ddbsymcnt; + + return (0); +} + +static int +link_elf_ctf_lookup_typename(linker_file_t lf, linker_ctf_t *lc, + const char *typename) +{ + if (link_elf_ctf_get_ddb(lf, lc)) + return (ENOENT); + + return (db_ctf_lookup_typename(lc, typename) ? 0 : ENOENT); +} diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c index c6eb0273c39d..1126bf967966 100644 --- a/sys/kern/kern_linker.c +++ b/sys/kern/kern_linker.c @@ -328,6 +328,26 @@ linker_file_register_sysctls(linker_file_t lf, bool enable) sx_xlock(&kld_sx); } +/* + * Invoke the LINKER_CTF_GET implementation for this file. Existing + * implementations will load CTF info from the filesystem upon the first call + * and cache it in the kernel thereafter. + */ +static void +linker_ctf_load_file(linker_file_t file) +{ + linker_ctf_t lc; + int error; + + error = linker_ctf_get(file, &lc); + if (error == 0) + return; + if (bootverbose) { + printf("failed to load CTF for %s: %d\n", file->filename, + error); + } +} + static void linker_file_enable_sysctls(linker_file_t lf) { @@ -490,6 +510,11 @@ linker_load_file(const char *filename, linker_file_t *result) return (ENOEXEC); } linker_file_enable_sysctls(lf); + + /* + * Ask the linker to load CTF data for this file. + */ + linker_ctf_load_file(lf); EVENTHANDLER_INVOKE(kld_load, lf); *result = lf; return (0); @@ -782,6 +807,35 @@ linker_ctf_get(linker_file_t file, linker_ctf_t *lc) return (LINKER_CTF_GET(file, lc)); } +int +linker_ctf_lookup_typename_ddb(linker_ctf_t *lc, const char *typename) +{ +#ifdef DDB + linker_file_t lf; + + TAILQ_FOREACH (lf, &linker_files, link){ + if (LINKER_CTF_LOOKUP_TYPENAME(lf, lc, typename) == 0) + return (0); + } +#endif + return (ENOENT); +} + +int +linker_ctf_lookup_sym_ddb(const char *symname, c_linker_sym_t *sym, + linker_ctf_t *lc) +{ +#ifdef DDB + linker_file_t lf; + + TAILQ_FOREACH (lf, &linker_files, link){ + if (LINKER_LOOKUP_DEBUG_SYMBOL_CTF(lf, symname, sym, lc) == 0) + return (0); + } +#endif + return (ENOENT); +} + static int linker_file_add_dependency(linker_file_t file, linker_file_t dep) { @@ -1781,9 +1835,21 @@ fail: sx_xunlock(&kld_sx); /* woohoo! we made it! */ } - SYSINIT(preload, SI_SUB_KLD, SI_ORDER_MIDDLE, linker_preload, NULL); +static void +linker_mountroot(void *arg __unused) +{ + linker_file_t lf; + + sx_xlock(&kld_sx); + TAILQ_FOREACH (lf, &linker_files, link) { + linker_ctf_load_file(lf); + } + sx_xunlock(&kld_sx); +} +EVENTHANDLER_DEFINE(mountroot, linker_mountroot, NULL, 0); + /* * Handle preload files that failed to load any modules. */ diff --git a/sys/kern/link_elf.c b/sys/kern/link_elf.c index 4c7af262addb..dddead849dc9 100644 --- a/sys/kern/link_elf.c +++ b/sys/kern/link_elf.c @@ -68,6 +68,10 @@ #include "linker_if.h" +#ifdef DDB_CTF +#include <ddb/db_ctf.h> +#endif + #define MAXSEGS 4 typedef struct elf_file { @@ -141,6 +145,9 @@ static int link_elf_lookup_symbol(linker_file_t, const char *, c_linker_sym_t *); static int link_elf_lookup_debug_symbol(linker_file_t, const char *, c_linker_sym_t *); +static int link_elf_lookup_debug_symbol_ctf(linker_file_t lf, + const char *name, c_linker_sym_t *sym, linker_ctf_t *lc); + static int link_elf_symbol_values(linker_file_t, c_linker_sym_t, linker_symval_t *); static int link_elf_debug_symbol_values(linker_file_t, c_linker_sym_t, @@ -167,6 +174,7 @@ static int elf_lookup(linker_file_t, Elf_Size, int, Elf_Addr *); static kobj_method_t link_elf_methods[] = { KOBJMETHOD(linker_lookup_symbol, link_elf_lookup_symbol), KOBJMETHOD(linker_lookup_debug_symbol, link_elf_lookup_debug_symbol), + KOBJMETHOD(linker_lookup_debug_symbol_ctf, link_elf_lookup_debug_symbol_ctf), KOBJMETHOD(linker_symbol_values, link_elf_symbol_values), KOBJMETHOD(linker_debug_symbol_values, link_elf_debug_symbol_values), KOBJMETHOD(linker_search_symbol, link_elf_search_symbol), @@ -178,6 +186,7 @@ static kobj_method_t link_elf_methods[] = { KOBJMETHOD(linker_each_function_name, link_elf_each_function_name), KOBJMETHOD(linker_each_function_nameval, link_elf_each_function_nameval), KOBJMETHOD(linker_ctf_get, link_elf_ctf_get), + KOBJMETHOD(linker_ctf_lookup_typename, link_elf_ctf_lookup_typename), KOBJMETHOD(linker_symtab_get, link_elf_symtab_get), KOBJMETHOD(linker_strtab_get, link_elf_strtab_get), #ifdef VIMAGE @@ -1588,6 +1597,34 @@ link_elf_lookup_debug_symbol(linker_file_t lf, const char *name, } static int +link_elf_lookup_debug_symbol_ctf(linker_file_t lf, const char *name, + c_linker_sym_t *sym, linker_ctf_t *lc) +{ + elf_file_t ef = (elf_file_t)lf; + const Elf_Sym *symp; + const char *strp; + int i; + + for (i = 0, symp = ef->ddbsymtab; i < ef->ddbsymcnt; i++, symp++) { + strp = ef->ddbstrtab + symp->st_name; + if (strcmp(name, strp) == 0) { + if (symp->st_shndx != SHN_UNDEF || + (symp->st_value != 0 && + (ELF_ST_TYPE(symp->st_info) == STT_FUNC || + ELF_ST_TYPE(symp->st_info) == + STT_GNU_IFUNC))) { + *sym = (c_linker_sym_t)symp; + break; + } + return (ENOENT); + } + } + + /* Populate CTF info structure if symbol was found. */ + return (i < ef->ddbsymcnt ? link_elf_ctf_get_ddb(lf, lc) : ENOENT); +} + +static int link_elf_symbol_values1(linker_file_t lf, c_linker_sym_t sym, linker_symval_t *symval, bool see_local) { diff --git a/sys/kern/link_elf_obj.c b/sys/kern/link_elf_obj.c index 1001cb53ba49..c8ccebea0832 100644 --- a/sys/kern/link_elf_obj.c +++ b/sys/kern/link_elf_obj.c @@ -131,6 +131,8 @@ static int link_elf_lookup_symbol(linker_file_t, const char *, c_linker_sym_t *); static int link_elf_lookup_debug_symbol(linker_file_t, const char *, c_linker_sym_t *); +static int link_elf_lookup_debug_symbol_ctf(linker_file_t lf, + const char *name, c_linker_sym_t *sym, linker_ctf_t *lc); static int link_elf_symbol_values(linker_file_t, c_linker_sym_t, linker_symval_t *); static int link_elf_debug_symbol_values(linker_file_t, c_linker_sym_t, @@ -159,6 +161,7 @@ static int elf_obj_lookup(linker_file_t lf, Elf_Size symidx, int deps, static kobj_method_t link_elf_methods[] = { KOBJMETHOD(linker_lookup_symbol, link_elf_lookup_symbol), KOBJMETHOD(linker_lookup_debug_symbol, link_elf_lookup_debug_symbol), + KOBJMETHOD(linker_lookup_debug_symbol_ctf, link_elf_lookup_debug_symbol_ctf), KOBJMETHOD(linker_symbol_values, link_elf_symbol_values), KOBJMETHOD(linker_debug_symbol_values, link_elf_debug_symbol_values), KOBJMETHOD(linker_search_symbol, link_elf_search_symbol), @@ -170,6 +173,7 @@ static kobj_method_t link_elf_methods[] = { KOBJMETHOD(linker_each_function_name, link_elf_each_function_name), KOBJMETHOD(linker_each_function_nameval, link_elf_each_function_nameval), KOBJMETHOD(linker_ctf_get, link_elf_ctf_get), + KOBJMETHOD(linker_ctf_lookup_typename, link_elf_ctf_lookup_typename), KOBJMETHOD(linker_symtab_get, link_elf_symtab_get), KOBJMETHOD(linker_strtab_get, link_elf_strtab_get), #ifdef VIMAGE @@ -1476,6 +1480,16 @@ link_elf_lookup_debug_symbol(linker_file_t lf, const char *name, } static int +link_elf_lookup_debug_symbol_ctf(linker_file_t lf, const char *name, + c_linker_sym_t *sym, linker_ctf_t *lc) +{ + if (link_elf_lookup_debug_symbol(lf, name, sym)) + return (ENOENT); + + return (link_elf_ctf_get_ddb(lf, lc)); +} + +static int link_elf_symbol_values1(linker_file_t lf, c_linker_sym_t sym, linker_symval_t *symval, bool see_local) { diff --git a/sys/kern/linker_if.m b/sys/kern/linker_if.m index a50ed1ea84a3..ce08b3040b9c 100644 --- a/sys/kern/linker_if.m +++ b/sys/kern/linker_if.m @@ -116,6 +116,29 @@ METHOD int ctf_get { }; # +# Look up a CTF type in the file's CTF section +# and return CTF info in the linker CTF structure. +# Return ENOENT if typename is not found, otherwise zero. +# +METHOD int ctf_lookup_typename { + linker_file_t file; + linker_ctf_t *lc; + const char *typename; +}; + +# +# Lookup a symbol in the file's symbol table and the file's CTF info. +# Return ENOENT if either the symbol or its CTF +# data is not loaded, otherwise return zero. +# +METHOD int lookup_debug_symbol_ctf { + linker_file_t file; + const char *name; + c_linker_sym_t *sym; + linker_ctf_t *lc; +}; + +# # Get the symbol table, returning it in **symtab. Return the # number of symbols, otherwise zero. # diff --git a/sys/sys/linker.h b/sys/sys/linker.h index b94cd9f27405..b6184f4fc876 100644 --- a/sys/sys/linker.h +++ b/sys/sys/linker.h @@ -319,6 +319,9 @@ typedef struct linker_ctf { } linker_ctf_t; int linker_ctf_get(linker_file_t, linker_ctf_t *); +int linker_ctf_lookup_sym_ddb(const char *symname, c_linker_sym_t *sym, + linker_ctf_t *lc); +int linker_ctf_lookup_typename_ddb(linker_ctf_t *lc, const char *typename); int elf_cpu_load_file(linker_file_t); int elf_cpu_unload_file(linker_file_t); |