public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Sasha Levin <sashal@kernel.org>
To: Andrew Morton <akpm@linux-foundation.org>,
	Masahiro Yamada <masahiroy@kernel.org>,
	Nathan Chancellor <nathan@kernel.org>,
	Nicolas Schier <nsc@kernel.org>
Cc: Thomas Gleixner <tglx@kernel.org>, Ingo Molnar <mingo@redhat.com>,
	Borislav Petkov <bp@alien8.de>,
	Dave Hansen <dave.hansen@linux.intel.com>,
	"H. Peter Anvin" <hpa@zytor.com>,
	Peter Zijlstra <peterz@infradead.org>,
	Josh Poimboeuf <jpoimboe@kernel.org>,
	Petr Mladek <pmladek@suse.com>,
	Alexei Starovoitov <ast@kernel.org>,
	Jonathan Corbet <corbet@lwn.net>, David Gow <davidgow@google.com>,
	Kees Cook <kees@kernel.org>, Greg KH <gregkh@linuxfoundation.org>,
	Luis Chamberlain <mcgrof@kernel.org>,
	Steven Rostedt <rostedt@goodmis.org>,
	Helge Deller <deller@gmx.de>,
	Randy Dunlap <rdunlap@infradead.org>,
	Geert Uytterhoeven <geert@linux-m68k.org>,
	Juergen Gross <jgross@suse.com>,
	James Bottomley <James.Bottomley@HansenPartnership.com>,
	Alexey Dobriyan <adobriyan@gmail.com>,
	Vlastimil Babka <vbabka@kernel.org>,
	Laurent Pinchart <laurent.pinchart@ideasonboard.com>,
	Petr Pavlu <petr.pavlu@suse.com>,
	x86@kernel.org, linux-kernel@vger.kernel.org,
	linux-kbuild@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-modules@vger.kernel.org, bpf@vger.kernel.org,
	Sasha Levin <sashal@kernel.org>
Subject: [PATCH 1/2] kallsyms: show function parameter info in oops/WARN dumps
Date: Mon, 23 Mar 2026 12:48:56 -0400	[thread overview]
Message-ID: <20260323164858.1939248-2-sashal@kernel.org> (raw)
In-Reply-To: <20260323164858.1939248-1-sashal@kernel.org>

Embed DWARF-derived function parameter name and type information in the
kernel image so that oops and WARN dumps display the crashing function's
register-passed arguments with their names, types, and values.

A new build-time tool (scripts/gen_paraminfo.c) parses DW_TAG_subprogram
and DW_TAG_formal_parameter entries from DWARF .debug_info, extracting
parameter names and human-readable type strings. The resulting tables are
stored in .rodata using the same two-phase link approach as lineinfo.

At runtime, kallsyms_show_paraminfo() performs a binary search on the
paraminfo tables, maps parameters to x86-64 calling convention registers
(RDI, RSI, RDX, RCX, R8, R9), and prints each parameter's name, type,
and value from pt_regs. If a parameter value matches the page fault
address (CR2), it is highlighted with "<-- fault address".

Integration at show_regs() means this works for both oops and WARN()
automatically, since both paths provide full pt_regs at the exception
point.

Example output:

  Function parameters (ext4_readdir):
    file     (struct file *)         = 0xffff888123456000
    ctx      (struct dir_context *)  = 0x0000000000001234  <-- fault address

Gated behind CONFIG_KALLSYMS_PARAMINFO (depends on CONFIG_KALLSYMS_LINEINFO).
Adds approximately 1-2 MB to the kernel image for ~58K functions.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 .../admin-guide/kallsyms-lineinfo.rst         |  31 +
 arch/x86/kernel/dumpstack.c                   |   6 +-
 include/linux/kallsyms.h                      |   9 +
 init/Kconfig                                  |  21 +
 kernel/kallsyms.c                             | 168 ++++++
 kernel/kallsyms_internal.h                    |   6 +
 lib/Kconfig.debug                             |  11 +
 lib/tests/Makefile                            |   3 +
 lib/tests/paraminfo_kunit.c                   | 256 ++++++++
 scripts/Makefile                              |   3 +
 scripts/empty_paraminfo.S                     |  18 +
 scripts/gen_paraminfo.c                       | 549 ++++++++++++++++++
 scripts/link-vmlinux.sh                       |  44 +-
 13 files changed, 1119 insertions(+), 6 deletions(-)
 create mode 100644 lib/tests/paraminfo_kunit.c
 create mode 100644 scripts/empty_paraminfo.S
 create mode 100644 scripts/gen_paraminfo.c

diff --git a/Documentation/admin-guide/kallsyms-lineinfo.rst b/Documentation/admin-guide/kallsyms-lineinfo.rst
index dd264830c8d5b..26921bb3f7f81 100644
--- a/Documentation/admin-guide/kallsyms-lineinfo.rst
+++ b/Documentation/admin-guide/kallsyms-lineinfo.rst
@@ -83,6 +83,37 @@ compression).
 Per-module lineinfo adds approximately 2-3 bytes per DWARF line entry to each
 ``.ko`` file.
 
+Function Parameter Info
+======================
+
+``CONFIG_KALLSYMS_PARAMINFO`` extends the debugging information by embedding
+function parameter names and types in the kernel image.  When an oops or WARN
+occurs, the faulting function's register-passed arguments are displayed with
+their names, types, and values from the saved registers.
+
+Enable in addition to the lineinfo options::
+
+    CONFIG_KALLSYMS_PARAMINFO=y
+
+Example oops output with paraminfo::
+
+    RIP: 0010:ext4_readdir+0x1a3/0x5b0 (fs/ext4/dir.c:421)
+    ...
+    Function parameters (ext4_readdir):
+      file     (struct file *)         = 0xffff888123456000
+      ctx      (struct dir_context *)  = 0x0000000000001234  <-- fault address
+
+The ``<-- fault address`` annotation appears when a parameter value matches
+the page fault address (CR2 on x86), helping quickly identify which argument
+caused the crash.
+
+This feature works for both oops and WARN() on x86-64.  Only register-passed
+parameters (up to 6 on x86-64: RDI, RSI, RDX, RCX, R8, R9) are displayed.
+The parameter info is only shown for the faulting/warning frame where full
+register state is available.
+
+The paraminfo tables add approximately 1-2 MiB to the kernel image.
+
 Known Limitations
 =================
 
diff --git a/arch/x86/kernel/dumpstack.c b/arch/x86/kernel/dumpstack.c
index b10684dedc589..4e9b5fd58fd1b 100644
--- a/arch/x86/kernel/dumpstack.c
+++ b/arch/x86/kernel/dumpstack.c
@@ -483,8 +483,10 @@ void show_regs(struct pt_regs *regs)
 	__show_regs(regs, print_kernel_regs, KERN_DEFAULT);
 
 	/*
-	 * When in-kernel, we also print out the stack at the time of the fault..
+	 * When in-kernel, show function parameter info and stack trace.
 	 */
-	if (!user_mode(regs))
+	if (!user_mode(regs)) {
+		kallsyms_show_paraminfo(regs);
 		show_trace_log_lvl(current, regs, NULL, KERN_DEFAULT);
+	}
 }
diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h
index 7d4c9dca06c87..17c9df520b2b0 100644
--- a/include/linux/kallsyms.h
+++ b/include/linux/kallsyms.h
@@ -104,6 +104,13 @@ int lookup_symbol_name(unsigned long addr, char *symname);
 bool kallsyms_lookup_lineinfo(unsigned long addr,
 			      const char **file, unsigned int *line);
 
+#ifdef CONFIG_KALLSYMS_PARAMINFO
+struct pt_regs;
+void kallsyms_show_paraminfo(struct pt_regs *regs);
+#else
+static inline void kallsyms_show_paraminfo(struct pt_regs *regs) {}
+#endif
+
 #else /* !CONFIG_KALLSYMS */
 
 static inline unsigned long kallsyms_lookup_name(const char *name)
@@ -179,6 +186,8 @@ static inline bool kallsyms_lookup_lineinfo(unsigned long addr,
 {
 	return false;
 }
+
+static inline void kallsyms_show_paraminfo(struct pt_regs *regs) {}
 #endif /*CONFIG_KALLSYMS*/
 
 static inline void print_ip_sym(const char *loglvl, unsigned long ip)
diff --git a/init/Kconfig b/init/Kconfig
index 6e3795b3dbd62..76d0c2da7d612 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2085,6 +2085,27 @@ config KALLSYMS_LINEINFO_MODULES
 
 	  If unsure, say N.
 
+config KALLSYMS_PARAMINFO
+	bool "Show function parameter info in oops/WARN dumps"
+	depends on KALLSYMS_LINEINFO
+	help
+	  Embeds function parameter name and type information in the kernel
+	  image, extracted from DWARF debug info at build time.  When an
+	  oops or WARN occurs, the crashing/warning function's register-
+	  passed arguments are displayed with their names, types, and
+	  values from pt_regs.
+
+	  When enabled, oops/WARN dumps include lines like:
+
+	    Function parameters (ext4_readdir):
+	      file   (struct file *)         = 0xffff888123456000
+	      ctx    (struct dir_context *)  = 0x0000000000001234
+
+	  Requires elfutils (libdw-dev/elfutils-devel) on the build host.
+	  Adds approximately 1-2 MB to the kernel image.
+
+	  If unsure, say N.
+
 # end of the "standard kernel features (expert users)" menu
 
 config ARCH_HAS_MEMBARRIER_CALLBACKS
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index e6f796d43dd70..af8de3d8e3ba3 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -501,6 +501,174 @@ bool kallsyms_lookup_lineinfo(unsigned long addr,
 	return lineinfo_search(&tbl, (unsigned int)raw_offset, file, line);
 }
 
+#ifdef CONFIG_KALLSYMS_PARAMINFO
+
+#include <linux/ptrace.h>
+#ifdef CONFIG_X86
+#include <asm/special_insns.h>
+#endif
+
+#define MAX_PARAMINFO_PARAMS 6
+
+/*
+ * 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)
+{
+	switch (idx) {
+	case 0: return regs->di;
+	case 1: return regs->si;
+	case 2: return regs->dx;
+	case 3: return regs->cx;
+	case 4: return regs->r8;
+	case 5: return regs->r9;
+	default: return 0;
+	}
+}
+#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)
+{
+	return 0;
+}
+#endif /* CONFIG_X86_64 */
+
+/*
+ * Binary search for the function containing the given offset in
+ * paraminfo_func_addrs[].  Returns the index of the function whose
+ * start address is <= offset, or -1 if not found.
+ */
+static int paraminfo_find_func(unsigned int offset)
+{
+	int lo = 0, hi = paraminfo_num_funcs - 1;
+	int result = -1;
+
+	if (!paraminfo_num_funcs)
+		return -1;
+
+	while (lo <= hi) {
+		int mid = lo + (hi - lo) / 2;
+
+		if (paraminfo_func_addrs[mid] <= offset) {
+			result = mid;
+			lo = mid + 1;
+		} else {
+			hi = mid - 1;
+		}
+	}
+
+	return result;
+}
+
+/*
+ * Show function parameter info for the faulting/warning instruction.
+ *
+ * Called from show_regs() on x86 when CONFIG_KALLSYMS_PARAMINFO is
+ * enabled.  Works for both oops (page fault, GPF, etc.) and WARN(),
+ * since both paths provide full pt_regs at the exception point.
+ */
+void kallsyms_show_paraminfo(struct pt_regs *regs)
+{
+	unsigned long long raw_offset;
+	unsigned int offset;
+	int func_idx;
+	const u8 *data;
+	unsigned int num_params, i;
+	unsigned long ip, fault_addr;
+	char sym_name[KSYM_NAME_LEN];
+	unsigned long sym_size, sym_offset;
+
+	if (!regs || !paraminfo_num_funcs)
+		return;
+
+	ip = regs->ip;
+
+	/* Only handle kernel-mode faults */
+	if (user_mode(regs))
+		return;
+
+	if (ip < (unsigned long)_text)
+		return;
+
+	raw_offset = ip - (unsigned long)_text;
+	if (raw_offset > UINT_MAX)
+		return;
+	offset = (unsigned int)raw_offset;
+
+	func_idx = paraminfo_find_func(offset);
+	if (func_idx < 0)
+		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.
+	 */
+	if (!kallsyms_lookup_size_offset(ip, &sym_size, &sym_offset))
+		return;
+
+	/* Decode the function's parameter data */
+	data = paraminfo_func_data + paraminfo_func_offsets[func_idx];
+	num_params = *data++;
+
+	if (num_params == 0 || num_params > MAX_PARAMINFO_PARAMS)
+		return;
+
+	/* Look up function name for the header */
+	if (lookup_symbol_name(ip - sym_offset, sym_name))
+		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.
+	 */
+#ifdef CONFIG_X86
+	fault_addr = read_cr2();
+#else
+	fault_addr = 0;
+#endif
+
+	printk(KERN_DEFAULT "Function parameters (%s):\n", sym_name);
+
+	for (i = 0; i < num_params; i++) {
+		u32 name_off, type_off;
+		const char *pname, *ptype;
+		unsigned long val;
+		bool is_fault_addr;
+
+		memcpy(&name_off, data, sizeof(u32));
+		data += sizeof(u32);
+		memcpy(&type_off, data, sizeof(u32));
+		data += sizeof(u32);
+
+		pname = paraminfo_strings + name_off;
+		ptype = paraminfo_strings + type_off;
+		val = paraminfo_get_reg(regs, i);
+
+		is_fault_addr = fault_addr && (val == fault_addr);
+
+		printk(KERN_DEFAULT "  %-8s (%-20s) = 0x%016lx%s\n",
+		       pname, ptype, val,
+		       is_fault_addr ? "  <-- fault address" : "");
+	}
+}
+EXPORT_SYMBOL_GPL(kallsyms_show_paraminfo);
+
+#endif /* CONFIG_KALLSYMS_PARAMINFO */
+
 /* Look up a kernel symbol and return it in a text buffer. */
 static int __sprint_symbol(char *buffer, unsigned long address,
 			   int symbol_offset, int add_offset, int add_buildid)
diff --git a/kernel/kallsyms_internal.h b/kernel/kallsyms_internal.h
index ffe4c658067ec..7287ee0859515 100644
--- a/kernel/kallsyms_internal.h
+++ b/kernel/kallsyms_internal.h
@@ -26,4 +26,10 @@ extern const u32 lineinfo_file_offsets[];
 extern const u32 lineinfo_filenames_size;
 extern const char lineinfo_filenames[];
 
+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[];
+
 #endif // LINUX_KALLSYMS_INTERNAL_H_
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 688bbcb3eaa62..be8cee0985fbd 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3058,6 +3058,17 @@ config LINEINFO_KUNIT_TEST
 
 	  If unsure, say N.
 
+config PARAMINFO_KUNIT_TEST
+	tristate "KUnit tests for kallsyms paraminfo" if !KUNIT_ALL_TESTS
+	depends on KUNIT && KALLSYMS_PARAMINFO
+	default KUNIT_ALL_TESTS
+	help
+	  KUnit tests for the kallsyms function parameter info feature.
+	  Verifies that paraminfo tables correctly map functions to their
+	  parameter names and types.
+
+	  If unsure, say N.
+
 config HW_BREAKPOINT_KUNIT_TEST
 	bool "Test hw_breakpoint constraints accounting" if !KUNIT_ALL_TESTS
 	depends on HAVE_HW_BREAKPOINT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index c6add3b04bbd5..70452942baf45 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -39,6 +39,9 @@ obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
 CFLAGS_lineinfo_kunit.o += $(call cc-option,-fno-inline-functions-called-once)
 obj-$(CONFIG_LINEINFO_KUNIT_TEST) += lineinfo_kunit.o
 
+CFLAGS_paraminfo_kunit.o += $(call cc-option,-fno-inline-functions-called-once)
+obj-$(CONFIG_PARAMINFO_KUNIT_TEST) += paraminfo_kunit.o
+
 obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o
 obj-$(CONFIG_MIN_HEAP_KUNIT_TEST) += min_heap_kunit.o
 CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare)
diff --git a/lib/tests/paraminfo_kunit.c b/lib/tests/paraminfo_kunit.c
new file mode 100644
index 0000000000000..e09efc4ddeb0e
--- /dev/null
+++ b/lib/tests/paraminfo_kunit.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for kallsyms paraminfo (CONFIG_KALLSYMS_PARAMINFO).
+ *
+ * Copyright (c) 2026 Sasha Levin <sashal@kernel.org>
+ *
+ * Verifies that the paraminfo tables correctly map function addresses
+ * to their parameter names and types.
+ *
+ * Build with: CONFIG_PARAMINFO_KUNIT_TEST=m (or =y)
+ */
+
+#include <kunit/test.h>
+#include <linux/kallsyms.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+/* ---- Test target functions with known signatures ---- */
+
+static noinline int paraminfo_test_two_args(struct kunit *test, int value)
+{
+	/* Prevent optimization */
+	return test ? value + 1 : 0;
+}
+
+static noinline void *paraminfo_test_ptr_arg(void *ptr, unsigned long size)
+{
+	if (ptr && size > 0)
+		return ptr;
+	return NULL;
+}
+
+static noinline long paraminfo_test_many_args(int a, int b, int c,
+					      int d, int e, int f)
+{
+	return (long)a + b + c + d + e + f;
+}
+
+static noinline void paraminfo_test_no_args(void)
+{
+	/* Function with no parameters */
+	barrier();
+}
+
+/* ---- 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[];
+
+struct param_result {
+	unsigned int num_params;
+	const char *names[6];
+	const char *types[6];
+};
+
+/*
+ * Look up paraminfo for a given kernel address.
+ * Returns true if found, filling in @result.
+ */
+static bool lookup_paraminfo(unsigned long addr, struct param_result *result)
+{
+	unsigned long long raw_offset;
+	unsigned int offset;
+	int lo, hi, func_idx;
+	const u8 *data;
+	unsigned int i;
+
+	if (!paraminfo_num_funcs)
+		return false;
+
+	if (addr < (unsigned long)_text)
+		return false;
+
+	raw_offset = addr - (unsigned long)_text;
+	if (raw_offset > UINT_MAX)
+		return false;
+	offset = (unsigned int)raw_offset;
+
+	/* Binary search for the function */
+	lo = 0;
+	hi = paraminfo_num_funcs - 1;
+	func_idx = -1;
+	while (lo <= hi) {
+		int mid = lo + (hi - lo) / 2;
+
+		if (paraminfo_func_addrs[mid] <= offset) {
+			func_idx = mid;
+			lo = mid + 1;
+		} else {
+			hi = mid - 1;
+		}
+	}
+
+	if (func_idx < 0)
+		return false;
+
+	/* Verify we're not too far from the function start */
+	if (offset - paraminfo_func_addrs[func_idx] > 0x10000)
+		return false;
+
+	data = paraminfo_func_data + paraminfo_func_offsets[func_idx];
+	result->num_params = *data++;
+
+	if (result->num_params > 6)
+		return false;
+
+	for (i = 0; i < result->num_params; i++) {
+		u32 name_off, type_off;
+
+		memcpy(&name_off, data, sizeof(u32));
+		data += sizeof(u32);
+		memcpy(&type_off, data, sizeof(u32));
+		data += sizeof(u32);
+
+		result->names[i] = paraminfo_strings + name_off;
+		result->types[i] = paraminfo_strings + type_off;
+	}
+
+	return true;
+}
+
+/* ---- Test cases ---- */
+
+static void test_paraminfo_two_args(struct kunit *test)
+{
+	struct param_result res;
+	bool found;
+
+	found = lookup_paraminfo((unsigned long)paraminfo_test_two_args, &res);
+
+	if (!IS_ENABLED(CONFIG_KALLSYMS_PARAMINFO)) {
+		KUNIT_EXPECT_FALSE(test, found);
+		return;
+	}
+
+	KUNIT_ASSERT_TRUE(test, found);
+	KUNIT_EXPECT_EQ(test, res.num_params, 2U);
+	KUNIT_EXPECT_STREQ(test, res.names[0], "test");
+	KUNIT_EXPECT_STREQ(test, res.names[1], "value");
+	KUNIT_EXPECT_TRUE(test, strstr(res.types[1], "int") != NULL);
+}
+
+static void test_paraminfo_ptr_arg(struct kunit *test)
+{
+	struct param_result res;
+	bool found;
+
+	found = lookup_paraminfo((unsigned long)paraminfo_test_ptr_arg, &res);
+
+	if (!IS_ENABLED(CONFIG_KALLSYMS_PARAMINFO)) {
+		KUNIT_EXPECT_FALSE(test, found);
+		return;
+	}
+
+	KUNIT_ASSERT_TRUE(test, found);
+	KUNIT_EXPECT_EQ(test, res.num_params, 2U);
+	KUNIT_EXPECT_STREQ(test, res.names[0], "ptr");
+	KUNIT_EXPECT_STREQ(test, res.names[1], "size");
+	/* First param should be a pointer type */
+	KUNIT_EXPECT_TRUE(test, strstr(res.types[0], "*") != NULL);
+}
+
+static void test_paraminfo_many_args(struct kunit *test)
+{
+	struct param_result res;
+	bool found;
+
+	found = lookup_paraminfo((unsigned long)paraminfo_test_many_args, &res);
+
+	if (!IS_ENABLED(CONFIG_KALLSYMS_PARAMINFO)) {
+		KUNIT_EXPECT_FALSE(test, found);
+		return;
+	}
+
+	KUNIT_ASSERT_TRUE(test, found);
+	KUNIT_EXPECT_EQ(test, res.num_params, 6U);
+	KUNIT_EXPECT_STREQ(test, res.names[0], "a");
+	KUNIT_EXPECT_STREQ(test, res.names[1], "b");
+	KUNIT_EXPECT_STREQ(test, res.names[2], "c");
+	KUNIT_EXPECT_STREQ(test, res.names[3], "d");
+	KUNIT_EXPECT_STREQ(test, res.names[4], "e");
+	KUNIT_EXPECT_STREQ(test, res.names[5], "f");
+}
+
+static void test_paraminfo_no_args(struct kunit *test)
+{
+	struct param_result res;
+	bool found;
+
+	/*
+	 * Functions with no parameters should not have entries in the
+	 * paraminfo table (they are filtered out at build time).
+	 * The lookup may find a nearby function instead, but if it
+	 * does find our function exactly, num_params should be 0.
+	 */
+	found = lookup_paraminfo((unsigned long)paraminfo_test_no_args, &res);
+
+	if (found) {
+		/* If it matched our exact function, it should have 0 params.
+		 * But it may have matched a preceding function instead.
+		 */
+		kunit_info(test, "lookup found func with %u params\n",
+			   res.num_params);
+	}
+}
+
+static void test_paraminfo_bogus_addr(struct kunit *test)
+{
+	struct param_result res;
+
+	/* Address 0 should not match anything */
+	KUNIT_EXPECT_FALSE(test, lookup_paraminfo(0, &res));
+
+	/* Address in userspace should not match */
+	KUNIT_EXPECT_FALSE(test, lookup_paraminfo(0x1000, &res));
+}
+
+static void test_paraminfo_tables_present(struct kunit *test)
+{
+	if (!IS_ENABLED(CONFIG_KALLSYMS_PARAMINFO)) {
+		kunit_skip(test, "CONFIG_KALLSYMS_PARAMINFO not enabled");
+		return;
+	}
+
+	KUNIT_EXPECT_GT(test, paraminfo_num_funcs, 0U);
+	kunit_info(test, "paraminfo: %u functions in table\n",
+		   paraminfo_num_funcs);
+}
+
+static struct kunit_case paraminfo_test_cases[] = {
+	KUNIT_CASE(test_paraminfo_tables_present),
+	KUNIT_CASE(test_paraminfo_two_args),
+	KUNIT_CASE(test_paraminfo_ptr_arg),
+	KUNIT_CASE(test_paraminfo_many_args),
+	KUNIT_CASE(test_paraminfo_no_args),
+	KUNIT_CASE(test_paraminfo_bogus_addr),
+	{}
+};
+
+static struct kunit_suite paraminfo_test_suite = {
+	.name = "paraminfo",
+	.test_cases = paraminfo_test_cases,
+};
+
+kunit_test_suite(paraminfo_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests for kallsyms paraminfo");
diff --git a/scripts/Makefile b/scripts/Makefile
index ffe89875b3295..f681b94c6d9e7 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -5,6 +5,7 @@
 
 hostprogs-always-$(CONFIG_KALLSYMS)			+= kallsyms
 hostprogs-always-$(CONFIG_KALLSYMS_LINEINFO)		+= gen_lineinfo
+hostprogs-always-$(CONFIG_KALLSYMS_PARAMINFO)		+= gen_paraminfo
 hostprogs-always-$(BUILD_C_RECORDMCOUNT)		+= recordmcount
 hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT)		+= sorttable
 hostprogs-always-$(CONFIG_ASN1)				+= asn1_compiler
@@ -39,6 +40,8 @@ HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/nu
 HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
 HOSTCFLAGS_gen_lineinfo.o = $(shell $(HOSTPKG_CONFIG) --cflags libdw 2> /dev/null)
 HOSTLDLIBS_gen_lineinfo = $(shell $(HOSTPKG_CONFIG) --libs libdw 2> /dev/null || echo -ldw -lelf -lz)
+HOSTCFLAGS_gen_paraminfo.o = $(shell $(HOSTPKG_CONFIG) --cflags libdw 2> /dev/null)
+HOSTLDLIBS_gen_paraminfo = $(shell $(HOSTPKG_CONFIG) --libs libdw 2> /dev/null || echo -ldw -lelf -lz)
 
 ifdef CONFIG_UNWINDER_ORC
 ifeq ($(ARCH),x86_64)
diff --git a/scripts/empty_paraminfo.S b/scripts/empty_paraminfo.S
new file mode 100644
index 0000000000000..a0e39dbfbd3cb
--- /dev/null
+++ b/scripts/empty_paraminfo.S
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Empty paraminfo stub for the initial vmlinux link.
+ * The real paraminfo is generated from .tmp_vmlinux1 by gen_paraminfo.
+ */
+	.section .rodata, "a"
+	.globl paraminfo_num_funcs
+	.balign 4
+paraminfo_num_funcs:
+	.long 0
+	.globl paraminfo_func_addrs
+paraminfo_func_addrs:
+	.globl paraminfo_func_offsets
+paraminfo_func_offsets:
+	.globl paraminfo_func_data
+paraminfo_func_data:
+	.globl paraminfo_strings
+paraminfo_strings:
diff --git a/scripts/gen_paraminfo.c b/scripts/gen_paraminfo.c
new file mode 100644
index 0000000000000..ea1d23f3ddd9a
--- /dev/null
+++ b/scripts/gen_paraminfo.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gen_paraminfo.c - Generate function parameter info tables from DWARF
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ *
+ * Reads DWARF .debug_info from a vmlinux ELF file and outputs an assembly
+ * file containing function parameter name/type tables that the kernel uses
+ * to annotate oops/WARN dumps with typed parameter values.
+ *
+ * Requires libdw from elfutils.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <elfutils/libdw.h>
+#include <dwarf.h>
+#include <elf.h>
+#include <gelf.h>
+#include <limits.h>
+
+/* Maximum register-passed parameters on x86-64 */
+#define MAX_PARAMS 6
+
+/* Maximum length for a type name string */
+#define MAX_TYPE_LEN 64
+
+struct param_info {
+	char name[64];
+	char type[MAX_TYPE_LEN];
+};
+
+struct func_entry {
+	unsigned int offset;		/* offset from _text */
+	unsigned int num_params;
+	struct param_info params[MAX_PARAMS];
+};
+
+static struct func_entry *funcs;
+static unsigned int num_funcs;
+static unsigned int funcs_capacity;
+
+/*
+ * String table for parameter names and type names.
+ * Deduplicated via a hash table.
+ */
+struct str_entry {
+	char *str;
+	unsigned int offset;	/* byte offset in concatenated output */
+};
+
+static struct str_entry *strtab;
+static unsigned int num_strings;
+static unsigned int strtab_capacity;
+static unsigned int strtab_total_size;
+
+#define STR_HASH_BITS 14
+#define STR_HASH_SIZE (1 << STR_HASH_BITS)
+
+struct str_hash_entry {
+	const char *str;
+	unsigned int idx;	/* index into strtab[] */
+};
+
+static struct str_hash_entry str_hash[STR_HASH_SIZE];
+
+static unsigned int hash_str(const char *s)
+{
+	unsigned int h = 5381;
+
+	for (; *s; s++)
+		h = h * 33 + (unsigned char)*s;
+	return h & (STR_HASH_SIZE - 1);
+}
+
+static unsigned int find_or_add_string(const char *s)
+{
+	unsigned int h = hash_str(s);
+
+	while (str_hash[h].str) {
+		if (!strcmp(str_hash[h].str, s))
+			return str_hash[h].idx;
+		h = (h + 1) & (STR_HASH_SIZE - 1);
+	}
+
+	if (num_strings >= strtab_capacity) {
+		strtab_capacity = strtab_capacity ? strtab_capacity * 2 : 8192;
+		strtab = realloc(strtab, strtab_capacity * sizeof(*strtab));
+		if (!strtab) {
+			fprintf(stderr, "out of memory\n");
+			exit(1);
+		}
+	}
+
+	strtab[num_strings].str = strdup(s);
+	strtab[num_strings].offset = strtab_total_size;
+	strtab_total_size += strlen(s) + 1;
+
+	str_hash[h].str = strtab[num_strings].str;
+	str_hash[h].idx = num_strings;
+
+	num_strings++;
+	return num_strings - 1;
+}
+
+static void add_func(struct func_entry *f)
+{
+	if (num_funcs >= funcs_capacity) {
+		funcs_capacity = funcs_capacity ? funcs_capacity * 2 : 16384;
+		funcs = realloc(funcs, funcs_capacity * sizeof(*funcs));
+		if (!funcs) {
+			fprintf(stderr, "out of memory\n");
+			exit(1);
+		}
+	}
+	funcs[num_funcs++] = *f;
+}
+
+/*
+ * 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)
+{
+	Dwarf_Die child;
+	Dwarf_Attribute attr;
+	const char *name;
+	int tag;
+
+	if (!type_die) {
+		snprintf(buf, bufsz, "void");
+		return;
+	}
+
+	tag = dwarf_tag(type_die);
+
+	switch (tag) {
+	case DW_TAG_base_type:
+		name = dwarf_diename(type_die);
+		snprintf(buf, bufsz, "%s", name ? name : "?");
+		break;
+
+	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);
+			if (strlen(buf) + 3 < bufsz)
+				strcat(buf, " *");
+		} else {
+			snprintf(buf, bufsz, "void *");
+		}
+		break;
+
+	case DW_TAG_const_type:
+		if (dwarf_attr(type_die, DW_AT_type, &attr) &&
+		    dwarf_formref_die(&attr, &child)) {
+			char tmp[MAX_TYPE_LEN - 10];
+
+			build_type_name(&child, tmp, sizeof(tmp));
+			snprintf(buf, bufsz, "const %s", tmp);
+		} else {
+			snprintf(buf, bufsz, "const void");
+		}
+		break;
+
+	case DW_TAG_volatile_type:
+		if (dwarf_attr(type_die, DW_AT_type, &attr) &&
+		    dwarf_formref_die(&attr, &child)) {
+			char tmp[MAX_TYPE_LEN - 10];
+
+			build_type_name(&child, tmp, sizeof(tmp));
+			snprintf(buf, bufsz, "volatile %s", tmp);
+		} else {
+			snprintf(buf, bufsz, "volatile void");
+		}
+		break;
+
+	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);
+		} else {
+			snprintf(buf, bufsz, "void");
+		}
+		break;
+
+	case DW_TAG_typedef:
+		name = dwarf_diename(type_die);
+		if (name) {
+			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);
+		} else {
+			snprintf(buf, bufsz, "?");
+		}
+		break;
+
+	case DW_TAG_structure_type:
+		name = dwarf_diename(type_die);
+		snprintf(buf, bufsz, "struct %s", name ? name : "(anon)");
+		break;
+
+	case DW_TAG_union_type:
+		name = dwarf_diename(type_die);
+		snprintf(buf, bufsz, "union %s", name ? name : "(anon)");
+		break;
+
+	case DW_TAG_enumeration_type:
+		name = dwarf_diename(type_die);
+		snprintf(buf, bufsz, "enum %s", name ? name : "(anon)");
+		break;
+
+	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);
+			if (strlen(buf) + 3 < bufsz)
+				strcat(buf, "[]");
+		} else {
+			snprintf(buf, bufsz, "?[]");
+		}
+		break;
+
+	case DW_TAG_subroutine_type:
+		snprintf(buf, bufsz, "func_ptr");
+		break;
+
+	default:
+		snprintf(buf, bufsz, "?");
+		break;
+	}
+}
+
+static unsigned long long find_text_addr(Elf *elf)
+{
+	size_t nsyms, i;
+	Elf_Scn *scn = NULL;
+	GElf_Shdr shdr;
+
+	while ((scn = elf_nextscn(elf, scn)) != NULL) {
+		Elf_Data *data;
+
+		if (!gelf_getshdr(scn, &shdr))
+			continue;
+		if (shdr.sh_type != SHT_SYMTAB)
+			continue;
+
+		data = elf_getdata(scn, NULL);
+		if (!data)
+			continue;
+
+		nsyms = shdr.sh_size / shdr.sh_entsize;
+		for (i = 0; i < nsyms; i++) {
+			GElf_Sym sym;
+			const char *name;
+
+			if (!gelf_getsym(data, i, &sym))
+				continue;
+			name = elf_strptr(elf, shdr.sh_link, sym.st_name);
+			if (name && !strcmp(name, "_text"))
+				return sym.st_value;
+		}
+	}
+
+	fprintf(stderr, "Cannot find _text symbol\n");
+	exit(1);
+}
+
+static int compare_funcs(const void *a, const void *b)
+{
+	const struct func_entry *fa = a;
+	const struct func_entry *fb = b;
+
+	if (fa->offset < fb->offset)
+		return -1;
+	if (fa->offset > fb->offset)
+		return 1;
+	return 0;
+}
+
+static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
+{
+	Dwarf_Off off = 0, next_off;
+	size_t hdr_size;
+
+	while (dwarf_nextcu(dwarf, off, &next_off, &hdr_size,
+			    NULL, NULL, NULL) == 0) {
+		Dwarf_Die cudie, child;
+
+		if (!dwarf_offdie(dwarf, off + hdr_size, &cudie))
+			goto next;
+
+		if (dwarf_child(&cudie, &child) != 0)
+			goto next;
+
+		do {
+			Dwarf_Die param;
+			Dwarf_Attribute attr;
+			Dwarf_Addr low_pc;
+			struct func_entry func;
+			int tag;
+
+			tag = dwarf_tag(&child);
+			if (tag != DW_TAG_subprogram)
+				continue;
+
+			/* Skip declarations (no body) */
+			if (dwarf_attr(&child, DW_AT_declaration, &attr))
+				continue;
+
+			/* Get function start address */
+			if (dwarf_lowpc(&child, &low_pc) != 0)
+				continue;
+
+			if (low_pc < text_addr)
+				continue;
+
+			{
+				unsigned long long raw_offset = low_pc - text_addr;
+
+				if (raw_offset > UINT_MAX)
+					continue;
+				func.offset = (unsigned int)raw_offset;
+			}
+
+			/* Iterate formal parameters */
+			func.num_params = 0;
+			if (dwarf_child(&child, &param) == 0) {
+				do {
+					Dwarf_Die type_die;
+					const char *pname;
+
+					if (dwarf_tag(&param) != DW_TAG_formal_parameter)
+						continue;
+					if (func.num_params >= MAX_PARAMS)
+						break;
+
+					pname = dwarf_diename(&param);
+					if (!pname)
+						pname = "?";
+
+					snprintf(func.params[func.num_params].name,
+						 sizeof(func.params[0].name),
+						 "%s", pname);
+
+					/* Resolve type */
+					if (dwarf_attr(&param, DW_AT_type, &attr) &&
+					    dwarf_formref_die(&attr, &type_die)) {
+						build_type_name(&type_die,
+								func.params[func.num_params].type,
+								MAX_TYPE_LEN);
+					} else {
+						snprintf(func.params[func.num_params].type,
+							 MAX_TYPE_LEN, "?");
+					}
+
+					func.num_params++;
+				} while (dwarf_siblingof(&param, &param) == 0);
+			}
+
+			/* Skip functions with no parameters */
+			if (func.num_params == 0)
+				continue;
+
+			add_func(&func);
+		} while (dwarf_siblingof(&child, &child) == 0);
+next:
+		off = next_off;
+	}
+}
+
+static void deduplicate(void)
+{
+	unsigned int i, j;
+
+	if (num_funcs < 2)
+		return;
+
+	/* Sort by offset */
+	qsort(funcs, num_funcs, sizeof(*funcs), compare_funcs);
+
+	/* Remove duplicates (same offset — keep first) */
+	j = 0;
+	for (i = 1; i < num_funcs; i++) {
+		if (funcs[i].offset == funcs[j].offset)
+			continue;
+		j++;
+		if (j != i)
+			funcs[j] = funcs[i];
+	}
+	num_funcs = j + 1;
+}
+
+static void print_escaped_asciz(const char *s)
+{
+	printf("\t.asciz \"");
+	for (; *s; s++) {
+		if (*s == '"' || *s == '\\')
+			putchar('\\');
+		putchar(*s);
+	}
+	printf("\"\n");
+}
+
+static void output_assembly(void)
+{
+	unsigned int i, j;
+
+	printf("/* SPDX-License-Identifier: GPL-2.0 */\n");
+	printf("/*\n");
+	printf(" * Automatically generated by scripts/gen_paraminfo\n");
+	printf(" * Do not edit.\n");
+	printf(" */\n\n");
+
+	printf("\t.section .rodata, \"a\"\n\n");
+
+	/* Number of functions */
+	printf("\t.globl paraminfo_num_funcs\n");
+	printf("\t.balign 4\n");
+	printf("paraminfo_num_funcs:\n");
+	printf("\t.long %u\n\n", num_funcs);
+
+	/* Function address offsets (sorted, for binary search) */
+	printf("\t.globl paraminfo_func_addrs\n");
+	printf("\t.balign 4\n");
+	printf("paraminfo_func_addrs:\n");
+	for (i = 0; i < num_funcs; i++)
+		printf("\t.long 0x%x\n", funcs[i].offset);
+	printf("\n");
+
+	/*
+	 * Function data offsets — byte offset into paraminfo_func_data
+	 * for each function's parameter list.
+	 */
+	printf("\t.globl paraminfo_func_offsets\n");
+	printf("\t.balign 4\n");
+	printf("paraminfo_func_offsets:\n");
+	for (i = 0; i < num_funcs; i++)
+		printf("\t.long .Lfunc_%u - paraminfo_func_data\n", i);
+	printf("\n");
+
+	/*
+	 * Register strings in the string table and build the func data.
+	 * Func data format per function:
+	 *   u8  num_params
+	 *   For each param:
+	 *     u32 name_str_offset
+	 *     u32 type_str_offset
+	 */
+	/* First pass: register all strings */
+	for (i = 0; i < num_funcs; i++) {
+		for (j = 0; j < funcs[i].num_params; j++) {
+			find_or_add_string(funcs[i].params[j].name);
+			find_or_add_string(funcs[i].params[j].type);
+		}
+	}
+
+	/* Function parameter data */
+	printf("\t.globl paraminfo_func_data\n");
+	printf("paraminfo_func_data:\n");
+	for (i = 0; i < num_funcs; i++) {
+		printf(".Lfunc_%u:\n", i);
+		printf("\t.byte %u\n", funcs[i].num_params);
+		for (j = 0; j < funcs[i].num_params; j++) {
+			unsigned int name_idx = find_or_add_string(funcs[i].params[j].name);
+			unsigned int type_idx = find_or_add_string(funcs[i].params[j].type);
+
+			printf("\t.long %u, %u\n",
+			       strtab[name_idx].offset,
+			       strtab[type_idx].offset);
+		}
+	}
+	printf("\n");
+
+	/* String table */
+	printf("\t.globl paraminfo_strings\n");
+	printf("paraminfo_strings:\n");
+	for (i = 0; i < num_strings; i++)
+		print_escaped_asciz(strtab[i].str);
+	printf("\n");
+}
+
+int main(int argc, char *argv[])
+{
+	int fd;
+	Elf *elf;
+	Dwarf *dwarf;
+	unsigned long long text_addr;
+
+	if (argc != 2) {
+		fprintf(stderr, "Usage: %s <vmlinux ELF>\n", argv[0]);
+		return 1;
+	}
+
+	fd = open(argv[1], O_RDONLY);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open %s: %s\n", argv[1],
+			strerror(errno));
+		return 1;
+	}
+
+	elf_version(EV_CURRENT);
+	elf = elf_begin(fd, ELF_C_READ, NULL);
+	if (!elf) {
+		fprintf(stderr, "elf_begin failed: %s\n",
+			elf_errmsg(elf_errno()));
+		close(fd);
+		return 1;
+	}
+
+	text_addr = find_text_addr(elf);
+
+	dwarf = dwarf_begin_elf(elf, DWARF_C_READ, NULL);
+	if (!dwarf) {
+		fprintf(stderr, "dwarf_begin_elf failed: %s\n",
+			dwarf_errmsg(dwarf_errno()));
+		fprintf(stderr, "Is %s built with CONFIG_DEBUG_INFO?\n",
+			argv[1]);
+		elf_end(elf);
+		close(fd);
+		return 1;
+	}
+
+	process_dwarf(dwarf, text_addr);
+	deduplicate();
+
+	fprintf(stderr, "paraminfo: %u functions, %u strings\n",
+		num_funcs, num_strings);
+
+	output_assembly();
+
+	dwarf_end(dwarf);
+	elf_end(elf);
+	close(fd);
+
+	/* Cleanup */
+	free(funcs);
+	for (unsigned int i = 0; i < num_strings; i++)
+		free(strtab[i].str);
+	free(strtab);
+
+	return 0;
+}
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index 39ca44fbb259b..d41b31dde4ef9 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -103,7 +103,7 @@ vmlinux_link()
 	${ld} ${ldflags} -o ${output}					\
 		${wl}--whole-archive ${objs} ${wl}--no-whole-archive	\
 		${wl}--start-group ${libs} ${wl}--end-group		\
-		${kallsymso} ${lineinfo_o} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}
+		${kallsymso} ${lineinfo_o} ${paraminfo_o} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}
 }
 
 # Create ${2}.o file with all symbols from the ${1} object file
@@ -149,6 +149,26 @@ gen_lineinfo()
 	lineinfo_o=.tmp_lineinfo.o
 }
 
+# Generate paraminfo tables from DWARF debug info in a temporary vmlinux.
+# ${1} - temporary vmlinux with debug info
+# Output: sets paraminfo_o to the generated .o file
+gen_paraminfo()
+{
+	info PARAMINFO .tmp_paraminfo.S
+	if ! scripts/gen_paraminfo "${1}" > .tmp_paraminfo.S; then
+		echo >&2 "Failed to generate paraminfo from ${1}"
+		echo >&2 "Try to disable CONFIG_KALLSYMS_PARAMINFO"
+		exit 1
+	fi
+
+	info AS .tmp_paraminfo.o
+	${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \
+	      ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \
+	      -c -o .tmp_paraminfo.o .tmp_paraminfo.S
+
+	paraminfo_o=.tmp_paraminfo.o
+}
+
 # Perform kallsyms for the given temporary vmlinux.
 sysmap_and_kallsyms()
 {
@@ -176,6 +196,7 @@ cleanup()
 {
 	rm -f .btf.*
 	rm -f .tmp_lineinfo.*
+	rm -f .tmp_paraminfo.*
 	rm -f .tmp_vmlinux.nm-sort
 	rm -f System.map
 	rm -f vmlinux
@@ -205,6 +226,7 @@ btf_vmlinux_bin_o=
 btfids_vmlinux=
 kallsymso=
 lineinfo_o=
+paraminfo_o=
 strip_debug=
 generate_map=
 
@@ -229,12 +251,22 @@ if is_enabled CONFIG_KALLSYMS_LINEINFO; then
 	lineinfo_o=.tmp_lineinfo.o
 fi
 
+if is_enabled CONFIG_KALLSYMS_PARAMINFO; then
+	# Assemble an empty paraminfo stub for the initial link.
+	# The real paraminfo is generated from .tmp_vmlinux1 by gen_paraminfo.
+	${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \
+	      ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \
+	      -c -o .tmp_paraminfo.o "${srctree}/scripts/empty_paraminfo.S"
+	paraminfo_o=.tmp_paraminfo.o
+fi
+
 if is_enabled CONFIG_KALLSYMS || is_enabled CONFIG_DEBUG_INFO_BTF; then
 
-	# The kallsyms linking does not need debug symbols, but BTF and
-	# lineinfo generation do.
+	# The kallsyms linking does not need debug symbols, but BTF,
+	# lineinfo and paraminfo generation do.
 	if ! is_enabled CONFIG_DEBUG_INFO_BTF &&
-	   ! is_enabled CONFIG_KALLSYMS_LINEINFO; then
+	   ! is_enabled CONFIG_KALLSYMS_LINEINFO &&
+	   ! is_enabled CONFIG_KALLSYMS_PARAMINFO; then
 		strip_debug=1
 	fi
 
@@ -256,6 +288,10 @@ if is_enabled CONFIG_KALLSYMS_LINEINFO; then
 	gen_lineinfo .tmp_vmlinux1
 fi
 
+if is_enabled CONFIG_KALLSYMS_PARAMINFO; then
+	gen_paraminfo .tmp_vmlinux1
+fi
+
 if is_enabled CONFIG_KALLSYMS; then
 
 	# kallsyms support
-- 
2.51.0


  reply	other threads:[~2026-03-23 16:49 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-23 16:48 [PATCH 0/2] kallsyms: show typed function parameters in oops/WARN dumps Sasha Levin
2026-03-23 16:48 ` Sasha Levin [this message]
2026-03-24 15:03   ` [PATCH 1/2] kallsyms: show function parameter info " Alexei Starovoitov
2026-03-24 16:00     ` Sasha Levin
2026-03-24 16:04       ` Alexei Starovoitov
2026-03-24 18:44         ` Sasha Levin
2026-03-25  1:18           ` [RFC] btf: split core BTF parsing out of BPF subsystem into kernel/btf/ Sasha Levin
2026-03-25  2:15             ` bot+bpf-ci
2026-03-24 17:34       ` [PATCH 1/2] kallsyms: show function parameter info in oops/WARN dumps Alan Maguire
2026-03-24 18:51         ` Sasha Levin
2026-03-23 16:48 ` [PATCH 2/2] kallsyms: add BTF-based deep parameter rendering in oops dumps Sasha Levin
2026-03-24 15:07   ` Alexei Starovoitov
2026-03-23 18:43 ` [PATCH 0/2] kallsyms: show typed function parameters in oops/WARN dumps Alexey Dobriyan
2026-03-23 22:58   ` Sasha Levin
2026-03-23 22:50 ` Andrew Morton
2026-03-23 23:08   ` Sasha Levin
2026-03-24  8:57 ` Jiri Olsa
2026-03-24 11:39   ` Sasha Levin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260323164858.1939248-2-sashal@kernel.org \
    --to=sashal@kernel.org \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=adobriyan@gmail.com \
    --cc=akpm@linux-foundation.org \
    --cc=ast@kernel.org \
    --cc=bp@alien8.de \
    --cc=bpf@vger.kernel.org \
    --cc=corbet@lwn.net \
    --cc=dave.hansen@linux.intel.com \
    --cc=davidgow@google.com \
    --cc=deller@gmx.de \
    --cc=geert@linux-m68k.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=hpa@zytor.com \
    --cc=jgross@suse.com \
    --cc=jpoimboe@kernel.org \
    --cc=kees@kernel.org \
    --cc=laurent.pinchart@ideasonboard.com \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kbuild@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-modules@vger.kernel.org \
    --cc=masahiroy@kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=mingo@redhat.com \
    --cc=nathan@kernel.org \
    --cc=nsc@kernel.org \
    --cc=peterz@infradead.org \
    --cc=petr.pavlu@suse.com \
    --cc=pmladek@suse.com \
    --cc=rdunlap@infradead.org \
    --cc=rostedt@goodmis.org \
    --cc=tglx@kernel.org \
    --cc=vbabka@kernel.org \
    --cc=x86@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox