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..29522a26a 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, @@ -5528,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/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/orc_info.c b/libdrgn/orc_info.c index d30b4f6da..4096c45d5 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 @@ -214,11 +215,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 +234,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 +316,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"); @@ -356,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) { @@ -368,18 +521,41 @@ 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); + 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 + // 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])); @@ -419,7 +595,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]; @@ -476,6 +651,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; } @@ -501,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 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/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}, 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. */ 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().