From 7e7287044dd4add775124b42f1bb3485a91a36fd Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Fri, 13 Dec 2024 10:12:19 -0800 Subject: [PATCH 1/5] orc_info: don't use ELF_Data when processing orc header This will allow orc_version_from_header() to be reused for upcoming ORC integration that does not use libelf. Signed-off-by: Stephen Brennan --- libdrgn/orc_info.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libdrgn/orc_info.c b/libdrgn/orc_info.c index d30b4f6da..64908d18a 100644 --- a/libdrgn/orc_info.c +++ b/libdrgn/orc_info.c @@ -214,11 +214,8 @@ remove_fdes_from_orc(struct drgn_module *module, unsigned int *indices, return NULL; } -static int orc_version_from_header(Elf_Data *orc_header) +static int orc_version_from_header(void *buffer) { - if (orc_header->d_size != 20) - return -1; - // Known version identifiers in .orc_header. These can be generated in // the kernel source tree with: // sh ./scripts/orc_hash.sh < arch/x86/include/asm/orc_types.h | sed -e 's/^#define ORC_HASH //' -e 's/,/, /g' @@ -236,9 +233,9 @@ static int orc_version_from_header(Elf_Data *orc_header) 0x17, 0xf8, 0xf7, 0x97, 0x83, 0xca, 0x98, 0x5c, 0x2c, 0x51, }; - if (memcmp(orc_header->d_buf, orc_hash_6_4, 20) == 0) + if (memcmp(buffer, orc_hash_6_4, 20) == 0) return 3; - else if (memcmp(orc_header->d_buf, orc_hash_6_3, 20) == 0) + else if (memcmp(buffer, orc_hash_6_3, 20) == 0) return 2; return -1; } @@ -318,7 +315,9 @@ static struct drgn_error *drgn_read_orc_sections(struct drgn_module *module) err = read_elf_section(orc_header_scn, &orc_header); if (err) return err; - module->orc.version = orc_version_from_header(orc_header); + module->orc.version = -1; + if (orc_header->d_size == 20) + module->orc.version = orc_version_from_header(orc_header->d_buf); if (module->orc.version < 0) { return drgn_error_create(DRGN_ERROR_OTHER, "unrecognized .orc_header"); From 278f0042ad3cc5ca0a3af59b08ee510d6664e340 Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Fri, 13 Dec 2024 15:52:45 -0800 Subject: [PATCH 2/5] module: add object This allows users to get the object which the module was created from. The primary use case is for Linux kernel modules, to return the "struct module" associated with the drgn module object. Signed-off-by: Stephen Brennan --- _drgn.pyi | 8 ++++++++ libdrgn/debug_info.c | 9 +++++++++ libdrgn/debug_info.h | 2 ++ libdrgn/drgn.h | 11 +++++++++++ libdrgn/linux_kernel.c | 26 +++++++++++++++++++++----- libdrgn/python/module.c | 19 +++++++++++++++++++ 6 files changed, 70 insertions(+), 5 deletions(-) diff --git a/_drgn.pyi b/_drgn.pyi index 6e72c5681..26ae2dc9e 100644 --- a/_drgn.pyi +++ b/_drgn.pyi @@ -1499,6 +1499,14 @@ class Module: module, it is set to the file's build ID if it is not already set. It can also be set manually. """ + object: Object + """ + The object this module was created from. + + For Linux kernel loadable modules, this is a reference to the ``struct + module`` this was created from. For other kinds, this is currently an absent + object. + """ loaded_file_status: ModuleFileStatus """Status of the module's :ref:`loaded file `.""" loaded_file_path: Optional[str] diff --git a/libdrgn/debug_info.c b/libdrgn/debug_info.c index 70b49cd4e..9b912894b 100644 --- a/libdrgn/debug_info.c +++ b/libdrgn/debug_info.c @@ -323,6 +323,7 @@ struct drgn_error *drgn_module_find_or_create(struct drgn_program *prog, module->prog = prog; module->kind = key->kind; + drgn_object_init(&module->object, prog); // Linux userspace core dumps usually filter out file-backed mappings // (see coredump_filter in core(5)), so we need the loaded file to read // the text. Additionally, .eh_frame is in the loaded file and not the @@ -414,6 +415,7 @@ struct drgn_error *drgn_module_find_or_create(struct drgn_program *prog, err_name: free(module->name); err_module: + drgn_object_deinit(&module->object); free(module); return err; } @@ -515,6 +517,7 @@ static void drgn_module_destroy(struct drgn_module *module) drgn_elf_file_destroy(module->loaded_file); free(module->build_id); free(module->name); + drgn_object_deinit(&module->object); free(module); } @@ -987,6 +990,12 @@ drgn_module_wanted_supplementary_debug_file(struct drgn_module *module, : DRGN_SUPPLEMENTARY_FILE_NONE; } +LIBDRGN_PUBLIC struct drgn_error * +drgn_module_object(struct drgn_module *module, struct drgn_object *ret) +{ + return drgn_object_copy(ret, &module->object); +} + static struct drgn_error * drgn_program_register_debug_info_finder_impl(struct drgn_program *prog, struct drgn_debug_info_finder *finder, diff --git a/libdrgn/debug_info.h b/libdrgn/debug_info.h index 2241ef3a8..79f6d3ae0 100644 --- a/libdrgn/debug_info.h +++ b/libdrgn/debug_info.h @@ -260,6 +260,8 @@ struct drgn_module { struct drgn_module_wanted_supplementary_file *wanted_supplementary_debug_file; /** Node in @ref drgn_debug_info::modules_pending_indexing. */ struct drgn_module *pending_indexing_next; + /** Object the module was created from */ + struct drgn_object object; }; struct drgn_error *drgn_module_find_or_create(struct drgn_program *prog, diff --git a/libdrgn/drgn.h b/libdrgn/drgn.h index f94e609f8..335efaee1 100644 --- a/libdrgn/drgn.h +++ b/libdrgn/drgn.h @@ -1570,6 +1570,17 @@ drgn_module_wanted_supplementary_debug_file(struct drgn_module *module, const void **checksum_ret, size_t *checksum_len_ret); +/** + * Return the object associated with this module. + * + * For Linux kernel loadable modules, this is a reference to the `struct module` + * they represent. Other kinds of modules may have other objects. + * + * @param[out] ret Initialized object where the module object is placed + */ +struct drgn_error * +drgn_module_object(struct drgn_module *module, struct drgn_object *ret); + /** Debugging information finder callback table. */ struct drgn_debug_info_finder_ops { /** diff --git a/libdrgn/linux_kernel.c b/libdrgn/linux_kernel.c index 61d5e5e52..e8f30597b 100644 --- a/libdrgn/linux_kernel.c +++ b/libdrgn/linux_kernel.c @@ -1246,7 +1246,7 @@ kernel_module_set_section_addresses(struct drgn_module *module, static struct drgn_error * kernel_module_find_or_create_internal(const struct drgn_object *module_obj, struct drgn_module **ret, bool *new_ret, - bool create, bool log) + bool create, bool log, uint64_t address) { struct drgn_error *err; struct drgn_program *prog = drgn_object_program(module_obj); @@ -1336,7 +1336,17 @@ kernel_module_find_or_create_internal(const struct drgn_object *module_obj, &module, &new); if (err) return err; - if (!new) { + if (new) { + // We take a struct module value object. But to expose to users, + // we need to create a reference object of the same type, so the + // users won't get stale data. + if (address) + err = drgn_object_set_reference(&module->object, + drgn_object_qualified_type(module_obj), + address, 0, 0); + if (err) + return err; + } else { *ret = no_cleanup_ptr(module); if (new_ret) *new_ret = new; @@ -1392,6 +1402,7 @@ drgn_module_find_or_create_linux_kernel_loadable_internal(const struct drgn_obje bool create) { struct drgn_error *err; + uint64_t module_address = 0; // kernel_module_find_or_create_internal() expects a `struct module` // value. @@ -1400,21 +1411,25 @@ drgn_module_find_or_create_linux_kernel_loadable_internal(const struct drgn_obje == DRGN_TYPE_POINTER) { drgn_object_init(&mod, drgn_object_program(module_obj)); err = drgn_object_dereference(&mod, module_obj); - if (!err) + if (!err) { + module_address = mod.address; err = drgn_object_read(&mod, &mod); + } module_obj = &mod; if (err) goto out; } else if (module_obj->kind != DRGN_OBJECT_VALUE) { drgn_object_init(&mod, drgn_object_program(module_obj)); err = drgn_object_read(&mod, module_obj); + if (!err) + module_address = module_obj->address; module_obj = &mod; if (err) goto out; } err = kernel_module_find_or_create_internal(module_obj, ret, new_ret, - create, false); + create, false, module_address); out: if (module_obj == &mod) drgn_object_deinit(&mod); @@ -1493,6 +1508,7 @@ yield_kernel_module(struct linux_kernel_loaded_module_iterator *it, // for /proc/kcore, it is faster to read the entire structure // (which is <2kB as of Linux 6.5) from the core dump all at // once than it is to read each field individually. + uint64_t address = mod.address; err = drgn_object_read(&mod, &mod); if (err) goto list_walk_err; @@ -1505,7 +1521,7 @@ yield_kernel_module(struct linux_kernel_loaded_module_iterator *it, goto list_walk_err; err = kernel_module_find_or_create_internal(&mod, ret, new_ret, - true, true); + true, true, address); if (err && !drgn_error_is_fatal(err)) { drgn_error_log_warning(prog, err, "ignoring module: "); drgn_error_destroy(err); diff --git a/libdrgn/python/module.c b/libdrgn/python/module.c index 06067f845..2d577af3e 100644 --- a/libdrgn/python/module.c +++ b/libdrgn/python/module.c @@ -285,6 +285,24 @@ static int Module_set_build_id(Module *self, PyObject *value, void *arg) return 0; } +static DrgnObject *Module_object(Module *self, void *arg) +{ + struct drgn_error *err; + + struct drgn_program *prog = drgn_module_program(self->module); + Program *prog_obj = container_of(prog, Program, prog); + _cleanup_pydecref_ DrgnObject *ret = DrgnObject_alloc(prog_obj); + if (!ret) + return NULL; + + err = drgn_module_object(self->module, &ret->obj); + if (err) { + set_drgn_error(err); + return NULL; + } + return_ptr(ret); +} + #define MODULE_FILE_STATUS_GETSET(which) \ static PyObject *Module_wants_##which##_file(Module *self) \ { \ @@ -400,6 +418,7 @@ static PyGetSetDef Module_getset[] = { (setter)Module_set_address_range, drgn_Module_address_range_DOC}, {"build_id", (getter)Module_get_build_id, (setter)Module_set_build_id, drgn_Module_build_id_DOC}, + {"object", (getter)Module_object, NULL, drgn_Module_object_DOC}, {"loaded_file_status", (getter)Module_get_loaded_file_status, (setter)Module_set_loaded_file_status, drgn_Module_loaded_file_status_DOC}, From b49d37d2828e9ccf777a9b6b12344013c678285c Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Sat, 14 Dec 2024 00:12:14 -0800 Subject: [PATCH 3/5] orc_info: enable the use of built-in ORC Since drgn typically assumes the presence of debug files, ORC has always been loaded from the ELF file, rather than the program. However, ORC is present in kernel core dumps, so it can be used when the debug file is unavailable. Implement the ability to load built-in ORC for vmlinux and kernel modules. We still prefer to load ORC from the debug file wherever possible, because this is almost certainly faster. Signed-off-by: Stephen Brennan --- libdrgn/debug_info.c | 16 ++++ libdrgn/orc_info.c | 194 ++++++++++++++++++++++++++++++++++++++++-- libdrgn/orc_info.h | 2 + libdrgn/stack_trace.c | 6 +- 4 files changed, 208 insertions(+), 10 deletions(-) diff --git a/libdrgn/debug_info.c b/libdrgn/debug_info.c index 9b912894b..29522a26a 100644 --- a/libdrgn/debug_info.c +++ b/libdrgn/debug_info.c @@ -5537,5 +5537,21 @@ drgn_module_find_cfi(struct drgn_program *prog, struct drgn_module *module, if (err != &drgn_not_found) return err; } + + if (!can_use_debug_file) { + if (!module->parsed_orc) { + err = drgn_module_parse_orc(module); + if (err) + return err; + module->parsed_orc = true; + } + + err = drgn_module_find_orc_cfi(module, pc, row_ret, + interrupted_ret, + ret_addr_regno_ret); + if (err != &drgn_not_found) + return err; + } + return &drgn_not_found; } diff --git a/libdrgn/orc_info.c b/libdrgn/orc_info.c index 64908d18a..c4e3202de 100644 --- a/libdrgn/orc_info.c +++ b/libdrgn/orc_info.c @@ -13,6 +13,7 @@ #include "debug_info.h" // IWYU pragma: associated #include "elf_file.h" #include "error.h" +#include "log.h" #include "orc.h" #include "platform.h" #include "program.h" @@ -34,7 +35,7 @@ static inline uint64_t drgn_raw_orc_pc(struct drgn_module *module, { int32_t offset; memcpy(&offset, &module->orc.pc_offsets[i], sizeof(offset)); - if (drgn_elf_file_bswap(module->debug_file)) + if (module->orc.bswap) offset = bswap_32(offset); return module->orc.pc_base + UINT64_C(4) * i + offset; } @@ -44,7 +45,7 @@ drgn_raw_orc_entry_is_terminator(struct drgn_module *module, unsigned int i) { uint16_t flags; memcpy(&flags, &module->orc.entries[i].flags, sizeof(flags)); - if (drgn_elf_file_bswap(module->debug_file)) + if (module->orc.bswap) flags = bswap_16(flags); if (module->orc.version >= 3) { // orc->type == ORC_TYPE_UNDEFINED @@ -63,7 +64,7 @@ drgn_raw_orc_entry_is_preferred(struct drgn_module *module, unsigned int i) { uint16_t flags; memcpy(&flags, &module->orc.entries[i].flags, sizeof(flags)); - if (drgn_elf_file_bswap(module->debug_file)) + if (module->orc.bswap) flags = bswap_16(flags); // ORC_REG_SP_INDIRECT is used for the stack switching pattern used in // the Linux kernel's call_on_stack()/call_on_irqstack() macros. See @@ -355,6 +356,159 @@ static struct drgn_error *drgn_read_orc_sections(struct drgn_module *module) return NULL; } +static struct drgn_error * +copy_builtin_orc_buffers(struct drgn_module *module, uint64_t num_entries, + uint64_t unwind, uint64_t unwind_ip, uint64_t header) +{ + uint8_t header_data[20]; + + struct drgn_error *err; + + if (header) { + err = drgn_program_read_memory(module->prog, header_data, + header, sizeof(header_data), + false); + + if (err) + return err; + + module->orc.version = orc_version_from_header(header_data); + if (module->orc.version < 0) + return drgn_error_create(DRGN_ERROR_OTHER, + "unrecognized .orc_header"); + } else { + module->orc.version = orc_version_from_osrelease(module->prog); + } + + _cleanup_free_ int32_t *pc_offsets = malloc_array(num_entries, + sizeof(pc_offsets[0])); + err = drgn_program_read_memory(module->prog, pc_offsets, unwind_ip, + num_entries * sizeof(pc_offsets[0]), false); + if (err) + return err; + + _cleanup_free_ struct drgn_orc_entry *entries = + malloc_array(num_entries, sizeof(entries[0])); + err = drgn_program_read_memory(module->prog, entries, unwind, + num_entries * sizeof(entries[0]), false); + if (err) + return err; + + module->orc.entries = no_cleanup_ptr(entries); + module->orc.pc_offsets = no_cleanup_ptr(pc_offsets); + module->orc.num_entries = num_entries; + module->orc.pc_base = unwind_ip; + drgn_log_debug(module->prog, "Loaded built-in ORC (v%d) for module %s", + module->orc.version, module->name); + return NULL; +} + +static struct drgn_error *drgn_read_vmlinux_orc(struct drgn_module *module) +{ + struct drgn_error *err; + struct drgn_symbol *sym; + + uint64_t unwind_ip_start, unwind_ip_end; + uint64_t unwind_start, unwind_end; + uint64_t header_start = 0, header_end = 0; + +#define get_symbol(name, var, optional) \ + err = drgn_program_find_symbol_by_name(module->prog, name, &sym); \ + if (!err) { \ + var = sym->address; \ + drgn_symbol_destroy(sym); \ + sym = NULL; \ + } else if (optional && err->code == DRGN_ERROR_LOOKUP) { \ + drgn_error_destroy(err); \ + sym = NULL; \ + err = NULL; \ + } else { \ + return err; \ + } + + get_symbol("__start_orc_unwind_ip", unwind_ip_start, false); + get_symbol("__stop_orc_unwind_ip", unwind_ip_end, false); + get_symbol("__start_orc_unwind", unwind_start, false); + get_symbol("__stop_orc_unwind", unwind_end, false); + get_symbol("__start_orc_header", header_start, true); + get_symbol("__stop_orc_header", header_end, true); +#undef get_symbol + + if ((unwind_ip_end - unwind_ip_start) % sizeof(int32_t)) + return drgn_error_create(DRGN_ERROR_OTHER, "invalid built-in orc_unwind_ip range"); + uint64_t num_entries = (unwind_ip_end - unwind_ip_start) / sizeof(int32_t); + if (num_entries > UINT_MAX) + return drgn_error_create(DRGN_ERROR_OTHER, + "built-in orc_unwind_ip range is too large"); + + if ((unwind_end - unwind_start) % sizeof(struct drgn_orc_entry) \ + || (unwind_end - unwind_start) / sizeof(struct drgn_orc_entry) != num_entries) + return drgn_error_create(DRGN_ERROR_OTHER, "invalid built-in orc_unwind range"); + + module->orc.version = -1; + if (header_start && header_end && header_end - header_start != 20) + return drgn_error_create(DRGN_ERROR_OTHER, "invalid built-in orc_header size"); + + return copy_builtin_orc_buffers(module, num_entries, unwind_start, + unwind_ip_start, header_start); +} + +static struct drgn_error *drgn_read_builtin_orc(struct drgn_module *module) +{ + if (!(module->prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)) + return NULL; + if (module->kind == DRGN_MODULE_MAIN) + return drgn_read_vmlinux_orc(module); + else if (module->kind != DRGN_MODULE_RELOCATABLE) + return NULL; + else if (module->object.kind == DRGN_OBJECT_ABSENT) + return NULL; + + // num_entries is implied by the size of the arrays. We can get the + // array addresses from the section address info, but not their size. So + // we need to find num_orcs by reading it out of the arch-specific + // module info. + DRGN_OBJECT(arch, module->prog); + DRGN_OBJECT(tmp, module->prog); + struct drgn_error *err = drgn_object_member(&arch, &module->object, "arch"); + if (err) + return err; + + err = drgn_object_member(&tmp, &arch, "num_orcs"); + + // If the kernel does not support ORC (e.g. it is too old), this will be + // the first lookup error we encounter. Catch it and don't return any + // error. + if (drgn_error_catch(&err, DRGN_ERROR_LOOKUP) || err) + return err; + + uint64_t num_entries; + err = drgn_object_read_unsigned(&tmp, &num_entries); + if (err) + return err; + + // We'll still use the section addresses for everything else, because + // the orc_header is only present there, and it should be a bit faster + // to read data which we already parsed, rather than going back to read + // it from program memory. + uint64_t orc_unwind; + uint64_t orc_unwind_ip; + uint64_t orc_header = 0; + drgn_module_get_section_address(module, ".orc_unwind", &orc_unwind); + if (err) + return err; + drgn_module_get_section_address(module, ".orc_unwind_ip", &orc_unwind_ip); + if (err) + return err; + drgn_module_get_section_address(module, ".orc_header", &orc_header); + drgn_error_catch(&err, DRGN_ERROR_LOOKUP); + if (err) + return err; + + return copy_builtin_orc_buffers(module, num_entries, orc_unwind, + orc_unwind_ip, orc_header); +} + static inline void drgn_module_clear_orc(struct drgn_module **modulep) { if (*modulep) { @@ -367,18 +521,40 @@ struct drgn_error *drgn_module_parse_orc(struct drgn_module *module) { struct drgn_error *err; - if (module->debug_file->platform.arch->arch != DRGN_ARCH_X86_64) + if (module->prog->platform.arch->arch != DRGN_ARCH_X86_64) return NULL; - // pc_offsets and entries point to the Elf_Data buffers until we're - // done. We don't want those freed by drgn_module_orc_info_deinit(), so - // clear them if anything goes wrong. + // Depending on the source of pc_offsets and entries, we may or may not + // need to free them. We should always clear the pointers out of the + // module to avoid an invalid or double free in + // drgn_module_orc_info_deinit(). _cleanup_(drgn_module_clear_orc) struct drgn_module *clear = module; + _cleanup_free_ void *cleanup_pc_offsets = NULL; + _cleanup_free_ void *cleanup_entries = NULL; + + if (module->debug_file) { + err = drgn_read_orc_sections(module); + } else { + // Buffers here are not from libelf, so we should free them. + // Since new copies are allocated below, they should always be + // freed, even on success. + err = drgn_read_builtin_orc(module); + cleanup_pc_offsets = module->orc.pc_offsets; + cleanup_entries = module->orc.entries; - err = drgn_read_orc_sections(module); + } if (err || !module->orc.num_entries) return err; + // We may need to byte swap ORC entries. Rather than checking the + // debug_file's platform, use the program's platform (since they are the + // same) because it's possible there is no debug_file (e.g. for builtin + // ORC). + bool bswap; + err = drgn_program_bswap(module->prog, &bswap); + if (err) + return err; + unsigned int num_entries = module->orc.num_entries; _cleanup_free_ unsigned int *indices = malloc_array(num_entries, sizeof(indices[0])); @@ -418,7 +594,6 @@ struct drgn_error *drgn_module_parse_orc(struct drgn_module *module) return &drgn_enomem; const int32_t *orig_offsets = module->orc.pc_offsets; const struct drgn_orc_entry *orig_entries = module->orc.entries; - const bool bswap = drgn_elf_file_bswap(module->debug_file); const int version = module->orc.version; for (unsigned int i = 0; i < num_entries; i++) { unsigned int index = indices[i]; @@ -475,6 +650,7 @@ struct drgn_error *drgn_module_parse_orc(struct drgn_module *module) module->orc.pc_offsets = no_cleanup_ptr(pc_offsets); module->orc.entries = no_cleanup_ptr(entries); module->orc.num_entries = num_entries; + module->orc.bswap = bswap; clear = NULL; return NULL; } diff --git a/libdrgn/orc_info.h b/libdrgn/orc_info.h index 49c07076c..cca0f6b77 100644 --- a/libdrgn/orc_info.h +++ b/libdrgn/orc_info.h @@ -72,6 +72,8 @@ struct drgn_module_orc_info { unsigned int num_entries; /** Version of the ORC format. See @ref orc.h. */ int version; + /** Whether to byte swap data */ + bool bswap; }; void drgn_module_orc_info_deinit(struct drgn_module *module); diff --git a/libdrgn/stack_trace.c b/libdrgn/stack_trace.c index 282910772..8bff99740 100644 --- a/libdrgn/stack_trace.c +++ b/libdrgn/stack_trace.c @@ -1061,6 +1061,10 @@ drgn_unwind_one_register(struct drgn_program *prog, struct drgn_elf_file *file, } case DRGN_CFI_RULE_AT_DWARF_EXPRESSION: case DRGN_CFI_RULE_DWARF_EXPRESSION: + // It is possible for file to be NULL when using built-in ORC. + // However, it should be impossible to encounter a DWARF + // expression for built-in ORC. + assert(file != NULL); err = drgn_eval_cfi_dwarf_expression(prog, file, rule, regs, buf, size); break; @@ -1116,7 +1120,7 @@ drgn_unwind_with_cfi(struct drgn_program *prog, struct drgn_cfi_row **row, if (!regs->module) return &drgn_not_found; - struct drgn_elf_file *file; + struct drgn_elf_file *file = NULL; bool interrupted; drgn_register_number ret_addr_regno; /* If we found the module, then we must have the PC. */ From 2026435128b6a4988d4bd7a4ddba24805ecd529a Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Mon, 16 Dec 2024 11:50:39 -0800 Subject: [PATCH 4/5] orc_info: apply debug file bias to pc_base at load When looking up CFI rules using ORC, we use module->debug_file_bias unconditionally. This makes sense when the ORC is always loaded from an ELF debug file. However, now that built-in ORC can be loaded, it is possible that: 1. ORC is loaded from the built-in source, prior to loading the debug file. The module->debug_file_bias == 0, so the ORC is interpreted correctly. 2. Later, a debug file is loaded, updating debug_file_bias. However, the ORC hasn't been loaded from the debug file, so the bias is not applicable. 3. Future CFI lookups using ORC fail due to the extra bias. To avoid this, apply the debug_file_bias once to module->orc.pc_base, at the time we load the ORC sections out of the debug file. This ensures that the bias is only applied to the ORC data when we know we need it. Signed-off-by: Stephen Brennan --- libdrgn/orc_info.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdrgn/orc_info.c b/libdrgn/orc_info.c index c4e3202de..4096c45d5 100644 --- a/libdrgn/orc_info.c +++ b/libdrgn/orc_info.c @@ -534,6 +534,7 @@ struct drgn_error *drgn_module_parse_orc(struct drgn_module *module) if (module->debug_file) { err = drgn_read_orc_sections(module); + module->orc.pc_base += module->debug_file_bias; } else { // Buffers here are not from libelf, so we should free them. // Since new copies are allocated below, they should always be @@ -676,11 +677,10 @@ drgn_module_find_orc_cfi(struct drgn_module *module, uint64_t pc, struct drgn_cfi_row **row_ret, bool *interrupted_ret, drgn_register_number *ret_addr_regno_ret) { - uint64_t unbiased_pc = pc - module->debug_file_bias; #define less_than_orc_pc(a, b) \ (*(a) < drgn_orc_pc(module, (b) - module->orc.pc_offsets)) size_t i = binary_search_gt(module->orc.pc_offsets, - module->orc.num_entries, &unbiased_pc, + module->orc.num_entries, &pc, less_than_orc_pc); #undef less_than_orc_pc // We can tell when the program counter is below the minimum program From 44e10fd2a62c3c2332ac8a7fd85a952e2b2e33bd Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Thu, 19 Dec 2024 14:37:05 -0800 Subject: [PATCH 5/5] tests: linux_kernel: test built-in ORC unwinding Loading built-in ORC is a difficult functionality to test: it is best tested when there is no debuginfo file. Thus, we add two tests: one simpler test in which the kernel has debuginfo, but a module does not, and we must unwind a stack with functions from the module. The second test is more complex, where we create a program with no debuginfo at all, and provide it just enough data to initialize the module API and unwind with built-in ORC. In both cases, to verify that drgn is actually using ORC, we capture its log messages. Signed-off-by: Stephen Brennan --- tests/__init__.py | 12 +++ tests/linux_kernel/test_stack_trace.py | 139 ++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 12a5d3264..322379011 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -3,6 +3,7 @@ import contextlib import functools +import logging import os import sys from typing import Any, Mapping, NamedTuple, Optional @@ -455,3 +456,14 @@ def modifyenv(vars: Mapping[str, Optional[str]]): del os.environ[key] else: os.environ[key] = old_value + + +@contextlib.contextmanager +def drgn_log_level(level: int): + logger = logging.getLogger("drgn") + old_level = logger.getEffectiveLevel() + logger.setLevel(level) + try: + yield + finally: + logger.setLevel(old_level) diff --git a/tests/linux_kernel/test_stack_trace.py b/tests/linux_kernel/test_stack_trace.py index d414306a9..3e4dea58b 100644 --- a/tests/linux_kernel/test_stack_trace.py +++ b/tests/linux_kernel/test_stack_trace.py @@ -1,12 +1,15 @@ # Copyright (c) Meta Platforms, Inc. and affiliates. # SPDX-License-Identifier: LGPL-2.1-or-later +import logging import os +import re import unittest from _drgn_util.platform import NORMALIZED_MACHINE_NAME -from drgn import Object, Program, reinterpret -from tests import assertReprPrettyEqualsStr, modifyenv +from drgn import MissingDebugInfoError, Object, Program, TypeMember, reinterpret +from drgn.helpers.linux import load_module_kallsyms, load_vmlinux_kallsyms +from tests import assertReprPrettyEqualsStr, drgn_log_level, modifyenv from tests.linux_kernel import ( LinuxKernelTestCase, fork_and_stop, @@ -59,6 +62,60 @@ def test_by_pid_dwarf(self): def test_by_pid_orc(self): self._test_by_pid(True) + def _check_logged_orc_message(self, captured_logs, module): + # To be sure that we actually used ORC to unwind through the drgn_test + # stack frames, search for the log output. We don't know which ORC + # version is used, so just ensure that we have a log line that mentions + # loading ORC. + expr = re.compile( + r"DEBUG:drgn:Loaded built-in ORC \(v\d+\) for module " + module + ) + for line in captured_logs.output: + if expr.fullmatch(line): + break + else: + self.fail(f"Did not load built-in ORC for {module}") + + @unittest.skipUnless( + NORMALIZED_MACHINE_NAME == "x86_64", + f"{NORMALIZED_MACHINE_NAME} does not use ORC", + ) + @skip_unless_have_test_kmod + def test_by_pid_builtin_orc(self): + # ORC was introduced in kernel 4.14. Detect the presence of ORC or skip + # the test. + try: + self.prog.symbol("__start_orc_unwind") + except LookupError: + ver = self.prog["UTS_RELEASE"].string_().decode() + self.skipTest(f"ORC is not available for {ver}") + + with drgn_log_level(logging.DEBUG): + # Create a program with the core kernel debuginfo loaded, + # but without module debuginfo. Load a symbol finder using + # kallsyms so that the module's stack traces can still have + # usable frame names. + prog = Program() + prog.set_kernel() + try: + prog.load_default_debug_info() + except MissingDebugInfoError: + pass + kallsyms = load_module_kallsyms(prog) + prog.register_symbol_finder("module_kallsyms", kallsyms, enable_index=1) + for thread in prog.threads(): + if b"drgn_test_kthread".startswith(thread.object.comm.string_()): + pid = thread.tid + break + else: + self.fail("couldn't find drgn_test_kthread") + # We must set drgn's log level manually, beacuse it won't log messages + # to the logger if it isn't enabled for them. + with self.assertLogs("drgn", logging.DEBUG) as log: + self._test_drgn_test_kthread_trace(prog.stack_trace(pid)) + + self._check_logged_orc_message(log, "drgn_test") + @skip_unless_have_test_kmod def test_by_pt_regs(self): pt_regs = self.prog["drgn_test_kthread_pt_regs"] @@ -104,6 +161,84 @@ def test_locals(self): else: self.fail("Couldn't find drgn_test_kthread_fn3 frame") + @unittest.skipUnless( + NORMALIZED_MACHINE_NAME == "x86_64", + f"{NORMALIZED_MACHINE_NAME} does not use ORC", + ) + def test_vmlinux_builtin_orc(self): + # ORC was introduced in kernel 4.14. Detect the presence of ORC or skip + # the test. + try: + self.prog.symbol("__start_orc_unwind") + except LookupError: + ver = self.prog["UTS_RELEASE"].string_().decode() + self.skipTest(f"ORC is not available for {ver}") + + with drgn_log_level(logging.DEBUG): + # It is difficult to test stack unwinding in a program without also + # loading types, which necessarily will also make DWARF CFI and ORC + # available in the debug file. The way we get around this is by creating + # a new program with no debuginfo, getting a pt_regs from the program + # that has debuginfo, and then using that to unwind the kernel. We still + # need a symbol finder, and we'll need the Module API to recognize the + # kernel address range correctly. + prog = Program() + prog.set_kernel() + prog.register_symbol_finder( + "vmlinux_kallsyms", load_vmlinux_kallsyms(prog), enable_index=0 + ) + main, _ = prog.main_module(name="kernel", create=True) + main.address_range = self.prog.main_module().address_range + + # Luckily, all drgn cares about for x86_64 pt_regs is that it is a + # structure. Rather than creating a matching struct pt_regs definition, + # we can just create a dummy one of the correct size: + # struct pt_regs { unsigned char[size]; }; + # Drgn will happily use that and reinterpret the bytes correctly. + real_pt_regs_type = self.prog.type("struct pt_regs") + fake_pt_regs_type = prog.struct_type( + tag="pt_regs", + size=real_pt_regs_type.size, + members=[ + TypeMember( + prog.array_type( + prog.int_type("unsigned char", 1, False), + real_pt_regs_type.size, + ), + "data", + ), + ], + ) + + with fork_and_stop() as pid: + trace = self.prog.stack_trace(pid) + regs_dict = trace[0].registers() + pt_regs_obj = Object( + self.prog, + real_pt_regs_type, + { + "bp": regs_dict["rbp"], + "sp": regs_dict["rsp"], + "ip": regs_dict["rip"], + "r15": regs_dict["r15"], + }, + ) + fake_pt_regs_obj = Object.from_bytes_( + prog, fake_pt_regs_type, pt_regs_obj.to_bytes_() + ) + # We must set drgn's log level manually, beacuse it won't log messages + # to the logger if it isn't enabled for them. + with self.assertLogs("drgn", logging.DEBUG) as log: + no_debuginfo_trace = prog.stack_trace(fake_pt_regs_obj) + + dwarf_pcs = [] + for frame in trace: + if not dwarf_pcs or dwarf_pcs[-1] != frame.pc: + dwarf_pcs.append(frame.pc) + orc_pcs = [frame.pc for frame in no_debuginfo_trace] + self.assertEqual(dwarf_pcs, orc_pcs) + self._check_logged_orc_message(log, "kernel") + def test_registers(self): # Smoke test that we get at least one register and that # StackFrame.registers() agrees with StackFrame.register().