* [PATCH v2 1/4] kallsyms: embed source file:line info in kernel stack traces
From: Sasha Levin @ 2026-03-07 17:20 UTC (permalink / raw)
To: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley
Cc: Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Petr Pavlu,
Daniel Gomez, Greg KH, Petr Mladek, Steven Rostedt, Kees Cook,
Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka, linux-kernel,
linux-kbuild, linux-modules, linux-doc, deller, rdunlap,
laurent.pinchart, Sasha Levin
In-Reply-To: <20260307172022.460402-1-sashal@kernel.org>
Add CONFIG_KALLSYMS_LINEINFO, which embeds a compact address-to-line
lookup table in the kernel image so stack traces directly print source
file and line number information:
root@localhost:~# echo c > /proc/sysrq-trigger
[ 11.201987] sysrq: Trigger a crash
[ 11.202831] Kernel panic - not syncing: sysrq triggered crash
[ 11.206218] Call Trace:
[ 11.206501] <TASK>
[ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
[ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
[ 11.208565] ? __lock_acquire+0x465/0x2240 (kernel/locking/lockdep.c:4674)
[ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
[ 11.211873] ? find_held_lock+0x2b/0x80 (kernel/locking/lockdep.c:5350)
[ 11.212597] ? lock_release+0xd3/0x300 (kernel/locking/lockdep.c:5535)
[ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
[ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
[ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
[ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
[ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
[ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
[ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
[ 11.223942] entry_SYSCALL_64_after_hwframe+0x77/0x7f (arch/x86/entry/entry_64.S:121)
At build time, a new host tool (scripts/gen_lineinfo) reads DWARF
.debug_line from vmlinux using libdw (elfutils), extracts all
address-to-file:line mappings, and generates an assembly file with
sorted parallel arrays (offsets from _text, file IDs, and line
numbers). These are linked into vmlinux as .rodata.
At runtime, kallsyms_lookup_lineinfo() does a binary search on the
table and __sprint_symbol() appends "(file:line)" to each stack frame.
The lookup uses offsets from _text so it works with KASLR, requires no
locks or allocations, and is safe in any context including panic.
The feature requires CONFIG_DEBUG_INFO (for DWARF data) and
elfutils (libdw-dev) on the build host.
Memory footprint measured with a 1852-option x86_64 config:
Table: 4,597,583 entries from 4,841 source files
lineinfo_addrs[] 4,597,583 x u32 = 17.5 MiB
lineinfo_file_ids[] 4,597,583 x u16 = 8.8 MiB
lineinfo_lines[] 4,597,583 x u32 = 17.5 MiB
file_offsets + filenames ~ 0.1 MiB
Total .rodata increase: ~ 44.0 MiB
vmlinux (stripped): 529 MiB -> 573 MiB (+44 MiB / +8.3%)
Note: this probably won't be something we roll into "production", but
it might be useful for the average user given the relatively low memory
footprint, in canary deployments for hyperscalers, or by default for
folks who run tests/fuzzing/etc.
Disclaimer: this was vibe coded over an afternoon with an AI coding
assistant.
The .config used for testing is a simple KVM guest configuration for
local development and testing.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
Documentation/admin-guide/index.rst | 1 +
.../admin-guide/kallsyms-lineinfo.rst | 72 +++
MAINTAINERS | 6 +
include/linux/kallsyms.h | 32 +-
init/Kconfig | 20 +
kernel/kallsyms.c | 61 +++
kernel/kallsyms_internal.h | 11 +
scripts/.gitignore | 1 +
scripts/Makefile | 3 +
scripts/gen_lineinfo.c | 510 ++++++++++++++++++
scripts/kallsyms.c | 16 +
scripts/link-vmlinux.sh | 70 ++-
12 files changed, 799 insertions(+), 4 deletions(-)
create mode 100644 Documentation/admin-guide/kallsyms-lineinfo.rst
create mode 100644 scripts/gen_lineinfo.c
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index b734f8a2a2c48..1801b9880aeb7 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -73,6 +73,7 @@ problems and bugs in particular.
ramoops
dynamic-debug-howto
init
+ kallsyms-lineinfo
kdump/index
perf/index
pstore-blk
diff --git a/Documentation/admin-guide/kallsyms-lineinfo.rst b/Documentation/admin-guide/kallsyms-lineinfo.rst
new file mode 100644
index 0000000000000..c8ec124394354
--- /dev/null
+++ b/Documentation/admin-guide/kallsyms-lineinfo.rst
@@ -0,0 +1,72 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================================
+Kallsyms Source Line Info (LINEINFO)
+====================================
+
+Overview
+========
+
+``CONFIG_KALLSYMS_LINEINFO`` embeds DWARF-derived source file and line number
+mappings into the kernel image so that stack traces include
+``(file.c:123)`` annotations next to each symbol. This makes it significantly
+easier to pinpoint the exact source location during debugging, without needing
+to manually cross-reference addresses with ``addr2line``.
+
+Enabling the Feature
+====================
+
+Enable the following kernel configuration options::
+
+ CONFIG_KALLSYMS=y
+ CONFIG_DEBUG_INFO=y
+ CONFIG_KALLSYMS_LINEINFO=y
+
+Build dependency: the host tool ``scripts/gen_lineinfo`` requires ``libdw``
+from elfutils. Install the development package:
+
+- Debian/Ubuntu: ``apt install libdw-dev``
+- Fedora/RHEL: ``dnf install elfutils-devel``
+- Arch Linux: ``pacman -S elfutils``
+
+Example Output
+==============
+
+Without ``CONFIG_KALLSYMS_LINEINFO``::
+
+ Call Trace:
+ <TASK>
+ dump_stack_lvl+0x5d/0x80
+ do_syscall_64+0x82/0x190
+ entry_SYSCALL_64_after_hwframe+0x76/0x7e
+
+With ``CONFIG_KALLSYMS_LINEINFO``::
+
+ Call Trace:
+ <TASK>
+ dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:123)
+ do_syscall_64+0x82/0x190 (arch/x86/entry/common.c:52)
+ entry_SYSCALL_64_after_hwframe+0x76/0x7e
+
+Note that assembly routines (such as ``entry_SYSCALL_64_after_hwframe``) are
+not annotated because they lack DWARF debug information.
+
+Memory Overhead
+===============
+
+The lineinfo tables are stored in ``.rodata`` and typically add approximately
+44 MiB to the kernel image for a standard configuration (~4.6 million DWARF
+line entries, ~10 bytes per entry after deduplication).
+
+Known Limitations
+=================
+
+- **vmlinux only**: Only symbols in the core kernel image are annotated.
+ Module symbols are not covered.
+- **4 GiB offset limit**: Address offsets from ``_text`` are stored as 32-bit
+ values. Entries beyond 4 GiB from ``_text`` are skipped at build time with
+ a warning.
+- **65535 file limit**: Source file IDs are stored as 16-bit values. Builds
+ with more than 65535 unique source files will fail with an error.
+- **No assembly annotations**: Functions implemented in assembly that lack
+ DWARF ``.debug_line`` data are not annotated.
diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c2..f061e69b6e32a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13728,6 +13728,12 @@ S: Maintained
F: Documentation/hwmon/k8temp.rst
F: drivers/hwmon/k8temp.c
+KALLSYMS LINEINFO
+M: Sasha Levin <sashal@kernel.org>
+S: Maintained
+F: Documentation/admin-guide/kallsyms-lineinfo.rst
+F: scripts/gen_lineinfo.c
+
KASAN
M: Andrey Ryabinin <ryabinin.a.a@gmail.com>
R: Alexander Potapenko <glider@google.com>
diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h
index d5dd54c53ace6..e1d00e1373779 100644
--- a/include/linux/kallsyms.h
+++ b/include/linux/kallsyms.h
@@ -16,10 +16,19 @@
#include <asm/sections.h>
#define KSYM_NAME_LEN 512
+
+#ifdef CONFIG_KALLSYMS_LINEINFO
+/* Extra space for " (path/to/file.c:12345)" suffix */
+#define KSYM_LINEINFO_LEN 128
+#else
+#define KSYM_LINEINFO_LEN 0
+#endif
+
#define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s %s]") + \
(KSYM_NAME_LEN - 1) + \
2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + \
- (BUILD_ID_SIZE_MAX * 2) + 1)
+ (BUILD_ID_SIZE_MAX * 2) + 1 + \
+ KSYM_LINEINFO_LEN)
struct cred;
struct module;
@@ -96,6 +105,19 @@ extern int sprint_backtrace_build_id(char *buffer, unsigned long address);
int lookup_symbol_name(unsigned long addr, char *symname);
+#ifdef CONFIG_KALLSYMS_LINEINFO
+bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
+ const char **file, unsigned int *line);
+#else
+static inline bool kallsyms_lookup_lineinfo(unsigned long addr,
+ unsigned long sym_start,
+ const char **file,
+ unsigned int *line)
+{
+ return false;
+}
+#endif
+
#else /* !CONFIG_KALLSYMS */
static inline unsigned long kallsyms_lookup_name(const char *name)
@@ -164,6 +186,14 @@ static inline int kallsyms_on_each_match_symbol(int (*fn)(void *, unsigned long)
{
return -EOPNOTSUPP;
}
+
+static inline bool kallsyms_lookup_lineinfo(unsigned long addr,
+ unsigned long sym_start,
+ const char **file,
+ unsigned int *line)
+{
+ return false;
+}
#endif /*CONFIG_KALLSYMS*/
static inline void print_ip_sym(const char *loglvl, unsigned long ip)
diff --git a/init/Kconfig b/init/Kconfig
index b55deae9256c7..c39f27e6393a8 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2050,6 +2050,26 @@ config KALLSYMS_ALL
Say N unless you really need all symbols, or kernel live patching.
+config KALLSYMS_LINEINFO
+ bool "Embed source file:line information in stack traces"
+ depends on KALLSYMS && DEBUG_INFO
+ help
+ Embeds an address-to-source-line mapping table in the kernel
+ image so that stack traces directly include file:line information,
+ similar to what scripts/decode_stacktrace.sh provides but without
+ needing external tools or a vmlinux with debug info at runtime.
+
+ When enabled, stack traces will look like:
+
+ kmem_cache_alloc_noprof+0x60/0x630 (mm/slub.c:3456)
+ anon_vma_clone+0x2ed/0xcf0 (mm/rmap.c:412)
+
+ This requires elfutils (libdw-dev/elfutils-devel) on the build host.
+ Adds approximately 44MB to a typical kernel image (10 bytes per
+ DWARF line-table entry, ~4.6M entries for a typical config).
+
+ 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 aec2f06858afd..c94d8f332c5df 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -467,6 +467,54 @@ static int append_buildid(char *buffer, const char *modname,
#endif /* CONFIG_STACKTRACE_BUILD_ID */
+#ifdef CONFIG_KALLSYMS_LINEINFO
+bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
+ const char **file, unsigned int *line)
+{
+ unsigned long long raw_offset;
+ unsigned int offset, low, high, mid, file_id;
+
+ if (!lineinfo_num_entries)
+ return false;
+
+ /* Compute offset from _text */
+ 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 largest entry <= offset */
+ low = 0;
+ high = lineinfo_num_entries;
+ while (low < high) {
+ mid = low + (high - low) / 2;
+ if (lineinfo_addrs[mid] <= offset)
+ low = mid + 1;
+ else
+ high = mid;
+ }
+
+ if (low == 0)
+ return false;
+ low--;
+
+ file_id = lineinfo_file_ids[low];
+ *line = lineinfo_lines[low];
+
+ if (file_id >= lineinfo_num_files)
+ return false;
+
+ if (lineinfo_file_offsets[file_id] >= lineinfo_filenames_size)
+ return false;
+
+ *file = &lineinfo_filenames[lineinfo_file_offsets[file_id]];
+ return true;
+}
+#endif /* CONFIG_KALLSYMS_LINEINFO */
+
/* 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)
@@ -497,6 +545,19 @@ static int __sprint_symbol(char *buffer, unsigned long address,
len += sprintf(buffer + len, "]");
}
+#ifdef CONFIG_KALLSYMS_LINEINFO
+ if (!modname) {
+ const char *li_file;
+ unsigned int li_line;
+ unsigned long sym_start = address - offset;
+
+ if (kallsyms_lookup_lineinfo(address, sym_start,
+ &li_file, &li_line))
+ len += snprintf(buffer + len, KSYM_SYMBOL_LEN - len,
+ " (%s:%u)", li_file, li_line);
+ }
+#endif
+
return len;
}
diff --git a/kernel/kallsyms_internal.h b/kernel/kallsyms_internal.h
index 81a867dbe57d4..982557aeff28d 100644
--- a/kernel/kallsyms_internal.h
+++ b/kernel/kallsyms_internal.h
@@ -15,4 +15,15 @@ extern const u16 kallsyms_token_index[];
extern const unsigned int kallsyms_markers[];
extern const u8 kallsyms_seqs_of_names[];
+#ifdef CONFIG_KALLSYMS_LINEINFO
+extern const u32 lineinfo_num_entries;
+extern const u32 lineinfo_addrs[];
+extern const u16 lineinfo_file_ids[];
+extern const u32 lineinfo_lines[];
+extern const u32 lineinfo_num_files;
+extern const u32 lineinfo_file_offsets[];
+extern const u32 lineinfo_filenames_size;
+extern const char lineinfo_filenames[];
+#endif
+
#endif // LINUX_KALLSYMS_INTERNAL_H_
diff --git a/scripts/.gitignore b/scripts/.gitignore
index 4215c2208f7e4..e175714c18b61 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
/asn1_compiler
+/gen_lineinfo
/gen_packed_field_checks
/generate_rust_target
/insert-sys-cert
diff --git a/scripts/Makefile b/scripts/Makefile
index 0941e5ce7b575..ffe89875b3295 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -4,6 +4,7 @@
# the kernel for the build process.
hostprogs-always-$(CONFIG_KALLSYMS) += kallsyms
+hostprogs-always-$(CONFIG_KALLSYMS_LINEINFO) += gen_lineinfo
hostprogs-always-$(BUILD_C_RECORDMCOUNT) += recordmcount
hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable
hostprogs-always-$(CONFIG_ASN1) += asn1_compiler
@@ -36,6 +37,8 @@ HOSTLDLIBS_sorttable = -lpthread
HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include
HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
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)
ifdef CONFIG_UNWINDER_ORC
ifeq ($(ARCH),x86_64)
diff --git a/scripts/gen_lineinfo.c b/scripts/gen_lineinfo.c
new file mode 100644
index 0000000000000..37d5e84971be4
--- /dev/null
+++ b/scripts/gen_lineinfo.c
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * gen_lineinfo.c - Generate address-to-source-line lookup tables from DWARF
+ *
+ * Copyright (C) 2026 Sasha Levin <sashal@kernel.org>
+ *
+ * Reads DWARF .debug_line from a vmlinux ELF file and outputs an assembly
+ * file containing sorted lookup tables that the kernel uses to annotate
+ * stack traces with source file:line information.
+ *
+ * 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>
+
+static unsigned int skipped_overflow;
+
+struct line_entry {
+ unsigned int offset; /* offset from _text */
+ unsigned int file_id;
+ unsigned int line;
+};
+
+struct file_entry {
+ char *name;
+ unsigned int id;
+ unsigned int str_offset;
+};
+
+static struct line_entry *entries;
+static unsigned int num_entries;
+static unsigned int entries_capacity;
+
+static struct file_entry *files;
+static unsigned int num_files;
+static unsigned int files_capacity;
+
+#define FILE_HASH_BITS 13
+#define FILE_HASH_SIZE (1 << FILE_HASH_BITS)
+
+struct file_hash_entry {
+ const char *name;
+ unsigned int id;
+};
+
+static struct file_hash_entry file_hash[FILE_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 & (FILE_HASH_SIZE - 1);
+}
+
+static void add_entry(unsigned int offset, unsigned int file_id,
+ unsigned int line)
+{
+ if (num_entries >= entries_capacity) {
+ entries_capacity = entries_capacity ? entries_capacity * 2 : 65536;
+ entries = realloc(entries, entries_capacity * sizeof(*entries));
+ if (!entries) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ }
+ entries[num_entries].offset = offset;
+ entries[num_entries].file_id = file_id;
+ entries[num_entries].line = line;
+ num_entries++;
+}
+
+static unsigned int find_or_add_file(const char *name)
+{
+ unsigned int h = hash_str(name);
+
+ /* Open-addressing lookup with linear probing */
+ while (file_hash[h].name) {
+ if (!strcmp(file_hash[h].name, name))
+ return file_hash[h].id;
+ h = (h + 1) & (FILE_HASH_SIZE - 1);
+ }
+
+ if (num_files >= 65535) {
+ fprintf(stderr,
+ "gen_lineinfo: too many source files (%u > 65535)\n",
+ num_files);
+ exit(1);
+ }
+
+ if (num_files >= files_capacity) {
+ files_capacity = files_capacity ? files_capacity * 2 : 4096;
+ files = realloc(files, files_capacity * sizeof(*files));
+ if (!files) {
+ fprintf(stderr, "out of memory\n");
+ exit(1);
+ }
+ }
+ files[num_files].name = strdup(name);
+ files[num_files].id = num_files;
+
+ /* Insert into hash table (points to files[] entry) */
+ file_hash[h].name = files[num_files].name;
+ file_hash[h].id = num_files;
+
+ num_files++;
+ return num_files - 1;
+}
+
+/*
+ * Well-known top-level directories in the kernel source tree.
+ * Used as a fallback to recover relative paths from absolute DWARF paths
+ * when comp_dir doesn't match (e.g. O= out-of-tree builds where comp_dir
+ * is the build directory but source paths point into the source tree).
+ */
+static const char * const kernel_dirs[] = {
+ "arch/", "block/", "certs/", "crypto/", "drivers/", "fs/",
+ "include/", "init/", "io_uring/", "ipc/", "kernel/", "lib/",
+ "mm/", "net/", "rust/", "samples/", "scripts/", "security/",
+ "sound/", "tools/", "usr/", "virt/",
+};
+
+/*
+ * Strip a filename to a kernel-relative path.
+ *
+ * For absolute paths, strip the comp_dir prefix (from DWARF) to get
+ * a kernel-tree-relative path. When that fails (e.g. O= builds where
+ * comp_dir is the build directory), scan for a well-known kernel
+ * top-level directory name in the path to recover the relative path.
+ * Fall back to the basename as a last resort.
+ *
+ * For relative paths (common in modules), libdw may produce a bogus
+ * doubled path like "net/foo/bar.c/net/foo/bar.c" due to ET_REL DWARF
+ * quirks. Detect and strip such duplicates.
+ */
+static const char *make_relative(const char *path, const char *comp_dir)
+{
+ const char *p;
+
+ /* If already relative, use as-is */
+ if (path[0] != '/')
+ return path;
+
+ /* comp_dir from DWARF is the most reliable method */
+ if (comp_dir) {
+ size_t len = strlen(comp_dir);
+
+ if (!strncmp(path, comp_dir, len) && path[len] == '/') {
+ const char *rel = path + len + 1;
+
+ /*
+ * If comp_dir pointed to a subdirectory
+ * (e.g. arch/parisc/kernel) rather than
+ * the tree root, stripping it leaves a
+ * bare filename. Fall through to the
+ * kernel_dirs scan so we recover the full
+ * relative path instead.
+ */
+ if (strchr(rel, '/'))
+ return rel;
+ }
+
+ /*
+ * comp_dir prefix didn't help — either it didn't match
+ * or it was too specific and left a bare filename.
+ * Scan for a known kernel top-level directory component
+ * to find where the relative path starts. This handles
+ * O= builds and arches where comp_dir is a subdirectory.
+ */
+ for (p = path + 1; *p; p++) {
+ if (*(p - 1) == '/') {
+ for (unsigned int i = 0; i < sizeof(kernel_dirs) /
+ sizeof(kernel_dirs[0]); i++) {
+ if (!strncmp(p, kernel_dirs[i],
+ strlen(kernel_dirs[i])))
+ return p;
+ }
+ }
+ }
+
+ /* Fall back to basename */
+ p = strrchr(path, '/');
+ return p ? p + 1 : path;
+ }
+
+ /* Fall back to basename */
+ p = strrchr(path, '/');
+ return p ? p + 1 : path;
+}
+
+static int compare_entries(const void *a, const void *b)
+{
+ const struct line_entry *ea = a;
+ const struct line_entry *eb = b;
+
+ if (ea->offset != eb->offset)
+ return ea->offset < eb->offset ? -1 : 1;
+ if (ea->file_id != eb->file_id)
+ return ea->file_id < eb->file_id ? -1 : 1;
+ if (ea->line != eb->line)
+ return ea->line < eb->line ? -1 : 1;
+ return 0;
+}
+
+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 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;
+ Dwarf_Lines *lines;
+ size_t nlines;
+ Dwarf_Attribute attr;
+ const char *comp_dir = NULL;
+
+ if (!dwarf_offdie(dwarf, off + hdr_size, &cudie))
+ goto next;
+
+ if (dwarf_attr(&cudie, DW_AT_comp_dir, &attr))
+ comp_dir = dwarf_formstring(&attr);
+
+ if (dwarf_getsrclines(&cudie, &lines, &nlines) != 0)
+ goto next;
+
+ for (size_t i = 0; i < nlines; i++) {
+ Dwarf_Line *line = dwarf_onesrcline(lines, i);
+ Dwarf_Addr addr;
+ const char *src;
+ const char *rel;
+ unsigned int file_id, loffset;
+ int lineno;
+
+ if (!line)
+ continue;
+
+ if (dwarf_lineaddr(line, &addr) != 0)
+ continue;
+ if (dwarf_lineno(line, &lineno) != 0)
+ continue;
+ if (lineno == 0)
+ continue;
+
+ src = dwarf_linesrc(line, NULL, NULL);
+ if (!src)
+ continue;
+
+ if (addr < text_addr)
+ continue;
+
+ {
+ unsigned long long raw_offset = addr - text_addr;
+
+ if (raw_offset > UINT_MAX) {
+ skipped_overflow++;
+ continue;
+ }
+ loffset = (unsigned int)raw_offset;
+ }
+
+ rel = make_relative(src, comp_dir);
+ file_id = find_or_add_file(rel);
+
+ add_entry(loffset, file_id, (unsigned int)lineno);
+ }
+next:
+ off = next_off;
+ }
+}
+
+static void deduplicate(void)
+{
+ unsigned int i, j;
+
+ if (num_entries < 2)
+ return;
+
+ /* Sort by offset, then file_id, then line for stability */
+ qsort(entries, num_entries, sizeof(*entries), compare_entries);
+
+ /*
+ * Remove duplicate entries:
+ * - Same offset: keep first (deterministic from stable sort keys)
+ * - Same file:line as previous kept entry: redundant for binary
+ * search -- any address between them resolves to the earlier one
+ */
+ j = 0;
+ for (i = 1; i < num_entries; i++) {
+ if (entries[i].offset == entries[j].offset)
+ continue;
+ if (entries[i].file_id == entries[j].file_id &&
+ entries[i].line == entries[j].line)
+ continue;
+ j++;
+ if (j != i)
+ entries[j] = entries[i];
+ }
+ num_entries = j + 1;
+}
+
+static void compute_file_offsets(void)
+{
+ unsigned int offset = 0;
+
+ for (unsigned int i = 0; i < num_files; i++) {
+ files[i].str_offset = offset;
+ offset += strlen(files[i].name) + 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)
+{
+ printf("/* SPDX-License-Identifier: GPL-2.0 */\n");
+ printf("/*\n");
+ printf(" * Automatically generated by scripts/gen_lineinfo\n");
+ printf(" * Do not edit.\n");
+ printf(" */\n\n");
+
+ printf("\t.section .rodata, \"a\"\n\n");
+
+ /* Number of entries */
+ printf("\t.globl lineinfo_num_entries\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_num_entries:\n");
+ printf("\t.long %u\n\n", num_entries);
+
+ /* Number of files */
+ printf("\t.globl lineinfo_num_files\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_num_files:\n");
+ printf("\t.long %u\n\n", num_files);
+
+ /* Sorted address offsets from _text */
+ printf("\t.globl lineinfo_addrs\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_addrs:\n");
+ for (unsigned int i = 0; i < num_entries; i++)
+ printf("\t.long 0x%x\n", entries[i].offset);
+ printf("\n");
+
+ /* File IDs, parallel to addrs (u16 -- supports up to 65535 files) */
+ printf("\t.globl lineinfo_file_ids\n");
+ printf("\t.balign 2\n");
+ printf("lineinfo_file_ids:\n");
+ for (unsigned int i = 0; i < num_entries; i++)
+ printf("\t.short %u\n", entries[i].file_id);
+ printf("\n");
+
+ /* Line numbers, parallel to addrs */
+ printf("\t.globl lineinfo_lines\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_lines:\n");
+ for (unsigned int i = 0; i < num_entries; i++)
+ printf("\t.long %u\n", entries[i].line);
+ printf("\n");
+
+ /* File string offset table */
+ printf("\t.globl lineinfo_file_offsets\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_file_offsets:\n");
+ for (unsigned int i = 0; i < num_files; i++)
+ printf("\t.long %u\n", files[i].str_offset);
+ printf("\n");
+
+ /* Filenames size */
+ {
+ unsigned int fsize = 0;
+
+ for (unsigned int i = 0; i < num_files; i++)
+ fsize += strlen(files[i].name) + 1;
+ printf("\t.globl lineinfo_filenames_size\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_filenames_size:\n");
+ printf("\t.long %u\n\n", fsize);
+ }
+
+ /* Concatenated NUL-terminated filenames */
+ printf("\t.globl lineinfo_filenames\n");
+ printf("lineinfo_filenames:\n");
+ for (unsigned int i = 0; i < num_files; i++)
+ print_escaped_asciz(files[i].name);
+ 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>\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);
+
+ if (skipped_overflow)
+ fprintf(stderr,
+ "lineinfo: warning: %u entries skipped (offset > 4 GiB from _text)\n",
+ skipped_overflow);
+
+ deduplicate();
+ compute_file_offsets();
+
+ fprintf(stderr, "lineinfo: %u entries, %u files\n",
+ num_entries, num_files);
+
+ output_assembly();
+
+ dwarf_end(dwarf);
+ elf_end(elf);
+ close(fd);
+
+ /* Cleanup */
+ free(entries);
+ for (unsigned int i = 0; i < num_files; i++)
+ free(files[i].name);
+ free(files);
+
+ return 0;
+}
diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index 37d5c095ad22a..42662c4fbc6c9 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -78,6 +78,17 @@ static char *sym_name(const struct sym_entry *s)
static bool is_ignored_symbol(const char *name, char type)
{
+ /* Ignore lineinfo symbols for kallsyms pass stability */
+ static const char * const lineinfo_syms[] = {
+ "lineinfo_addrs",
+ "lineinfo_file_ids",
+ "lineinfo_file_offsets",
+ "lineinfo_filenames",
+ "lineinfo_lines",
+ "lineinfo_num_entries",
+ "lineinfo_num_files",
+ };
+
if (type == 'u' || type == 'n')
return true;
@@ -90,6 +101,11 @@ static bool is_ignored_symbol(const char *name, char type)
return true;
}
+ for (size_t i = 0; i < ARRAY_SIZE(lineinfo_syms); i++) {
+ if (!strcmp(name, lineinfo_syms[i]))
+ return true;
+ }
+
return false;
}
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index f99e196abeea4..9222659806700 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} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}
+ ${kallsymso} ${lineinfo_o} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}
}
# Create ${2}.o file with all symbols from the ${1} object file
@@ -129,6 +129,26 @@ kallsyms()
kallsymso=${2}.o
}
+# Generate lineinfo tables from DWARF debug info in a temporary vmlinux.
+# ${1} - temporary vmlinux with debug info
+# Output: sets lineinfo_o to the generated .o file
+gen_lineinfo()
+{
+ info LINEINFO .tmp_lineinfo.S
+ if ! scripts/gen_lineinfo "${1}" > .tmp_lineinfo.S; then
+ echo >&2 "Failed to generate lineinfo from ${1}"
+ echo >&2 "Try to disable CONFIG_KALLSYMS_LINEINFO"
+ exit 1
+ fi
+
+ info AS .tmp_lineinfo.o
+ ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \
+ ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \
+ -c -o .tmp_lineinfo.o .tmp_lineinfo.S
+
+ lineinfo_o=.tmp_lineinfo.o
+}
+
# Perform kallsyms for the given temporary vmlinux.
sysmap_and_kallsyms()
{
@@ -155,6 +175,7 @@ sorttable()
cleanup()
{
rm -f .btf.*
+ rm -f .tmp_lineinfo.*
rm -f .tmp_vmlinux.nm-sort
rm -f System.map
rm -f vmlinux
@@ -183,6 +204,7 @@ fi
btf_vmlinux_bin_o=
btfids_vmlinux=
kallsymso=
+lineinfo_o=
strip_debug=
generate_map=
@@ -198,10 +220,48 @@ if is_enabled CONFIG_KALLSYMS; then
kallsyms .tmp_vmlinux0.syms .tmp_vmlinux0.kallsyms
fi
+if is_enabled CONFIG_KALLSYMS_LINEINFO; then
+ # Generate a dummy empty lineinfo object for the initial link,
+ # same pattern as the dummy kallsyms above. The real lineinfo
+ # is generated from .tmp_vmlinux1 after it has been linked with
+ # debug info.
+ cat > .tmp_lineinfo.S <<'EOAS'
+ .section .rodata, "a"
+ .globl lineinfo_num_entries
+ .balign 4
+lineinfo_num_entries:
+ .long 0
+ .globl lineinfo_num_files
+ .balign 4
+lineinfo_num_files:
+ .long 0
+ .globl lineinfo_addrs
+lineinfo_addrs:
+ .globl lineinfo_file_ids
+lineinfo_file_ids:
+ .globl lineinfo_lines
+lineinfo_lines:
+ .globl lineinfo_file_offsets
+lineinfo_file_offsets:
+ .globl lineinfo_filenames_size
+ .balign 4
+lineinfo_filenames_size:
+ .long 0
+ .globl lineinfo_filenames
+lineinfo_filenames:
+EOAS
+ ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \
+ ${KBUILD_AFLAGS} ${KBUILD_AFLAGS_KERNEL} \
+ -c -o .tmp_lineinfo.o .tmp_lineinfo.S
+ lineinfo_o=.tmp_lineinfo.o
+fi
+
if is_enabled CONFIG_KALLSYMS || is_enabled CONFIG_DEBUG_INFO_BTF; then
- # The kallsyms linking does not need debug symbols, but the BTF does.
- if ! is_enabled CONFIG_DEBUG_INFO_BTF; then
+ # The kallsyms linking does not need debug symbols, but BTF and
+ # lineinfo generation do.
+ if ! is_enabled CONFIG_DEBUG_INFO_BTF &&
+ ! is_enabled CONFIG_KALLSYMS_LINEINFO; then
strip_debug=1
fi
@@ -219,6 +279,10 @@ if is_enabled CONFIG_DEBUG_INFO_BTF; then
btfids_vmlinux=.tmp_vmlinux1.BTF_ids
fi
+if is_enabled CONFIG_KALLSYMS_LINEINFO; then
+ gen_lineinfo .tmp_vmlinux1
+fi
+
if is_enabled CONFIG_KALLSYMS; then
# kallsyms support
--
2.51.0
^ permalink raw reply related
* [PATCH v2 0/4] kallsyms: embed source file:line info in kernel stack traces
From: Sasha Levin @ 2026-03-07 17:20 UTC (permalink / raw)
To: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley
Cc: Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Petr Pavlu,
Daniel Gomez, Greg KH, Petr Mladek, Steven Rostedt, Kees Cook,
Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka, linux-kernel,
linux-kbuild, linux-modules, linux-doc, deller, rdunlap,
laurent.pinchart, Sasha Levin
This series adds CONFIG_KALLSYMS_LINEINFO, which embeds source file:line
information directly in the kernel image so that stack traces annotate
every frame with the originating source location - no external tools, no
debug symbols at runtime, and safe to use in NMI/panic context.
Motivation
==========
The recent "slowly decommission bugzilla?" thread [1] surfaced a recurring
problem: when users encounter kernel crashes they see stack traces like
`func+0x1ec/0x240` but have no way to identify which subsystem or
maintainer to contact. Richard Weinberger proposed building a database
mapping symbols to source files using nm/DWARF. Linus pointed to
scripts/decode_stacktrace.sh as the existing solution. But as the
discussion progressed, it became clear that decode_stacktrace.sh has
significant practical barriers that prevent it from being useful in the
common case.
Problems with scripts/decode_stacktrace.sh
==========================================
- Requires debug symbols: the script needs vmlinux with DWARF debug info.
Many distros don't retain debug symbols for older or security kernels,
and even when available, asking users to obtain matching debuginfo
packages is a significant hurdle.
- Requires toolchain: users need addr2line and nm installed.
- Version-matching requirement: debug symbols must exactly match the
running kernel binary.
What this series does
=====================
Patch 1: CONFIG_KALLSYMS_LINEINFO
At build time, a host tool (scripts/gen_lineinfo) reads DWARF
.debug_line from vmlinux, extracts address-to-file:line mappings,
and embeds them as sorted lookup tables in .rodata. At runtime,
kallsyms_lookup_lineinfo() binary-searches the table and
__sprint_symbol() appends "(file:line)" to each stack frame.
NMI/panic-safe (no locks, no allocations), KASLR-compatible.
Patch 2: CONFIG_KALLSYMS_LINEINFO_MODULES
Extends lineinfo to loadable modules. Each .ko gets a .mod_lineinfo
section embedded at build time. The module loader picks it up at load
time. Same zero-allocation, NMI-safe lookup.
Patch 3: delta compression
Block-indexed delta-encoding with ULEB128 varints, implementing
the approach suggested by Juergen Gross in the RFC review. Reduces
overhead from ~44 MiB to ~11 MiB (~3.7 bytes/entry), addressing the
primary size concern from the RFC.
Patch 4: KUnit tests
New in v2. 30 KUnit tests covering the lineinfo lookup paths,
delta-decode logic, boundary conditions, and integration with the
backtrace formatting APIs.
Example output
==============
[ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
[ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
[ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
[ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
[ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
[ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
[ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
[ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
[ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
[ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
Size impact
===========
Measured with a Debian kernel config:
bzImage: +3.6 MiB (14 MiB -> 18 MiB, +26%)
Runtime memory: +5.9 MiB (text+data+bss)
Code overhead: +5.0 KiB (.text, lookup functions only)
Data overhead: +5.9 MiB (.data, lineinfo tables)
Lineinfo data breakdown:
lineinfo_data (delta-compressed): 5,728 KiB (97%)
lineinfo_block_addrs: 99 KiB
lineinfo_block_offsets: 99 KiB
lineinfo_filenames: 111 KiB
lineinfo_file_offsets: 17 KiB
The ~5.9 MiB is after 2.7x delta compression; uncompressed would be
~16 MiB. This is a fraction of the cost of shipping full DWARF debug
info (hundreds of MiB), which distros must store and serve for every
kernel version.
For distros, maintaining debug symbol repositories is expensive:
storage, mirrors, and CDN bandwidth for hundreds of MiB per kernel
build add up quickly. A ~5.9 MiB increase in the kernel image itself
is a modest cost that eliminates the need for users to find, download,
and version-match debuginfo packages just to make a crash report
useful.
For developers, the file:line annotations appear immediately in crash
traces - no post-processing with decode_stacktrace.sh needed.
Changes since v1
================
- Fix path stripping regression on architectures where DWARF comp_dir
is a subdirectory (e.g. arch/parisc/kernel) rather than the source
tree root: paths now correctly show "kernel/traps.c:212" instead of
bare "traps.c:212". Added kernel_dirs[] fallback scan and bare-
filename recovery via comp_dir. (Reported by Helge Deller)
- Fix RST heading: overline/underline must be at least as long as the
heading text in kallsyms-lineinfo.rst. (Reported by Randy Dunlap)
- Fix MAINTAINERS alphabetical ordering: move KALLSYMS LINEINFO entry
before KASAN. (Reported by Randy Dunlap)
- Fix arch-portability of .debug_line relocation handling: replace
hardcoded R_X86_64_32 with r_type_abs32() supporting x86, arm,
arm64, riscv, s390, mips, ppc, loongarch, and parisc.
- Fix vmlinux compressed-path data_end for the last block: use
lineinfo_data_size instead of UINT_MAX.
- Add file_offsets[] and filenames_size bounds checks in vmlinux
lookup path (the module path already had them).
- Add alignment padding for file_offsets[] in module .mod_lineinfo
binary format (data[] is variable-length u8, followed by u32[]).
- Remove sym_start cross-validation check that incorrectly rejected
valid lineinfo entries for assembly-adjacent functions.
- Add KUnit test suite (new patch 4/4): 30 tests covering vmlinux
lookup, module lookup, delta decode, boundary conditions, and
backtrace formatting integration.
Changes since RFC
=================
- Added module support (patch 2)
- Added delta compression (patch 3), reducing size from ~44 MiB to
~11 MiB, addressing the primary concern from RFC review
- Added documentation (Documentation/admin-guide/kallsyms-lineinfo.rst)
- Added MAINTAINERS entry
Link: https://lore.kernel.org/all/1786920159.1633.1772291851870.JavaMail.zimbra@nod.at/ [1]
Sasha Levin (4):
kallsyms: embed source file:line info in kernel stack traces
kallsyms: extend lineinfo to loadable modules
kallsyms: delta-compress lineinfo tables for ~2.7x size reduction
kallsyms: add KUnit tests for lineinfo feature
Documentation/admin-guide/index.rst | 1 +
.../admin-guide/kallsyms-lineinfo.rst | 97 ++
MAINTAINERS | 9 +
include/linux/kallsyms.h | 32 +-
include/linux/mod_lineinfo.h | 140 +++
include/linux/module.h | 19 +
init/Kconfig | 35 +
kernel/kallsyms.c | 136 +++
kernel/kallsyms_internal.h | 13 +
kernel/module/kallsyms.c | 153 +++
kernel/module/main.c | 4 +
lib/Kconfig.debug | 10 +
lib/tests/Makefile | 3 +
lib/tests/lineinfo_kunit.c | 772 +++++++++++++++
scripts/.gitignore | 1 +
scripts/Makefile | 4 +
scripts/Makefile.modfinal | 6 +
scripts/gen-mod-lineinfo.sh | 48 +
scripts/gen_lineinfo.c | 928 ++++++++++++++++++
scripts/kallsyms.c | 17 +
scripts/link-vmlinux.sh | 78 +-
21 files changed, 2502 insertions(+), 4 deletions(-)
create mode 100644 Documentation/admin-guide/kallsyms-lineinfo.rst
create mode 100644 include/linux/mod_lineinfo.h
create mode 100644 lib/tests/lineinfo_kunit.c
create mode 100755 scripts/gen-mod-lineinfo.sh
create mode 100644 scripts/gen_lineinfo.c
--
2.51.0
^ permalink raw reply
* Re: [PATCH v2 1/2] module: expose imported namespaces via sysfs
From: Peter Zijlstra @ 2026-03-07 9:30 UTC (permalink / raw)
To: Nicholas Sielicki
Cc: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Matthias Maennich, Jonathan Corbet, Shuah Khan,
Randy Dunlap, linux-modules, linux-doc, linux-kernel
In-Reply-To: <20260307090010.20828-2-linux@opensource.nslick.com>
On Sat, Mar 07, 2026 at 03:00:09AM -0600, Nicholas Sielicki wrote:
> Previously, the only way for userspace to inspect the symbol
> namespaces a module imports is to locate the .ko on disk and invoke
> modinfo(8) to decompress/parse the metadata. The kernel validated
> namespaces at load time, but it was otherwise discarded.
>
> Add /sys/module/*/import_ns to expose imported namespaces for
> currently loaded modules. The file contains one namespace per line and
> only exists for modules that import at least one namespace.
What I'm missing here is why users would care about this?
^ permalink raw reply
* [PATCH v2 2/2] docs: symbol-namespaces: mention sysfs attribute
From: Nicholas Sielicki @ 2026-03-07 9:00 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez
Cc: Sami Tolvanen, Aaron Tomlin, Matthias Maennich, Peter Zijlstra,
Jonathan Corbet, Shuah Khan, Randy Dunlap, linux-modules,
linux-doc, linux-kernel, Nicholas Sielicki
In-Reply-To: <20260307090010.20828-1-linux@opensource.nslick.com>
Reference the new /sys/module/*/import_ns sysfs attribute in docs as an
alternative to modinfo for inspecting imported namespaces of loaded
modules.
Signed-off-by: Nicholas Sielicki <linux@opensource.nslick.com>
---
Documentation/core-api/symbol-namespaces.rst | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Documentation/core-api/symbol-namespaces.rst b/Documentation/core-api/symbol-namespaces.rst
index 034898e81ba2..2304d5bffcce 100644
--- a/Documentation/core-api/symbol-namespaces.rst
+++ b/Documentation/core-api/symbol-namespaces.rst
@@ -114,6 +114,11 @@ inspected with modinfo::
import_ns: USB_STORAGE
[...]
+For modules that are currently loaded, imported namespaces are also available
+via sysfs::
+
+ $ cat /sys/module/ums_karma/import_ns
+ USB_STORAGE
It is advisable to add the MODULE_IMPORT_NS() statement close to other module
metadata definitions like MODULE_AUTHOR() or MODULE_LICENSE().
--
2.53.0
^ permalink raw reply related
* [PATCH v2 1/2] module: expose imported namespaces via sysfs
From: Nicholas Sielicki @ 2026-03-07 9:00 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez
Cc: Sami Tolvanen, Aaron Tomlin, Matthias Maennich, Peter Zijlstra,
Jonathan Corbet, Shuah Khan, Randy Dunlap, linux-modules,
linux-doc, linux-kernel, Nicholas Sielicki
In-Reply-To: <20260307090010.20828-1-linux@opensource.nslick.com>
Previously, the only way for userspace to inspect the symbol
namespaces a module imports is to locate the .ko on disk and invoke
modinfo(8) to decompress/parse the metadata. The kernel validated
namespaces at load time, but it was otherwise discarded.
Add /sys/module/*/import_ns to expose imported namespaces for
currently loaded modules. The file contains one namespace per line and
only exists for modules that import at least one namespace.
Signed-off-by: Nicholas Sielicki <linux@opensource.nslick.com>
---
Documentation/ABI/testing/sysfs-module | 9 ++++
include/linux/module.h | 1 +
kernel/module/main.c | 69 +++++++++++++++++++++++++-
3 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/sysfs-module b/Documentation/ABI/testing/sysfs-module
index 6bc9af6229f0..d5b7d19bd310 100644
--- a/Documentation/ABI/testing/sysfs-module
+++ b/Documentation/ABI/testing/sysfs-module
@@ -48,6 +48,15 @@ Contact: Kay Sievers <kay.sievers@vrfy.org>
Description: Show the initialization state(live, coming, going) of
the module.
+What: /sys/module/*/import_ns
+Date: January 2026
+KernelVersion: 7.1
+Contact: linux-modules@vger.kernel.org
+Description: List of symbol namespaces imported by this module via
+ MODULE_IMPORT_NS(). Each namespace appears on a separate line.
+ This file only exists for modules that import at least one
+ namespace.
+
What: /sys/module/*/taint
Date: Jan 2012
KernelVersion: 3.3
diff --git a/include/linux/module.h b/include/linux/module.h
index 14f391b186c6..60ed1c3e0ed9 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -413,6 +413,7 @@ struct module {
struct module_attribute *modinfo_attrs;
const char *version;
const char *srcversion;
+ const char *imported_namespaces;
struct kobject *holders_dir;
/* Exported symbols */
diff --git a/kernel/module/main.c b/kernel/module/main.c
index c3ce106c70af..53e421ff2ede 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -607,6 +607,36 @@ static const struct module_attribute modinfo_##field = { \
MODINFO_ATTR(version);
MODINFO_ATTR(srcversion);
+static void setup_modinfo_import_ns(struct module *mod, const char *s)
+{
+ mod->imported_namespaces = NULL;
+}
+
+static ssize_t show_modinfo_import_ns(const struct module_attribute *mattr,
+ struct module_kobject *mk, char *buffer)
+{
+ return sysfs_emit(buffer, "%s\n", mk->mod->imported_namespaces);
+}
+
+static int modinfo_import_ns_exists(struct module *mod)
+{
+ return mod->imported_namespaces != NULL;
+}
+
+static void free_modinfo_import_ns(struct module *mod)
+{
+ kfree(mod->imported_namespaces);
+ mod->imported_namespaces = NULL;
+}
+
+static const struct module_attribute modinfo_import_ns = {
+ .attr = { .name = "import_ns", .mode = 0444 },
+ .show = show_modinfo_import_ns,
+ .setup = setup_modinfo_import_ns,
+ .test = modinfo_import_ns_exists,
+ .free = free_modinfo_import_ns,
+};
+
static struct {
char name[MODULE_NAME_LEN];
char taints[MODULE_FLAGS_BUF_SIZE];
@@ -1058,6 +1088,7 @@ const struct module_attribute *const modinfo_attrs[] = {
&module_uevent,
&modinfo_version,
&modinfo_srcversion,
+ &modinfo_import_ns,
&modinfo_initstate,
&modinfo_coresize,
#ifdef CONFIG_ARCH_WANTS_MODULES_DATA_IN_VMALLOC
@@ -1760,11 +1791,43 @@ static void module_license_taint_check(struct module *mod, const char *license)
}
}
+static int copy_modinfo_import_ns(struct module *mod, struct load_info *info)
+{
+ char *ns;
+ size_t len, total_len = 0;
+ char *buf, *p;
+
+ for_each_modinfo_entry(ns, info, "import_ns")
+ total_len += strlen(ns) + 1;
+
+ if (!total_len) {
+ mod->imported_namespaces = NULL;
+ return 0;
+ }
+
+ buf = kmalloc(total_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ p = buf;
+ for_each_modinfo_entry(ns, info, "import_ns") {
+ len = strlen(ns);
+ memcpy(p, ns, len);
+ p += len;
+ *p++ = '\n';
+ }
+ /* Replace trailing newline with null terminator. */
+ *(p - 1) = '\0';
+
+ mod->imported_namespaces = buf;
+ return 0;
+}
+
static int setup_modinfo(struct module *mod, struct load_info *info)
{
const struct module_attribute *attr;
char *imported_namespace;
- int i;
+ int i, err;
for (i = 0; (attr = modinfo_attrs[i]); i++) {
if (attr->setup)
@@ -1783,6 +1846,10 @@ static int setup_modinfo(struct module *mod, struct load_info *info)
}
}
+ err = copy_modinfo_import_ns(mod, info);
+ if (err)
+ return err;
+
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v2 0/2] module: expose imported namespaces via sysfs
From: Nicholas Sielicki @ 2026-03-07 9:00 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez
Cc: Sami Tolvanen, Aaron Tomlin, Matthias Maennich, Peter Zijlstra,
Jonathan Corbet, Shuah Khan, Randy Dunlap, linux-modules,
linux-doc, linux-kernel, Nicholas Sielicki
Add /sys/module/*/import_ns to expose the symbol namespaces imported
by a loaded module.
Changes since v1:
- Simplified commit message to drop unnecessary/incorrect background
- Use .setup/.free callbacks in module_attribute to ensure
imported_namespaces is NULL-initialized before error paths and
NULL'd after kfree (Sami)
- Updated KernelVersion to 7.1 in docs for next merge window
^ permalink raw reply
* Re: [PATCH 2/8] rust: module_param: wire StringParam into the module! macro
From: Sami Tolvanen @ 2026-03-06 19:27 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
In-Reply-To: <20260226234736.428341-3-thepacketgeek@gmail.com>
On Thu, Feb 26, 2026 at 03:47:28PM -0800, Matthew Wood wrote:
> +/// Set a string module parameter from a string.
> +///
> +/// Similar to [`set_param`] but for [`StringParam`].
> +///
> +/// # Safety
> +///
> +/// Same requirements as [`set_param`].
> +unsafe extern "C" fn set_string_param(
> + val: *const c_char,
> + param: *const bindings::kernel_param,
> +) -> c_int {
> + if val.is_null() {
> + crate::pr_warn!("Null pointer passed to `module_param::set_string_param`");
> + return EINVAL.to_errno();
> + }
> +
> + crate::error::from_result(|| {
> + // SAFETY: val points to a valid C string from the kernel.
> + let cstr_param = unsafe { StringParam::from_ptr(val) };
> +
> + // SAFETY: By function safety requirements, param.arg points to our SetOnce<StringParam>.
> + let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<StringParam>>()) };
I do realize this matches set_param, and there's a good chance I
missed something when reading the macros, but doesn't arg actually
point to ModuleParamAccess<T> here? Since the struct is not repr(C),
isn't the compiler technically speaking allowed to reorder the
fields, which means SetOnce<T> might not actually be at offset 0?
> +
> + container
> + .populate(cstr_param)
> + .then_some(0)
> + .ok_or(kernel::error::code::EEXIST)
Does this mean the behavior for Rust modules differs from C modules if
the user specifies multiple instances of the same parameter? I believe
we just use the last value of the parameter instead of failing in C.
Sami
^ permalink raw reply
* Re: [PATCH] kthread: remove kthread_exit()
From: Linus Torvalds @ 2026-03-06 18:27 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Christian Brauner, linux-kernel, linux-modules, linux-nfs, bpf,
linux-kselftest, kunit-dev, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Chuck Lever,
Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
Trond Myklebust, Anna Schumaker, Alexei Starovoitov,
Daniel Borkmann, John Fastabend, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, Brendan Higgins,
David Gow, Rae Moar, Christian Loehle
In-Reply-To: <aaroReSCj1qXUeQb@infradead.org>
On Fri, 6 Mar 2026 at 06:44, Christoph Hellwig <hch@infradead.org> wrote:
>
> More a comment on the previous patch, but I think exporting do_exit
> which can work on any task is a really bad idea.
What is the advantage of a module only being able to do
kthread_exit(), and not able to do a regular do_exit()?
I think the only real advantage of having a "kthread_exit()" is that
it's a nicer name.
Because if that's the main issue, then I agree that "do_exit()" is
really not a great name, and it matches a very traditional "this is an
internal function" naming convention, and not typically a "this is
what random modules should use".
So kthread_exit() is a much better name, but it basically *has* to act
exactly like do_exit(), and adding a limitation to only work on
kthreads doesn't actually seem like an improvement.
Why make a function that is intentionally limited with no real
technical upside? It's not like there's any real reason why a module
couldn't call exit - we may not have exported it before, but we do
have code that looks like it *could* be a module that calls do_exit()
today.
For example, I'm looking at kernel/vhost_task.c, and the only users
are things that *are* modules, and it's not hugely obvious that
there's a big advantage to saying "that task handling has to be
built-in for those modules".
So my reaction is that "no, do_exit() is not a great name, but there's
no real technical upside to havign a separate kthread_exit()"
function.
If it's just about naming, maybe we could just unify it all and call
it "task_exit()" or something?
Linus
^ permalink raw reply
* Re: [PATCH 1/3] kallsyms: embed source file:line info in kernel stack traces
From: Helge Deller @ 2026-03-06 17:53 UTC (permalink / raw)
To: Randy Dunlap, Sasha Levin
Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley, Jonathan Corbet, Nathan Chancellor,
Nicolas Schier, Petr Pavlu, Daniel Gomez, Greg KH, Petr Mladek,
Steven Rostedt, Kees Cook, Peter Zijlstra, Thorsten Leemhuis,
Vlastimil Babka, linux-kernel, linux-kbuild, linux-modules,
linux-doc
In-Reply-To: <14977e29-cd76-424c-89c3-9f8c73186e27@infradead.org>
On 3/6/26 06:31, Randy Dunlap wrote:
> On 3/5/26 2:26 PM, Helge Deller wrote:
>> On 3/5/26 03:18, Sasha Levin wrote:
>>> On Wed, Mar 04, 2026 at 09:17:37PM +0100, Helge Deller wrote:
>>>> On 3/3/26 19:21, Sasha Levin wrote:
>>>>> Add CONFIG_KALLSYMS_LINEINFO, which embeds a compact address-to-line
>>>>> lookup table in the kernel image so stack traces directly print source
>>>>> file and line number information:
>>>>>
>>>>> root@localhost:~# echo c > /proc/sysrq-trigger
>>>>> [ 11.201987] sysrq: Trigger a crash
>>>>> [ 11.202831] Kernel panic - not syncing: sysrq triggered crash
>>>>> [ 11.206218] Call Trace:
>>>>> [ 11.206501] <TASK>
>>>>> [ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
>>>>> [ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
>>>>> [ 11.208565] ? __lock_acquire+0x465/0x2240 (kernel/locking/lockdep.c:4674)
>>>>> [ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
>>>>> [ 11.211873] ? find_held_lock+0x2b/0x80 (kernel/locking/lockdep.c:5350)
>>>>> [ 11.212597] ? lock_release+0xd3/0x300 (kernel/locking/lockdep.c:5535)
>>>>> [ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
>>>>> [ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
>>>>> [ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
>>>>> [ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
>>>>> [ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
>>>>> [ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
>>>>> [ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
>>>>> [ 11.223942] entry_SYSCALL_64_after_hwframe+0x77/0x7f (arch/x86/entry/entry_64.S:121)
>>>>
>>>> As mentioned in the other series, I really like this patch series.
>>>>
>>>> I tested this series again on the parisc architecture, and the relative
>>>> directories are now stripped with this version of your patch.
>>>> IIRC, the previous patch did show the subdirectory names.
>>>> [ 132.840382] Backtrace:
>>>> [ 132.840382] [<104254d8>] show_stack+0x50/0x64 (traps.c:212)
>>>> [ 132.840382] [<1041c0c8>] dump_stack_lvl+0x6c/0xa0 (dump_stack.c:122)
>>>> [ 132.840382] [<1041c118>] dump_stack+0x1c/0x2c (dump_stack.c:130)
>>>> [ 132.840382] [<10402218>] vpanic+0x154/0x344 (panic.c:550)
>>>> [ 132.840382] [<10402438>] panic+0x30/0x34 (panic.c:787)
>>>> [ 132.840382] [<10bebea8>] sysrq_handle_crash+0x30/0x34 (rcupdate.h:110)
>>>> [ 132.840382] [<10bec720>] __handle_sysrq+0xc0/0x1e4 (preempt.h:14)
>>>
>>> Ugh... Can you confirm that you've build this kernel with O=?
>>
>> Yes. Both -Os and -O2 do not show the relative path.
>
> Helge,
> I'm fairly sure that Sasha meant with O=build_dir_name,
> not -O for optimization levels.
Ah, ok.
Anyway, I checked again.
I did *not* used a "O=..." parameter in either case.
I recompiled again with the RFC patch, and here the relative paths show up again (even without O=):
root@debian:~# echo c > /proc/sysrq-trigger
[ 121.172011] sysrq: Trigger a crash
[ 121.173986] Kernel panic - not syncing: sysrq triggered crash
[ 121.176141] CPU: 1 UID: 0 PID: 382 Comm: bash Not tainted 7.0.0-rc2-32bit+ #2971 VOLUNTARY
[ 121.177041] Hardware name: 9000/778/B160L
[ 121.178092] Backtrace:
[ 121.178533] [<104254d8>] show_stack+0x50/0x64 (kernel/traps.c:212)
[ 121.181408] [<1041c0c8>] dump_stack_lvl+0x6c/0xa0 (lib/dump_stack.c:122)
[ 121.182145] [<1041c118>] dump_stack+0x1c/0x2c (lib/dump_stack.c:130)
[ 121.182779] [<10402218>] vpanic+0x154/0x344 (kernel/panic.c:552)
[ 121.182871] [<10402438>] panic+0x30/0x34 (kernel/panic.c:787)
[ 121.182871] [<10beb5a0>] sysrq_handle_crash+0x30/0x34 (drivers/tty/sysrq.c:154)
[ 121.182871] [<10bebe18>] __handle_sysrq+0xc0/0x1e4 (arch/parisc/include/asm/current.h:13)
[ 121.182871] [<10bec7c0>] write_sysrq_trigger+0x8c/0xcc (drivers/tty/sysrq.c:1223)
[ 121.182871] [<106ba460>] proc_reg_write+0xd0/0x10c (fs/proc/inode.c:343)
[ 121.182871] [<1060faf0>] vfs_write+0xb8/0x46c (fs/read_write.c:691)
[ 121.182871] [<1061005c>] ksys_write+0x78/0x118 (fs/read_write.c:741)
[ 121.182871] [<10610114>] sys_write+0x18/0x28 (fs/read_write.c:748)
[ 121.182871] [<10421334>] syscall_exit+0x0/0x10 (kernel/entry.S:1722)
Helge
>>> The RFC had a dirty dirty hack around how we turn these absolute paths into
>>> relative ones, but I tried to re-do it so no one would yell at me :)
>>
>> Seems it is needed...
^ permalink raw reply
* Re: [PATCH 6/8] rust: macros: add early_param support to module! macro
From: Petr Pavlu @ 2026-03-06 17:22 UTC (permalink / raw)
To: Matthew Wood
Cc: Miguel Ojeda, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Tamir Duberstein, David Gow,
José Expósito, linux-modules, rust-for-linux,
linux-kernel
In-Reply-To: <20260226234736.428341-7-thepacketgeek@gmail.com>
On 2/27/26 12:47 AM, Matthew Wood wrote:
> Allow module parameters to be registered as early boot command-line
> parameters via the existing __setup infrastructure.
>
> Add an optional `early_param` field to the parameter block in the
> module! macro. When specified, the macro generates:
>
> - A setup callback that calls ModuleParam::from_setup_arg() and
> stores the result via ModuleParamAccess::set_value().
> - A static string in .init.rodata with the command-line prefix.
> - An ObsKernelParam entry in the .init.setup section linking the
> two together.
The early_param() macro on the C side creates an obs_kernel_param entry
with early=1. However, this patch handles the new early_param setting in
Rust by creating an obs_kernel_param with early=0. Non-early
obs_kernel_params are processed by obsolete_checksetup() during the
parse_args("Booting kernel") call in start_kernel(), which also handles
regular kernel_params.
Actual early parameters are handled by parse_early_param(). Note that
the command line parsed in this case is marked as __initdata, so it
would not be available for the kernel's entire lifetime, as the previous
patches assume.
So I'm somewhat confused by this patch, or perhaps I misunderstood
something?
> diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
> index 0c36194d9971..83dcc89a425a 100644
> --- a/rust/macros/lib.rs
> +++ b/rust/macros/lib.rs
[...]
> + #[link_section = ".init.setup"]
> + #[used(compiler)]
> + static #setup_name: ::kernel::module_param::ObsKernelParam =
> + ::kernel::module_param::ObsKernelParam {
> + str_: #setup_str_name.as_ptr().cast(),
> + setup_func: ::core::option::Option::Some(#setup_fn_name),
> + early: 0,
> + };
--
Thanks,
Petr
^ permalink raw reply
* Re: [PATCH 1/3] kallsyms: embed source file:line info in kernel stack traces
From: Sasha Levin @ 2026-03-06 17:14 UTC (permalink / raw)
To: Petr Mladek
Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley, Jonathan Corbet, Nathan Chancellor,
Nicolas Schier, Petr Pavlu, Daniel Gomez, Greg KH, Steven Rostedt,
Kees Cook, Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka,
linux-kernel, linux-kbuild, linux-modules, linux-doc
In-Reply-To: <aasClESfxETxliLB@pathway.suse.cz>
On Fri, Mar 06, 2026 at 05:36:36PM +0100, Petr Mladek wrote:
>On Tue 2026-03-03 13:21:01, Sasha Levin wrote:
>> Add CONFIG_KALLSYMS_LINEINFO, which embeds a compact address-to-line
>> lookup table in the kernel image so stack traces directly print source
>> file and line number information:
>>
>> root@localhost:~# echo c > /proc/sysrq-trigger
>> [ 11.201987] sysrq: Trigger a crash
>> [ 11.202831] Kernel panic - not syncing: sysrq triggered crash
>> [ 11.206218] Call Trace:
>> [ 11.206501] <TASK>
>> [ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
>> [ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
>> [ 11.208565] ? __lock_acquire+0x465/0x2240 (kernel/locking/lockdep.c:4674)
>> [ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
>> [ 11.211873] ? find_held_lock+0x2b/0x80 (kernel/locking/lockdep.c:5350)
>> [ 11.212597] ? lock_release+0xd3/0x300 (kernel/locking/lockdep.c:5535)
>> [ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
>> [ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
>> [ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
>> [ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
>> [ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
>> [ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
>> [ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
>> [ 11.223942] entry_SYSCALL_64_after_hwframe+0x77/0x7f (arch/x86/entry/entry_64.S:121)
>>
>> --- a/include/linux/kallsyms.h
>> +++ b/include/linux/kallsyms.h
>> @@ -16,10 +16,19 @@
>> #include <asm/sections.h>
>>
>> #define KSYM_NAME_LEN 512
>> +
>> +#ifdef CONFIG_KALLSYMS_LINEINFO
>> +/* Extra space for " (path/to/file.c:12345)" suffix */
>> +#define KSYM_LINEINFO_LEN 128
>> +#else
>> +#define KSYM_LINEINFO_LEN 0
>> +#endif
>> +
>> #define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s %s]") + \
>
>I guess that this is used also in ftrace where there formatting
>is delayed. We might want:
>
> #define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s %s] (%s:%u)") + \
KSYM_LINEINFO_LEN already covers the full expansion of the path and line
number, not just the literal format characters. ftrace stores raw addresses and
formats via %pS at print time into a KSYM_SYMBOL_LEN-sized buffer, so there
shouldn't be an issue here.
>> (KSYM_NAME_LEN - 1) + \
>> 2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + \
>> - (BUILD_ID_SIZE_MAX * 2) + 1)
>> + (BUILD_ID_SIZE_MAX * 2) + 1 + \
>> + KSYM_LINEINFO_LEN)
>>
>> struct cred;
>> struct module;
>> --- a/kernel/kallsyms.c
>> +++ b/kernel/kallsyms.c
>> @@ -467,6 +467,62 @@ static int append_buildid(char *buffer, const char *modname,
>>
>> #endif /* CONFIG_STACKTRACE_BUILD_ID */
>>
>> +#ifdef CONFIG_KALLSYMS_LINEINFO
>> +bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
>> + const char **file, unsigned int *line)
>> +{
>> + unsigned long long raw_offset;
>> + unsigned int offset, low, high, mid, file_id;
>> + unsigned long line_addr;
>> +
>> + if (!lineinfo_num_entries)
>> + return false;
>> +
>> + /* Compute offset from _text */
>> + 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 largest entry <= offset */
>> + low = 0;
>> + high = lineinfo_num_entries;
>> + while (low < high) {
>> + mid = low + (high - low) / 2;
>> + if (lineinfo_addrs[mid] <= offset)
>> + low = mid + 1;
>> + else
>> + high = mid;
>> + }
>> +
>> + if (low == 0)
>> + return false;
>> + low--;
>> +
>> + /*
>> + * Validate that the matched lineinfo entry belongs to the same
>> + * symbol. Without this check, assembly routines or other
>> + * functions lacking DWARF data would inherit the file:line of
>> + * a preceding C function.
>> + */
>> + line_addr = (unsigned long)_text + lineinfo_addrs[low];
>> + if (line_addr < sym_start)
>> + return false;
>
>This is suspicious. The binary search does "low = mid + 1".
>I would expect that lineinfo_addrs[low] would point to
>a higher address when the exact match is not found.
>
>Anyway, I think that we should accept only the exact match and do:
>
> if (lineinfo_addrs[low] != offset)
> return false;
>
>Or do I miss something? (Friday evening here ;-)
Right, when there's no exact match, low ends up pointing to the next higher
entry. The table is sparse, with one entry per source-line transition, not per
instruction address. The correct result for a given PC is the nearest
preceding entry, so the code uses low - 1 in that case. Same semantics as
kallsyms symbol lookup.
>> + file_id = lineinfo_file_ids[low];
>> + *line = lineinfo_lines[low];
>> +
>> + if (file_id >= lineinfo_num_files)
>> + return false;
>> +
>> + *file = &lineinfo_filenames[lineinfo_file_offsets[file_id]];
>> + return true;
>> +}
>> +#endif /* CONFIG_KALLSYMS_LINEINFO */
>> +
>> /* 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)
>> @@ -497,6 +553,19 @@ static int __sprint_symbol(char *buffer, unsigned long address,
>> len += sprintf(buffer + len, "]");
>> }
>>
>> +#ifdef CONFIG_KALLSYMS_LINEINFO
>> + if (!modname) {
>> + const char *li_file;
>> + unsigned int li_line;
>> + unsigned long sym_start = address - offset;
>> +
>> + if (kallsyms_lookup_lineinfo(address, sym_start,
>> + &li_file, &li_line))
>> + len += snprintf(buffer + len, KSYM_SYMBOL_LEN - len,
>
>s/KSYM_SYMBOL_LEN/KSYM_LINEINFO_LEN/
KSYM_SYMBOL_LEN - len is the remaining capacity of the output buffer. len
tracks total bytes written (symbol + offset + module + buildid), which can
easily exceed 128.
>> + " (%s:%u)", li_file, li_line);
>> + }
>> +#endif
>> +
>> return len;
>> }
>
>
>I was rather curious how the code looked like and the mentioned things
>caught my eyes. And I focused on the kernel/kallsyms code.
>
>Unfortunately, I do not have time for a proper full review at the
>moment.
>
>The code seems to work. And it generates relative paths for me, for example:
>
>[ 305.678609] sysrq: Show backtrace of all active CPUs
>[ 305.680615] NMI backtrace for cpu 0
>[ 305.680620] CPU: 0 UID: 0 PID: 1540 Comm: bash Kdump: loaded Not tainted 7.0.0-rc2-default+ #561 PREEMPT(full) 0d0ba470fd9bf64113a65472ab47c033a2658d88
>[ 305.680626] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-2-g4f253b9b-prebuilt.qemu.org 04/01/2014
>[ 305.680628] Call Trace:
>[ 305.680631] <TASK>
>[ 305.680640] dump_stack_lvl+0x6c/0xa0 (lib/dump_stack.c:94)
>[ 305.680680] nmi_cpu_backtrace.cold+0x51/0x6a (lib/nmi_backtrace.c:113)
>[ 305.680689] ? __pfx_nmi_raise_cpu_backtrace+0x10/0x10
>[ 305.680702] nmi_trigger_cpumask_backtrace+0x113/0x130 (lib/nmi_backtrace.c:62)
>[ 305.680720] __handle_sysrq.cold+0x9b/0xde (drivers/tty/sysrq.c:611)
>[ 305.680734] write_sysrq_trigger+0x6a/0xb0 (drivers/tty/sysrq.c:1221)
>[ 305.680750] proc_reg_write+0x59/0xa0 (fs/proc/inode.c:330)
>[ 305.680763] vfs_write+0xd0/0x570 (fs/read_write.c:686)
>[ 305.680771] ? srso_alias_return_thunk+0x5/0xfbef5 (arch/x86/lib/retpoline.S:220)
>[ 305.680776] ? srso_alias_return_thunk+0x5/0xfbef5 (arch/x86/lib/retpoline.S:220)
>[ 305.680779] ? __lock_release.isra.0+0x1c9/0x300 (kernel/locking/lockdep.c:342)
>[ 305.680796] ? srso_alias_return_thunk+0x5/0xfbef5 (arch/x86/lib/retpoline.S:220)
>[ 305.680813] ksys_write+0x70/0xf0 (fs/read_write.c:738)
>[ 305.680826] do_syscall_64+0x11d/0x660 (arch/x86/entry/syscall_64.c:63)
>[ 305.680832] ? irqentry_exit+0x94/0x5f0 (./include/linux/irq-entry-common.h:298)
>[ 305.680846] entry_SYSCALL_64_after_hwframe+0x76/0x7e (arch/x86/entry/entry_64.S:121)
Thanks for the review and testing! Have a great weekend!
--
Thanks,
Sasha
^ permalink raw reply
* Re: [PATCH 1/3] kallsyms: embed source file:line info in kernel stack traces
From: Petr Mladek @ 2026-03-06 16:36 UTC (permalink / raw)
To: Sasha Levin
Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley, Jonathan Corbet, Nathan Chancellor,
Nicolas Schier, Petr Pavlu, Daniel Gomez, Greg KH, Steven Rostedt,
Kees Cook, Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka,
linux-kernel, linux-kbuild, linux-modules, linux-doc
In-Reply-To: <20260303182103.3523438-2-sashal@kernel.org>
On Tue 2026-03-03 13:21:01, Sasha Levin wrote:
> Add CONFIG_KALLSYMS_LINEINFO, which embeds a compact address-to-line
> lookup table in the kernel image so stack traces directly print source
> file and line number information:
>
> root@localhost:~# echo c > /proc/sysrq-trigger
> [ 11.201987] sysrq: Trigger a crash
> [ 11.202831] Kernel panic - not syncing: sysrq triggered crash
> [ 11.206218] Call Trace:
> [ 11.206501] <TASK>
> [ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
> [ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
> [ 11.208565] ? __lock_acquire+0x465/0x2240 (kernel/locking/lockdep.c:4674)
> [ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
> [ 11.211873] ? find_held_lock+0x2b/0x80 (kernel/locking/lockdep.c:5350)
> [ 11.212597] ? lock_release+0xd3/0x300 (kernel/locking/lockdep.c:5535)
> [ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
> [ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
> [ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
> [ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
> [ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
> [ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
> [ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
> [ 11.223942] entry_SYSCALL_64_after_hwframe+0x77/0x7f (arch/x86/entry/entry_64.S:121)
>
> --- a/include/linux/kallsyms.h
> +++ b/include/linux/kallsyms.h
> @@ -16,10 +16,19 @@
> #include <asm/sections.h>
>
> #define KSYM_NAME_LEN 512
> +
> +#ifdef CONFIG_KALLSYMS_LINEINFO
> +/* Extra space for " (path/to/file.c:12345)" suffix */
> +#define KSYM_LINEINFO_LEN 128
> +#else
> +#define KSYM_LINEINFO_LEN 0
> +#endif
> +
> #define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s %s]") + \
I guess that this is used also in ftrace where there formatting
is delayed. We might want:
#define KSYM_SYMBOL_LEN (sizeof("%s+%#lx/%#lx [%s %s] (%s:%u)") + \
> (KSYM_NAME_LEN - 1) + \
> 2*(BITS_PER_LONG*3/10) + (MODULE_NAME_LEN - 1) + \
> - (BUILD_ID_SIZE_MAX * 2) + 1)
> + (BUILD_ID_SIZE_MAX * 2) + 1 + \
> + KSYM_LINEINFO_LEN)
>
> struct cred;
> struct module;
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
> @@ -467,6 +467,62 @@ static int append_buildid(char *buffer, const char *modname,
>
> #endif /* CONFIG_STACKTRACE_BUILD_ID */
>
> +#ifdef CONFIG_KALLSYMS_LINEINFO
> +bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
> + const char **file, unsigned int *line)
> +{
> + unsigned long long raw_offset;
> + unsigned int offset, low, high, mid, file_id;
> + unsigned long line_addr;
> +
> + if (!lineinfo_num_entries)
> + return false;
> +
> + /* Compute offset from _text */
> + 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 largest entry <= offset */
> + low = 0;
> + high = lineinfo_num_entries;
> + while (low < high) {
> + mid = low + (high - low) / 2;
> + if (lineinfo_addrs[mid] <= offset)
> + low = mid + 1;
> + else
> + high = mid;
> + }
> +
> + if (low == 0)
> + return false;
> + low--;
> +
> + /*
> + * Validate that the matched lineinfo entry belongs to the same
> + * symbol. Without this check, assembly routines or other
> + * functions lacking DWARF data would inherit the file:line of
> + * a preceding C function.
> + */
> + line_addr = (unsigned long)_text + lineinfo_addrs[low];
> + if (line_addr < sym_start)
> + return false;
This is suspicious. The binary search does "low = mid + 1".
I would expect that lineinfo_addrs[low] would point to
a higher address when the exact match is not found.
Anyway, I think that we should accept only the exact match and do:
if (lineinfo_addrs[low] != offset)
return false;
Or do I miss something? (Friday evening here ;-)
> + file_id = lineinfo_file_ids[low];
> + *line = lineinfo_lines[low];
> +
> + if (file_id >= lineinfo_num_files)
> + return false;
> +
> + *file = &lineinfo_filenames[lineinfo_file_offsets[file_id]];
> + return true;
> +}
> +#endif /* CONFIG_KALLSYMS_LINEINFO */
> +
> /* 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)
> @@ -497,6 +553,19 @@ static int __sprint_symbol(char *buffer, unsigned long address,
> len += sprintf(buffer + len, "]");
> }
>
> +#ifdef CONFIG_KALLSYMS_LINEINFO
> + if (!modname) {
> + const char *li_file;
> + unsigned int li_line;
> + unsigned long sym_start = address - offset;
> +
> + if (kallsyms_lookup_lineinfo(address, sym_start,
> + &li_file, &li_line))
> + len += snprintf(buffer + len, KSYM_SYMBOL_LEN - len,
s/KSYM_SYMBOL_LEN/KSYM_LINEINFO_LEN/
> + " (%s:%u)", li_file, li_line);
> + }
> +#endif
> +
> return len;
> }
I was rather curious how the code looked like and the mentioned things
caught my eyes. And I focused on the kernel/kallsyms code.
Unfortunately, I do not have time for a proper full review at the
moment.
The code seems to work. And it generates relative paths for me, for example:
[ 305.678609] sysrq: Show backtrace of all active CPUs
[ 305.680615] NMI backtrace for cpu 0
[ 305.680620] CPU: 0 UID: 0 PID: 1540 Comm: bash Kdump: loaded Not tainted 7.0.0-rc2-default+ #561 PREEMPT(full) 0d0ba470fd9bf64113a65472ab47c033a2658d88
[ 305.680626] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.17.0-2-g4f253b9b-prebuilt.qemu.org 04/01/2014
[ 305.680628] Call Trace:
[ 305.680631] <TASK>
[ 305.680640] dump_stack_lvl+0x6c/0xa0 (lib/dump_stack.c:94)
[ 305.680680] nmi_cpu_backtrace.cold+0x51/0x6a (lib/nmi_backtrace.c:113)
[ 305.680689] ? __pfx_nmi_raise_cpu_backtrace+0x10/0x10
[ 305.680702] nmi_trigger_cpumask_backtrace+0x113/0x130 (lib/nmi_backtrace.c:62)
[ 305.680720] __handle_sysrq.cold+0x9b/0xde (drivers/tty/sysrq.c:611)
[ 305.680734] write_sysrq_trigger+0x6a/0xb0 (drivers/tty/sysrq.c:1221)
[ 305.680750] proc_reg_write+0x59/0xa0 (fs/proc/inode.c:330)
[ 305.680763] vfs_write+0xd0/0x570 (fs/read_write.c:686)
[ 305.680771] ? srso_alias_return_thunk+0x5/0xfbef5 (arch/x86/lib/retpoline.S:220)
[ 305.680776] ? srso_alias_return_thunk+0x5/0xfbef5 (arch/x86/lib/retpoline.S:220)
[ 305.680779] ? __lock_release.isra.0+0x1c9/0x300 (kernel/locking/lockdep.c:342)
[ 305.680796] ? srso_alias_return_thunk+0x5/0xfbef5 (arch/x86/lib/retpoline.S:220)
[ 305.680813] ksys_write+0x70/0xf0 (fs/read_write.c:738)
[ 305.680826] do_syscall_64+0x11d/0x660 (arch/x86/entry/syscall_64.c:63)
[ 305.680832] ? irqentry_exit+0x94/0x5f0 (./include/linux/irq-entry-common.h:298)
[ 305.680846] entry_SYSCALL_64_after_hwframe+0x76/0x7e (arch/x86/entry/entry_64.S:121)
HTH,
Petr
^ permalink raw reply
* Re: [PATCH] kthread: remove kthread_exit()
From: Christoph Hellwig @ 2026-03-06 14:44 UTC (permalink / raw)
To: Christian Brauner
Cc: linux-kernel, linux-modules, linux-nfs, bpf, linux-kselftest,
kunit-dev, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen, Aaron Tomlin, Chuck Lever, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Trond Myklebust,
Anna Schumaker, Alexei Starovoitov, Daniel Borkmann,
John Fastabend, Andrii Nakryiko, Martin KaFai Lau,
Eduard Zingerman, Song Liu, Yonghong Song, KP Singh,
Stanislav Fomichev, Hao Luo, Jiri Olsa, Brendan Higgins,
David Gow, Rae Moar, Christian Loehle, Linus Torvalds
In-Reply-To: <20260306-work-kernel-exit-v1-1-8f871f6281cb@kernel.org>
More a comment on the previous patch, but I think exporting do_exit
which can work on any task is a really bad idea. I'd much rather re-add
a kthread_exit wrapper that checks PF_KTHREAD first and only export
that, which would obsolete this patch.
^ permalink raw reply
* [PATCH] kthread: remove kthread_exit()
From: Christian Brauner @ 2026-03-06 14:07 UTC (permalink / raw)
To: linux-kernel, linux-modules, linux-nfs, bpf, linux-kselftest,
kunit-dev
Cc: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Chuck Lever, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, Trond Myklebust,
Anna Schumaker, Alexei Starovoitov, Daniel Borkmann,
John Fastabend, Andrii Nakryiko, Martin KaFai Lau,
Eduard Zingerman, Song Liu, Yonghong Song, KP Singh,
Stanislav Fomichev, Hao Luo, Jiri Olsa, Brendan Higgins,
David Gow, Rae Moar, Christian Loehle, Linus Torvalds,
Christian Brauner
In-Reply-To: <1ff1bce2-8bb4-463c-a631-16e14f4ea7e2@arm.com>
In 28aaa9c39945 ("kthread: consolidate kthread exit paths to prevent use-after-free")
we folded kthread_exit() into do_exit() when we fixed a nasty UAF bug.
We left kthread_exit() around as an alias to do_exit(). Remove it
completely.
Reported-by: Christian Loehle <christian.loehle@arm.com>
Link: https://lore.kernel.org/1ff1bce2-8bb4-463c-a631-16e14f4ea7e2@arm.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
include/linux/kthread.h | 1 -
include/linux/module.h | 2 +-
include/linux/sunrpc/svc.h | 2 +-
kernel/bpf/verifier.c | 1 -
kernel/kthread.c | 8 ++++----
kernel/module/main.c | 2 +-
lib/kunit/try-catch.c | 2 +-
7 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/include/linux/kthread.h b/include/linux/kthread.h
index a01a474719a7..37982eca94f1 100644
--- a/include/linux/kthread.h
+++ b/include/linux/kthread.h
@@ -116,7 +116,6 @@ void *kthread_probe_data(struct task_struct *k);
int kthread_park(struct task_struct *k);
void kthread_unpark(struct task_struct *k);
void kthread_parkme(void);
-#define kthread_exit(result) do_exit(result)
void kthread_complete_and_exit(struct completion *, long) __noreturn;
int kthreads_update_housekeeping(void);
void kthread_do_exit(struct kthread *, long);
diff --git a/include/linux/module.h b/include/linux/module.h
index 14f391b186c6..79ac4a700b39 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -855,7 +855,7 @@ static inline int unregister_module_notifier(struct notifier_block *nb)
return 0;
}
-#define module_put_and_kthread_exit(code) kthread_exit(code)
+#define module_put_and_kthread_exit(code) do_exit(code)
static inline void print_modules(void)
{
diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h
index 4dc14c7a711b..c86fc8a87eae 100644
--- a/include/linux/sunrpc/svc.h
+++ b/include/linux/sunrpc/svc.h
@@ -338,7 +338,7 @@ static inline void svc_thread_init_status(struct svc_rqst *rqstp, int err)
{
store_release_wake_up(&rqstp->rq_err, err);
if (err)
- kthread_exit(1);
+ do_exit(1);
}
struct svc_deferred_req {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 401d6c4960ec..8db79e593156 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -25261,7 +25261,6 @@ BTF_ID(func, __x64_sys_exit_group)
BTF_ID(func, do_exit)
BTF_ID(func, do_group_exit)
BTF_ID(func, kthread_complete_and_exit)
-BTF_ID(func, kthread_exit)
BTF_ID(func, make_task_dead)
BTF_SET_END(noreturn_deny)
diff --git a/kernel/kthread.c b/kernel/kthread.c
index 791210daf8b4..1447c14c8540 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -323,7 +323,7 @@ void __noreturn kthread_complete_and_exit(struct completion *comp, long code)
if (comp)
complete(comp);
- kthread_exit(code);
+ do_exit(code);
}
EXPORT_SYMBOL(kthread_complete_and_exit);
@@ -395,7 +395,7 @@ static int kthread(void *_create)
if (!done) {
kfree(create->full_name);
kfree(create);
- kthread_exit(-EINTR);
+ do_exit(-EINTR);
}
self->full_name = create->full_name;
@@ -435,7 +435,7 @@ static int kthread(void *_create)
__kthread_parkme(self);
ret = threadfn(data);
}
- kthread_exit(ret);
+ do_exit(ret);
}
/* called from kernel_clone() to get node information for about to be created task */
@@ -738,7 +738,7 @@ EXPORT_SYMBOL_GPL(kthread_park);
* instead of calling wake_up_process(): the thread will exit without
* calling threadfn().
*
- * If threadfn() may call kthread_exit() itself, the caller must ensure
+ * If threadfn() may call do_exit() itself, the caller must ensure
* task_struct can't go away.
*
* Returns the result of threadfn(), or %-EINTR if wake_up_process()
diff --git a/kernel/module/main.c b/kernel/module/main.c
index c3ce106c70af..340b4dc5c692 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -228,7 +228,7 @@ static int mod_strncmp(const char *str_a, const char *str_b, size_t n)
void __noreturn __module_put_and_kthread_exit(struct module *mod, long code)
{
module_put(mod);
- kthread_exit(code);
+ do_exit(code);
}
EXPORT_SYMBOL(__module_put_and_kthread_exit);
diff --git a/lib/kunit/try-catch.c b/lib/kunit/try-catch.c
index d84a879f0a78..99d9603a2cfd 100644
--- a/lib/kunit/try-catch.c
+++ b/lib/kunit/try-catch.c
@@ -18,7 +18,7 @@
void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)
{
try_catch->try_result = -EFAULT;
- kthread_exit(0);
+ do_exit(0);
}
EXPORT_SYMBOL_GPL(kunit_try_catch_throw);
---
base-commit: c107785c7e8dbabd1c18301a1c362544b5786282
change-id: 20260306-work-kernel-exit-2e8fd9e2c2a1
^ permalink raw reply related
* [PATCH] module: Fix freeing of charp module parameters when CONFIG_SYSFS=n
From: Petr Pavlu @ 2026-03-06 11:10 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen
Cc: Aaron Tomlin, Greg Kroah-Hartman, Rafael J. Wysocki,
Danilo Krummrich, linux-modules, linux-kernel
When setting a charp module parameter, the param_set_charp() function
allocates memory to store a copy of the input value. Later, when the module
is potentially unloaded, the destroy_params() function is called to free
this allocated memory.
However, destroy_params() is available only when CONFIG_SYSFS=y, otherwise
only a dummy variant is present. In the unlikely case that the kernel is
configured with CONFIG_MODULES=y and CONFIG_SYSFS=n, this results in
a memory leak of charp values when a module is unloaded.
Fix this issue by making destroy_params() always available when
CONFIG_MODULES=y. Rename the function to module_destroy_params() to clarify
that it is intended for use by the module loader.
Fixes: e180a6b7759a ("param: fix charp parameters set via sysfs")
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/linux/moduleparam.h | 12 ++++--------
kernel/module/main.c | 4 ++--
kernel/params.c | 27 ++++++++++++++++++---------
3 files changed, 24 insertions(+), 19 deletions(-)
diff --git a/include/linux/moduleparam.h b/include/linux/moduleparam.h
index 7d22d4c4ea2e..6283665ec614 100644
--- a/include/linux/moduleparam.h
+++ b/include/linux/moduleparam.h
@@ -426,14 +426,10 @@ extern char *parse_args(const char *name,
void *arg, parse_unknown_fn unknown);
/* Called by module remove. */
-#ifdef CONFIG_SYSFS
-extern void destroy_params(const struct kernel_param *params, unsigned num);
-#else
-static inline void destroy_params(const struct kernel_param *params,
- unsigned num)
-{
-}
-#endif /* !CONFIG_SYSFS */
+#ifdef CONFIG_MODULES
+extern void module_destroy_params(const struct kernel_param *params,
+ unsigned num);
+#endif
/* All the helper functions */
/* The macros to do compile-time type checking stolen from Jakub
diff --git a/kernel/module/main.c b/kernel/module/main.c
index c3ce106c70af..ef2e2130972f 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1408,7 +1408,7 @@ static void free_module(struct module *mod)
module_unload_free(mod);
/* Free any allocated parameters. */
- destroy_params(mod->kp, mod->num_kp);
+ module_destroy_params(mod->kp, mod->num_kp);
if (is_livepatch_module(mod))
free_module_elf(mod);
@@ -3519,7 +3519,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
mod_sysfs_teardown(mod);
coming_cleanup:
mod->state = MODULE_STATE_GOING;
- destroy_params(mod->kp, mod->num_kp);
+ module_destroy_params(mod->kp, mod->num_kp);
blocking_notifier_call_chain(&module_notify_list,
MODULE_STATE_GOING, mod);
klp_module_going(mod);
diff --git a/kernel/params.c b/kernel/params.c
index 7188a12dbe86..1a436c9d6140 100644
--- a/kernel/params.c
+++ b/kernel/params.c
@@ -745,15 +745,6 @@ void module_param_sysfs_remove(struct module *mod)
}
#endif
-void destroy_params(const struct kernel_param *params, unsigned num)
-{
- unsigned int i;
-
- for (i = 0; i < num; i++)
- if (params[i].ops->free)
- params[i].ops->free(params[i].arg);
-}
-
struct module_kobject * __init_or_module
lookup_or_create_module_kobject(const char *name)
{
@@ -985,3 +976,21 @@ static int __init param_sysfs_builtin_init(void)
late_initcall(param_sysfs_builtin_init);
#endif /* CONFIG_SYSFS */
+
+#ifdef CONFIG_MODULES
+
+/*
+ * module_destroy_params - free all parameters for one module
+ * @params: module parameters (array)
+ * @num: number of module parameters
+ */
+void module_destroy_params(const struct kernel_param *params, unsigned num)
+{
+ unsigned int i;
+
+ for (i = 0; i < num; i++)
+ if (params[i].ops->free)
+ params[i].ops->free(params[i].arg);
+}
+
+#endif /* CONFIG_MODULES */
base-commit: c107785c7e8dbabd1c18301a1c362544b5786282
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v2] module: print version for external modules in print_modules()
From: Petr Pavlu @ 2026-03-06 10:10 UTC (permalink / raw)
To: Sami Tolvanen; +Cc: Yafang Shao, mcgrof, da.gomez, atomlin, linux-modules
In-Reply-To: <20260305234344.GA788042@google.com>
On 3/6/26 12:43 AM, Sami Tolvanen wrote:
> On Wed, Dec 31, 2025 at 05:40:04PM +0800, Yafang Shao wrote:
>> --- a/kernel/module/main.c
>> +++ b/kernel/module/main.c
>> @@ -3901,7 +3901,11 @@ void print_modules(void)
>> list_for_each_entry_rcu(mod, &modules, list) {
>> if (mod->state == MODULE_STATE_UNFORMED)
>> continue;
>> - pr_cont(" %s%s", mod->name, module_flags(mod, buf, true));
>> + pr_cont(" %s", mod->name);
>> + /* Only append version for out-of-tree modules */
>> + if (mod->version && test_bit(TAINT_OOT_MODULE, &mod->taints))
>> + pr_cont("-%s", mod->version);
>> + pr_cont("%s", module_flags(mod, buf, true));
>
> On second thought, is using mod->version here safe? We release the
> memory for mod->version in:
>
> free_module
> -> mod_sysfs_teardown
> -> module_remove_modinfo_attrs
> -> attr->free = free_modinfo_version
>
> And this happens before the module is removed from the
> list. Couldn't there be a race condition where we read a non-NULL
> mod->version here, but the buffer is being concurrently released
> by another core that's unloading the module, resulting in a
> use-after-free in the pr_cont call?
>
> In order to do this safely, we should presumably drop the attr->free
> call from module_remove_modinfo_attrs and release the attributes
> only after the synchronize_rcu call in free_module (there's already
> free_modinfo we can use), so mod->version is valid for the entire
> time the module is on the list.
This looks reasonable to me as a simple fix. I also noticed that
setup_modinfo() with its attr->setup() calls is invoked unconditionally
in kernel/module/main.c, while module_remove_modinfo_attrs() with
attr->free() is present in kernel/module/sysfs.c, which is conditional
on CONFIG_SYSFS. In the unlikely configuration where CONFIG_SYSFS=n and
CONFIG_MODULES=y, this can result in a memory leak of module::version
when a module is unloaded.
In general, I think this could benefit from more cleanup in the future.
Most of the code related to modinfo_attrs should be moved into
kernel/module/sysfs.c. Since module::version is now used from
print_modules(), which is part of the general module loader code, the
initialization of the variable should be independent of all sysfs logic.
Ideally, the sysfs code should only read module::version and no longer
manage it.
--
Thanks,
Petr
^ permalink raw reply
* Re: [PATCH] kernel/trace/ftrace: introduce ftrace module notifier
From: Petr Mladek @ 2026-03-06 9:57 UTC (permalink / raw)
To: Song Chen
Cc: Steven Rostedt, Miroslav Benes, mcgrof, petr.pavlu, da.gomez,
samitolvanen, atomlin, mhiramat, mark.rutland, mathieu.desnoyers,
linux-modules, linux-kernel, linux-trace-kernel, live-patching
In-Reply-To: <321d4670-27cb-453f-a50d-426c83894074@189.cn>
On Fri 2026-02-27 09:34:59, Song Chen wrote:
> Hi,
>
> 在 2026/2/27 01:30, Steven Rostedt 写道:
> > On Thu, 26 Feb 2026 11:51:53 +0100 (CET)
> > Miroslav Benes <mbenes@suse.cz> wrote:
> >
> > > > Let me see if there is any way to use notifier and remain below calling
> > > > sequence:
> > > >
> > > > ftrace_module_enable
> > > > klp_module_coming
> > > > blocking_notifier_call_chain_robust(MODULE_STATE_COMING)
> > > >
> > > > blocking_notifier_call_chain(MODULE_STATE_GOING)
> > > > klp_module_going
> > > > ftrace_release_mod
> > >
> > > Both klp and ftrace used module notifiers in the past. We abandoned that
> > > and opted for direct calls due to issues with ordering at the time. I do
> > > not have the list of problems at hand but I remember it was very fragile.
> > >
> > > See commits 7dcd182bec27 ("ftrace/module: remove ftrace module
> > > notifier"), 7e545d6eca20 ("livepatch/module: remove livepatch module
> > > notifier") and their surroundings.
> > >
> > > So unless there is a reason for the change (which should be then carefully
> > > reviewed and properly tested), I would prefer to keep it as is. What is
> > > the motivation? I am failing to find it in the commit log.
>
> There is no special motivation, i just read btf initialization in module
> loading and found direct calls of ftrace and klp, i thought they were just
> forgotten to use notifier and i even didn't search git log to verify, sorry
> about that.
>
> >
> > Honestly, I do think just decoupling ftrace and live kernel patching from
> > modules is rationale enough, as it makes the code a bit cleaner. But to do
> > so, we really need to make sure there is absolutely no regressions.
> >
> > Thus, to allow such a change, I would ask those that are proposing it, show
> > a full work flow of how ftrace, live kernel patching, and modules work with
> > each other and why those functions are currently injected in the module code.
> >
> > As Miroslav stated, we tried to do it via notifiers in the past and it
> > failed. I don't want to find out why they failed by just adding them back
> > to notifiers again. Instead, the reasons must be fully understood and
> > updates made to make sure they will not fail in the future.
>
> Yes, you are right, i read commit msg of 7dcd182bec27, this patch just
> reverses it simply and will introduce order issue back. I will try to find
> out the problem in the past at first.
AFAIK, the root of the problem is that livepatch uses the ftrace
framework. It means that:
+ ftrace must be initialized before livepatch gets enabled
+ livepatch must be disabled before ftrace support gets removed
My understanding is that this can't be achieved by notifiers easily
because they are always proceed in the same order.
An elegant solution would be to introduce notifier_reverse_call_chain()
which would process the callbacks in the reverse order. But it might
be non-trivial:
+ We would need to make sure that it does not break some
existing "hidden" dependencies.
+ notifier_call_chain() uses RCU to process the list of registered
callbacks. I am not sure how complicated would be to make it safe
in both directions.
Best Regards,
Petr
^ permalink raw reply
* Re: [PATCH v2] module: print version for external modules in print_modules()
From: Yafang Shao @ 2026-03-06 8:53 UTC (permalink / raw)
To: Sami Tolvanen; +Cc: mcgrof, petr.pavlu, da.gomez, atomlin, linux-modules
In-Reply-To: <20260305234344.GA788042@google.com>
On Fri, Mar 6, 2026 at 7:43 AM Sami Tolvanen <samitolvanen@google.com> wrote:
>
> On Wed, Dec 31, 2025 at 05:40:04PM +0800, Yafang Shao wrote:
> > --- a/kernel/module/main.c
> > +++ b/kernel/module/main.c
> > @@ -3901,7 +3901,11 @@ void print_modules(void)
> > list_for_each_entry_rcu(mod, &modules, list) {
> > if (mod->state == MODULE_STATE_UNFORMED)
> > continue;
> > - pr_cont(" %s%s", mod->name, module_flags(mod, buf, true));
> > + pr_cont(" %s", mod->name);
> > + /* Only append version for out-of-tree modules */
> > + if (mod->version && test_bit(TAINT_OOT_MODULE, &mod->taints))
> > + pr_cont("-%s", mod->version);
> > + pr_cont("%s", module_flags(mod, buf, true));
>
> On second thought, is using mod->version here safe? We release the
> memory for mod->version in:
>
> free_module
> -> mod_sysfs_teardown
> -> module_remove_modinfo_attrs
> -> attr->free = free_modinfo_version
>
> And this happens before the module is removed from the
> list. Couldn't there be a race condition where we read a non-NULL
> mod->version here, but the buffer is being concurrently released
> by another core that's unloading the module, resulting in a
> use-after-free in the pr_cont call?
You're right. This can happen.
>
> In order to do this safely, we should presumably drop the attr->free
> call from module_remove_modinfo_attrs and release the attributes
> only after the synchronize_rcu call in free_module (there's already
> free_modinfo we can use), so mod->version is valid for the entire
> time the module is on the list.
>
> Thoughts?
It looks like this could work. I'll analyze it further—thanks for the
suggestion.
--
Regards
Yafang
^ permalink raw reply
* Re: [PATCH v2] module.lds,codetag: force 0 sh_addr for sections
From: Petr Pavlu @ 2026-03-06 8:15 UTC (permalink / raw)
To: Joe Lawrence
Cc: Luis Chamberlain, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Petr Mladek, Josh Poimboeuf, linux-modules, linux-kernel
In-Reply-To: <20260305015237.299727-1-joe.lawrence@redhat.com>
On 3/5/26 2:52 AM, Joe Lawrence wrote:
> Commit 1ba9f8979426 ("vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and
> related macros") added .text and made .data, .bss, and .rodata sections
> unconditional in the module linker script, but without an explicit
> address like the other sections in the same file.
>
> When linking modules with ld.bfd -r, sections defined without an address
> inherit the location counter, resulting in non-zero sh_addr values in
> the .ko. Relocatable objects are expected to have sh_addr=0 for these
> sections and these non-zero addresses confuse elfutils and have been
> reported to cause segmentation faults in SystemTap [1].
>
> Add the 0 address specifier to all sections in module.lds, including the
> .codetag.* sections via MOD_SEPARATE_CODETAG_SECTIONS macro.
>
> Link: https://sourceware.org/bugzilla/show_bug.cgi?id=33958
> Fixes: 1ba9f8979426 ("vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and related macros")
> Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
--
Thanks,
Petr
^ permalink raw reply
* Re: [PATCH 1/3] kallsyms: embed source file:line info in kernel stack traces
From: Randy Dunlap @ 2026-03-06 5:31 UTC (permalink / raw)
To: Helge Deller, Sasha Levin
Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley, Jonathan Corbet, Nathan Chancellor,
Nicolas Schier, Petr Pavlu, Daniel Gomez, Greg KH, Petr Mladek,
Steven Rostedt, Kees Cook, Peter Zijlstra, Thorsten Leemhuis,
Vlastimil Babka, linux-kernel, linux-kbuild, linux-modules,
linux-doc
In-Reply-To: <eab67de3-74af-45d9-bf67-1bf2c10aec37@gmx.de>
On 3/5/26 2:26 PM, Helge Deller wrote:
> On 3/5/26 03:18, Sasha Levin wrote:
>> On Wed, Mar 04, 2026 at 09:17:37PM +0100, Helge Deller wrote:
>>> On 3/3/26 19:21, Sasha Levin wrote:
>>>> Add CONFIG_KALLSYMS_LINEINFO, which embeds a compact address-to-line
>>>> lookup table in the kernel image so stack traces directly print source
>>>> file and line number information:
>>>>
>>>> root@localhost:~# echo c > /proc/sysrq-trigger
>>>> [ 11.201987] sysrq: Trigger a crash
>>>> [ 11.202831] Kernel panic - not syncing: sysrq triggered crash
>>>> [ 11.206218] Call Trace:
>>>> [ 11.206501] <TASK>
>>>> [ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
>>>> [ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
>>>> [ 11.208565] ? __lock_acquire+0x465/0x2240 (kernel/locking/lockdep.c:4674)
>>>> [ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
>>>> [ 11.211873] ? find_held_lock+0x2b/0x80 (kernel/locking/lockdep.c:5350)
>>>> [ 11.212597] ? lock_release+0xd3/0x300 (kernel/locking/lockdep.c:5535)
>>>> [ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
>>>> [ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
>>>> [ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
>>>> [ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
>>>> [ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
>>>> [ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
>>>> [ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
>>>> [ 11.223942] entry_SYSCALL_64_after_hwframe+0x77/0x7f (arch/x86/entry/entry_64.S:121)
>>>
>>> As mentioned in the other series, I really like this patch series.
>>>
>>> I tested this series again on the parisc architecture, and the relative
>>> directories are now stripped with this version of your patch.
>>> IIRC, the previous patch did show the subdirectory names.
>>> [ 132.840382] Backtrace:
>>> [ 132.840382] [<104254d8>] show_stack+0x50/0x64 (traps.c:212)
>>> [ 132.840382] [<1041c0c8>] dump_stack_lvl+0x6c/0xa0 (dump_stack.c:122)
>>> [ 132.840382] [<1041c118>] dump_stack+0x1c/0x2c (dump_stack.c:130)
>>> [ 132.840382] [<10402218>] vpanic+0x154/0x344 (panic.c:550)
>>> [ 132.840382] [<10402438>] panic+0x30/0x34 (panic.c:787)
>>> [ 132.840382] [<10bebea8>] sysrq_handle_crash+0x30/0x34 (rcupdate.h:110)
>>> [ 132.840382] [<10bec720>] __handle_sysrq+0xc0/0x1e4 (preempt.h:14)
>>
>> Ugh... Can you confirm that you've build this kernel with O=?
>
> Yes. Both -Os and -O2 do not show the relative path.
Helge,
I'm fairly sure that Sasha meant with O=build_dir_name,
not -O for optimization levels.
>> The RFC had a dirty dirty hack around how we turn these absolute paths into
>> relative ones, but I tried to re-do it so no one would yell at me :)
>
> Seems it is needed...
>
> Helge
>
--
~Randy
^ permalink raw reply
* Re: [PATCH 1/3] kallsyms: embed source file:line info in kernel stack traces
From: Randy Dunlap @ 2026-03-06 5:28 UTC (permalink / raw)
To: Sasha Levin, Andrew Morton, Masahiro Yamada, Luis Chamberlain,
Linus Torvalds, Richard Weinberger, Juergen Gross,
Geert Uytterhoeven, James Bottomley
Cc: Jonathan Corbet, Nathan Chancellor, Nicolas Schier, Petr Pavlu,
Daniel Gomez, Greg KH, Petr Mladek, Steven Rostedt, Kees Cook,
Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka, linux-kernel,
linux-kbuild, linux-modules, linux-doc
In-Reply-To: <20260303182103.3523438-2-sashal@kernel.org>
On 3/3/26 10:21 AM, Sasha Levin wrote:
> diff --git a/Documentation/admin-guide/kallsyms-lineinfo.rst b/Documentation/admin-guide/kallsyms-lineinfo.rst
> new file mode 100644
> index 0000000000000..4dffc18dbcf5a
> --- /dev/null
> +++ b/Documentation/admin-guide/kallsyms-lineinfo.rst
> @@ -0,0 +1,72 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +==================================
> +Kallsyms Source Line Info (LINEINFO)
> +==================================
Heading over/under lines must be at least as long as the heading.
> +
> +Overview
> +========
> +
> +``CONFIG_KALLSYMS_LINEINFO`` embeds DWARF-derived source file and line number
> +mappings into the kernel image so that stack traces include
> +``(file.c:123)`` annotations next to each symbol. This makes it significantly
> +easier to pinpoint the exact source location during debugging, without needing
> +to manually cross-reference addresses with ``addr2line``.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 61bf550fd37c2..ab987e74bb0f5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14278,6 +14278,12 @@ F: lib/Kconfig.kmsan
> F: mm/kmsan/
> F: scripts/Makefile.kmsan
>
> +KALLSYMS LINEINFO
> +M: Sasha Levin <sashal@kernel.org>
> +S: Maintained
> +F: Documentation/admin-guide/kallsyms-lineinfo.rst
> +F: scripts/gen_lineinfo.c
This entry should be in alphabetical order, just before KASAN.
> +
> KPROBES
> M: Naveen N Rao <naveen@kernel.org>
> M: "David S. Miller" <davem@davemloft.net>
--
~Randy
^ permalink raw reply
* Re: [PATCH v2] module: print version for external modules in print_modules()
From: Sami Tolvanen @ 2026-03-05 23:43 UTC (permalink / raw)
To: Yafang Shao; +Cc: mcgrof, petr.pavlu, da.gomez, atomlin, linux-modules
In-Reply-To: <20251231094004.37851-1-laoar.shao@gmail.com>
On Wed, Dec 31, 2025 at 05:40:04PM +0800, Yafang Shao wrote:
> --- a/kernel/module/main.c
> +++ b/kernel/module/main.c
> @@ -3901,7 +3901,11 @@ void print_modules(void)
> list_for_each_entry_rcu(mod, &modules, list) {
> if (mod->state == MODULE_STATE_UNFORMED)
> continue;
> - pr_cont(" %s%s", mod->name, module_flags(mod, buf, true));
> + pr_cont(" %s", mod->name);
> + /* Only append version for out-of-tree modules */
> + if (mod->version && test_bit(TAINT_OOT_MODULE, &mod->taints))
> + pr_cont("-%s", mod->version);
> + pr_cont("%s", module_flags(mod, buf, true));
On second thought, is using mod->version here safe? We release the
memory for mod->version in:
free_module
-> mod_sysfs_teardown
-> module_remove_modinfo_attrs
-> attr->free = free_modinfo_version
And this happens before the module is removed from the
list. Couldn't there be a race condition where we read a non-NULL
mod->version here, but the buffer is being concurrently released
by another core that's unloading the module, resulting in a
use-after-free in the pr_cont call?
In order to do this safely, we should presumably drop the attr->free
call from module_remove_modinfo_attrs and release the attributes
only after the synchronize_rcu call in free_module (there's already
free_modinfo we can use), so mod->version is valid for the entire
time the module is on the list.
Thoughts?
Sami
^ permalink raw reply
* Re: [PATCH 1/2] module: expose imported namespaces via sysfs
From: Sami Tolvanen @ 2026-03-05 22:32 UTC (permalink / raw)
To: Nicholas Sielicki
Cc: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Aaron Tomlin,
Matthias Maennich, Peter Zijlstra, Jonathan Corbet, Randy Dunlap,
linux-modules, linux-kernel
In-Reply-To: <20260108044710.33036-1-linux@opensource.nslick.com>
On Wed, Jan 07, 2026 at 10:47:09PM -0600, Nicholas Sielicki wrote:
>
> +static ssize_t show_modinfo_import_ns(const struct module_attribute *mattr,
> + struct module_kobject *mk, char *buffer)
> +{
> + return sysfs_emit(buffer, "%s\n", mk->mod->imported_namespaces);
> +}
> +
> +static int modinfo_import_ns_exists(struct module *mod)
> +{
> + return mod->imported_namespaces != NULL;
> +}
> +
> +static const struct module_attribute modinfo_import_ns = {
> + .attr = { .name = "import_ns", .mode = 0444 },
> + .show = show_modinfo_import_ns,
> + .test = modinfo_import_ns_exists,
> +};
> +
Don't we need a .setup function that initializes mod->imported_namespaces
to NULL? Currently, if setup_modinfo returns an error, the pointer remains
initialized to whatever value we read from .gnu.linkonce.this_module, and
we'll pass that arbitrary pointer to kfree.
This isn't normally a problem since modpost zero-initializes the field, but
we don't want to rely on userspace to initialize our pointers.
Also, define .free to release the buffer instead of adding a direct call
to free_modinfo.
> static struct {
> char name[MODULE_NAME_LEN];
> char taints[MODULE_FLAGS_BUF_SIZE];
> @@ -1058,6 +1075,7 @@ const struct module_attribute *const modinfo_attrs[] = {
> &module_uevent,
> &modinfo_version,
> &modinfo_srcversion,
> + &modinfo_import_ns,
> &modinfo_initstate,
> &modinfo_coresize,
> #ifdef CONFIG_ARCH_WANTS_MODULES_DATA_IN_VMALLOC
> @@ -1753,11 +1771,48 @@ static void module_license_taint_check(struct module *mod, const char *license)
> }
> }
>
> +static int copy_modinfo_import_ns(struct module *mod, struct load_info *info)
> +{
> + char *ns;
> + size_t len, total_len = 0;
> + char *buf, *p;
> +
> + for_each_modinfo_entry(ns, info, "import_ns")
> + total_len += strlen(ns) + 1;
> +
> + if (!total_len) {
> + mod->imported_namespaces = NULL;
> + return 0;
> + }
> +
> + buf = kmalloc(total_len, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
For example, if kmalloc fails, mod->imported_namespaces isn't initialized.
> +
> + p = buf;
> + for_each_modinfo_entry(ns, info, "import_ns") {
> + len = strlen(ns);
> + memcpy(p, ns, len);
> + p += len;
> + *p++ = '\n';
> + }
> + /* Replace trailing newline with null terminator. */
> + *(p - 1) = '\0';
> +
> + mod->imported_namespaces = buf;
> + return 0;
> +}
> +
> +static void free_modinfo_import_ns(struct module *mod)
> +{
> + kfree(mod->imported_namespaces);
mod->imported_namespaces = NULL;
> +}
> +
> static int setup_modinfo(struct module *mod, struct load_info *info)
> {
> const struct module_attribute *attr;
> char *imported_namespace;
> - int i;
> + int i, err;
>
> for (i = 0; (attr = modinfo_attrs[i]); i++) {
> if (attr->setup)
> @@ -1776,6 +1831,10 @@ static int setup_modinfo(struct module *mod, struct load_info *info)
> }
> }
Also setup_modinfo can fail before copy_modinfo_import_ns is even
called.
> + err = copy_modinfo_import_ns(mod, info);
> + if (err)
> + return err;
> +
Sami
^ permalink raw reply
* Re: [PATCH 1/3] kallsyms: embed source file:line info in kernel stack traces
From: Helge Deller @ 2026-03-05 22:26 UTC (permalink / raw)
To: Sasha Levin
Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley, Jonathan Corbet, Nathan Chancellor,
Nicolas Schier, Petr Pavlu, Daniel Gomez, Greg KH, Petr Mladek,
Steven Rostedt, Kees Cook, Peter Zijlstra, Thorsten Leemhuis,
Vlastimil Babka, linux-kernel, linux-kbuild, linux-modules,
linux-doc
In-Reply-To: <aajoETEtX9r2XzT7@laps>
On 3/5/26 03:18, Sasha Levin wrote:
> On Wed, Mar 04, 2026 at 09:17:37PM +0100, Helge Deller wrote:
>> On 3/3/26 19:21, Sasha Levin wrote:
>>> Add CONFIG_KALLSYMS_LINEINFO, which embeds a compact address-to-line
>>> lookup table in the kernel image so stack traces directly print source
>>> file and line number information:
>>>
>>> root@localhost:~# echo c > /proc/sysrq-trigger
>>> [ 11.201987] sysrq: Trigger a crash
>>> [ 11.202831] Kernel panic - not syncing: sysrq triggered crash
>>> [ 11.206218] Call Trace:
>>> [ 11.206501] <TASK>
>>> [ 11.206749] dump_stack_lvl+0x5d/0x80 (lib/dump_stack.c:94)
>>> [ 11.207403] vpanic+0x36e/0x620 (kernel/panic.c:650)
>>> [ 11.208565] ? __lock_acquire+0x465/0x2240 (kernel/locking/lockdep.c:4674)
>>> [ 11.209324] panic+0xc9/0xd0 (kernel/panic.c:787)
>>> [ 11.211873] ? find_held_lock+0x2b/0x80 (kernel/locking/lockdep.c:5350)
>>> [ 11.212597] ? lock_release+0xd3/0x300 (kernel/locking/lockdep.c:5535)
>>> [ 11.213312] sysrq_handle_crash+0x1a/0x20 (drivers/tty/sysrq.c:154)
>>> [ 11.214005] __handle_sysrq.cold+0x66/0x256 (drivers/tty/sysrq.c:611)
>>> [ 11.214712] write_sysrq_trigger+0x65/0x80 (drivers/tty/sysrq.c:1221)
>>> [ 11.215424] proc_reg_write+0x1bd/0x3c0 (fs/proc/inode.c:330)
>>> [ 11.216061] vfs_write+0x1c6/0xff0 (fs/read_write.c:686)
>>> [ 11.218848] ksys_write+0xfa/0x200 (fs/read_write.c:740)
>>> [ 11.222394] do_syscall_64+0xf3/0x690 (arch/x86/entry/syscall_64.c:63)
>>> [ 11.223942] entry_SYSCALL_64_after_hwframe+0x77/0x7f (arch/x86/entry/entry_64.S:121)
>>
>> As mentioned in the other series, I really like this patch series.
>>
>> I tested this series again on the parisc architecture, and the relative
>> directories are now stripped with this version of your patch.
>> IIRC, the previous patch did show the subdirectory names.
>> [ 132.840382] Backtrace:
>> [ 132.840382] [<104254d8>] show_stack+0x50/0x64 (traps.c:212)
>> [ 132.840382] [<1041c0c8>] dump_stack_lvl+0x6c/0xa0 (dump_stack.c:122)
>> [ 132.840382] [<1041c118>] dump_stack+0x1c/0x2c (dump_stack.c:130)
>> [ 132.840382] [<10402218>] vpanic+0x154/0x344 (panic.c:550)
>> [ 132.840382] [<10402438>] panic+0x30/0x34 (panic.c:787)
>> [ 132.840382] [<10bebea8>] sysrq_handle_crash+0x30/0x34 (rcupdate.h:110)
>> [ 132.840382] [<10bec720>] __handle_sysrq+0xc0/0x1e4 (preempt.h:14)
>
> Ugh... Can you confirm that you've build this kernel with O=?
Yes. Both -Os and -O2 do not show the relative path.
> The RFC had a dirty dirty hack around how we turn these absolute paths into
> relative ones, but I tried to re-do it so no one would yell at me :)
Seems it is needed...
Helge
^ permalink raw reply
* Re: [PATCH v2] module.lds,codetag: force 0 sh_addr for sections
From: Josh Poimboeuf @ 2026-03-05 19:55 UTC (permalink / raw)
To: Joe Lawrence
Cc: linux-modules, linux-kernel, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Petr Mladek
In-Reply-To: <20260305015237.299727-1-joe.lawrence@redhat.com>
On Wed, Mar 04, 2026 at 08:52:37PM -0500, Joe Lawrence wrote:
> Commit 1ba9f8979426 ("vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and
> related macros") added .text and made .data, .bss, and .rodata sections
> unconditional in the module linker script, but without an explicit
> address like the other sections in the same file.
>
> When linking modules with ld.bfd -r, sections defined without an address
> inherit the location counter, resulting in non-zero sh_addr values in
> the .ko. Relocatable objects are expected to have sh_addr=0 for these
> sections and these non-zero addresses confuse elfutils and have been
> reported to cause segmentation faults in SystemTap [1].
>
> Add the 0 address specifier to all sections in module.lds, including the
> .codetag.* sections via MOD_SEPARATE_CODETAG_SECTIONS macro.
>
> Link: https://sourceware.org/bugzilla/show_bug.cgi?id=33958
> Fixes: 1ba9f8979426 ("vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and related macros")
> Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
> ---
> include/asm-generic/codetag.lds.h | 2 +-
> scripts/module.lds.S | 12 ++++++------
> 2 files changed, 7 insertions(+), 7 deletions(-)
>
> v2:
> - Update the MOD_SEPARATE_CODETAG_SECTION for .codetag.* as well [Petr]
>
> v1: https://lore.kernel.org/lkml/20260304160611.143862-1-joe.lawrence@redhat.com
Thanks for the fix!
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
--
Josh
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox