[PATCH 2/2] kallsyms: add BTF-based deep parameter rendering in oops dumps

Sasha Levin posted 2 patches 1 week, 5 days ago
[PATCH 2/2] kallsyms: add BTF-based deep parameter rendering in oops dumps
Posted by Sasha Levin 1 week, 5 days ago
When CONFIG_KALLSYMS_PARAMINFO_BTF is enabled and a function parameter
is a pointer to a kernel struct, use BTF type information to safely
dereference the pointer and display struct member values in oops/WARN
dumps.

The rendering uses btf_type_snprintf_show() which internally uses
copy_from_kernel_nofault() for safe memory access, making it safe to
call in oops/panic context.  Struct members are printed in a compact
two-column layout for readability.

Example output:

  Function parameters (paraminfo_demo_crash):
    file     (struct file *)         = 0xffff8bc043cb36c0
      .f_mode = (fmode_t)67993630               .f_flags = (unsigned int)32769
      .f_mapping = (struct address_space *)0x..  .f_inode = (struct inode *)0x..
      .f_cred = (struct cred *)0x...             .prev_pos = (loff_t)-1

Gated behind CONFIG_KALLSYMS_PARAMINFO_BTF which depends on both
CONFIG_KALLSYMS_PARAMINFO and CONFIG_DEBUG_INFO_BTF.  No additional
kernel image size beyond BTF itself.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 init/Kconfig                    |  19 +++
 kernel/Makefile                 |   1 +
 kernel/kallsyms.c               |  47 ++++--
 kernel/kallsyms_paraminfo_btf.c | 267 ++++++++++++++++++++++++++++++++
 lib/tests/paraminfo_kunit.c     |  13 +-
 scripts/gen_paraminfo.c         |  80 ++++++++--
 6 files changed, 384 insertions(+), 43 deletions(-)
 create mode 100644 kernel/kallsyms_paraminfo_btf.c

diff --git a/init/Kconfig b/init/Kconfig
index 76d0c2da7d612..602594c86bf7e 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2106,6 +2106,25 @@ config KALLSYMS_PARAMINFO
 
 	  If unsure, say N.
 
+config KALLSYMS_PARAMINFO_BTF
+	bool "Render struct contents for pointer parameters in oops dumps"
+	depends on KALLSYMS_PARAMINFO && DEBUG_INFO_BTF
+	help
+	  When a function parameter is a pointer to a kernel struct and BTF
+	  type information is available, dereference the pointer and display
+	  key struct members (1 level deep) in oops/WARN dumps.
+
+	  When enabled, oops dumps may include additional indented lines
+	  showing struct member values in a two-column layout:
+
+	    file   (struct file *)         = 0xffff888123456000
+	      .f_flags = (unsigned int)32769  .f_mode = (fmode_t)29
+
+	  Requires CONFIG_DEBUG_INFO_BTF.
+	  No additional kernel image size beyond BTF itself.
+
+	  If unsure, say N.
+
 # end of the "standard kernel features (expert users)" menu
 
 config ARCH_HAS_MEMBARRIER_CALLBACKS
diff --git a/kernel/Makefile b/kernel/Makefile
index 6785982013dce..e47d911340cad 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_UID16) += uid16.o
 obj-$(CONFIG_MODULE_SIG_FORMAT) += module_signature.o
 obj-$(CONFIG_KALLSYMS) += kallsyms.o
 obj-$(CONFIG_KALLSYMS_SELFTEST) += kallsyms_selftest.o
+obj-$(CONFIG_KALLSYMS_PARAMINFO_BTF) += kallsyms_paraminfo_btf.o
 obj-$(CONFIG_BSD_PROCESS_ACCT) += acct.o
 obj-$(CONFIG_VMCORE_INFO) += vmcore_info.o elfcorehdr.o
 obj-$(CONFIG_CRASH_RESERVE) += crash_reserve.o
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index af8de3d8e3ba3..362cfa80b2c08 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -510,16 +510,19 @@ bool kallsyms_lookup_lineinfo(unsigned long addr,
 
 #define MAX_PARAMINFO_PARAMS 6
 
+#ifdef CONFIG_KALLSYMS_PARAMINFO_BTF
+void paraminfo_btf_show_ptr(unsigned long ptr_val, const char *type_str);
+#else
+static inline void paraminfo_btf_show_ptr(unsigned long ptr_val,
+					   const char *type_str) {}
+#endif
+
 /*
  * x86-64 calling convention: arguments are passed in registers
  * RDI, RSI, RDX, RCX, R8, R9 (in that order).
  */
 #ifdef CONFIG_X86_64
 
-static const char * const paraminfo_reg_names[] = {
-	"RDI", "RSI", "RDX", "RCX", "R8", "R9"
-};
-
 static unsigned long paraminfo_get_reg(const struct pt_regs *regs,
 				       unsigned int idx)
 {
@@ -534,9 +537,6 @@ static unsigned long paraminfo_get_reg(const struct pt_regs *regs,
 	}
 }
 #else
-/* Stub for non-x86-64 architectures */
-static const char * const paraminfo_reg_names[] = {};
-
 static unsigned long paraminfo_get_reg(const struct pt_regs *regs,
 				       unsigned int idx)
 {
@@ -586,13 +586,13 @@ void kallsyms_show_paraminfo(struct pt_regs *regs)
 	const u8 *data;
 	unsigned int num_params, i;
 	unsigned long ip, fault_addr;
-	char sym_name[KSYM_NAME_LEN];
+	char sym_name[128];
 	unsigned long sym_size, sym_offset;
 
 	if (!regs || !paraminfo_num_funcs)
 		return;
 
-	ip = regs->ip;
+	ip = instruction_pointer(regs);
 
 	/* Only handle kernel-mode faults */
 	if (user_mode(regs))
@@ -611,14 +611,23 @@ void kallsyms_show_paraminfo(struct pt_regs *regs)
 		return;
 
 	/*
-	 * Verify the IP is within a reasonable range of the function
-	 * start.  paraminfo_func_addrs[] contains function start offsets;
-	 * check that we're not too far past the start.  Use kallsyms to
-	 * verify we're in the right function.
+	 * Verify the paraminfo entry actually matches the function
+	 * containing the IP.  Without this, if the faulting function
+	 * has no paraminfo, the binary search silently returns the
+	 * preceding function's entry — showing wrong parameter info.
 	 */
 	if (!kallsyms_lookup_size_offset(ip, &sym_size, &sym_offset))
 		return;
 
+	{
+		unsigned int func_start_offset;
+
+		func_start_offset = (unsigned int)(ip - sym_offset -
+						   (unsigned long)_text);
+		if (paraminfo_func_addrs[func_idx] != func_start_offset)
+			return;
+	}
+
 	/* Decode the function's parameter data */
 	data = paraminfo_func_data + paraminfo_func_offsets[func_idx];
 	num_params = *data++;
@@ -631,9 +640,9 @@ void kallsyms_show_paraminfo(struct pt_regs *regs)
 		return;
 
 	/*
-	 * Read the fault address for highlighting.  On x86, CR2 holds
-	 * the page fault linear address.  On other architectures this
-	 * would need a different mechanism.
+	 * Read CR2 for fault address highlighting.  CR2 is only meaningful
+	 * for page faults; for GPF, BUG, WARN, etc. it may hold a stale
+	 * value.  This is best-effort — a false match is harmless.
 	 */
 #ifdef CONFIG_X86
 	fault_addr = read_cr2();
@@ -660,9 +669,13 @@ void kallsyms_show_paraminfo(struct pt_regs *regs)
 
 		is_fault_addr = fault_addr && (val == fault_addr);
 
-		printk(KERN_DEFAULT "  %-8s (%-20s) = 0x%016lx%s\n",
+		printk(KERN_DEFAULT " %-8s (%-20s) = 0x%016lx%s\n",
 		       pname, ptype, val,
 		       is_fault_addr ? "  <-- fault address" : "");
+
+		/* If this is a pointer to a struct, try BTF deep rendering */
+		if (val && strstr(ptype, "*"))
+			paraminfo_btf_show_ptr(val, ptype);
 	}
 }
 EXPORT_SYMBOL_GPL(kallsyms_show_paraminfo);
diff --git a/kernel/kallsyms_paraminfo_btf.c b/kernel/kallsyms_paraminfo_btf.c
new file mode 100644
index 0000000000000..28ce1dd45f7a8
--- /dev/null
+++ b/kernel/kallsyms_paraminfo_btf.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * kallsyms_paraminfo_btf.c - BTF-based deep rendering for paraminfo
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ *
+ * When CONFIG_KALLSYMS_PARAMINFO_BTF is enabled and a function parameter
+ * is a pointer to a kernel struct, this module uses BTF type information
+ * to safely dereference the pointer and display key struct members in
+ * oops/WARN dumps.
+ */
+
+#include <linux/btf.h>
+#include <linux/bpf.h>
+#include <linux/kallsyms.h>
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+/* Declared in kernel/kallsyms.c under CONFIG_KALLSYMS_PARAMINFO_BTF */
+void paraminfo_btf_show_ptr(unsigned long ptr_val, const char *type_str);
+
+extern struct btf *btf_vmlinux;
+
+/*
+ * Maximum output buffer for BTF rendering.  Large structs (e.g.,
+ * struct dentry) need substantial space.  This is declared static
+ * rather than on the stack because 4096 bytes would exceed the
+ * frame size limit.  Oops context is effectively single-threaded
+ * (other CPUs are stopped or spinning), so a static buffer is safe.
+ */
+#define BTF_SHOW_BUF_LEN	4096
+
+/*
+ * Parse a type string like "struct file *" to extract the struct name.
+ * Writes into caller-provided @name_buf of size @bufsz.
+ * Returns @name_buf on success, or NULL if not a struct/union pointer type.
+ */
+static const char *extract_struct_name(const char *type_str, bool *is_union,
+				       char *name_buf, size_t bufsz)
+{
+	const char *p, *end;
+
+	*is_union = false;
+
+	/* Must end with " *" to be a pointer */
+	end = type_str + strlen(type_str);
+	if (end - type_str < 3 || end[-1] != '*' || end[-2] != ' ')
+		return NULL;
+
+	if (!strncmp(type_str, "struct ", 7)) {
+		p = type_str + 7;
+	} else if (!strncmp(type_str, "union ", 6)) {
+		p = type_str + 6;
+		*is_union = true;
+	} else {
+		return NULL;
+	}
+
+	/* Copy name up to the " *" */
+	{
+		size_t len = (end - 2) - p;
+
+		if (len == 0 || len >= bufsz)
+			return NULL;
+		memcpy(name_buf, p, len);
+		name_buf[len] = '\0';
+	}
+
+	return name_buf;
+}
+
+/*
+ * Show BTF-rendered struct contents for a pointer parameter.
+ * Called from kallsyms_show_paraminfo() when the parameter is a
+ * struct/union pointer.
+ *
+ * Uses btf_type_snprintf_show() which internally uses
+ * copy_from_kernel_nofault() for safe memory access, making it
+ * safe to call in oops/panic context.
+ */
+void paraminfo_btf_show_ptr(unsigned long ptr_val, const char *type_str)
+{
+	static char buf[BTF_SHOW_BUF_LEN];
+	char sname[64];
+	const char *name;
+	bool is_union;
+	s32 type_id;
+	int ret;
+
+	if (!btf_vmlinux || !ptr_val)
+		return;
+
+	/* Only handle kernel pointers */
+	if (ptr_val < PAGE_OFFSET)
+		return;
+
+	name = extract_struct_name(type_str, &is_union, sname, sizeof(sname));
+	if (!name)
+		return;
+
+	type_id = btf_find_by_name_kind(btf_vmlinux, name,
+					is_union ? BTF_KIND_UNION
+						 : BTF_KIND_STRUCT);
+	if (type_id < 0)
+		return;
+
+	/*
+	 * Render without BTF_SHOW_COMPACT so each member gets its own
+	 * line with proper indentation from BTF.  Use BTF_SHOW_PTR_RAW
+	 * to print real kernel addresses instead of hashed pointers —
+	 * this is oops context where address visibility is critical.
+	 */
+	ret = btf_type_snprintf_show(btf_vmlinux, type_id, (void *)ptr_val,
+				     buf, sizeof(buf), BTF_SHOW_PTR_RAW);
+	if (ret < 0)
+		return;
+
+	buf[sizeof(buf) - 1] = '\0';
+
+	/*
+	 * Filter the multi-line BTF output: skip lines that contain
+	 * only braces/brackets/whitespace (structural noise), collect
+	 * meaningful member lines, and print them two per row.
+	 */
+	{
+		/*
+		 * Collect filtered lines as pointers into buf[] (which
+		 * we NUL-terminate in place).  Stack budget: ~64 pointers.
+		 */
+#define MAX_BTF_LINES 64
+#define BTF_COL_WIDTH 40
+		char *lines[MAX_BTF_LINES];
+		int nlines = 0;
+		char *line, *next;
+
+		for (line = buf; line && *line; line = next) {
+			char *s, *c;
+			bool has_content = false;
+
+			next = strchr(line, '\n');
+			if (next)
+				*next++ = '\0';
+
+			s = line;
+			while (*s == ' ' || *s == '\t')
+				s++;
+
+			/* Skip structural-only lines: {}[](), */
+			for (c = s; *c; c++) {
+				if (*c != '{' && *c != '}' &&
+				    *c != '(' && *c != ')' &&
+				    *c != '[' && *c != ']' &&
+				    *c != ',' && *c != ' ' &&
+				    *c != '\t') {
+					has_content = true;
+					break;
+				}
+			}
+			if (!has_content)
+				continue;
+
+			/* Skip type-only prefix lines */
+			if (*s == '(' && !strchr(s, '=') && !strchr(s, '['))
+				continue;
+
+			/* Trim trailing commas/spaces */
+			{
+				size_t len = strlen(s);
+
+				while (len > 0 && (s[len - 1] == ','
+						|| s[len - 1] == ' '))
+					s[--len] = '\0';
+			}
+
+			if (*s && nlines < MAX_BTF_LINES)
+				lines[nlines++] = s;
+		}
+
+		/*
+		 * Coalesce char array elements into strings.
+		 *
+		 * BTF renders char[] as individual elements:
+		 *   .sysname = (char[])[
+		 *   'L'
+		 *   'i'
+		 *   'n'  ...
+		 *
+		 * Detect lines containing "= (char[])[" or
+		 * "= (unsigned char[])[" and collect following
+		 * single-quoted-char lines into a readable string
+		 * like: .sysname = "Linux"
+		 */
+#define MAX_COALESCED 8
+		{
+			static char coalesced[MAX_COALESCED][128];
+			int ci = 0, i;
+
+			for (i = 0; i < nlines && ci < MAX_COALESCED; i++) {
+				char *eq;
+				int spos, j, pfxlen;
+
+				eq = strstr(lines[i], "(char[])[");
+				if (!eq)
+					eq = strstr(lines[i], "(unsigned char[])[");
+				if (!eq)
+					continue;
+
+				/* Extract prefix up to and including '=' */
+				eq = strstr(lines[i], "= ");
+				if (!eq)
+					continue;
+				pfxlen = eq - lines[i] + 2;
+				if (pfxlen > 60)
+					pfxlen = 60;
+
+				memcpy(coalesced[ci], lines[i], pfxlen);
+				coalesced[ci][pfxlen] = '"';
+				spos = pfxlen + 1;
+
+				/* Gather chars from subsequent lines */
+				for (j = i + 1; j < nlines &&
+				     spos < (int)sizeof(coalesced[0]) - 2; j++) {
+					char *s = lines[j];
+
+					if (s[0] == '\'' && s[2] == '\'' &&
+					    (s[3] == '\0' || s[3] == ',')) {
+						coalesced[ci][spos++] = s[1];
+						lines[j] = "";
+					} else {
+						break;
+					}
+				}
+				coalesced[ci][spos++] = '"';
+				coalesced[ci][spos] = '\0';
+				lines[i] = coalesced[ci];
+				ci++;
+			}
+		}
+#undef MAX_COALESCED
+
+		/* Print in two columns, skipping empty (consumed) lines */
+		{
+			int i, col = 0;
+			char *pending = NULL;
+
+			for (i = 0; i < nlines; i++) {
+				if (!lines[i][0])
+					continue;
+				if (col == 0) {
+					pending = lines[i];
+					col = 1;
+				} else {
+					printk(KERN_DEFAULT "  %-*s  %s\n",
+					       BTF_COL_WIDTH, pending,
+					       lines[i]);
+					col = 0;
+				}
+			}
+			if (col == 1)
+				printk(KERN_DEFAULT "  %s\n", pending);
+		}
+#undef MAX_BTF_LINES
+#undef BTF_COL_WIDTH
+	}
+}
diff --git a/lib/tests/paraminfo_kunit.c b/lib/tests/paraminfo_kunit.c
index e09efc4ddeb0e..74a4436163a98 100644
--- a/lib/tests/paraminfo_kunit.c
+++ b/lib/tests/paraminfo_kunit.c
@@ -7,7 +7,8 @@
  * Verifies that the paraminfo tables correctly map function addresses
  * to their parameter names and types.
  *
- * Build with: CONFIG_PARAMINFO_KUNIT_TEST=m (or =y)
+ * Build with: CONFIG_PARAMINFO_KUNIT_TEST=y (must be built-in; paraminfo
+ * tables are vmlinux-only, so module test functions won't be found)
  */
 
 #include <kunit/test.h>
@@ -45,15 +46,7 @@ static noinline void paraminfo_test_no_args(void)
 
 /* ---- Helpers to query paraminfo tables directly ---- */
 
-/*
- * These access the raw paraminfo tables to verify correctness.
- * The tables are defined in kernel/kallsyms_internal.h.
- */
-extern const u32 paraminfo_num_funcs;
-extern const u32 paraminfo_func_addrs[];
-extern const u32 paraminfo_func_offsets[];
-extern const u8  paraminfo_func_data[];
-extern const char paraminfo_strings[];
+#include "../../kernel/kallsyms_internal.h"
 
 struct param_result {
 	unsigned int num_params;
diff --git a/scripts/gen_paraminfo.c b/scripts/gen_paraminfo.c
index ea1d23f3ddd9a..b64dd1232c77c 100644
--- a/scripts/gen_paraminfo.c
+++ b/scripts/gen_paraminfo.c
@@ -58,7 +58,7 @@ static unsigned int num_strings;
 static unsigned int strtab_capacity;
 static unsigned int strtab_total_size;
 
-#define STR_HASH_BITS 14
+#define STR_HASH_BITS 18
 #define STR_HASH_SIZE (1 << STR_HASH_BITS)
 
 struct str_hash_entry {
@@ -87,6 +87,13 @@ static unsigned int find_or_add_string(const char *s)
 		h = (h + 1) & (STR_HASH_SIZE - 1);
 	}
 
+	if (num_strings >= STR_HASH_SIZE * 3 / 4) {
+		fprintf(stderr,
+			"gen_paraminfo: string hash table overflow (%u entries)\n",
+			num_strings);
+		exit(1);
+	}
+
 	if (num_strings >= strtab_capacity) {
 		strtab_capacity = strtab_capacity ? strtab_capacity * 2 : 8192;
 		strtab = realloc(strtab, strtab_capacity * sizeof(*strtab));
@@ -97,6 +104,10 @@ static unsigned int find_or_add_string(const char *s)
 	}
 
 	strtab[num_strings].str = strdup(s);
+	if (!strtab[num_strings].str) {
+		fprintf(stderr, "out of memory\n");
+		exit(1);
+	}
 	strtab[num_strings].offset = strtab_total_size;
 	strtab_total_size += strlen(s) + 1;
 
@@ -120,20 +131,32 @@ static void add_func(struct func_entry *f)
 	funcs[num_funcs++] = *f;
 }
 
+/* Max recursion depth to prevent stack overflow on pathological DWARF */
+#define MAX_TYPE_DEPTH 16
+
+static void __build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz,
+			      int depth);
+
 /*
  * Build a human-readable type name string from a DWARF type DIE.
  * Follows the type chain (pointers, const, etc.) to produce strings like:
  *   "struct file *", "const char *", "unsigned long", "void *"
  */
 static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
+{
+	__build_type_name(type_die, buf, bufsz, 0);
+}
+
+static void __build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz,
+			      int depth)
 {
 	Dwarf_Die child;
 	Dwarf_Attribute attr;
 	const char *name;
 	int tag;
 
-	if (!type_die) {
-		snprintf(buf, bufsz, "void");
+	if (!type_die || depth > MAX_TYPE_DEPTH) {
+		snprintf(buf, bufsz, depth > MAX_TYPE_DEPTH ? "..." : "void");
 		return;
 	}
 
@@ -148,7 +171,7 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 	case DW_TAG_pointer_type:
 		if (dwarf_attr(type_die, DW_AT_type, &attr) &&
 		    dwarf_formref_die(&attr, &child)) {
-			build_type_name(&child, buf, bufsz);
+			__build_type_name(&child, buf, bufsz, depth + 1);
 			if (strlen(buf) + 3 < bufsz)
 				strcat(buf, " *");
 		} else {
@@ -161,7 +184,7 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 		    dwarf_formref_die(&attr, &child)) {
 			char tmp[MAX_TYPE_LEN - 10];
 
-			build_type_name(&child, tmp, sizeof(tmp));
+			__build_type_name(&child, tmp, sizeof(tmp), depth + 1);
 			snprintf(buf, bufsz, "const %s", tmp);
 		} else {
 			snprintf(buf, bufsz, "const void");
@@ -173,7 +196,7 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 		    dwarf_formref_die(&attr, &child)) {
 			char tmp[MAX_TYPE_LEN - 10];
 
-			build_type_name(&child, tmp, sizeof(tmp));
+			__build_type_name(&child, tmp, sizeof(tmp), depth + 1);
 			snprintf(buf, bufsz, "volatile %s", tmp);
 		} else {
 			snprintf(buf, bufsz, "volatile void");
@@ -183,7 +206,7 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 	case DW_TAG_restrict_type:
 		if (dwarf_attr(type_die, DW_AT_type, &attr) &&
 		    dwarf_formref_die(&attr, &child)) {
-			build_type_name(&child, buf, bufsz);
+			__build_type_name(&child, buf, bufsz, depth + 1);
 		} else {
 			snprintf(buf, bufsz, "void");
 		}
@@ -195,7 +218,7 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 			snprintf(buf, bufsz, "%s", name);
 		} else if (dwarf_attr(type_die, DW_AT_type, &attr) &&
 			   dwarf_formref_die(&attr, &child)) {
-			build_type_name(&child, buf, bufsz);
+			__build_type_name(&child, buf, bufsz, depth + 1);
 		} else {
 			snprintf(buf, bufsz, "?");
 		}
@@ -219,7 +242,7 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 	case DW_TAG_array_type:
 		if (dwarf_attr(type_die, DW_AT_type, &attr) &&
 		    dwarf_formref_die(&attr, &child)) {
-			build_type_name(&child, buf, bufsz);
+			__build_type_name(&child, buf, bufsz, depth + 1);
 			if (strlen(buf) + 3 < bufsz)
 				strcat(buf, "[]");
 		} else {
@@ -231,8 +254,23 @@ static void build_type_name(Dwarf_Die *type_die, char *buf, size_t bufsz)
 		snprintf(buf, bufsz, "func_ptr");
 		break;
 
+	case DW_TAG_unspecified_type:
+		name = dwarf_diename(type_die);
+		snprintf(buf, bufsz, "%s", name ? name : "void");
+		break;
+
 	default:
-		snprintf(buf, bufsz, "?");
+		/*
+		 * Unknown tag — try to follow DW_AT_type if present
+		 * (handles DW_TAG_atomic_type and others).
+		 */
+		if (dwarf_attr(type_die, DW_AT_type, &attr) &&
+		    dwarf_formref_die(&attr, &child)) {
+			__build_type_name(&child, buf, bufsz, depth + 1);
+		} else {
+			name = dwarf_diename(type_die);
+			snprintf(buf, bufsz, "%s", name ? name : "?");
+		}
 		break;
 	}
 }
@@ -311,10 +349,15 @@ static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
 				continue;
 
 			/* Skip declarations (no body) */
-			if (dwarf_attr(&child, DW_AT_declaration, &attr))
+			if (dwarf_attr_integrate(&child, DW_AT_declaration, &attr))
 				continue;
 
-			/* Get function start address */
+			/*
+			 * Get function start address.
+			 * dwarf_lowpc handles DW_AT_low_pc directly, but
+			 * for concrete inlined instances the address may be
+			 * in a ranges table.
+			 */
 			if (dwarf_lowpc(&child, &low_pc) != 0)
 				continue;
 
@@ -341,6 +384,12 @@ static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
 					if (func.num_params >= MAX_PARAMS)
 						break;
 
+					/*
+					 * Use dwarf_attr_integrate to follow
+					 * DW_AT_abstract_origin chains — inlined
+					 * or outlined functions may store param
+					 * names/types in an abstract instance.
+					 */
 					pname = dwarf_diename(&param);
 					if (!pname)
 						pname = "?";
@@ -349,8 +398,8 @@ static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
 						 sizeof(func.params[0].name),
 						 "%s", pname);
 
-					/* Resolve type */
-					if (dwarf_attr(&param, DW_AT_type, &attr) &&
+					/* Resolve type (follow abstract origin) */
+					if (dwarf_attr_integrate(&param, DW_AT_type, &attr) &&
 					    dwarf_formref_die(&attr, &type_die)) {
 						build_type_name(&type_die,
 								func.params[func.num_params].type,
@@ -530,8 +579,7 @@ int main(int argc, char *argv[])
 	process_dwarf(dwarf, text_addr);
 	deduplicate();
 
-	fprintf(stderr, "paraminfo: %u functions, %u strings\n",
-		num_funcs, num_strings);
+	fprintf(stderr, "paraminfo: %u functions\n", num_funcs);
 
 	output_assembly();
 
-- 
2.51.0

Re: [PATCH 2/2] kallsyms: add BTF-based deep parameter rendering in oops dumps
Posted by Alexei Starovoitov 1 week, 5 days ago
On Mon, Mar 23, 2026 at 9:49 AM Sasha Levin <sashal@kernel.org> wrote:
>
> +static const char *extract_struct_name(const char *type_str, bool *is_union,
> +                                      char *name_buf, size_t bufsz)
> +{
> +       const char *p, *end;
> +
> +       *is_union = false;
> +
> +       /* Must end with " *" to be a pointer */
> +       end = type_str + strlen(type_str);
> +       if (end - type_str < 3 || end[-1] != '*' || end[-2] != ' ')
> +               return NULL;
> +
> +       if (!strncmp(type_str, "struct ", 7)) {
> +               p = type_str + 7;
> +       } else if (!strncmp(type_str, "union ", 6)) {
> +               p = type_str + 6;
> +               *is_union = true;
> +       } else {
> +               return NULL;
> +       }
> +
> +       /* Copy name up to the " *" */
> +       {
> +               size_t len = (end - 2) - p;
> +
> +               if (len == 0 || len >= bufsz)
> +                       return NULL;
> +               memcpy(name_buf, p, len);
> +               name_buf[len] = '\0';
> +       }
> +
> +       return name_buf;


Nack. This is just awful.
You didn't even bother to reformat what claude spat out.
Which means you didn't think it through.
It prints something that looks plausible
which is the opposite of what kernel crash dump should be.
crash output should be accurate and not a guess work.