Re: [PATCH v3 2/4] kallsyms: extend lineinfo to loadable modules

From: Petr Pavlu

Date: Tue Mar 31 2026 - 12:30:09 EST


On 3/19/26 5:43 PM, Sasha Levin wrote:
>>> --- /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
>>
>> Modules are relocatable objects. The typical way to express a reference
>> from one section to data in another section is to use relocations.
>> Choosing to use an implicit base and resolved offsets means that the
>> code has trouble correctly referencing the .text section and can't
>> express line information data for other sections, such as .exit.text.
>
> I agree, which is why I scoped this just to .text :)
>
> My thinking was that using ELF relocations would add significant complexity to
> both the build tool and the runtime lookup path, which must remain NMI-safe and
> allocation-free.

I agree that using ELF relocations would be somewhat more complex. I had
a look at adding a new u32/u64 "target" member to the
mod_lineinfo_header struct which would point to the .text section in the
resulting module via an absolute relocation. The idea was to keep the
current offset and compression schema and only have a relocation that
identifies the associated text section. I hoped to replace the objcopy
steps in scripts/gen-mod-lineinfo.sh with an additional partial link to
combine ${KO} with ${KO}.lineinfo.o.

However, the issue with this approach is that the relocation in
${KO}.lineinfo.o cannot cross-reference the .text symbol in ${KO}. It
could be worked around by defining a start .text symbol in
scripts/module.lds.S but this doesn't scale well if more sections need
to be covered in the future. A separate utility would likely be required
to join the two objects properly together.

For the runtime part, I don't immediately see any issues. Relocations
are resolved by the module loader when a module is loaded, so this would
have no effect on the functionality being NMI-safe or allocation-free.

>>> +
>>> +#endif /* _LINUX_MOD_LINEINFO_H */
>>> diff --git a/include/linux/module.h b/include/linux/module.h
>>> index 14f391b186c6d..d23e0cd9c7210 100644
>>> --- a/include/linux/module.h
>>> +++ b/include/linux/module.h
>>> @@ -508,6 +508,8 @@ struct module {
>>> void *btf_data;
>>> void *btf_base_data;
>>> #endif
>>> + void *lineinfo_data; /* .mod_lineinfo section in MOD_RODATA */
>>> + unsigned int lineinfo_data_size;
>>
>> The lineinfo-specific members should be enclosed within the `#ifdef
>> CONFIG_KALLSYMS_LINEINFO_MODULES`.
>>
>> This will require module_lookup_lineinfo() to be conditionally compiled
>> based on CONFIG_KALLSYMS_LINEINFO_MODULES, with a dummy version provided
>> otherwise. Alternatively, accessors to module::lineinfo_data and
>> module::lineinfo_data_size that handle CONFIG_KALLSYMS_LINEINFO_MODULES
>> could be introduced in include/linux/module.h. For example, see
>> module_buildid() or is_livepatch_module.
>
> The struct members were deliberately left without #ifdef guards following Helge
> Deller's suggestion in the v1 review[1]. I don't really mind either way, but
> I'd prefer to have a consensus before flipping it back and forth.

The alternative suggestion I mentioned proposes using accessors to
module::lineinfo_data and module::lineinfo_data_size that would handle
CONFIG_KALLSYMS_LINEINFO_MODULES. This approach avoids having unused
data in the module struct when CONFIG_KALLSYMS_LINEINFO_MODULES=n, while
also allowing module_lookup_lineinfo() to use the
IS_ENABLED(CONFIG_KALLSYMS_LINEINFO_MODULES) pattern and be always
syntax-checked. Only the accessors won't be always syntax-checked but
their code should be trivial.

The file include/linux/module.h could contain something like this:

struct {
[...]
#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
void *lineinfo_data; /* .mod_lineinfo section in MOD_RODATA */
unsigned int lineinfo_data_size;
#endif
[...]
}

[...]

static inline void *module_lineinfo_data(struct module *mod, unsigned int *size)
{
#ifdef CONFIG_KALLSYMS_LINEINFO_MODULES
*size = mod->lineinfo_data_size;
return mod->lineinfo_data;
#else
*size = 0;
return NULL;
#endif
}

>>> @@ -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))
>>
>> Should this be 'if_changed_except.. vmlinux'?
>
> Lineinfo generation doesn't depend on vmlinux - it reads DWARF directly from
> the .ko file itself. Unlike BTF (which uses vmlinux as a base for
> deduplication), there's no vmlinux prerequisite to exclude.

The Makefile rule is:

%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE

If $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN)) then
$(objtree)/vmlinux is added as a prerequisite for the target. Since the
new lineinfo_ko call uses $(newer-prereqs), it is invoked even when
vmlinux changes.

However, the mentioned change to 'if_changed_except.. vmlinux' won't
quite work either because if_changed_except appears to have a similar
limitation as if_changed. It can be used only once per target due to
storing the executed command in a corresponding .cmd file.

Additionally, even the already present if_changed_except call for
ld_ko_o doesn't seem to work as intended because it excludes "./vmlinux"
but the same rule prerequisite is canonicalized to only "vmlinux". This
means modules are always linked even when only vmlinux changes. I'll
send a fix for this separately.

>
>>> @@ -194,9 +200,45 @@ static const char *make_relative(const char *path, const char *comp_dir)
>>> 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").
>>> + */
>>
>> When does this quirk occur? Is it a bug in libdw?
>
> This occurs with elfutils libdw when processing ET_REL .ko files. libdw
> constructs source paths by concatenating DW_AT_comp_dir with DW_AT_name from
> the compilation unit. For modules where both are relative paths with the same
> prefix, this can produce doubled results like "net/foo/bar.c/net/foo/bar.c". It
> appears to be a libdw quirk with ET_REL DWARF handling.

Thanks for the explanation.

-- Petr