From 933785a02729b0160e398bb05e6a44079b1afdbf Mon Sep 17 00:00:00 2001 From: John Baldwin Date: Thu, 17 Jan 2008 21:43:12 +0000 Subject: [PATCH] Add a new 'add-kld ' command to kgdb to make it easier to analyze crash dumps with kernel modules. The command is basically a wrapper around add-symbol-file except that it uses the kernel linker data structures and the ELF section headers of the kld to calculate the section addresses add-symbol-file needs. The 'kld' parameter may either be an absolute path or a relative path. kgdb looks for the kld in several locations checking for variants with ".symbols" or ".debug" suffixes in each location. The first location it tries is just opening the specified path (this handles absolute paths and looks for the kld relative to the current directory otherwise). Next it tries to find the module in the same directory of the kernel image being used. If that fails it extracts the kern.module_path from the kernel being debugged and looks in each of those paths. The upshot is that for the common cases of debugging /boot/kernel/kernel where the module is in either /boot/kernel or /boot/modules one can merely do 'add-kld foo.ko'. MFC after: 1 week --- gnu/usr.bin/gdb/kgdb/kgdb.h | 2 + gnu/usr.bin/gdb/kgdb/main.c | 4 +- gnu/usr.bin/gdb/kgdb/trgt.c | 271 ++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) diff --git a/gnu/usr.bin/gdb/kgdb/kgdb.h b/gnu/usr.bin/gdb/kgdb/kgdb.h index d723a5ff7fa4..5d0498adc1a7 100644 --- a/gnu/usr.bin/gdb/kgdb/kgdb.h +++ b/gnu/usr.bin/gdb/kgdb/kgdb.h @@ -32,6 +32,7 @@ struct thread_info; extern kvm_t *kvm; +extern char *kernel; struct kthr { struct kthr *next; @@ -63,5 +64,6 @@ struct kthr *kgdb_thr_select(struct kthr *); char *kgdb_thr_extra_thread_info(int); uintptr_t kgdb_lookup(const char *sym); +CORE_ADDR kgdb_parse(const char *exp); #endif /* _KGDB_H_ */ diff --git a/gnu/usr.bin/gdb/kgdb/main.c b/gnu/usr.bin/gdb/kgdb/main.c index 12bc3c468f91..629b9c5b346d 100644 --- a/gnu/usr.bin/gdb/kgdb/main.c +++ b/gnu/usr.bin/gdb/kgdb/main.c @@ -75,7 +75,7 @@ static int dumpnr; static int verbose; static char crashdir[PATH_MAX]; -static char *kernel; +char *kernel; static char *remote; static char *vmcore; @@ -178,7 +178,7 @@ out: kgdb_new_objfile_chain(objfile); } -static CORE_ADDR +CORE_ADDR kgdb_parse(const char *exp) { struct cleanup *old_chain; diff --git a/gnu/usr.bin/gdb/kgdb/trgt.c b/gnu/usr.bin/gdb/kgdb/trgt.c index f79e4345d1b5..2cbb64ead657 100644 --- a/gnu/usr.bin/gdb/kgdb/trgt.c +++ b/gnu/usr.bin/gdb/kgdb/trgt.c @@ -29,9 +29,12 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include +#include #include +#include #include #include @@ -40,6 +43,9 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include +#include +#include #include "kgdb.h" @@ -199,6 +205,268 @@ kgdb_set_tid_cmd (char *arg, int from_tty) kgdb_switch_to_thread(thr); } +static int +kld_ok (char *path) +{ + struct stat sb; + + if (stat(path, &sb) == 0 && S_ISREG(sb.st_mode)) + return (1); + return (0); +} + +/* + * Look for a matching file in the following order: + * - filename + ".symbols" (e.g. foo.ko.symbols) + * - filename + ".debug" (e.g. foo.ko.debug) + * - filename (e.g. foo.ko) + * - dirname(kernel) + filename + ".symbols" (e.g. /boot/kernel/foo.ko.symbols) + * - dirname(kernel) + filename + ".debug" (e.g. /boot/kernel/foo.ko.debug) + * - dirname(kernel) + filename (e.g. /boot/kernel/foo.ko) + * - iterate over each path in the module path looking for: + * - dir + filename + ".symbols" (e.g. /boot/modules/foo.ko.symbols) + * - dir + filename + ".debug" (e.g. /boot/modules/foo.ko.debug) + * - dir + filename (e.g. /boot/modules/foo.ko) + */ +static int +find_kld_path (char *filename, char *path, size_t path_size) +{ + CORE_ADDR module_path_addr; + char module_path[PATH_MAX]; + char *kernel_dir, *module_dir, *cp; + + snprintf(path, path_size, "%s.symbols", filename); + if (kld_ok(path)) + return (1); + snprintf(path, path_size, "%s.debug", filename); + if (kld_ok(path)) + return (1); + snprintf(path, path_size, "%s", filename); + if (kld_ok(path)) + return (1); + kernel_dir = dirname(kernel); + if (kernel_dir != NULL) { + snprintf(path, path_size, "%s/%s.symbols", kernel_dir, + filename); + if (kld_ok(path)) + return (1); + snprintf(path, path_size, "%s/%s.debug", kernel_dir, filename); + if (kld_ok(path)) + return (1); + snprintf(path, path_size, "%s/%s", kernel_dir, filename); + if (kld_ok(path)) + return (1); + } + module_path_addr = kgdb_parse("linker_path"); + if (module_path_addr != 0 && + kvm_read(kvm, module_path_addr, module_path, sizeof(module_path)) == + sizeof(module_path)) { + module_path[PATH_MAX - 1] = '\0'; + cp = module_path; + while ((module_dir = strsep(&cp, ";")) != NULL) { + snprintf(path, path_size, "%s/%s.symbols", module_dir, + filename); + if (kld_ok(path)) + return (1); + snprintf(path, path_size, "%s/%s.debug", module_dir, + filename); + if (kld_ok(path)) + return (1); + snprintf(path, path_size, "%s/%s", module_dir, + filename); + if (kld_ok(path)) + return (1); + } + } + return (0); +} + +/* + * Read a kernel pointer given a KVA in 'address'. + */ +static CORE_ADDR +read_pointer (CORE_ADDR address) +{ + union { + uint32_t d32; + uint64_t d64; + } val; + + switch (TARGET_PTR_BIT) { + case 32: + if (kvm_read(kvm, address, &val.d32, sizeof(val.d32)) != + sizeof(val.d32)) + return (0); + return (val.d32); + case 64: + if (kvm_read(kvm, address, &val.d64, sizeof(val.d64)) != + sizeof(val.d64)) + return (0); + return (val.d64); + default: + return (0); + } +} + +/* + * Try to find this kld in the kernel linker's list of linker files. + */ +static int +find_kld_address (char *arg, CORE_ADDR *address) +{ + CORE_ADDR kld, filename_addr; + CORE_ADDR off_address, off_filename, off_next; + char kld_filename[PATH_MAX]; + char *filename; + size_t filelen; + + /* Compute offsets of relevant members in struct linker_file. */ + off_address = kgdb_parse("&((struct linker_file *)0)->address"); + off_filename = kgdb_parse("&((struct linker_file *)0)->filename"); + off_next = kgdb_parse("&((struct linker_file *)0)->link.tqe_next"); + if (off_address == 0 || off_filename == 0 || off_next == 0) + return (0); + + filename = basename(arg); + filelen = strlen(filename) + 1; + kld = kgdb_parse("linker_files.tqh_first"); + while (kld != 0) { + /* Try to read this linker file's filename. */ + filename_addr = read_pointer(kld + off_filename); + if (filename_addr == 0) + goto next_kld; + if (kvm_read(kvm, filename_addr, kld_filename, filelen) != + filelen) + goto next_kld; + + /* Compare this kld's filename against our passed in name. */ + if (kld_filename[filelen - 1] != '\0') + goto next_kld; + if (strcmp(kld_filename, filename) != 0) + goto next_kld; + + /* + * We found a match, use its address as the base + * address if we can read it. + */ + *address = read_pointer(kld + off_address); + if (*address == 0) + return (0); + return (1); + + next_kld: + kld = read_pointer(kld + off_next); + } + return (0); +} + +static void +add_section(struct section_addr_info *section_addrs, int *sect_indexp, + char *name, CORE_ADDR address) +{ + int sect_index; + + sect_index = *sect_indexp; + section_addrs->other[sect_index].name = name; + section_addrs->other[sect_index].addr = address; + printf_unfiltered("\t%s_addr = %s\n", name, + local_hex_string(address)); + sect_index++; + *sect_indexp = sect_index; +} + +static void +kgdb_add_kld_cmd (char *arg, int from_tty) +{ + struct section_addr_info *section_addrs; + struct cleanup *cleanup; + char path[PATH_MAX]; + asection *sect; + CORE_ADDR base_addr; + bfd *bfd; + CORE_ADDR text_addr, data_addr, bss_addr, rodata_addr; + int sect_count, sect_index; + + if (!find_kld_path(arg, path, sizeof(path))) { + error("unable to locate kld"); + return; + } + + if (!find_kld_address(arg, &base_addr)) { + error("unable to find kld in kernel"); + return; + } + + /* Open the kld and find the offsets of the various sections. */ + bfd = bfd_openr(path, gnutarget); + if (bfd == NULL) { + error("\"%s\": can't open: %s", path, + bfd_errmsg(bfd_get_error())); + return; + } + cleanup = make_cleanup_bfd_close(bfd); + + if (!bfd_check_format(bfd, bfd_object)) { + do_cleanups(cleanup); + error("\%s\": not an object file", path); + return; + } + + data_addr = bss_addr = rodata_addr = 0; + sect = bfd_get_section_by_name (bfd, ".text"); + if (sect == NULL) { + do_cleanups(cleanup); + error("\"%s\": can't find text section", path); + return; + } + text_addr = bfd_get_section_vma(bfd, sect); + sect_count = 1; + + /* Save the offsets of relevant sections. */ + sect = bfd_get_section_by_name (bfd, ".data"); + if (sect != NULL) { + data_addr = bfd_get_section_vma(bfd, sect); + sect_count++; + } + + sect = bfd_get_section_by_name (bfd, ".bss"); + if (sect != NULL) { + bss_addr = bfd_get_section_vma(bfd, sect); + sect_count++; + } + + sect = bfd_get_section_by_name (bfd, ".rodata"); + if (sect != NULL) { + rodata_addr = bfd_get_section_vma(bfd, sect); + sect_count++; + } + + do_cleanups(cleanup); + + printf_unfiltered("add symbol table from file \"%s\" at\n", path); + + /* Build a section table for symbol_file_add(). */ + section_addrs = alloc_section_addr_info(sect_count); + cleanup = make_cleanup(xfree, section_addrs); + sect_index = 0; + add_section(section_addrs, §_index, ".text", base_addr + text_addr); + if (data_addr != 0) + add_section(section_addrs, §_index, ".data", + base_addr + data_addr); + if (bss_addr != 0) + add_section(section_addrs, §_index, ".bss", + base_addr + bss_addr); + if (rodata_addr != 0) + add_section(section_addrs, §_index, ".rodata", + base_addr + rodata_addr); + + symbol_file_add(path, from_tty, section_addrs, 0, OBJF_USERLOADED); + + reinit_frame_cache(); + + do_cleanups(cleanup); +} + void kgdb_target(void) { @@ -236,4 +504,7 @@ kgdb_target(void) "Set current process context"); add_com ("tid", class_obscure, kgdb_set_tid_cmd, "Set current thread context"); + add_com ("add-kld", class_files, kgdb_add_kld_cmd, + "Usage: add-kld FILE\n\ +Load the symbols from the kernel loadable module FILE."); }