From: wya <wya(a)qti.qualcomm.com>
Build dump module data during module_init from readable forward and
reverse modules links and module_kset. Reuse do_list_no_hash through a
thin kset callback walker, validate module backlinks and memory ranges,
and deduplicate module addresses before constructing the symbol table.
Warn only when module_kset adds modules absent from the collected list.
Use the existing module and symbol tables for bt and object-file loading.
Keep mod -s and -S as the loading interface without new command options
or a partial-module state flag. Later loads do not rebuild the table or
discard previously loaded object-file data. Honor --no_modules.
Keep symbol sizing inside symbols.c and feed collected addresses to the
existing core/init and module.mem loader implementations. Bound symbol
counts and kallsyms buffer accesses, retaining usable modules when
optional symbol data is damaged. Keep live, legacy kallsyms-header and
PaX module initialization on their existing paths.
Release the external namespace during initialization even when no valid
candidates remain. Register address-array cleanup for interrupted normal
reinitialization. Append new offset_table members and preserve the
public two-argument module symbol loader interfaces for extensions.
Signed-off-by: wya <wya(a)qti.qualcomm.com>
Signed-off-by: Chao Huang <chahuan(a)qti.qualcomm.com>
---
defs.h | 5 +
kernel.c | 276 ++++++++++++++++++++++++++++++++++++++---
symbols.c | 357 ++++++++++++++++++++++++++++++++++++++++++++----------
tools.c | 29 +++++
4 files changed, 590 insertions(+), 77 deletions(-)
diff --git a/defs.h b/defs.h
index 5b768d2..70642e0 100644
--- a/defs.h
+++ b/defs.h
@@ -2299,6 +2299,9 @@ struct offset_table { /* stash of commonly-used
offsets */
long page_compound_info;
long kmem_cache_per_node;
long kmem_cache_per_node_ptrs_node;
+ long module_mkobj;
+ long module_kobject_kobj;
+ long module_kobject_mod;
};
struct size_table { /* stash of commonly-used sizes */
@@ -5698,6 +5701,7 @@ char *shift_string_right(char *, int);
int bracketed(char *, char *, int);
void backspace(int);
int do_list(struct list_data *);
+void kset_walk(ulong, int (*)(void *, void *), void *);
int do_list_no_hash(struct list_data *);
struct radix_tree_ops {
void (*entry)(ulong node, ulong slot, const char *path,
@@ -5845,6 +5849,7 @@ void dump_union(char *, ulong, unsigned);
void store_module_symbols_v1(ulong, int);
void store_module_symbols_v2(ulong, int);
void store_module_symbols_6_4(ulong, int);
+void store_recovered_module_symbols(int, ulong *);
int is_datatype_command(void);
int is_typedef(char *);
int arg_to_datatype(char *, struct datatype_member *, ulong);
diff --git a/kernel.c b/kernel.c
index ec8df88..e40255e 100644
--- a/kernel.c
+++ b/kernel.c
@@ -22,6 +22,7 @@
#include <libgen.h>
#include <ctype.h>
#include <stdbool.h>
+#include <limits.h>
#include "xendump.h"
#if defined(GDB_7_6) || defined(GDB_10_2) || defined(GDB_16_2)
#define __CONFIG_H__ 1
@@ -3616,6 +3617,183 @@ get_dumpfile_regs(struct bt_info *bt, ulong *eip, ulong *esp)
}
+struct recovered_modules {
+ char *module_buf;
+ long name_size;
+ ulong *addresses;
+ int count;
+ int allocated;
+ int stopped;
+ int skipped;
+};
+
+/* The address array survives collection until loading; also clean up on longjmp. */
+static void
+module_recovery_cleanup(void *addresses)
+{
+ free(addresses);
+ pc->cmd_cleanup = NULL;
+ pc->cmd_cleanup_arg = NULL;
+}
+
+static int
+collect_recovered_module(void *address, void *data)
+{
+ struct recovered_modules *modules = data;
+ ulong module = (ulong)address, base, size, core_size = 0, *addresses;
+ char *name;
+ int i, t, allocated;
+
+ if (modules->stopped)
+ return TRUE;
+ for (i = 0; i < modules->count; i++) {
+ if (modules->addresses[i] == module)
+ return FALSE;
+ }
+ if (!IS_KVADDR(module) || !readmem(module, KVADDR,
+ modules->module_buf, SIZE(module), "recovered module",
+ RETURN_ON_ERROR|QUIET))
+ goto invalid;
+
+ name = modules->module_buf + OFFSET(module_name);
+ if (!*name || !memchr(name, '\0', modules->name_size))
+ goto invalid;
+ for (t = 0; t < (MODULE_MEMORY() ? MOD_INIT_TEXT : 1); t++) {
+ if (MODULE_MEMORY()) {
+ base = ULONG(modules->module_buf + OFFSET(module_mem) +
+ SIZE(module_memory) * t + OFFSET(module_memory_base));
+ size = UINT(modules->module_buf + OFFSET(module_mem) +
+ SIZE(module_memory) * t + OFFSET(module_memory_size));
+ } else {
+ base = ULONG(modules->module_buf + MODULE_OFFSET2(module_module_core, rx));
+ if (THIS_KERNEL_VERSION >= LINUX(2,6,27))
+ size = UINT(modules->module_buf + MODULE_OFFSET2(module_core_size, rx));
+ else
+ size = ULONG(modules->module_buf + MODULE_OFFSET2(module_core_size, rx));
+ }
+ /* GETBUF() accepts a signed long size; do not truncate damaged sizes. */
+ if (size && (!IS_KVADDR(base) || size > LONG_MAX || size > ~0UL - base))
+ goto invalid;
+ if (size > ULONG_MAX - core_size)
+ goto invalid;
+ core_size += size;
+ }
+ if (!core_size)
+ goto invalid;
+ if (modules->count == modules->allocated) {
+ if (modules->allocated == INT_MAX)
+ goto allocation_failure;
+ allocated = modules->allocated > INT_MAX / 2 ? INT_MAX :
+ (modules->allocated ? modules->allocated * 2 : 1);
+ if ((size_t)allocated > SIZE_MAX / sizeof(ulong))
+ goto allocation_failure;
+ addresses = realloc(modules->addresses, allocated * sizeof(ulong));
+ if (!addresses)
+ goto allocation_failure;
+ modules->addresses = addresses;
+ modules->allocated = allocated;
+ pc->cmd_cleanup_arg = addresses;
+ }
+ modules->addresses[modules->count++] = module;
+ return FALSE;
+
+invalid:
+ modules->skipped++;
+ return FALSE;
+
+allocation_failure:
+ error(WARNING, "module recovery address allocation failed; keeping collected
modules\n");
+ modules->stopped = TRUE;
+ return TRUE;
+}
+
+static int
+collect_module_kobject(void *address, void *data)
+{
+ struct recovered_modules *modules = data;
+ ulong module_kobj = (ulong)address - OFFSET(module_kobject_kobj);
+ ulong module;
+
+ if (modules->stopped)
+ return TRUE;
+ if (!readmem(module_kobj + OFFSET(module_kobject_mod), KVADDR,
+ &module, sizeof(ulong), "module_kobject.mod", RETURN_ON_ERROR|QUIET))
{
+ modules->skipped++;
+ return FALSE;
+ }
+ /* Built-in module sysfs objects have no struct module. */
+ if (!module)
+ return FALSE;
+ /* Reject a stale or mismatched kobject before trusting its module pointer. */
+ if (!IS_KVADDR(module) || module + OFFSET(module_mkobj) != module_kobj) {
+ modules->skipped++;
+ return FALSE;
+ }
+ return collect_recovered_module((void *)module, data);
+}
+
+static int
+recover_modules_from_module_kset(void *module_buf, ulong **module_list)
+{
+ struct recovered_modules modules;
+ struct list_data ld;
+ ulong module_kset, head, next;
+ int direction, list_count;
+
+ *module_list = NULL;
+ BZERO(&modules, sizeof(modules));
+ modules.module_buf = module_buf;
+ modules.name_size = MEMBER_SIZE("module", "name");
+ if (modules.name_size <= 0 ||
+ modules.name_size > SIZE(module) - OFFSET(module_name))
+ return 0;
+ pc->cmd_cleanup = module_recovery_cleanup;
+ pc->cmd_cleanup_arg = NULL;
+
+ /* A broken module list can still have a readable prefix or suffix. */
+ head = kt->kernel_module;
+ if (!modules.stopped && IS_KVADDR(head)) {
+ for (direction = 0; direction < 2 && !modules.stopped; direction++) {
+ if (!readmem(head + (direction ? OFFSET(list_head_prev) :
+ OFFSET(list_head_next)), KVADDR, &next, sizeof(ulong),
+ "modules list head", RETURN_ON_ERROR|QUIET) || next == head)
+ continue;
+ BZERO(&ld, sizeof(ld));
+ ld.flags = LIST_CALLBACK|CALLBACK_RETURN|RETURN_ON_LIST_ERROR|LIST_HEAD_FORMAT;
+ ld.start = next;
+ ld.end = head;
+ ld.member_offset = direction ? OFFSET(list_head_prev) : OFFSET(list_head_next);
+ ld.list_head_offset = OFFSET(module_list);
+ ld.callback_func = collect_recovered_module;
+ ld.callback_data = &modules;
+ do_list_no_hash(&ld);
+ }
+ }
+
+ list_count = modules.count;
+ if (!modules.stopped && kernel_symbol_exists("module_kset")
&&
+ VALID_MEMBER(module_mkobj) && VALID_MEMBER(module_kobject_kobj) &&
+ VALID_MEMBER(module_kobject_mod) &&
+ try_get_symbol_data("module_kset", sizeof(ulong), &module_kset))
+ kset_walk(module_kset, collect_module_kobject, &modules);
+ if (modules.count > list_count)
+ error(WARNING, "supplemented %d modules from module_kset\n",
+ modules.count - list_count);
+
+ if (!modules.count) {
+ module_recovery_cleanup(modules.addresses);
+ return 0;
+ }
+ *module_list = modules.addresses;
+ if (modules.skipped)
+ error(WARNING, "skipped %d invalid module candidates; module data may be
incomplete\n",
+ modules.skipped);
+ if (CRASHDEBUG(1))
+ error(INFO, "collected %d modules from modules list and module_kset\n",
modules.count);
+ return modules.count;
+}
+
+
/*
* Store the head of the kernel module list for future use.
* Count the number of symbols defined by all modules in the system,
@@ -3624,10 +3802,11 @@ get_dumpfile_regs(struct bt_info *bt, ulong *eip, ulong *esp)
void
module_init(void)
{
- int i, c;
+ int i, c, recover;
ulong size, mod, mod_next;
uint nsyms;
ulong total, numksyms;
+ ulong *module_addr_list;
char *modbuf, *kallsymsbuf;
ulong kallsyms_header;
struct syment *sp, *sp_array[10];
@@ -3647,6 +3826,9 @@ module_init(void)
else
error(WARNING, "cannot determine how modules are linked\n");
+ /* Build dump module data once, before any object files are loaded. */
+ recover = DUMPFILE() && (kt->flags & KMOD_V2) &&
+ !(kt->flags & KALLSYMS_V1) && !(kt->flags2 & KMOD_PAX);
if (kt->flags & NO_MODULE_ACCESS || !(kt->flags &
(KMOD_V1|KMOD_V2))) {
error(WARNING, "no kernel module access\n\n");
kt->module_list = 0;
@@ -3791,6 +3973,24 @@ module_init(void)
}
MEMBER_OFFSET_INIT(module_percpu, "module", "percpu");
+ /* The sanitizer handles core/init and module.mem, not PaX regions. */
+ if (kt->flags2 & KMOD_PAX)
+ recover = FALSE;
+ if (MEMBER_EXISTS("module", "mkobj"))
+ MEMBER_OFFSET_INIT(module_mkobj, "module", "mkobj");
+ if (STRUCT_EXISTS("module_kobject")) {
+ if (MEMBER_EXISTS("module_kobject", "kobj"))
+ MEMBER_OFFSET_INIT(module_kobject_kobj,
+ "module_kobject", "kobj");
+ if (MEMBER_EXISTS("module_kobject", "mod"))
+ MEMBER_OFFSET_INIT(module_kobject_mod,
+ "module_kobject", "mod");
+ }
+ if (STRUCT_EXISTS("kset") && MEMBER_EXISTS("kset",
"list"))
+ MEMBER_OFFSET_INIT(kset_list, "kset", "list");
+ if (STRUCT_EXISTS("kobject") &&
+ MEMBER_EXISTS("kobject", "entry"))
+ MEMBER_OFFSET_INIT(kobject_entry, "kobject", "entry");
/*
* Make sure to pick the kernel "modules" list_head symbol,
@@ -3799,9 +3999,15 @@ module_init(void)
* (empty) or contain vmalloc'd module addresses; the ia64/sn
* modules array contains a list of kmalloc'd addresses.
*/
- if ((c = get_syment_array("modules", sp_array, 10)) > 1) {
+ if ((c = get_syment_array("modules", sp_array, 10)) > 1) {
modules_found = FALSE;
- for (i = 0; i < c; i++) {
+ for (i = 0; recover && i < c; i++) {
+ if (sp_array[i]->value == kt->kernel_module) {
+ modules_found = TRUE;
+ break;
+ }
+ }
+ for (i = 0; !modules_found && i < c; i++) {
sp = sp_array[i];
if (!readmem(sp->value, KVADDR,
@@ -3810,6 +4016,19 @@ module_init(void)
RETURN_ON_ERROR|QUIET))
continue;
+ if (recover) {
+ /* Either readable direction can identify a damaged head. */
+ if (((ulong)list.next == sp->value &&
+ (ulong)list.prev == sp->value) ||
+ IS_VMALLOC_ADDR((ulong)list.next) ||
+ IS_VMALLOC_ADDR((ulong)list.prev)) {
+ kt->kernel_module = sp->value;
+ modules_found = TRUE;
+ break;
+ }
+ continue;
+ }
+
if ((ulong)list.next == symbol_value("modules")) {
kt->mods_installed = 0;
return;
@@ -3828,43 +4047,65 @@ module_init(void)
error(WARNING,
"cannot determine which of %d \"modules\" symbols is
appropriate\n\n",
c);
+ if (recover) {
+ /* The kset can still supply candidates without a list head. */
+ kt->kernel_module = 0;
+ break;
+ }
kt->mods_installed = 0;
kt->flags |= NO_MODULE_ACCESS;
return;
}
} else {
+ if (recover) {
+ kt->kernel_module = symbol_value("modules");
+ break;
+ }
get_symbol_data("modules", sizeof(ulong),
&kt->module_list);
if (kt->module_list == symbol_value("modules")) {
kt->mods_installed = 0;
return;
}
- kt->kernel_module = symbol_value("modules");
+ kt->kernel_module = symbol_value("modules");
}
+ if (recover)
+ break;
kt->module_list -= OFFSET(module_list);
break;
}
+ /* Shared module/symbol data serves bt and the unchanged -s/-S loaders. */
+ if (recover) {
+ modbuf = GETBUF(SIZE(module));
+ c = recover_modules_from_module_kset(modbuf, &module_addr_list);
+ FREEBUF(modbuf);
+ store_recovered_module_symbols(c, module_addr_list);
+ module_recovery_cleanup(module_addr_list);
+ return;
+ }
total = kt->mods_installed = 0;
-
- modbuf = GETBUF(SIZE(module));
+ modbuf = GETBUF(SIZE(module));
kallsymsbuf = kt->flags & KALLSYMS_V1 ?
GETBUF(SIZE(kallsyms_header)) : NULL;
please_wait("gathering module symbol data");
- for (mod = kt->module_list; mod != kt->kernel_module; mod = mod_next) {
+ for (mod = kt->module_list; mod != kt->kernel_module; mod = mod_next) {
if (CRASHDEBUG(3))
fprintf(fp, "module: %lx\n", mod);
if (!readmem(mod, KVADDR, modbuf, SIZE(module),
"module struct", RETURN_ON_ERROR|QUIET)) {
- error(WARNING,
- "%scannot access vmalloc'd module memory\n\n",
- DUMPFILE() ? "\n" : "");
- kt->mods_installed = 0;
- kt->flags |= NO_MODULE_ACCESS;
- FREEBUF(modbuf);
+ error(WARNING,
+ "%scannot access vmalloc'd module memory\n\n",
+ DUMPFILE() ? "\n" : "");
+ kt->mods_installed = 0;
+ kt->flags |= NO_MODULE_ACCESS;
+ FREEBUF(modbuf);
+ if (kallsymsbuf)
+ FREEBUF(kallsymsbuf);
+ please_wait_done();
return;
}
@@ -3944,6 +4185,9 @@ module_init(void)
kt->mods_installed = 0;
kt->flags |= NO_MODULE_ACCESS;
FREEBUF(modbuf);
+ if (kallsymsbuf)
+ FREEBUF(kallsymsbuf);
+ please_wait_done();
return;
}
@@ -4146,7 +4390,7 @@ cmd_mod(void)
error(FATAL, "cannot access vmalloc'd module memory\n");
if (!verify_modules()) {
- error(NOTE,
+ error(NOTE,
"modules have changed on this system -- reinitializing\n");
reinit_modules();
}
@@ -4248,7 +4492,7 @@ cmd_mod(void)
if (flag)
cmd_usage(pc->curcmd, SYNOPSIS);
if (kt->flags & KMOD_V1)
- error(INFO,
+ error(INFO,
"-o option is not applicable to this kernel version\n");
st->flags |= USE_OLD_ADD_SYM;
return;
@@ -4266,7 +4510,7 @@ cmd_mod(void)
else
flag = LOAD_SPECIFIED_MODULE_SYMBOLS;
- if (hexadecimal(optarg, 0) &&
+ if (hexadecimal(optarg, 0) &&
(strlen(optarg) == VADDR_PRLEN)) {
address = htol(optarg, FAULT_ON_ERROR, NULL);
if (!is_module_address(address, buf))
diff --git a/symbols.c b/symbols.c
index 4962697..414b13a 100644
--- a/symbols.c
+++ b/symbols.c
@@ -17,6 +17,7 @@
#include "defs.h"
#include <elf.h>
+#include <limits.h>
#if defined(GDB_7_6) || defined(GDB_10_2) || defined(GDB_16_2)
#define __CONFIG_H__ 1
#include "config.h"
@@ -72,6 +73,8 @@ static int check_gnu_debuglink(bfd *);
static int separate_debug_file_exists(const char *, unsigned long, int *);
static int store_module_kallsyms_v1(struct load_module *, int, int, char *);
static int store_module_kallsyms_v2(struct load_module *, int, int, char *);
+static void store_module_symbols_v2_internal(ulong, int, ulong *);
+static void store_module_symbols_6_4_internal(ulong, int, ulong *);
static void datatype_error(void **, char *, char *, char *, int);
static char *get_thisfile(void);
struct elf_common;
@@ -1921,12 +1924,182 @@ modsym_value(ulong syms, union kernel_symbol *modsym, int i)
}
/*
- * Linux 6.4 introduced module.mem memory layout
+ * Exported tables may occupy any module region. ELF kallsyms are read from
+ * core/init buffers, or MOD_DATA/MOD_INIT_DATA with the Linux 6.4 layout.
*/
+static ulong
+recovered_symbol_space(char *modbuf, ulong address, int kallsyms)
+{
+ ulong base, size;
+ int t, regions = MODULE_MEMORY() ? MOD_MEM_NUM_TYPES : 2;
+
+ for (t = 0; t < regions; t++) {
+ if (MODULE_MEMORY()) {
+ if (kallsyms && t != MOD_DATA && t != MOD_INIT_DATA)
+ continue;
+ base = ULONG(modbuf + OFFSET(module_mem) +
+ SIZE(module_memory) * t + OFFSET(module_memory_base));
+ size = UINT(modbuf + OFFSET(module_mem) +
+ SIZE(module_memory) * t + OFFSET(module_memory_size));
+ } else {
+ base = ULONG(modbuf + (t ? MODULE_OFFSET2(module_module_init, rx) :
+ MODULE_OFFSET2(module_module_core, rx)));
+ if (THIS_KERNEL_VERSION >= LINUX(2,6,27))
+ size = UINT(modbuf + (t ? MODULE_OFFSET2(module_init_size, rx) :
+ MODULE_OFFSET2(module_core_size, rx)));
+ else
+ size = ULONG(modbuf + (t ? MODULE_OFFSET2(module_init_size, rx) :
+ MODULE_OFFSET2(module_core_size, rx)));
+ }
+ if (IS_KVADDR(base) && size <= ~0UL - base &&
+ address >= base && address - base < size)
+ return size - (address - base);
+ }
+ return 0;
+}
+
+/* Sanitize only the local copy, so sizing and loading use identical counts. */
+static ulong
+count_recovered_module_symbols(char *modbuf, int quiet)
+{
+ ulong count, base, size;
+ /* Reserve start/end markers for two legacy regions or all seven mem types. */
+ ulong total = MODULE_MEMORY() ? 14 : 4;
+ ulong symbol_size = kernel_symbol_type_init();
+ long size_offset;
+ int t, invalid = FALSE;
+
+ /* Init regions may already have been freed when the dump was captured. */
+ for (t = MODULE_MEMORY() ? MOD_INIT_TEXT : 0;
+ t < (MODULE_MEMORY() ? MOD_MEM_NUM_TYPES : 1); t++) {
+ if (MODULE_MEMORY()) {
+ base = ULONG(modbuf + OFFSET(module_mem) +
+ SIZE(module_memory) * t + OFFSET(module_memory_base));
+ size_offset = OFFSET(module_mem) + SIZE(module_memory) * t +
+ OFFSET(module_memory_size);
+ size = UINT(modbuf + size_offset);
+ } else {
+ base = ULONG(modbuf + MODULE_OFFSET2(module_module_init, rx));
+ size_offset = MODULE_OFFSET2(module_init_size, rx);
+ size = THIS_KERNEL_VERSION >= LINUX(2,6,27) ?
+ UINT(modbuf + size_offset) : ULONG(modbuf + size_offset);
+ }
+ if (size && (!IS_KVADDR(base) || size > LONG_MAX || size > ~0UL - base))
{
+ BZERO(modbuf + size_offset, MODULE_MEMORY() ||
+ THIS_KERNEL_VERSION >= LINUX(2,6,27) ? sizeof(uint) : sizeof(ulong));
+ invalid = TRUE;
+ }
+ }
+
+ count = UINT(modbuf + OFFSET(module_num_syms));
+ if (count > recovered_symbol_space(modbuf,
+ ULONG(modbuf + OFFSET(module_syms)), FALSE) / symbol_size) {
+ BZERO(modbuf + OFFSET(module_num_syms), sizeof(uint));
+ count = 0;
+ invalid = TRUE;
+ }
+ total += count;
+ if (VALID_MEMBER(module_num_gpl_syms)) {
+ count = UINT(modbuf + OFFSET(module_num_gpl_syms));
+ if (count > recovered_symbol_space(modbuf,
+ ULONG(modbuf + OFFSET(module_gpl_syms)), FALSE) / symbol_size) {
+ BZERO(modbuf + OFFSET(module_num_gpl_syms), sizeof(uint));
+ count = 0;
+ invalid = TRUE;
+ }
+ total += count;
+ }
+ if (kt->flags & KALLSYMS_V2) {
+ if (THIS_KERNEL_VERSION >= LINUX(2,6,27))
+ count = UINT(modbuf + OFFSET(module_num_symtab));
+ else
+ count = ULONG(modbuf + OFFSET(module_num_symtab));
+ symbol_size = BITS() == 32 ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym);
+ if (count && (count > recovered_symbol_space(modbuf,
+ ULONG(modbuf + OFFSET(module_symtab)), TRUE) / symbol_size ||
+ !recovered_symbol_space(modbuf,
+ ULONG(modbuf + OFFSET(module_strtab)), TRUE))) {
+ BZERO(modbuf + OFFSET(module_num_symtab),
+ THIS_KERNEL_VERSION >= LINUX(2,6,27) ? sizeof(uint) : sizeof(ulong));
+ count = 0;
+ invalid = TRUE;
+ }
+ total += count;
+ }
+ if (invalid && !quiet)
+ error(WARNING, "%s: ignoring invalid recovered memory or symbol data\n",
+ modbuf + OFFSET(module_name));
+ return total;
+}
+
+/* Keep the public two-argument loader interfaces used by extensions. */
+void
+store_module_symbols_v2(ulong total, int mods_installed)
+{
+ store_module_symbols_v2_internal(total, mods_installed, NULL);
+}
+
void
store_module_symbols_6_4(ulong total, int mods_installed)
{
- int i, m, t;
+ store_module_symbols_6_4_internal(total, mods_installed, NULL);
+}
+
+void
+store_recovered_module_symbols(int count, ulong *addresses)
+{
+ char *modbuf;
+ ulong total = 0, symbols;
+ int i, valid = 0;
+
+ if (count) {
+ modbuf = GETBUF(SIZE(module));
+ for (i = 0; i < count; i++) {
+ if (!readmem(addresses[i], KVADDR, modbuf, SIZE(module),
+ "recovered module", RETURN_ON_ERROR|QUIET))
+ continue;
+ symbols = count_recovered_module_symbols(modbuf, FALSE);
+ /* Both loaders index their symbol arrays with int. */
+ if (symbols > INT_MAX - total) {
+ error(WARNING, "%s: recovered symbol count exceeds loader capacity,
skipping\n",
+ modbuf + OFFSET(module_name));
+ continue;
+ }
+ total += symbols;
+ addresses[valid++] = addresses[i];
+ }
+ FREEBUF(modbuf);
+ }
+ kt->mods_installed = st->mods_installed = valid;
+ kt->module_list = valid ? addresses[0] : 0;
+ /* Normal reinitialization leaves the previous external namespace allocated. */
+ if (st->ext_module_namespace.address)
+ namespace_ctl(NAMESPACE_FREE, &st->ext_module_namespace, NULL, NULL);
+ if (!valid)
+ return;
+
+ /* Called only by initialization, with object-file data already absent. */
+ kt->flags |= NO_MODULE_ACCESS;
+ please_wait("gathering module symbol data");
+ if (MODULE_MEMORY())
+ store_module_symbols_6_4_internal(total, valid, addresses);
+ else
+ store_module_symbols_v2_internal(total, valid, addresses);
+ if ((st->flags & MODULE_SYMS) && st->mods_installed)
+ kt->flags &= ~NO_MODULE_ACCESS;
+ else
+ kt->module_list = 0;
+ please_wait_done();
+}
+
+/*
+ * Linux 6.4 introduced module.mem memory layout
+ */
+static void
+store_module_symbols_6_4_internal(ulong total, int mods_installed,
+ ulong *module_addr_list)
+{
+ int i, m, t, index = 0;
ulong mod, mod_next;
char *mod_name;
uint nsyms, ngplsyms;
@@ -1976,10 +2149,19 @@ store_module_symbols_6_4(ulong total, int mods_installed)
modsymbuf = NULL;
m = mcnt = mod_next = 0;
- for (mod = kt->module_list; mod != kt->kernel_module; mod = mod_next) {
+ for (mod = kt->module_list; ; ) {
+ if (module_addr_list) {
+ if (index >= mods_installed)
+ break;
+ mod = module_addr_list[index++];
+ } else if (mod == kt->kernel_module)
+ break;
- readmem(mod, KVADDR, modbuf, SIZE(module),
- "module buffer", FAULT_ON_ERROR);
+ if (!readmem(mod, KVADDR, modbuf, SIZE(module), "module buffer",
+ module_addr_list ? RETURN_ON_ERROR|QUIET : FAULT_ON_ERROR))
+ continue;
+ if (module_addr_list)
+ count_recovered_module_symbols(modbuf, TRUE);
syms = ULONG(modbuf + OFFSET(module_syms));
nsyms = UINT(modbuf + OFFSET(module_num_syms));
@@ -1991,7 +2173,7 @@ store_module_symbols_6_4(ulong total, int mods_installed)
ngplsyms = 0;
}
- nksyms = UINT(modbuf + OFFSET(module_num_symtab));
+ nksyms = kt->flags & KALLSYMS_V2 ? UINT(modbuf + OFFSET(module_num_symtab)) :
0;
mod_name = modbuf + OFFSET(module_name);
@@ -2059,9 +2241,14 @@ store_module_symbols_6_4(ulong total, int mods_installed)
if (nsyms) {
modsymbuf = GETBUF(kernel_symbol_size*nsyms);
- readmem((ulong)syms, KVADDR, modsymbuf,
+ if (!readmem((ulong)syms, KVADDR, modsymbuf,
nsyms * kernel_symbol_size,
- "module symbols", FAULT_ON_ERROR);
+ "module symbols", module_addr_list ? RETURN_ON_ERROR|QUIET :
FAULT_ON_ERROR)) {
+ error(WARNING, "%s: cannot read exported symbols, retaining module\n",
lm->mod_name);
+ FREEBUF(modsymbuf);
+ modsymbuf = NULL;
+ nsyms = 0;
+ }
}
for (i = first = last = 0; i < nsyms; i++) {
@@ -2074,7 +2261,7 @@ store_module_symbols_6_4(ulong total, int mods_installed)
last = modsym_name(syms, modsym, i);
}
- if (last > first) {
+ if (!module_addr_list && last > first) {
/* The buffer should not go over the block. */
ulong end = module_mem_end(first, lm);
@@ -2130,9 +2317,14 @@ store_module_symbols_6_4(ulong total, int mods_installed)
if (ngplsyms) {
modsymbuf = GETBUF(kernel_symbol_size * ngplsyms);
- readmem((ulong)gpl_syms, KVADDR, modsymbuf,
+ if (!readmem((ulong)gpl_syms, KVADDR, modsymbuf,
ngplsyms * kernel_symbol_size,
- "module gpl symbols", FAULT_ON_ERROR);
+ "module gpl symbols", module_addr_list ? RETURN_ON_ERROR|QUIET :
FAULT_ON_ERROR)) {
+ error(WARNING, "%s: cannot read GPL symbols, retaining module\n",
lm->mod_name);
+ FREEBUF(modsymbuf);
+ modsymbuf = NULL;
+ ngplsyms = 0;
+ }
}
for (i = first = last = 0; i < ngplsyms; i++) {
@@ -2145,7 +2337,7 @@ store_module_symbols_6_4(ulong total, int mods_installed)
last = modsym_name(gpl_syms, modsym, i);
}
- if (last > first) {
+ if (!module_addr_list && last > first) {
ulong end = module_mem_end(first, lm);
strbuflen = (last-first) + BUFSIZE;
@@ -2225,11 +2417,15 @@ store_module_symbols_6_4(ulong total, int mods_installed)
lm->mod_ext_symcnt = mcnt - lm->mod_ext_symcnt;
- NEXT_MODULE(mod_next, modbuf);
+ if (!module_addr_list)
+ NEXT_MODULE(mod_next, modbuf);
+ mod = mod_next;
}
FREEBUF(modbuf);
+ if (module_addr_list)
+ kt->mods_installed = st->mods_installed = m;
st->ext_module_symcnt = mcnt;
st->ext_module_symend = &st->ext_module_symtable[mcnt];
@@ -2283,10 +2479,11 @@ store_module_symbols_6_4(ulong total, int mods_installed)
error(FATAL, "store_module_symbols_6_4: total: %ld mcnt: %d\n", total,
mcnt);
}
-void
-store_module_symbols_v2(ulong total, int mods_installed)
+static void
+store_module_symbols_v2_internal(ulong total, int mods_installed,
+ ulong *module_addr_list)
{
- int i, m;
+ int i, m, index = 0;
ulong mod, mod_next;
char *mod_name;
uint nsyms, ngplsyms;
@@ -2341,21 +2538,35 @@ store_module_symbols_v2(ulong total, int mods_installed)
modsymbuf = NULL;
m = mcnt = mod_next = 0;
- for (mod = kt->module_list; mod != kt->kernel_module; mod = mod_next) {
+ for (mod = kt->module_list; ; ) {
+ if (module_addr_list) {
+ if (index >= mods_installed)
+ break;
+ mod = module_addr_list[index++];
+ } else if (mod == kt->kernel_module)
+ break;
- readmem(mod, KVADDR, modbuf, SIZE(module),
- "module buffer", FAULT_ON_ERROR);
+ if (!readmem(mod, KVADDR, modbuf, SIZE(module), "module buffer",
+ module_addr_list ? RETURN_ON_ERROR|QUIET : FAULT_ON_ERROR))
+ continue;
+ if (module_addr_list)
+ count_recovered_module_symbols(modbuf, TRUE);
syms = ULONG(modbuf + OFFSET(module_syms));
- gpl_syms = ULONG(modbuf + OFFSET(module_gpl_syms));
nsyms = UINT(modbuf + OFFSET(module_num_syms));
- ngplsyms = UINT(modbuf + OFFSET(module_num_gpl_syms));
+ if (VALID_MEMBER(module_gpl_syms)) {
+ gpl_syms = ULONG(modbuf + OFFSET(module_gpl_syms));
+ ngplsyms = UINT(modbuf + OFFSET(module_num_gpl_syms));
+ } else {
+ gpl_syms = 0;
+ ngplsyms = 0;
+ }
if (THIS_KERNEL_VERSION >= LINUX(2,6,27)) {
- nksyms = UINT(modbuf + OFFSET(module_num_symtab));
+ nksyms = kt->flags & KALLSYMS_V2 ? UINT(modbuf + OFFSET(module_num_symtab)) :
0;
size = UINT(modbuf + MODULE_OFFSET2(module_core_size, rx));
} else {
- nksyms = ULONG(modbuf + OFFSET(module_num_symtab));
+ nksyms = kt->flags & KALLSYMS_V2 ? ULONG(modbuf + OFFSET(module_num_symtab)) :
0;
size = ULONG(modbuf + MODULE_OFFSET2(module_core_size, rx));
}
@@ -2435,9 +2646,14 @@ store_module_symbols_v2(ulong total, int mods_installed)
if (nsyms) {
modsymbuf = GETBUF(kernel_symbol_size*nsyms);
- readmem((ulong)syms, KVADDR, modsymbuf,
+ if (!readmem((ulong)syms, KVADDR, modsymbuf,
nsyms * kernel_symbol_size,
- "module symbols", FAULT_ON_ERROR);
+ "module symbols", module_addr_list ? RETURN_ON_ERROR|QUIET :
FAULT_ON_ERROR)) {
+ error(WARNING, "%s: cannot read exported symbols, retaining module\n",
lm->mod_name);
+ FREEBUF(modsymbuf);
+ modsymbuf = NULL;
+ nsyms = 0;
+ }
}
for (i = first = last = 0; i < nsyms; i++) {
@@ -2450,7 +2666,7 @@ store_module_symbols_v2(ulong total, int mods_installed)
last = modsym_name(syms, modsym, i);
}
- if (last > first) {
+ if (!module_addr_list && last > first) {
strbuflen = (last-first) + BUFSIZE;
if ((first + strbuflen) >=
(lm->mod_base + lm->mod_size)) {
@@ -2509,9 +2725,14 @@ store_module_symbols_v2(ulong total, int mods_installed)
if (ngplsyms) {
modsymbuf = GETBUF(kernel_symbol_size *
ngplsyms);
- readmem((ulong)gpl_syms, KVADDR, modsymbuf,
+ if (!readmem((ulong)gpl_syms, KVADDR, modsymbuf,
ngplsyms * kernel_symbol_size,
- "module gpl symbols", FAULT_ON_ERROR);
+ "module gpl symbols", module_addr_list ? RETURN_ON_ERROR|QUIET :
FAULT_ON_ERROR)) {
+ error(WARNING, "%s: cannot read GPL symbols, retaining module\n",
lm->mod_name);
+ FREEBUF(modsymbuf);
+ modsymbuf = NULL;
+ ngplsyms = 0;
+ }
}
for (i = first = last = 0; i < ngplsyms; i++) {
@@ -2524,7 +2745,7 @@ store_module_symbols_v2(ulong total, int mods_installed)
last = modsym_name(gpl_syms, modsym, i);
}
- if (last > first) {
+ if (!module_addr_list && last > first) {
strbuflen = (last-first) + BUFSIZE;
if ((first + strbuflen) >=
(lm->mod_base + lm->mod_size)) {
@@ -2619,11 +2840,15 @@ store_module_symbols_v2(ulong total, int mods_installed)
if (!lm->mod_etext_guess)
find_mod_etext(lm);
- NEXT_MODULE(mod_next, modbuf);
+ if (!module_addr_list)
+ NEXT_MODULE(mod_next, modbuf);
+ mod = mod_next;
}
FREEBUF(modbuf);
+ if (module_addr_list)
+ kt->mods_installed = st->mods_installed = m;
st->ext_module_symcnt = mcnt;
st->ext_module_symend = &st->ext_module_symtable[mcnt];
@@ -2879,10 +3104,17 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int
curr,
int mcnt;
int mcnt_idx;
char *module_buf_init = NULL;
- ulong base, base_init, size, size_init;
+ ulong base, base_init, size, size_init, symspace, strspace;
+ size_t elf_symbol_size;
if (!(kt->flags & KALLSYMS_V2))
return 0;
+ if (THIS_KERNEL_VERSION >= LINUX(2,6,27))
+ nksyms = UINT(modbuf + OFFSET(module_num_symtab));
+ else
+ nksyms = ULONG(modbuf + OFFSET(module_num_symtab));
+ if (!nksyms)
+ return 0;
mcnt = 0;
BZERO(&elf_common, sizeof(struct elf_common));
@@ -2903,6 +3135,8 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int
curr,
size_init = lm->mod_init_size;
}
+ if (!size)
+ return 0;
module_buf = GETBUF(size);
if (!readmem(base, KVADDR, module_buf, size,
@@ -2912,50 +3146,41 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int
curr,
return 0;
}
- if (lm->mod_init_size > 0) {
+ if (size_init > 0) {
module_buf_init = GETBUF(size_init);
if (!readmem(base_init, KVADDR, module_buf_init, size_init,
"module init (kallsyms)", RETURN_ON_ERROR|QUIET)) {
error(WARNING,"cannot access module init kallsyms\n");
FREEBUF(module_buf_init);
+ module_buf_init = NULL;
}
}
- if (THIS_KERNEL_VERSION >= LINUX(2,6,27))
- nksyms = UINT(modbuf + OFFSET(module_num_symtab));
- else
- nksyms = ULONG(modbuf + OFFSET(module_num_symtab));
-
ksymtab = ULONG(modbuf + OFFSET(module_symtab));
- if (!IN_MODULE(ksymtab, lm) && !IN_MODULE_INIT(ksymtab, lm)) {
- error(WARNING,
- "%s: module.symtab outside of module address space\n",
- lm->mod_name);
- FREEBUF(module_buf);
- if (module_buf_init)
- FREEBUF(module_buf_init);
- return 0;
- }
- if (IN_MODULE(ksymtab, lm))
+ if (ksymtab >= base && ksymtab - base < size) {
locsymtab = module_buf + (ksymtab - base);
- else
+ symspace = size - (ksymtab - base);
+ } else if (module_buf_init && ksymtab >= base_init &&
+ ksymtab - base_init < size_init) {
locsymtab = module_buf_init + (ksymtab - base_init);
+ symspace = size_init - (ksymtab - base_init);
+ } else
+ goto invalid_tables;
+ elf_symbol_size = BITS() == 32 ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym);
+ if (nksyms > symspace / elf_symbol_size)
+ goto invalid_tables;
kstrtab = ULONG(modbuf + OFFSET(module_strtab));
- if (!IN_MODULE(kstrtab, lm) && !IN_MODULE_INIT(kstrtab, lm)) {
- error(WARNING,
- "%s: module.strtab outside of module address space\n",
- lm->mod_name);
- FREEBUF(module_buf);
- if (module_buf_init)
- FREEBUF(module_buf_init);
- return 0;
- }
- if (IN_MODULE(kstrtab, lm))
+ if (kstrtab >= base && kstrtab - base < size) {
locstrtab = module_buf + (kstrtab - base);
- else
+ strspace = size - (kstrtab - base);
+ } else if (module_buf_init && kstrtab >= base_init &&
+ kstrtab - base_init < size_init) {
locstrtab = module_buf_init + (kstrtab - base_init);
+ strspace = size_init - (kstrtab - base_init);
+ } else
+ goto invalid_tables;
for (i = 1; i < nksyms; i++) { /* ELF starts real symbols at 1 */
switch (BITS())
@@ -2976,7 +3201,7 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int
curr,
if (ec->st_shndx == SHN_UNDEF)
continue;
- if (!IN_MODULE(kstrtab + ec->st_name, lm) && !IN_MODULE_INIT(kstrtab +
ec->st_name, lm)) {
+ if (ec->st_name >= strspace) {
if (CRASHDEBUG(3)) {
error(WARNING,
"%s: bad st_name index: %lx -> %lx\n "
@@ -2990,7 +3215,7 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int
curr,
}
nameptr = locstrtab + ec->st_name;
- if (*nameptr == '\0')
+ if (*nameptr == '\0' || !memchr(nameptr, '\0', strspace -
ec->st_name))
continue;
/*
@@ -3049,6 +3274,13 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int
curr,
FREEBUF(module_buf_init);
return mcnt;
+
+invalid_tables:
+ error(WARNING, "%s: invalid or unreadable kallsyms tables, retaining
module\n", lm->mod_name);
+ FREEBUF(module_buf);
+ if (module_buf_init)
+ FREEBUF(module_buf_init);
+ return 0;
}
/*
@@ -10427,6 +10659,9 @@ dump_offset_table(char *spec, ulong makestruct)
fprintf(fp, " module_mem: %ld\n", OFFSET(module_mem));
fprintf(fp, " module_memory_base: %ld\n",
OFFSET(module_memory_base));
fprintf(fp, " module_memory_size: %ld\n",
OFFSET(module_memory_size));
+ fprintf(fp, " module_mkobj: %ld\n", OFFSET(module_mkobj));
+ fprintf(fp, " module_kobject_kobj: %ld\n",
OFFSET(module_kobject_kobj));
+ fprintf(fp, " module_kobject_mod: %ld\n",
OFFSET(module_kobject_mod));
fprintf(fp, " module_sect_attrs: %ld\n",
OFFSET(module_sect_attrs));
diff --git a/tools.c b/tools.c
index cf9d7c0..e39a1d7 100644
--- a/tools.c
+++ b/tools.c
@@ -3983,6 +3983,35 @@ do_list(struct list_data *ld)
return count;
}
+/*
+ * Visit kobjects in both directions, keeping callbacks made before a list error.
+ * A nonzero callback return stops that pass. Callbacks may see an object twice
+ * and must not nest do_list_no_hash(), whose Brent state is not reentrant.
+ */
+void
+kset_walk(ulong kset, int (*callback)(void *, void *), void *data)
+{
+ struct list_data ld;
+ int direction;
+
+ if (!VALID_MEMBER(kset_list) || !VALID_MEMBER(kobject_entry) ||
+ !IS_KVADDR(kset))
+ return;
+
+ BZERO(&ld, sizeof(ld));
+ ld.flags = LIST_CALLBACK|CALLBACK_RETURN|RETURN_ON_LIST_ERROR|LIST_HEAD_FORMAT;
+ ld.end = kset + OFFSET(kset_list);
+ ld.list_head_offset = OFFSET(kobject_entry);
+ ld.callback_func = callback;
+ ld.callback_data = data;
+ for (direction = 0; direction < 2; direction++) {
+ ld.member_offset = direction ? OFFSET(list_head_prev) : OFFSET(list_head_next);
+ if (readmem(ld.end + ld.member_offset, KVADDR, &ld.start, sizeof(ulong),
+ "kset list head", RETURN_ON_ERROR|QUIET) && ld.start != ld.end)
+ do_list_no_hash(&ld);
+ }
+}
+
static void
do_list_debug_entry(struct list_data *ld)
{
--
2.43.0