[PATCH 2/3] kallsyms: extend lineinfo to loadable modules
From: Sasha Levin
Date: Tue Mar 03 2026 - 13:26:11 EST
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@xxxxxxxxxx>
---
.../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@xxxxxxxxxx>
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