All of lore.kernel.org
 help / color / mirror / Atom feed
From: Sasha Levin <sashal@kernel.org>
To: Andrew Morton <akpm@linux-foundation.org>,
	Masahiro Yamada <masahiroy@kernel.org>,
	Luis Chamberlain <mcgrof@kernel.org>,
	Linus Torvalds <torvalds@linux-foundation.org>,
	Richard Weinberger <richard@nod.at>,
	Juergen Gross <jgross@suse.com>,
	Geert Uytterhoeven <geert@linux-m68k.org>,
	James Bottomley <James.Bottomley@HansenPartnership.com>
Cc: Jonathan Corbet <corbet@lwn.net>,
	Nathan Chancellor <nathan@kernel.org>,
	Nicolas Schier <nsc@kernel.org>, Petr Pavlu <petr.pavlu@suse.com>,
	Daniel Gomez <da.gomez@kernel.org>,
	Greg KH <gregkh@linuxfoundation.org>,
	Petr Mladek <pmladek@suse.com>,
	Steven Rostedt <rostedt@goodmis.org>, Kees Cook <kees@kernel.org>,
	Peter Zijlstra <peterz@infradead.org>,
	Thorsten Leemhuis <linux@leemhuis.info>,
	Vlastimil Babka <vbabka@kernel.org>,
	linux-kernel@vger.kernel.org, linux-kbuild@vger.kernel.org,
	linux-modules@vger.kernel.org, linux-doc@vger.kernel.org,
	Sasha Levin <sashal@kernel.org>
Subject: [PATCH 2/3] kallsyms: extend lineinfo to loadable modules
Date: Tue,  3 Mar 2026 13:21:02 -0500	[thread overview]
Message-ID: <20260303182103.3523438-3-sashal@kernel.org> (raw)
In-Reply-To: <20260303182103.3523438-1-sashal@kernel.org>

Add CONFIG_KALLSYMS_LINEINFO_MODULES, which extends the
CONFIG_KALLSYMS_LINEINFO feature to loadable kernel modules.

At build time, each .ko is post-processed by scripts/gen-mod-lineinfo.sh
(modeled on gen-btf.sh) which runs scripts/gen_lineinfo --module on the
.ko, generates a .mod_lineinfo section containing a compact binary table
of .text-relative offsets, file IDs, line numbers, and filenames, and
embeds it back into the .ko via objcopy.

At runtime, module_lookup_lineinfo() performs a binary search on the
module's .mod_lineinfo section, and __sprint_symbol() calls it for
addresses that fall within a module.  The lookup is NMI/panic-safe
(no locks, no allocations) - the data lives in read-only module memory
and is freed automatically when the module is unloaded.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 .../admin-guide/kallsyms-lineinfo.rst         |  40 ++++-
 MAINTAINERS                                   |   2 +
 include/linux/mod_lineinfo.h                  |  68 +++++++
 include/linux/module.h                        |  19 ++
 init/Kconfig                                  |  13 ++
 kernel/kallsyms.c                             |  22 ++-
 kernel/module/kallsyms.c                      |  95 ++++++++++
 kernel/module/main.c                          |   4 +
 scripts/Makefile                              |   1 +
 scripts/Makefile.modfinal                     |   6 +
 scripts/gen-mod-lineinfo.sh                   |  48 +++++
 scripts/gen_lineinfo.c                        | 166 ++++++++++++++++--
 12 files changed, 458 insertions(+), 26 deletions(-)
 create mode 100644 include/linux/mod_lineinfo.h
 create mode 100755 scripts/gen-mod-lineinfo.sh

diff --git a/Documentation/admin-guide/kallsyms-lineinfo.rst b/Documentation/admin-guide/kallsyms-lineinfo.rst
index 4dffc18dbcf5a..21450569d5324 100644
--- a/Documentation/admin-guide/kallsyms-lineinfo.rst
+++ b/Documentation/admin-guide/kallsyms-lineinfo.rst
@@ -51,22 +51,46 @@ With ``CONFIG_KALLSYMS_LINEINFO``::
 Note that assembly routines (such as ``entry_SYSCALL_64_after_hwframe``) are
 not annotated because they lack DWARF debug information.
 
+Module Support
+==============
+
+``CONFIG_KALLSYMS_LINEINFO_MODULES`` extends the feature to loadable kernel
+modules.  When enabled, each ``.ko`` is post-processed at build time to embed
+a ``.mod_lineinfo`` section containing the same kind of address-to-source
+mapping.
+
+Enable in addition to the base options::
+
+    CONFIG_MODULES=y
+    CONFIG_KALLSYMS_LINEINFO_MODULES=y
+
+Stack traces from module code will then include annotations::
+
+    my_driver_func+0x30/0x100 [my_driver] (drivers/foo/bar.c:123)
+
+The ``.mod_lineinfo`` section is loaded into read-only module memory alongside
+the module text.  No additional runtime memory allocation is required; the data
+is freed when the module is unloaded.
+
 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).
+The vmlinux 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).
+
+Per-module lineinfo adds approximately 10 bytes per DWARF line entry to each
+``.ko`` file.
 
 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.
+- **4 GiB offset limit**: Address offsets from ``_text`` (vmlinux) or
+  ``.text`` base (modules) are stored as 32-bit values.  Entries beyond
+  4 GiB 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.
+- **No init text**: For modules, functions in ``.init.text`` are not annotated
+  because that memory is freed after module initialization.
diff --git a/MAINTAINERS b/MAINTAINERS
index ab987e74bb0f5..d04abafd9eb77 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14282,6 +14282,8 @@ KALLSYMS LINEINFO
 M:	Sasha Levin <sashal@kernel.org>
 S:	Maintained
 F:	Documentation/admin-guide/kallsyms-lineinfo.rst
+F:	include/linux/mod_lineinfo.h
+F:	scripts/gen-mod-lineinfo.sh
 F:	scripts/gen_lineinfo.c
 
 KPROBES
diff --git a/include/linux/mod_lineinfo.h b/include/linux/mod_lineinfo.h
new file mode 100644
index 0000000000000..d62e9608f0f82
--- /dev/null
+++ b/include/linux/mod_lineinfo.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * mod_lineinfo.h - Binary format for per-module source line information
+ *
+ * This header defines the layout of the .mod_lineinfo section embedded
+ * in loadable kernel modules.  It is dual-use: included from both the
+ * kernel and the userspace gen_lineinfo tool.
+ *
+ * Section layout (all values in target-native endianness):
+ *
+ *   struct mod_lineinfo_header     (16 bytes)
+ *   u32 addrs[num_entries]         -- offsets from .text base, sorted
+ *   u16 file_ids[num_entries]      -- parallel to addrs
+ *   <2-byte pad if num_entries is odd>
+ *   u32 lines[num_entries]         -- parallel to addrs
+ *   u32 file_offsets[num_files]    -- byte offset into filenames[]
+ *   char filenames[filenames_size] -- concatenated NUL-terminated strings
+ */
+#ifndef _LINUX_MOD_LINEINFO_H
+#define _LINUX_MOD_LINEINFO_H
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#else
+#include <stdint.h>
+typedef uint32_t u32;
+typedef uint16_t u16;
+#endif
+
+struct mod_lineinfo_header {
+	u32 num_entries;
+	u32 num_files;
+	u32 filenames_size;	/* total bytes of concatenated filenames */
+	u32 reserved;		/* padding, must be 0 */
+};
+
+/* Offset helpers: compute byte offset from start of section to each array */
+
+static inline u32 mod_lineinfo_addrs_off(void)
+{
+	return sizeof(struct mod_lineinfo_header);
+}
+
+static inline u32 mod_lineinfo_file_ids_off(u32 num_entries)
+{
+	return mod_lineinfo_addrs_off() + num_entries * sizeof(u32);
+}
+
+static inline u32 mod_lineinfo_lines_off(u32 num_entries)
+{
+	/* u16 file_ids[] may need 2-byte padding to align lines[] to 4 bytes */
+	u32 off = mod_lineinfo_file_ids_off(num_entries) +
+		  num_entries * sizeof(u16);
+	return (off + 3) & ~3u;
+}
+
+static inline u32 mod_lineinfo_file_offsets_off(u32 num_entries)
+{
+	return mod_lineinfo_lines_off(num_entries) + num_entries * sizeof(u32);
+}
+
+static inline u32 mod_lineinfo_filenames_off(u32 num_entries, u32 num_files)
+{
+	return mod_lineinfo_file_offsets_off(num_entries) +
+	       num_files * sizeof(u32);
+}
+
+#endif /* _LINUX_MOD_LINEINFO_H */
diff --git a/include/linux/module.h b/include/linux/module.h
index 14f391b186c6d..1c5840e736ec7 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -508,6 +508,10 @@ struct module {
 	void *btf_data;
 	void *btf_base_data;
 #endif
+#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
+	void *lineinfo_data;		/* .mod_lineinfo section in MOD_RODATA */
+	unsigned int lineinfo_data_size;
+#endif
 #ifdef CONFIG_JUMP_LABEL
 	struct jump_entry *jump_entries;
 	unsigned int num_jump_entries;
@@ -1021,6 +1025,21 @@ static inline unsigned long find_kallsyms_symbol_value(struct module *mod,
 
 #endif  /* CONFIG_MODULES && CONFIG_KALLSYMS */
 
+#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
+bool module_lookup_lineinfo(struct module *mod, unsigned long addr,
+			    unsigned long sym_start,
+			    const char **file, unsigned int *line);
+#else
+static inline bool module_lookup_lineinfo(struct module *mod,
+					  unsigned long addr,
+					  unsigned long sym_start,
+					  const char **file,
+					  unsigned int *line)
+{
+	return false;
+}
+#endif
+
 /* Define __free(module_put) macro for struct module *. */
 DEFINE_FREE(module_put, struct module *, if (_T) module_put(_T))
 
diff --git a/init/Kconfig b/init/Kconfig
index c39f27e6393a8..bf53275bc405a 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2070,6 +2070,19 @@ config KALLSYMS_LINEINFO
 
 	  If unsure, say N.
 
+config KALLSYMS_LINEINFO_MODULES
+	bool "Embed source file:line information in module stack traces"
+	depends on KALLSYMS_LINEINFO && MODULES
+	help
+	  Extends KALLSYMS_LINEINFO to loadable kernel modules.  Each .ko
+	  gets a lineinfo table generated from its DWARF data at build time,
+	  so stack traces from module code include (file.c:123) annotations.
+
+	  Requires elfutils (libdw-dev/elfutils-devel) on the build host.
+	  Increases .ko sizes by approximately 10 bytes per DWARF line entry.
+
+	  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 2b9c9d6322a3e..cea74992e5427 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -554,13 +554,27 @@ static int __sprint_symbol(char *buffer, unsigned long address,
 	}
 
 #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))
+		bool found = false;
+
+		if (!modname)
+			found = kallsyms_lookup_lineinfo(address, sym_start,
+							 &li_file, &li_line);
+#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
+		else {
+			struct module *mod = __module_address(address);
+
+			if (mod)
+				found = module_lookup_lineinfo(mod, address,
+							      sym_start,
+							      &li_file,
+							      &li_line);
+		}
+#endif
+		if (found)
 			len += snprintf(buffer + len, KSYM_SYMBOL_LEN - len,
 					" (%s:%u)", li_file, li_line);
 	}
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
index 0fc11e45df9b9..7af414bd65e79 100644
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -494,3 +494,98 @@ int module_kallsyms_on_each_symbol(const char *modname,
 	mutex_unlock(&module_mutex);
 	return ret;
 }
+
+#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
+#include <linux/mod_lineinfo.h>
+
+/*
+ * Look up source file:line for an address within a loaded module.
+ * Uses the .mod_lineinfo section embedded in the .ko at build time.
+ *
+ * Safe in NMI/panic context: no locks, no allocations.
+ * Caller must hold RCU read lock (or be in a context where the module
+ * cannot be unloaded).
+ */
+bool module_lookup_lineinfo(struct module *mod, unsigned long addr,
+			    unsigned long sym_start,
+			    const char **file, unsigned int *line)
+{
+	const struct mod_lineinfo_header *hdr;
+	const void *base;
+	const u32 *addrs, *lines, *file_offsets;
+	const u16 *file_ids;
+	const char *filenames;
+	u32 num_entries, num_files, filenames_size;
+	unsigned long text_base;
+	unsigned int offset;
+	unsigned long long raw_offset;
+	unsigned int low, high, mid;
+	u16 file_id;
+
+	base = mod->lineinfo_data;
+	if (!base)
+		return false;
+
+	if (mod->lineinfo_data_size < sizeof(*hdr))
+		return false;
+
+	hdr = base;
+	num_entries = hdr->num_entries;
+	num_files = hdr->num_files;
+	filenames_size = hdr->filenames_size;
+
+	if (num_entries == 0)
+		return false;
+
+	/* Validate section is large enough for all arrays */
+	if (mod->lineinfo_data_size <
+	    mod_lineinfo_filenames_off(num_entries, num_files) + filenames_size)
+		return false;
+
+	addrs = base + mod_lineinfo_addrs_off();
+	file_ids = base + mod_lineinfo_file_ids_off(num_entries);
+	lines = base + mod_lineinfo_lines_off(num_entries);
+	file_offsets = base + mod_lineinfo_file_offsets_off(num_entries);
+	filenames = base + mod_lineinfo_filenames_off(num_entries, num_files);
+
+	/* Compute offset from module .text base */
+	text_base = (unsigned long)mod->mem[MOD_TEXT].base;
+	if (addr < text_base)
+		return false;
+
+	raw_offset = addr - text_base;
+	if (raw_offset > UINT_MAX)
+		return false;
+	offset = (unsigned int)raw_offset;
+
+	/* Binary search for largest entry <= offset */
+	low = 0;
+	high = num_entries;
+	while (low < high) {
+		mid = low + (high - low) / 2;
+		if (addrs[mid] <= offset)
+			low = mid + 1;
+		else
+			high = mid;
+	}
+
+	if (low == 0)
+		return false;
+	low--;
+
+	/* Ensure the matched entry belongs to the same symbol */
+	if (text_base + addrs[low] < sym_start)
+		return false;
+
+	file_id = file_ids[low];
+	if (file_id >= num_files)
+		return false;
+
+	if (file_offsets[file_id] >= filenames_size)
+		return false;
+
+	*file = &filenames[file_offsets[file_id]];
+	*line = lines[low];
+	return true;
+}
+#endif /* CONFIG_KALLSYMS_LINEINFO_MODULES */
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 2bac4c7cd019a..7b6ff9f7411b0 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2648,6 +2648,10 @@ static int find_module_sections(struct module *mod, struct load_info *info)
 	mod->btf_base_data = any_section_objs(info, ".BTF.base", 1,
 					      &mod->btf_base_data_size);
 #endif
+#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
+	mod->lineinfo_data = any_section_objs(info, ".mod_lineinfo", 1,
+					      &mod->lineinfo_data_size);
+#endif
 #ifdef CONFIG_JUMP_LABEL
 	mod->jump_entries = section_objs(info, "__jump_table",
 					sizeof(*mod->jump_entries),
diff --git a/scripts/Makefile b/scripts/Makefile
index ffe89875b3295..651df2a867ffb 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -5,6 +5,7 @@
 
 hostprogs-always-$(CONFIG_KALLSYMS)			+= kallsyms
 hostprogs-always-$(CONFIG_KALLSYMS_LINEINFO)		+= gen_lineinfo
+hostprogs-always-$(CONFIG_KALLSYMS_LINEINFO_MODULES)	+= gen_lineinfo
 hostprogs-always-$(BUILD_C_RECORDMCOUNT)		+= recordmcount
 hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT)		+= sorttable
 hostprogs-always-$(CONFIG_ASN1)				+= asn1_compiler
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index adcbcde16a071..3941cf624526b 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -46,6 +46,9 @@ quiet_cmd_btf_ko = BTF [M] $@
 		$(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \
 	fi;
 
+quiet_cmd_lineinfo_ko = LINEINFO [M] $@
+      cmd_lineinfo_ko = $(CONFIG_SHELL) $(srctree)/scripts/gen-mod-lineinfo.sh $@
+
 # Same as newer-prereqs, but allows to exclude specified extra dependencies
 newer_prereqs_except = $(filter-out $(PHONY) $(1),$?)
 
@@ -59,6 +62,9 @@ if_changed_except = $(if $(call newer_prereqs_except,$(2))$(cmd-check),      \
 	+$(call if_changed_except,ld_ko_o,$(objtree)/vmlinux)
 ifdef CONFIG_DEBUG_INFO_BTF_MODULES
 	+$(if $(newer-prereqs),$(call cmd,btf_ko))
+endif
+ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
+	+$(if $(newer-prereqs),$(call cmd,lineinfo_ko))
 endif
 	+$(call cmd,check_tracepoint)
 
diff --git a/scripts/gen-mod-lineinfo.sh b/scripts/gen-mod-lineinfo.sh
new file mode 100755
index 0000000000000..fa8a914b8363b
--- /dev/null
+++ b/scripts/gen-mod-lineinfo.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+#
+# gen-mod-lineinfo.sh - Embed source line info into a kernel module (.ko)
+#
+# Reads DWARF from the .ko, generates a .mod_lineinfo section, and
+# embeds it back into the .ko.  Modeled on scripts/gen-btf.sh.
+
+set -e
+
+if [ $# -ne 1 ]; then
+	echo "Usage: $0 <module.ko>" >&2
+	exit 1
+fi
+
+KO="$1"
+
+cleanup() {
+	rm -f "${KO}.lineinfo.S" "${KO}.lineinfo.o" "${KO}.lineinfo.bin"
+}
+trap cleanup EXIT
+
+case "${KBUILD_VERBOSE}" in
+*1*)
+	set -x
+	;;
+esac
+
+# Generate assembly from DWARF -- if it fails (no DWARF), silently skip
+if ! ${objtree}/scripts/gen_lineinfo --module "${KO}" > "${KO}.lineinfo.S" 2>/dev/null; then
+	exit 0
+fi
+
+# Compile assembly to object file
+${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \
+	${KBUILD_AFLAGS} ${KBUILD_AFLAGS_MODULE} \
+	-c -o "${KO}.lineinfo.o" "${KO}.lineinfo.S"
+
+# Extract raw section content
+${OBJCOPY} -O binary --only-section=.mod_lineinfo \
+	"${KO}.lineinfo.o" "${KO}.lineinfo.bin"
+
+# Embed into the .ko with alloc,readonly flags
+${OBJCOPY} --add-section ".mod_lineinfo=${KO}.lineinfo.bin" \
+	--set-section-flags .mod_lineinfo=alloc,readonly \
+	"${KO}"
+
+exit 0
diff --git a/scripts/gen_lineinfo.c b/scripts/gen_lineinfo.c
index 9eebfaca5857c..609de59f47ffd 100644
--- a/scripts/gen_lineinfo.c
+++ b/scripts/gen_lineinfo.c
@@ -23,8 +23,16 @@
 #include <gelf.h>
 #include <limits.h>
 
+#include "../include/linux/mod_lineinfo.h"
+
+static int module_mode;
+
 static unsigned int skipped_overflow;
 
+/* .text range for module mode (keep only runtime code) */
+static unsigned long long text_section_start;
+static unsigned long long text_section_end;
+
 struct line_entry {
 	unsigned int offset;	/* offset from _text */
 	unsigned int file_id;
@@ -123,26 +131,46 @@ static unsigned int find_or_add_file(const char *name)
  *
  * For absolute paths, strip the comp_dir prefix (from DWARF) to get
  * a kernel-tree-relative path, or fall back to the basename.
+ *
+ * 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;
+	if (path[0] == '/') {
+		/* Try comp_dir prefix from DWARF */
+		if (comp_dir) {
+			size_t len = strlen(comp_dir);
 
-	/* 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] == '/')
+				return path + len + 1;
+		}
 
-		if (!strncmp(path, comp_dir, len) && path[len] == '/')
-			return path + len + 1;
+		/* Fall back to basename */
+		p = strrchr(path, '/');
+		return p ? p + 1 : path;
 	}
 
-	/* Fall back to basename */
-	p = strrchr(path, '/');
-	return p ? p + 1 : path;
+	/*
+	 * Relative path — check for duplicated-path quirk from libdw
+	 * on ET_REL files (e.g., "a/b.c/a/b.c" → "a/b.c").
+	 */
+	{
+		size_t len = strlen(path);
+
+		for (p = path; (p = strchr(p, '/')) != NULL; p++) {
+			size_t prefix = p - path;
+			size_t rest = len - prefix - 1;
+
+			if (rest == prefix && !memcmp(path, p + 1, prefix))
+				return p + 1;
+		}
+	}
+
+	return path;
 }
 
 static int compare_entries(const void *a, const void *b)
@@ -194,6 +222,29 @@ static unsigned long long find_text_addr(Elf *elf)
 	exit(1);
 }
 
+static void find_text_section_range(Elf *elf)
+{
+	Elf_Scn *scn = NULL;
+	GElf_Shdr shdr;
+	size_t shstrndx;
+
+	if (elf_getshdrstrndx(elf, &shstrndx) != 0)
+		return;
+
+	while ((scn = elf_nextscn(elf, scn)) != NULL) {
+		const char *name;
+
+		if (!gelf_getshdr(scn, &shdr))
+			continue;
+		name = elf_strptr(elf, shstrndx, shdr.sh_name);
+		if (name && !strcmp(name, ".text")) {
+			text_section_start = shdr.sh_addr;
+			text_section_end = shdr.sh_addr + shdr.sh_size;
+			return;
+		}
+	}
+}
+
 static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
 {
 	Dwarf_Off off = 0, next_off;
@@ -241,6 +292,16 @@ static void process_dwarf(Dwarf *dwarf, unsigned long long text_addr)
 			if (addr < text_addr)
 				continue;
 
+			/*
+			 * In module mode, keep only .text addresses.
+			 * In ET_REL .ko files, .init.text/.exit.text may
+			 * overlap with .text address ranges, so we must
+			 * explicitly check against the .text bounds.
+			 */
+			if (module_mode && text_section_end > text_section_start &&
+			    (addr < text_section_start || addr >= text_section_end))
+				continue;
+
 			{
 				unsigned long long raw_offset = addr - text_addr;
 
@@ -374,6 +435,63 @@ static void output_assembly(void)
 	printf("\n");
 }
 
+static void output_module_assembly(void)
+{
+	unsigned int filenames_size = 0;
+
+	for (unsigned int i = 0; i < num_files; i++)
+		filenames_size += strlen(files[i].name) + 1;
+
+	printf("/* SPDX-License-Identifier: GPL-2.0 */\n");
+	printf("/*\n");
+	printf(" * Automatically generated by scripts/gen_lineinfo --module\n");
+	printf(" * Do not edit.\n");
+	printf(" */\n\n");
+
+	printf("\t.section .mod_lineinfo, \"a\"\n\n");
+
+	/* Header: num_entries, num_files, filenames_size, reserved */
+	printf("\t.balign 4\n");
+	printf("\t.long %u\n", num_entries);
+	printf("\t.long %u\n", num_files);
+	printf("\t.long %u\n", filenames_size);
+	printf("\t.long 0\n\n");
+
+	/* addrs[] */
+	for (unsigned int i = 0; i < num_entries; i++)
+		printf("\t.long 0x%x\n", entries[i].offset);
+	if (num_entries)
+		printf("\n");
+
+	/* file_ids[] */
+	for (unsigned int i = 0; i < num_entries; i++)
+		printf("\t.short %u\n", entries[i].file_id);
+
+	/* Padding to align lines[] to 4 bytes */
+	if (num_entries & 1)
+		printf("\t.short 0\n");
+	if (num_entries)
+		printf("\n");
+
+	/* lines[] */
+	for (unsigned int i = 0; i < num_entries; i++)
+		printf("\t.long %u\n", entries[i].line);
+	if (num_entries)
+		printf("\n");
+
+	/* file_offsets[] */
+	for (unsigned int i = 0; i < num_files; i++)
+		printf("\t.long %u\n", files[i].str_offset);
+	if (num_files)
+		printf("\n");
+
+	/* filenames[] */
+	for (unsigned int i = 0; i < num_files; i++)
+		print_escaped_asciz(files[i].name);
+	if (num_files)
+		printf("\n");
+}
+
 int main(int argc, char *argv[])
 {
 	int fd;
@@ -381,8 +499,14 @@ int main(int argc, char *argv[])
 	Dwarf *dwarf;
 	unsigned long long text_addr;
 
+	if (argc >= 2 && !strcmp(argv[1], "--module")) {
+		module_mode = 1;
+		argv++;
+		argc--;
+	}
+
 	if (argc != 2) {
-		fprintf(stderr, "Usage: %s <vmlinux>\n", argv[0]);
+		fprintf(stderr, "Usage: %s [--module] <ELF file>\n", argv[0]);
 		return 1;
 	}
 
@@ -402,7 +526,18 @@ int main(int argc, char *argv[])
 		return 1;
 	}
 
-	text_addr = find_text_addr(elf);
+	if (module_mode) {
+		/*
+		 * .ko files are ET_REL after ld -r.  libdw applies
+		 * relocations using section addresses, so DWARF addresses
+		 * include the .text sh_addr.  Use .text sh_addr as the
+		 * base so offsets are .text-relative.
+		 */
+		find_text_section_range(elf);
+		text_addr = text_section_start;
+	} else {
+		text_addr = find_text_addr(elf);
+	}
 
 	dwarf = dwarf_begin_elf(elf, DWARF_C_READ, NULL);
 	if (!dwarf) {
@@ -428,7 +563,10 @@ int main(int argc, char *argv[])
 	fprintf(stderr, "lineinfo: %u entries, %u files\n",
 		num_entries, num_files);
 
-	output_assembly();
+	if (module_mode)
+		output_module_assembly();
+	else
+		output_assembly();
 
 	dwarf_end(dwarf);
 	elf_end(elf);
-- 
2.51.0


  parent reply	other threads:[~2026-03-03 18:22 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-03 18:21 [PATCH 0/3] kallsyms: embed source file:line info in kernel stack traces Sasha Levin
2026-03-03 18:21 ` [PATCH 1/3] " Sasha Levin
2026-03-04 20:17   ` Helge Deller
2026-03-05  2:18     ` Sasha Levin
2026-03-05 22:26       ` Helge Deller
2026-03-06  5:31         ` Randy Dunlap
2026-03-06 17:53           ` Helge Deller
2026-03-06  5:28   ` Randy Dunlap
2026-03-06 16:36   ` Petr Mladek
2026-03-06 17:14     ` Sasha Levin
2026-03-10 15:20       ` Petr Mladek
2026-03-11  0:58         ` Sasha Levin
2026-03-03 18:21 ` Sasha Levin [this message]
2026-03-03 18:21 ` [PATCH 3/3] kallsyms: delta-compress lineinfo tables for ~2.7x size reduction Sasha Levin
2026-03-03 21:25   ` Geert Uytterhoeven
2026-03-04  1:11     ` Sasha Levin
2026-03-11  3:34   ` Vivian Wang
2026-03-11  4:13     ` Vivian Wang
2026-03-11 14:49     ` Sasha Levin
2026-03-12  2:03       ` Vivian Wang
2026-03-12  2:18         ` Sasha Levin

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20260303182103.3523438-3-sashal@kernel.org \
    --to=sashal@kernel.org \
    --cc=James.Bottomley@HansenPartnership.com \
    --cc=akpm@linux-foundation.org \
    --cc=corbet@lwn.net \
    --cc=da.gomez@kernel.org \
    --cc=geert@linux-m68k.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=jgross@suse.com \
    --cc=kees@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kbuild@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-modules@vger.kernel.org \
    --cc=linux@leemhuis.info \
    --cc=masahiroy@kernel.org \
    --cc=mcgrof@kernel.org \
    --cc=nathan@kernel.org \
    --cc=nsc@kernel.org \
    --cc=peterz@infradead.org \
    --cc=petr.pavlu@suse.com \
    --cc=pmladek@suse.com \
    --cc=richard@nod.at \
    --cc=rostedt@goodmis.org \
    --cc=torvalds@linux-foundation.org \
    --cc=vbabka@kernel.org \
    /path/to/YOUR_REPLY

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

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