diff --git a/libdrgn/debug_info.c b/libdrgn/debug_info.c index 3f3aef63f..6d1edba1e 100644 --- a/libdrgn/debug_info.c +++ b/libdrgn/debug_info.c @@ -1228,9 +1228,9 @@ static bool drgn_module_elf_file_bias(struct drgn_module *module, SWITCH_ENUM(module->kind) { case DRGN_MODULE_MAIN: if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) { - *ret = prog->vmcoreinfo.kaslr_offset; + *ret = prog->ktext_offset; drgn_log_debug(prog, - "got bias 0x%" PRIx64 " from VMCOREINFO", + "got bias 0x%" PRIx64 " of kernel", *ret); return true; } else { @@ -1376,6 +1376,21 @@ drgn_module_maybe_use_elf_file(struct drgn_module *module, goto unused; } + /* This may be initialized by drgn_find_ktext of existing vmcore */ + if (prog->ktext_mapped) { + uint64_t s, e; + /* Dummy load of ELF under the assumption that + * _text is always on the lowest address. */ + if (!drgn_elf_file_address_range(file, &s, &e)) { + drgn_log_warning(prog, "Failed ktext_offset calculation."); + goto noktext_offset; + } + + prog->ktext_offset = prog->ktext_mapped - s; + drgn_log_info(prog, "Calculated ktext_offset=%lx", prog->ktext_offset); + } + +noktext_offset: uint64_t bias; if (!drgn_module_elf_file_bias(module, file, &bias)) { err = NULL; @@ -5049,7 +5064,7 @@ load_debug_info_try_provided_vmlinux(struct drgn_module *module, release_len) == 0) { drgn_log_debug(prog, "%s: %s Linux version matches", module->name, file->path); - } else { + } else if (strlen(prog->vmcoreinfo.osrelease) > 0) { drgn_log_debug(prog, "%s: %s Linux version (%.*s) does not match", module->name, file->path, @@ -5057,6 +5072,8 @@ load_debug_info_try_provided_vmlinux(struct drgn_module *module, ? INT_MAX : (int)release_len, release); continue; + } else { + drgn_log_debug(prog, "Linux version in VMCOREINFO not found"); } if (!it.entry->matched) { diff --git a/libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch b/libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch index a9d8dff07..d56332de2 100644 --- a/libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch +++ b/libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch @@ -101,7 +101,7 @@ struct drgn_error *drgn_program_parse_vmcoreinfo(struct drgn_program *prog, break; @case "KERNELOFFSET"@ err = parse_vmcoreinfo_u64(value, newline, 16, - &prog->vmcoreinfo.kaslr_offset); + &prog->ktext_offset); if (err) return err; break; @@ -159,18 +159,21 @@ struct drgn_error *drgn_program_parse_vmcoreinfo(struct drgn_program *prog, @endswitch@ } if (!prog->vmcoreinfo.osrelease[0]) { - return drgn_error_create(DRGN_ERROR_OTHER, + drgn_log_error(prog, "VMCOREINFO does not contain valid OSRELEASE"); + // XXX make users of osrelease more robust } ignore_broken_vmcoreinfo_build_id(prog); if (!is_power_of_two(prog->vmcoreinfo.page_size)) { - return drgn_error_create(DRGN_ERROR_OTHER, + drgn_log_error(prog, "VMCOREINFO does not contain valid PAGESIZE"); + // XXX make users of osrelease more robust || assume page_size } prog->vmcoreinfo.page_shift = ctz(prog->vmcoreinfo.page_size); if (!prog->vmcoreinfo.swapper_pg_dir) { - return drgn_error_create(DRGN_ERROR_OTHER, + drgn_log_error(prog, "VMCOREINFO does not contain valid swapper_pg_dir"); + // XXX make users of swapper_pg_dir more robust } // Everything else is optional. return NULL; diff --git a/libdrgn/kdump.c b/libdrgn/kdump.c index ad6d07316..196ed4db2 100644 --- a/libdrgn/kdump.c +++ b/libdrgn/kdump.c @@ -5,8 +5,12 @@ #include #include #include +#ifdef WITH_LIBKDUMPFILE +#include +#endif #include "linux_kernel.h" +#include "log.h" #include "plugins.h" #include "program.h" // IWYU pragma: associated #include "util.h" @@ -125,6 +129,53 @@ static struct drgn_error *drgn_read_kdump(void *buf, uint64_t address, return NULL; } +static struct drgn_error *drgn_find_ktext(struct drgn_program *prog, kdump_ctx_t *ctx) +{ + struct drgn_error *err; + kdump_status ks; + addrxlat_sys_t *sys; + addrxlat_map_t *map; + addrxlat_addr_t addr; + const addrxlat_range_t *range; + size_t i, n; + bool found = false; + + ks = kdump_get_addrxlat(ctx, NULL, &sys); + if (ks != KDUMP_OK) + return drgn_error_format(DRGN_ERROR_OTHER, "Cannot setup addrxlat"); + + map = addrxlat_sys_get_map(sys, ADDRXLAT_SYS_MAP_KV_PHYS); + if (!map) { + err = drgn_error_format(DRGN_ERROR_OTHER, "Cannot get addrxlat map"); + goto err; + } + + n = addrxlat_map_len(map); + addr = 0; + range = addrxlat_map_ranges(map); + for (i = 0; i < n; ++i) { + if (range->meth == ADDRXLAT_SYS_METH_KTEXT) { + prog->ktext_mapped = addr; + found = true; + drgn_log_debug(prog, "addrxlat found ktext at %x", addr); + break; + } + + addr += range->endoff + 1; + ++range; + } + if (!found) { + err = drgn_error_format(DRGN_ERROR_OTHER, + "addrxlat cannot locate KTEXT"); + goto err; + } + return NULL; + +err: + addrxlat_sys_decref(sys); + return err; +} + struct drgn_error *drgn_program_set_kdump(struct drgn_program *prog) { struct drgn_error *err; @@ -221,20 +272,19 @@ struct drgn_error *drgn_program_set_kdump(struct drgn_program *prog) #endif } else { #if KDUMPFILE_VERSION >= KDUMPFILE_MKVER(0, 4, 1) - char *vmcoreinfo; + char *vmcoreinfo = NULL; #else const char *vmcoreinfo; #endif ks = kdump_vmcoreinfo_raw(ctx, &vmcoreinfo); - if (ks != KDUMP_OK) { - err = drgn_error_format(DRGN_ERROR_OTHER, - "kdump_vmcoreinfo_raw: %s", - kdump_get_err(ctx)); - goto err; + if (ks == KDUMP_OK) { + err = drgn_program_parse_vmcoreinfo(prog, vmcoreinfo, + strlen(vmcoreinfo) + 1); + } else { + drgn_log_warning(prog, "No vmcoreinfo (%s), relying on addrxlat and debuginfo", kdump_get_err(ctx)); + err = drgn_find_ktext(prog, ctx); } - err = drgn_program_parse_vmcoreinfo(prog, vmcoreinfo, - strlen(vmcoreinfo) + 1); /* * As of libkdumpfile 0.4.1, the string returned by * kdump_vmcoreinfo_raw() needs to be freed. diff --git a/libdrgn/linux_kernel.c b/libdrgn/linux_kernel.c index a84a4f848..d0b4009c3 100644 --- a/libdrgn/linux_kernel.c +++ b/libdrgn/linux_kernel.c @@ -29,6 +29,7 @@ #include "helpers.h" #include "hexlify.h" #include "io.h" +#include "log.h" #include "linux_kernel.h" #include "log.h" #include "platform.h" diff --git a/libdrgn/program.c b/libdrgn/program.c index 396cc0131..4ea76cde4 100644 --- a/libdrgn/program.c +++ b/libdrgn/program.c @@ -338,7 +338,6 @@ drgn_program_set_core_dump_fd_internal(struct drgn_program *prog, int fd, size_t phnum, i; size_t num_file_segments, j; bool have_phys_addrs = false; - bool have_qemu_note = false; const char *vmcoreinfo_note = NULL; size_t vmcoreinfo_size = 0; bool have_nt_taskstruct = false, is_proc_kcore; @@ -454,10 +453,6 @@ drgn_program_set_core_dump_fd_internal(struct drgn_program *prog, int fd, */ have_phys_addrs = true; have_vmcoreinfo = true; - } else if (nhdr.n_namesz == sizeof("QEMU") && - memcmp(name, "QEMU", - sizeof("QEMU")) == 0) { - have_qemu_note = true; } } } @@ -480,7 +475,7 @@ drgn_program_set_core_dump_fd_internal(struct drgn_program *prog, int fd, is_proc_kcore = false; } - if (have_vmcoreinfo && !is_proc_kcore) { + if (!is_proc_kcore) { /* libkdumpfile for kernel ELF vmcores */ char *env; /* Use libkdumpfile for ELF vmcores if it was requested. */ @@ -631,7 +626,7 @@ drgn_program_set_core_dump_fd_internal(struct drgn_program *prog, int fd, j++; } } - if (vmcoreinfo_note && !prog->vmcoreinfo.raw) { + if (vmcoreinfo_note && !prog->vmcoreinfo.raw) { /* override vmcoreinfo from ELF */ err = drgn_program_parse_vmcoreinfo(prog, vmcoreinfo_note, vmcoreinfo_size); if (err) @@ -651,15 +646,6 @@ drgn_program_set_core_dump_fd_internal(struct drgn_program *prog, int fd, prog->core = NULL; } else if (have_vmcoreinfo) { prog->flags |= DRGN_PROGRAM_IS_LINUX_KERNEL; - } else if (have_qemu_note) { - err = drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, - "unrecognized QEMU memory dump; " - "for Linux guests, run QEMU with '-device vmcoreinfo', " - "compile the kernel with CONFIG_FW_CFG_SYSFS and CONFIG_KEXEC, " - "and load the qemu_fw_cfg kernel module " - "before dumping the guest memory " - "(requires Linux >= 4.17 and QEMU >= 2.11)"); - goto out_segments; } if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) { err = drgn_program_finish_set_kernel(prog); diff --git a/libdrgn/program.h b/libdrgn/program.h index 90413d16d..d5d3ea3a2 100644 --- a/libdrgn/program.h +++ b/libdrgn/program.h @@ -184,6 +184,7 @@ struct drgn_program { * * This is non-zero if kernel address space * layout randomization (KASLR) is enabled. + * XXX unused */ uint64_t kaslr_offset; /** Kernel page table. */ @@ -237,6 +238,21 @@ struct drgn_program { * Whether @ref drgn_program::mod_text has been cached. */ bool mod_text_cached; + /** + * The offset from the compiled address of the + * kernel image to its actual address in memory. + * + * This is non-zero if kernel address space + * layout randomization (KASLR) is enabled. + */ + uint64_t ktext_offset; + /** + * ktext_mapped = &_text + ktext_offset + * where + * &_text is ELF before loading + * ktext_mapped is from vmcore's mapping probing + */ + uint64_t ktext_mapped; /* * Whether we are currently in address translation. Used * to prevent address translation from recursing. diff --git a/libdrgn/python/helpers.c b/libdrgn/python/helpers.c index a49956214..b4c687d59 100644 --- a/libdrgn/python/helpers.c +++ b/libdrgn/python/helpers.c @@ -277,7 +277,7 @@ PyObject *drgnpy_linux_helper_kaslr_offset(PyObject *self, PyObject *arg) Program *prog = (Program *)arg; if (!(prog->prog.flags & DRGN_PROGRAM_IS_LINUX_KERNEL)) return PyErr_Format(PyExc_ValueError, "not Linux kernel"); - return PyLong_FromUint64(prog->prog.vmcoreinfo.kaslr_offset); + return PyLong_FromUint64(prog->prog.ktext_offset); } PyObject *drgnpy_linux_helper_pgtable_l5_enabled(PyObject *self, PyObject *arg)