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 <[email protected]>
---
 .../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 <[email protected]>
 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


Reply via email to