* Re: [PATCH v2] rust: module_param: return value by copy from `value`
From: Miguel Ojeda @ 2026-07-10 14:41 UTC (permalink / raw)
To: Alice Ryhl
Cc: Petr Pavlu, Andreas Hindborg, Miguel Ojeda, Luis Chamberlain,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Trevor Gross,
Danilo Krummrich, linux-modules, linux-kernel, rust-for-linux
In-Reply-To: <alEBInX9gD1M5NAr@google.com>
On Fri, Jul 10, 2026 at 4:26 PM Alice Ryhl <aliceryhl@google.com> wrote:
>
> I understand this is probably due to a new function in Rust stdlib that
> is no_return, so probably not caused by this patch even if this is
> triggering it.
I can take a look and perhaps push a patch via `rust-fixes`, which
would make it fixed by the time this one lands in the merge window.
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH v2] rust: module_param: return value by copy from `value`
From: Alice Ryhl @ 2026-07-10 14:26 UTC (permalink / raw)
To: Petr Pavlu
Cc: Miguel Ojeda, Andreas Hindborg, Miguel Ojeda, Luis Chamberlain,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Trevor Gross,
Danilo Krummrich, linux-modules, linux-kernel, rust-for-linux
In-Reply-To: <30e500ce-e9e6-43cc-bbdf-ded7c0ae56cd@suse.com>
On Fri, Jul 10, 2026 at 10:13:53AM +0200, Petr Pavlu wrote:
> On 7/9/26 4:13 PM, Miguel Ojeda wrote:
> > On Thu, Jul 9, 2026 at 1:48 PM Petr Pavlu <petr.pavlu@suse.com> wrote:
> >>
> >> @Miguel, please let me know if I should take this patch on modules-next,
> >> or if you'd prefer for it to go through the Rust tree.
> >
> > Please feel free to take it through modules-next, of course.
>
> Thanks for the confirmation. Queued now on modules-next for v7.3-rc1.
I'm seeing this error on x86 with latest nightly rust compiler:
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramaEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramhEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramhEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramiEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramiEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramjEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramjEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramlEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramlEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_parammEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_parammEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramsEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramsEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramtEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramtEB4_() falls through to next function _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramyEB4_()
rust/kernel.o: error: objtool: _RINvNtCs65fqAZscXCv_6kernel12module_param9set_paramyEB4_() falls through to next function _RINvNvNtCs65fqAZscXCv_6kernel6devres16register_foreign8callbackINtNtCsbtRzKR1rrUg_4core3pin3PinINtNtNtB6_5alloc4kbox3BoxNtNtNtB6_3pci3irq21IrqVectorRegistrationNtNtB1A_9allocator7KmallocEEEB6_()
rust/kernel.o: error: objtool: _RNvMNtCs65fqAZscXCv_6kernel6deviceNtB2_6Device10get_device() falls through to next function _RNvMNtCs65fqAZscXCv_6kernel6devresINtB2_6DevresNtNtB4_3i2c12RegistrationE19devres_node_releaseB4_()
rust/kernel.o: error: objtool: _RNvMNtCs65fqAZscXCv_6kernel6devresINtB2_6DevresNtNtB4_3i2c12RegistrationE19devres_node_releaseB4_() falls through to next function _RNvMNtCs65fqAZscXCv_6kernel6devresINtB2_6DevresNtNtB4_3i2c12RegistrationE21devres_node_free_nodeB4_()
I understand this is probably due to a new function in Rust stdlib that
is no_return, so probably not caused by this patch even if this is
triggering it.
Alice
^ permalink raw reply
* Re: [PATCH 1/1] rust: module_param: support bool parameters
From: Petr Pavlu @ 2026-07-10 14:10 UTC (permalink / raw)
To: Wenzhao Liao
Cc: mcgrof, da.gomez, samitolvanen, ojeda, linux-modules,
rust-for-linux, atomlin, boqun, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, dakr, linux-kernel, Greg KH
In-Reply-To: <20260411130254.3510128-2-wenzhaoliao@ruc.edu.cn>
On 4/11/26 3:02 PM, Wenzhao Liao wrote:
> Add support for parsing boolean module parameters in the Rust
> module! macro.
>
> Currently, only integer types are supported by the `module_param!`
> macros. This patch implements the `ModuleParam` trait for `bool`
> by delegating the string parsing to the existing C implementation
> via `kstrtobool_bytes()`. It also wires up `PARAM_OPS_BOOL` so that
> the Rust parameter system correctly links to the C `param_ops_bool`
> structure.
>
> For demonstration and verification, a boolean parameter is added
> to `samples/rust/rust_minimal.rs`.
>
> Assisted-by: Codex:GPT-5
> Signed-off-by: Wenzhao Liao <wenzhaoliao@ruc.edu.cn>
I've queued the patch on modules-next for v7.3-rc1, with the following
minor changes:
* Added a short description of the motivation to the commit message:
Support for boolean parameters will initially be used by the Rust
null block driver [1].
Link: https://lore.kernel.org/all/20260609-rnull-v6-19-rc5-send-v2-4-82c7404542e2@kernel.org/ [1]
* Rebased the patch on top of '[PATCH v2] rust: module_param: return
value by copy from `value`' [1], which required removing the
dereference from '*module_parameters::test_bool_parameter.value()' in
samples/rust/rust_minimal.rs.
[1] https://lore.kernel.org/linux-modules/20260601-modules-value-ref-v2-1-12ebbf0510c9@kernel.org/
--
Thanks,
Petr
^ permalink raw reply
* Re: [PATCH 2/2] module: Bring includes in linux/kmod.h up to date
From: Aaron Tomlin @ 2026-07-10 13:57 UTC (permalink / raw)
To: Petr Pavlu
Cc: Tony Luck, Borislav Petkov, Thomas Gleixner, Ingo Molnar,
Dave Hansen, x86, H. Peter Anvin, Philipp Reisner, Lars Ellenberg,
Christoph Böhmwalder, Jens Axboe, Johan Hovold, Alex Elder,
Greg Kroah-Hartman, Rafael J. Wysocki, Michal Januszewski,
Helge Deller, Alexander Viro, Christian Brauner, Jan Kara,
Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, Mark Fasheh,
Joel Becker, Joseph Qi, Tejun Heo, Johannes Weiner,
Michal Koutný, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Pavel Machek, Len Brown, Andrew Morton, Danilo Krummrich,
Nikolay Aleksandrov, Ido Schimmel, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, David Howells,
Jarkko Sakkinen, Paul Moore, James Morris, Serge E. Hallyn,
Kentaro Takeda, Tetsuo Handa, linux-edac, linux-kernel, drbd-dev,
linux-block, greybus-dev, linuxppc-dev, linux-acpi, linux-fbdev,
dri-devel, linux-fsdevel, linux-nfs, ocfs2-devel, cgroups,
linux-modules, linux-pm, driver-core, bridge, netdev, keyrings,
linux-security-module
In-Reply-To: <20260708154510.6794-3-petr.pavlu@suse.com>
On Wed, Jul 08, 2026 at 05:44:30PM +0200, Petr Pavlu wrote:
> Including linux/kmod.h alone results in 1.5 MB of preprocessed output, even
> though it provides only a few functions and macros.
>
> The header currently depends on:
>
> * __printf() -> linux/compiler_attributes.h,
> * ENOSYS -> linux/errno.h,
> * bool -> linux/types.h.
>
> Include only these files, reducing the preprocessed output to 10 kB.
>
> Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
> ---
> include/linux/kmod.h | 12 ++----------
> 1 file changed, 2 insertions(+), 10 deletions(-)
>
> diff --git a/include/linux/kmod.h b/include/linux/kmod.h
> index 9a07c3215389..b9474a62a568 100644
> --- a/include/linux/kmod.h
> +++ b/include/linux/kmod.h
> @@ -2,17 +2,9 @@
> #ifndef __LINUX_KMOD_H__
> #define __LINUX_KMOD_H__
>
> -/*
> - * include/linux/kmod.h
> - */
> -
> -#include <linux/umh.h>
> -#include <linux/gfp.h>
> -#include <linux/stddef.h>
> +#include <linux/compiler_attributes.h>
> #include <linux/errno.h>
> -#include <linux/compiler.h>
> -#include <linux/workqueue.h>
> -#include <linux/sysctl.h>
> +#include <linux/types.h>
>
> #ifdef CONFIG_MODULES
> /* modprobe exit status on success, -ve on error. Return value
> --
> 2.54.0
>
LGTM. Thank you.
Reviewed-by: Aaron Tomlin <atomlin@atomlin.com>
--
Aaron Tomlin
^ permalink raw reply
* Re: [PATCH 4/4] module: Limit ELF includes in linux/module.h to uapi/linux/elf.h
From: Aaron Tomlin @ 2026-07-10 13:56 UTC (permalink / raw)
To: Petr Pavlu
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev, Peter Huewe,
Jarkko Sakkinen, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Xin Li, H. Peter Anvin, Andy Lutomirski, Christian Borntraeger,
Sven Schnelle, Janosch Frank, Claudio Imbrenda, David Hildenbrand,
Jason Gunthorpe, linux-s390, kvm, linux-integrity, linux-modules,
linux-kernel
In-Reply-To: <20260707153819.4172541-5-petr.pavlu@suse.com>
[-- Attachment #1: Type: text/plain, Size: 1631 bytes --]
On Tue, Jul 07, 2026 at 05:37:07PM +0200, Petr Pavlu wrote:
> The linux/module.h header is included by every *.mod.c file to provide
> `struct module` and other related definitions. This makes it a "hot"
> header, so it should avoid pulling in unnecessary definitions.
>
> The header currently includes linux/elf.h but this is largely unnecessary
> because it requires only the base ELF structures, such as Elf_Ehdr and
> Elf_Sym, which are declared in uapi/linux/elf.h.
>
> Replace the linux/elf.h include with uapi/linux/elf.h. On x86_64_defconfig,
> including linux/module.h alone results in 2.2 MB of preprocessed data. This
> change reduces that to 1.5 MB.
>
> Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
> ---
> include/linux/module.h | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/include/linux/module.h b/include/linux/module.h
> index 7566815fabbe..7661faadbedd 100644
> --- a/include/linux/module.h
> +++ b/include/linux/module.h
> @@ -17,7 +17,6 @@
> #include <linux/cleanup.h>
> #include <linux/kmod.h>
> #include <linux/init.h>
> -#include <linux/elf.h>
> #include <linux/stringify.h>
> #include <linux/kobject.h>
> #include <linux/moduleparam.h>
> @@ -29,8 +28,9 @@
> #include <linux/srcu.h>
> #include <linux/static_call_types.h>
> #include <linux/dynamic_debug.h>
> -
> #include <linux/percpu.h>
> +
> +#include <uapi/linux/elf.h>
> #include <asm/module.h>
>
> #define MODULE_NAME_LEN __MODULE_NAME_LEN
> --
> 2.54.0
>
Thank you Petr. LGTM.
Reviewed-by: Aaron Tomlin <atomlin@atomlin.com>
--
Aaron Tomlin
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v2 1/2] module/kallsyms: fix nextval for data symbol lookup
From: Petr Pavlu @ 2026-07-10 12:36 UTC (permalink / raw)
To: Stanislaw Gruszka
Cc: linux-modules, Sami Tolvanen, Luis Chamberlain, linux-kernel,
linux-trace-kernel, live-patching, Daniel Gomez, Aaron Tomlin,
Steven Rostedt, Masami Hiramatsu, Jordan Rome, Viktor Malik
In-Reply-To: <20260327110005.16499-1-stf_xl@wp.pl>
On 3/27/26 12:00 PM, Stanislaw Gruszka wrote:
> The symbol lookup code assumes the queried address resides in either
> MOD_TEXT or MOD_INIT_TEXT. This breaks for addresses in other module
> memory regions (e.g. rodata or data), resulting in incorrect upper
> bounds and wrong symbol size.
>
> Select the module memory region the address belongs to instead of
> hardcoding text sections. Also initialize the lower bound to the start
> of that region, as searching from address 0 is unnecessary.
>
> Signed-off-by: Stanislaw Gruszka <stf_xl@wp.pl>
I've queued this first patch on modules-fixes.
--
Thanks,
Petr
^ permalink raw reply
* Re: [PATCH v7 0/4] kallsyms: embed source file:line info in kernel stack traces
From: Geert Uytterhoeven @ 2026-07-10 10:04 UTC (permalink / raw)
To: Sasha Levin
Cc: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, 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, Helge Deller,
Randy Dunlap, Laurent Pinchart, Vivian Wang, Zhen Lei,
Sami Tolvanen, linux-kernel, linux-kbuild, linux-modules,
linux-doc
In-Reply-To: <20260709163833.3851179-1-sashal@kernel.org>
Hi Sasha,
On Thu, 9 Jul 2026 at 18:38, Sasha Levin <sashal@kernel.org> wrote:
> Changes since v6
> ================
>
> - Address Sashiko AI review comments.
What does that mean?
Please list the changes, so reviewers know what to look at.
Thanks!
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* Re: [PATCH] module: validate string table section types
From: Markus Elfring @ 2026-07-10 8:23 UTC (permalink / raw)
To: Thiébaud Weksteen, linux-modules, Daniel Gomez,
Luis Chamberlain, Petr Pavlu, Sami Tolvanen
Cc: LKML, Aaron Tomlin, Siddharth Nayyar
In-Reply-To: <20260708012107.1621513-1-tweek@google.com>
…
> Validate that both string section headers are of type SHT_STRTAB before
> caching them.
How do you think about to add any tags (like “Fixes” and “Cc”) accordingly?
Regards,
Markus
^ permalink raw reply
* Re: [PATCH v2] rust: module_param: return value by copy from `value`
From: Petr Pavlu @ 2026-07-10 8:13 UTC (permalink / raw)
To: Miguel Ojeda
Cc: Andreas Hindborg, Miguel Ojeda, Luis Chamberlain, Daniel Gomez,
Sami Tolvanen, Aaron Tomlin, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
Danilo Krummrich, linux-modules, linux-kernel, rust-for-linux
In-Reply-To: <CANiq72m3w7L5CmAsFvOpSh_jNk4jL8+A4vz8GEp+E8FY=TJcRA@mail.gmail.com>
On 7/9/26 4:13 PM, Miguel Ojeda wrote:
> On Thu, Jul 9, 2026 at 1:48 PM Petr Pavlu <petr.pavlu@suse.com> wrote:
>>
>> @Miguel, please let me know if I should take this patch on modules-next,
>> or if you'd prefer for it to go through the Rust tree.
>
> Please feel free to take it through modules-next, of course.
Thanks for the confirmation. Queued now on modules-next for v7.3-rc1.
-- Petr
^ permalink raw reply
* Re: [PATCH v2] module: procfs: fix signed integer overflow in module_total_size()
From: Petr Pavlu @ 2026-07-10 7:36 UTC (permalink / raw)
To: Sami Tolvanen
Cc: Naveen Kumar Chaudhary, mcgrof, da.gomez, atomlin, linux-modules
In-Reply-To: <CABCJKuczwBmtKXiyzh31Efwbt6VfwUpStLPjXkUFNHwwSPQA5g@mail.gmail.com>
On 7/9/26 5:25 PM, Sami Tolvanen wrote:
> On Thu, Jul 9, 2026 at 1:45 AM Petr Pavlu <petr.pavlu@suse.com> wrote:
>>
>> On 6/7/26 6:18 AM, Naveen Kumar Chaudhary wrote:
>>> module_total_size() returns unsigned int but uses a signed int
>>> accumulator. While the result is numerically correct, the type
>>> mismatch is misleading.
>>>
>>> Change the accumulator to unsigned int to match the return type.
>>>
>>> Signed-off-by: Naveen Kumar Chaudhary <naveen.osdev@gmail.com>
>>
>> Queued on modules-next for v7.3-rc1.
>
> Note that the commit title still claims to fix a signed integer
> overflow, which isn't accurate. Otherwise LGTM.
Thanks for noticing this. I tweaked the title to:
module: procfs: use matching type for accumulator in module_total_size()
-- Petr
^ permalink raw reply
* Re: [PATCH v19 04/40] dept: tie to lockdep and IRQ tracing
From: Byungchul Park @ 2026-07-10 6:54 UTC (permalink / raw)
To: sashiko-reviews
Cc: linux-rt-devel, linux-kernel, dlemoal, cassel, tytso, Chuck Lever,
Anna Schumaker, linux-modules, Jeff Layton, ojeda, dri-devel,
linux-ide, kernel_team
In-Reply-To: <20260707073322.A9C851F000E9@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:22AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [Critical] dept_key_destroy() synchronously recycles dept_class objects to the lockless pool before the RCU grace period completes, causing UAF and list traversal corruption for concurrent lockless readers.
> - [High] rwsem_acquire_read and rwsem_acquire incorrectly use ldt_lock(), breaking reader-writer dependency tracking and causing false-positive deadlocks.
> - [High] ldt_init() is called before the `key` parameter is validated, causing DEPT to dereference a garbage pointer if `key` is NULL.
> - [Medium] dept_key_init() and dept_key_destroy() are called on static keys before validation, erroneously disabling DEPT or corrupting tracking state.
> - [Low] STATIC_LOCKDEP_MAP_INIT creates double-quoted strings for map names due to `#n` stringification in DEPT_MAP_INITIALIZER.
> --
>
> commit 6c1a95ee3182a2a82a1d61196d75acd387d7f3e3
> Author: Byungchul Park <byungchul@sk.com>
>
> dept: tie to lockdep and IRQ tracing
>
> This patch integrates DEPT (Dependency Tracker) into the lockdep framework
> and IRQ tracing paths. It modifies various locking primitives and lockdep
> interfaces to initialize and track DEPT maps alongside lockdep maps.
>
> > diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
> > index 621566345406d..60b17a2ff555f 100644
> > --- a/include/linux/lockdep.h
> > +++ b/include/linux/lockdep.h
> [ ... ]
> > @@ -428,7 +431,8 @@ enum xhlock_context_t {
> > * Note that _name must not be NULL.
> > */
> > #define STATIC_LOCKDEP_MAP_INIT(_name, _key) \
> > - { .name = (_name), .key = (void *)(_key), }
> > + { .name = (_name), .key = (void *)(_key), \
> > + .dmap = DEPT_MAP_INITIALIZER(_name, _key) }
>
> [Severity: Low]
> Since _name is usually passed as a string literal to
> STATIC_LOCKDEP_MAP_INIT, does passing it to DEPT_MAP_INITIALIZER, which
> uses the stringification operator on its first argument, result in
> double-quoted strings for map names in DEPT's debug output?
I will check it. Thanks.
> [ ... ]
> > -#define rwsem_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i)
> > -#define rwsem_acquire_nest(l, s, t, n, i) lock_acquire_exclusive(l, s, t, n, i)
> > -#define rwsem_acquire_read(l, s, t, i) lock_acquire_shared(l, s, t, NULL, i)
> > -#define rwsem_release(l, i) lock_release(l, i)
> [ ... ]
> > +#define rwsem_acquire(l, s, t, i) \
> > +do { \
> > + ldt_lock(&(l)->dmap, s, t, NULL, i); \
> > + lock_acquire_exclusive(l, s, t, NULL, i); \
> > +} while (0)
> > +#define rwsem_acquire_nest(l, s, t, n, i) \
> > +do { \
> > + ldt_lock(&(l)->dmap, s, t, n, i); \
> > + lock_acquire_exclusive(l, s, t, n, i); \
> > +} while (0)
> > +#define rwsem_acquire_read(l, s, t, i) \
> > +do { \
> > + ldt_lock(&(l)->dmap, s, t, NULL, i); \
> > + lock_acquire_shared(l, s, t, NULL, i); \
> > +} while (0)
>
> [Severity: High]
> Is it intentional that rwsem_acquire_read() uses ldt_lock() instead of
> ldt_rlock(), and rwsem_acquire() uses ldt_lock() instead of ldt_wlock()?
It's intended. rwsem is a queued lock for fairness. So it should be
considered as a normal lock to detect a potential deadlock.
> This appears to mistakenly model a shared lock as an exclusive lock. Does
> this cause DEPT to incorrectly model concurrent read locks as mutually
> exclusive, leading to false-positive circular dependency (deadlock) reports
> when concurrent readers take an rwsem and interact with other locks?
As I told you, rwsem is a queued lock. So this is not true.
> > diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
> > index 2d4c5bab5af88..dc97f2753ef81 100644
> > --- a/kernel/locking/lockdep.c
> > +++ b/kernel/locking/lockdep.c
> > @@ -1224,6 +1224,8 @@ void lockdep_register_key(struct lock_class_key *key)
> > struct hlist_head *hash_head;
> > struct lock_class_key *k;
> > unsigned long flags;
> >
> > + dept_key_init(&key->dkey);
> > +
> > if (WARN_ON_ONCE(static_obj(key)))
> > return;
>
> [Severity: Medium]
> In lockdep_register_key(), dept_key_init() is called before the
> static_obj() check. If a static key is improperly passed, lockdep gracefully
> rejects it by warning and returning early.
>
> However, processing a static key in dept_key_init() triggers a DEPT_STOP
> abort. Should the DEPT initialization occur after validating the key?
I will check it.
> [ ... ]
> > @@ -4940,6 +4950,8 @@ void lockdep_init_map_type(struct lockdep_map *lock, const char *name,
> > {
> > int i;
> >
> > + ldt_init(&lock->dmap, &key->dkey, subclass, name);
> > +
> > for (i = 0; i < NR_LOCKDEP_CACHING_CLASSES; i++)
> > lock->class_cache[i] = NULL;
>
> [Severity: High]
> In lockdep_init_map_type(), ldt_init() is called before the parameter
> validation checks for a NULL key further down in the function.
>
> If key is NULL, &key->dkey yields a non-NULL offset pointer. Does DEPT accept
> this garbage pointer in valid_key(), causing a kernel panic when
> check_new_class() dereferences it?
I will check it.
> [ ... ]
> > @@ -6588,6 +6608,8 @@ void lockdep_unregister_key(struct lock_class_key *key)
> > bool found = false;
> > bool need_callback = false;
> >
> > + dept_key_destroy(&key->dkey);
> > +
> > might_sleep();
> >
> > if (WARN_ON_ONCE(static_obj(key)))
> > return;
>
> [Severity: Medium]
> Similarly to lockdep_register_key(), dept_key_destroy() is called here
> unconditionally before the static_obj() check. If a driver incorrectly calls
> dynamic lock destruction on a static key, it eagerly destroys its DEPT state
> while lockdep retains it. Should this also be moved after the static_obj()
> check?
Sure. I will.
> [Severity: Critical]
> Additionally, in dept_key_destroy() called here, the function removes a class
> from the RCU hash table and drops its refcount to 0 via del_class(). This
> recycles the object to the DEPT pool before synchronize_rcu() is called.
No.
del_class() never drops refcount to 0. That happens only through:
disconnect_class()->call_rcu(del_dep_rcu)
// after the grace period
del_dep_rcu()->del_dep()->put_ecxt()->put_class()
del_dep_rcu()->del_dep()->put_wait()->put_class()
So it's safe.
> Can a concurrent lockless reader in hash_lookup_class() traversing the same
> bucket via hlist_for_each_entry_rcu() access this recycled object, leading to
> a use-after-free and hash list corruption?
No.
Byungchul
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=4
^ permalink raw reply
* Re: [PATCH v19 13/40] dept: apply sdt_might_sleep_{start, end}() to hashed-waitqueue wait
From: Byungchul Park @ 2026-07-10 6:42 UTC (permalink / raw)
To: sashiko-reviews
Cc: Chuck Lever, tytso, Anna Schumaker, linux-kernel, dlemoal,
dri-devel, Jeff Layton, linux-rt-devel, ojeda, linux-modules,
cassel, linux-ide, kernel_team
In-Reply-To: <20260707073317.37C831F000E9@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:16AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] The `sdt_might_sleep_end()` macro is bypassed when `___wait_var_event` is interrupted, leaving the DEPT tracker in an inconsistent state.
> - [Medium] The `sdt_might_sleep_end()` macro is bypassed when `___wait_event` is interrupted, leaving the DEPT tracker in an inconsistent state.
> --
>
> commit 0bd2d42866fa6b852ee5317edcaf6890a90c3c65
> Author: Byungchul Park <byungchul@sk.com>
>
> dept: apply sdt_might_sleep_{start,end}() to hashed-waitqueue wait
>
> Make dept able to track dependencies by hashed-waitqueue waits.
>
> > diff --git a/include/linux/wait_bit.h b/include/linux/wait_bit.h
> > index 9e29d79fc790a..179a616ad245a 100644
> > --- a/include/linux/wait_bit.h
> > +++ b/include/linux/wait_bit.h
> [ ... ]
> > @@ -257,6 +258,7 @@ extern wait_queue_head_t *__var_waitqueue(void *p);
> > struct wait_bit_queue_entry __wbq_entry; \
> > long __ret = ret; /* explicit shadow */ \
> > \
> > + sdt_might_sleep_start(NULL); \
> > init_wait_var_entry(&__wbq_entry, var, \
> > exclusive ? WQ_FLAG_EXCLUSIVE : 0); \
> > for (;;) { \
> > @@ -274,6 +276,7 @@ extern wait_queue_head_t *__var_waitqueue(void *p);
> > cmd; \
> > } \
> > finish_wait(__wq_head, &__wbq_entry.wq_entry); \
> > + sdt_might_sleep_end(); \
> > __out: __ret; \
> > })
>
> [Severity: Medium]
> If this wait is interrupted by a signal, the loop executes a goto __out
> which bypasses sdt_might_sleep_end(). Does this leave the dept tracker
> in an inconsistent state where the task's stage remains permanently armed?
>
> This could cause false positive dependency tracking splats on subsequent
> waits, or fail to record new dependencies because the tracker is blinded by
> the stale stage.
>
> I noticed the exact same issue is present in ___wait_event():
>
> include/linux/wait.h:___wait_event() {
> ...
> if (___wait_is_interruptible(state) && __int) {
> __ret = __int;
> goto __out;
> }
> ...
> sdt_might_sleep_end();
> __out: __ret;
> })
I will fix it.
> [Severity: Medium]
> Does ___wait_event() also skip sdt_might_sleep_end() when interrupted?
> By missing the paired cleanup call, the task's stage could remain
> permanently armed here as well.
>
> Would it make sense to move the __out label above sdt_might_sleep_end(),
> or otherwise ensure the state is cleaned up on the interrupted paths for
> both of these macros?
Right. I will fix it.
Byungchul
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=13
^ permalink raw reply
* Re: [PATCH v19 19/40] dept: apply timeout consideration to hashed-waitqueue wait
From: Byungchul Park @ 2026-07-10 6:41 UTC (permalink / raw)
To: sashiko-reviews
Cc: ojeda, linux-ide, linux-kernel, Anna Schumaker, Chuck Lever,
dlemoal, linux-rt-devel, Jeff Layton, tytso, cassel,
linux-modules, dri-devel, kernel_team
In-Reply-To: <20260707073337.097CA1F000E9@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:36AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Missing `sdt_might_sleep_end()` cleanup in the error path of wait macros (e.g., `___wait_var_event`, `___wait_event`, `___swait_event`) bypasses `dept_clean_stage()`, leaking the staged wait event state and corrupting DEPT tracking.
> --
>
> commit cf04720896ab037609320947c88e8faa57dbddd7
> Author: Byungchul Park <byungchul@sk.com>
>
> dept: apply timeout consideration to hashed-waitqueue wait
>
> Now that CONFIG_DEPT_AGGRESSIVE_TIMEOUT_WAIT was introduced, apply the
> consideration to hashed-waitqueue wait, assuming an input 'ret' in
> ___wait_var_event() macro is used as a timeout value.
>
> > diff --git a/include/linux/wait_bit.h b/include/linux/wait_bit.h
> > index 179a616ad245a..9885ac4e1ded5 100644
> > --- a/include/linux/wait_bit.h
> > +++ b/include/linux/wait_bit.h
> > @@ -258,7 +258,7 @@ extern wait_queue_head_t *__var_waitqueue(void *p);
> > struct wait_bit_queue_entry __wbq_entry; \
> > long __ret = ret; /* explicit shadow */ \
> > \
> > - sdt_might_sleep_start(NULL); \
> > + sdt_might_sleep_start_timeout(NULL, __ret); \
>
> [Severity: High]
> Does this error path properly clean up the dept wait state?
>
> Lower in the ___wait_var_event() macro, if a signal is received it jumps to
> __out:
>
> if (___wait_is_interruptible(state) && __int) { \
> __ret = __int; \
> goto __out; \
> } \
>
> ...
>
> sdt_might_sleep_end(); \
> __out: __ret; \
>
> Because the goto __out branch skips sdt_might_sleep_end(), does it leave a
> dangling stage context in the dept subsystem's tracking graph?
>
> If the stage context remains populated, could a subsequent wait on the same
> task be incorrectly seen as a nested stage, potentially corrupting dept
> tracking or leading to false deadlock reports?
I will fix it.
> (Note: A similar goto __out pattern skipping sdt_might_sleep_end() also
> exists in ___wait_event() in include/linux/wait.h and ___swait_event() in
> include/linux/swait.h).
I will fix it.
Byungchul
>
> > init_wait_var_entry(&__wbq_entry, var, \
> > exclusive ? WQ_FLAG_EXCLUSIVE : 0); \
> > for (;;) { \
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=19
^ permalink raw reply
* Re: [PATCH v19 07/40] dept: distinguish each work from another
From: Byungchul Park @ 2026-07-10 6:40 UTC (permalink / raw)
To: sashiko-reviews
Cc: linux-rt-devel, cassel, linux-ide, tytso, linux-kernel,
Anna Schumaker, linux-modules, Chuck Lever, ojeda, dlemoal,
dri-devel, Jeff Layton, kernel_team
In-Reply-To: <20260707073334.0E5A61F000E9@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:33AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] `dept_update_cxt()` hardcodes the process context ID, failing to track bottom-half (BH) work items in softirq context and corrupting the context of preempted tasks.
> --
>
> commit b8dda7d95e5377770d28c6712cf8fe7fafa98ade
> Author: Byungchul Park <byungchul@sk.com>
>
> dept: distinguish each work from another
>
> This commit updates DEPT to assign a different context ID to each work item.
> Since workqueues provide their own concurrency control, wait operations in a
> work item do not prevent events in other works from executing.
>
> > diff --git a/kernel/workqueue.c b/kernel/workqueue.c
> > index c6ea96d5b7167..4a4075d0697c7 100644
> > --- a/kernel/workqueue.c
> > +++ b/kernel/workqueue.c
> [ ... ]
> > @@ -3204,6 +3205,8 @@ __acquires(&pool->lock)
> >
> > lockdep_copy_map(&lockdep_map, &work->lockdep_map);
> > #endif
> > + dept_update_cxt();
> > +
>
> [Severity: Medium]
> Will this unconditionally modify the process context ID, even when tracking
> bottom-half (BH) work items running in softirq context?
Am I missing something? AFAIK, workqueue's workers run in process
context, not softirq context.
> When a BH workqueue executes multiple work items in a single softirq batch,
> process_one_work() executes them and calls dept_update_cxt(). However,
> dept_update_cxt() is implemented as:
It's becasue its not racing with other contexts.
> kernel/dependency/dept.c:dept_update_cxt() {
> struct dept_task *dt = dept_task();
> dt->cxt_id[DEPT_CXT_PROCESS] += 1UL << DEPT_CXTS_NR;
> }
>
> This hardcodes the increment to DEPT_CXT_PROCESS rather than checking
> cur_cxt() to increment DEPT_CXT_SIRQ. Does this cause DEPT to fail in
> distinguishing sequential BH work items and falsely report dependencies?
Are BH works running in softirq context? I will check it.
> Also, can this corrupt the continuous dependency tracking context of
> whatever task was preempted by the softirq?
Process context's cxt_id is referred only in the process context, and
irq context's cxt_id is referred only in the irq context. So no problem.
Byungchul
> > /* ensure we're on the correct CPU */
> > WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
> > raw_smp_processor_id() != pool->cpu);
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=7
^ permalink raw reply
* Re: [PATCH v19 28/40] dept: assign unique dept_key to each distinct dma fence caller
From: Byungchul Park @ 2026-07-10 6:24 UTC (permalink / raw)
To: sashiko-reviews
Cc: Chuck Lever, dlemoal, Jeff Layton, linux-rt-devel, linux-modules,
dri-devel, linux-ide, tytso, cassel, ojeda, Anna Schumaker,
linux-kernel, kernel_team
In-Reply-To: <20260707073344.7392B1F000E9@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:44AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] The newly introduced statement expression macros for DMA fence waiting evaluate their timeout argument (`t`) multiple times.
> - [Medium] Local variable shadowing in the statement expression macros due to the unhygienic `__ret` variable declaration.
> --
>
> commit 4d4caad8d173eededcba1493c060f5ddecbd6b70
> Author: Byungchul Park <byungchul@sk.com>
>
> dept: assign unique dept_key to each distinct dma fence caller
>
> dma fence can be used at various points in the code and it's very hard
> to distinguish dma fences between different usages. Using a single
> dept_key for all the dma fences could trigger false positive reports.
>
> > diff --git a/include/linux/dma-fence.h b/include/linux/dma-fence.h
> > index d4c92fd350924..3732849a30b7e 100644
> > --- a/include/linux/dma-fence.h
> > +++ b/include/linux/dma-fence.h
>
> [ ... ]
>
> > @@ -370,8 +370,22 @@ bool dma_fence_check_and_signal_locked(struct dma_fence *fence);
> > void dma_fence_signal_locked(struct dma_fence *fence);
> > void dma_fence_signal_timestamp(struct dma_fence *fence, ktime_t timestamp);
> > void dma_fence_signal_timestamp_locked(struct dma_fence *fence, ktime_t timestamp);
> > -signed long dma_fence_default_wait(struct dma_fence *fence,
> > +signed long __dma_fence_default_wait(struct dma_fence *fence,
> > bool intr, signed long timeout);
> > +
> > +/*
> > + * Associate every caller with its own dept map.
> > + */
> > +#define dma_fence_default_wait(f, intr, t) \
> > +({ \
> > + signed long __ret; \
>
> [Severity: Medium]
> Could this local variable declaration lead to shadowing issues? If a caller
> passes arguments that reference a variable named __ret in their own scope,
> this uninitialized local variable will shadow the caller's variable, causing
> undefined behavior.
I will fix it.
> > + \
> > + sdt_might_sleep_start_timeout(NULL, t); \
> > + __ret = __dma_fence_default_wait(f, intr, t); \
>
> [Severity: Medium]
> Is it safe to evaluate the timeout argument t multiple times here?
>
> If a caller passes an expression with side effects, such as timeout--, it
> would be evaluated twice, which could lead to incorrect timeout logic.
Sure, I will fix it.
> > + sdt_might_sleep_end(); \
> > + __ret; \
> > +})
> > +
>
> [ ... ]
>
> > @@ -628,12 +642,37 @@ static inline ktime_t dma_fence_timestamp(struct dma_fence *fence)
> > return fence->timestamp;
> > }
> >
> > -signed long dma_fence_wait_timeout(struct dma_fence *,
> > +signed long __dma_fence_wait_timeout(struct dma_fence *,
> > bool intr, signed long timeout);
> > -signed long dma_fence_wait_any_timeout(struct dma_fence **fences,
> > +signed long __dma_fence_wait_any_timeout(struct dma_fence **fences,
> > uint32_t count,
> > bool intr, signed long timeout,
> > uint32_t *idx);
> > +/*
> > + * Associate every caller with its own dept map.
> > + */
> > +#define dma_fence_wait_timeout(f, intr, t) \
> > +({ \
> > + signed long __ret; \
> > + \
> > + sdt_might_sleep_start_timeout(NULL, t); \
> > + __ret = __dma_fence_wait_timeout(f, intr, t); \
>
> [Severity: Medium]
> Does this code also suffer from the same macro hygiene issues with shadowing
> __ret and evaluating the t argument twice?
Okay, I will fix it.
> > + sdt_might_sleep_end(); \
> > + __ret; \
> > +})
> > +
> > +/*
> > + * Associate every caller with its own dept map.
> > + */
> > +#define dma_fence_wait_any_timeout(fpp, count, intr, t, idx) \
> > +({ \
> > + signed long __ret; \
> > + \
> > + sdt_might_sleep_start_timeout(NULL, t); \
> > + __ret = __dma_fence_wait_any_timeout(fpp, count, intr, t, idx); \
>
> [Severity: Medium]
> Would it be safer to use a more unique variable name instead of __ret, and
Suggestion?
> evaluate t only once before passing it to sdt_might_sleep_start_timeout()
> and __dma_fence_wait_any_timeout(), similar to the other macros?
Sure.
Byungchul
> > + sdt_might_sleep_end(); \
> > + __ret; \
> > +})
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=28
^ permalink raw reply
* Re: [PATCH v19 34/40] rcu/update: fix same dept key collision between various types of RCU
From: Byungchul Park @ 2026-07-10 6:20 UTC (permalink / raw)
To: sashiko-reviews
Cc: dri-devel, linux-ide, linux-kernel, dlemoal, cassel,
Anna Schumaker, ojeda, tytso, Chuck Lever, Jeff Layton,
linux-rt-devel, linux-modules, kernel_team
In-Reply-To: <20260707073353.1CD771F00A3D@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:52AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Unused struct member `dkey` inflates stack footprint
> --
>
> commit de6aa9797881496b4a8d7d66626b1bfd19cfdd54
> Author: Yunseong Kim <ysk@kzalloc.com>
>
> rcu/update: fix same dept key collision between various types of RCU
>
> This commit addresses a false positive circular dependency report in dept
> by assigning separate dept keys and maps to each struct rcu_synchronize
> execution context.
>
> > diff --git a/include/linux/rcupdate_wait.h b/include/linux/rcupdate_wait.h
> > --- a/include/linux/rcupdate_wait.h
> > +++ b/include/linux/rcupdate_wait.h
> > @@ -19,17 +19,20 @@ struct rcu_synchronize {
> >
> > /* This is for debugging. */
> > struct rcu_gp_oldstate oldstate;
> > + struct dept_map dmap;
> > + struct dept_key dkey;
>
> [Severity: Low]
> Does this code ever use the dkey member of struct rcu_synchronize?
>
> The _wait_rcu_gp() macro defines its own static struct dept_key __key,
> which it passes as an argument to __wait_rcu_gp(). This leaves the dkey
> member of struct rcu_synchronize completely unused and uninitialized, while
> needlessly increasing the stack footprint of the rs_array allocated in the
> macro.
I will fix it.
Byungchul
> > };
> > void wakeme_after_rcu(struct rcu_head *head);
> >
> > void __wait_rcu_gp(bool checktiny, unsigned int state, int n, call_rcu_func_t *crcu_array,
> > - struct rcu_synchronize *rs_array);
> > + struct rcu_synchronize *rs_array, struct dept_key *dkey);
> >
> > #define _wait_rcu_gp(checktiny, state, ...) \
> > -do { \
> > - call_rcu_func_t __crcu_array[] = { __VA_ARGS__ }; \
> > - struct rcu_synchronize __rs_array[ARRAY_SIZE(__crcu_array)]; \
> > - __wait_rcu_gp(checktiny, state, ARRAY_SIZE(__crcu_array), __crcu_array, __rs_array); \
> > +do { \
> > + call_rcu_func_t __crcu_array[] = { __VA_ARGS__ }; \
> > + static struct dept_key __key; \
> > + struct rcu_synchronize __rs_array[ARRAY_SIZE(__crcu_array)]; \
> > + __wait_rcu_gp(checktiny, state, ARRAY_SIZE(__crcu_array), __crcu_array, __rs_array, &__key); \
> > } while (0)
>
> [ ... ]
>
> > diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
> > --- a/kernel/rcu/update.c
> > +++ b/kernel/rcu/update.c
>
> [ ... ]
>
> > @@ -426,7 +426,8 @@ void __wait_rcu_gp(bool checktiny, unsigned int state, int n, call_rcu_func_t *c
> > break;
> > if (j == i) {
> > init_rcu_head_on_stack(&rs_array[i].head);
> > - init_completion(&rs_array[i].completion);
> > + sdt_map_init_key(&rs_array[i].dmap, dkey);
> > + init_completion_dmap(&rs_array[i].completion, &rs_array[i].dmap);
> > (crcu_array[i])(&rs_array[i].head, wakeme_after_rcu);
> > }
> > }
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=34
^ permalink raw reply
* Re: [PATCH v19 32/40] completion, dept: introduce init_completion_dmap() API
From: Byungchul Park @ 2026-07-10 6:12 UTC (permalink / raw)
To: sashiko-reviews
Cc: dlemoal, Jeff Layton, linux-modules, dri-devel, linux-rt-devel,
cassel, linux-kernel, tytso, Chuck Lever, linux-ide,
Anna Schumaker, ojeda, kernel_team
In-Reply-To: <20260707073350.0965F1F00A3E@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:49AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] Double evaluation of arguments in wait_for_completion macros leads to skipped array indices and memory corruption.
> - [Medium] Unconditional struct growth of a widely used primitive for a debug feature.
> - [Medium] Wait event macros skip calling sdt_might_sleep_end() on signal interruptions, leaking DEPT tracking state and stacks.
> --
>
> commit 5b626a37ee940081b6d37187cde51c2f272e370c
> Author: Byungchul Park <byungchul@sk.com>
>
> completion, dept: introduce init_completion_dmap() API
>
> Currently, dept uses dept's map embedded in task_struct to track
> dependencies related to wait_for_completion() and its family. So it
> doesn't need an explicit map basically.
>
> However, for those who want to set the maps with customized class or
> key, introduce a new API to use external maps.
>
> > diff --git a/include/linux/completion.h b/include/linux/completion.h
> > index 4d8fb1d95c0a6..e50f7d9b4b974 100644
> > --- a/include/linux/completion.h
> > +++ b/include/linux/completion.h
> > @@ -27,17 +27,15 @@
> > struct completion {
> > unsigned int done;
> > struct swait_queue_head wait;
> > + struct dept_map *dmap;
> > };
>
> [Severity: Medium]
> Since struct completion is heavily embedded in many kernel data structures,
> does unconditionally adding the dmap pointer increase the global memory
> footprint even on production non-debug kernels? Should this new field be
> guarded by an #ifdef?
Good point. I will fix it, but with dkey. It will look like:
+ struct dept_key_ptr dkey_ptr;
> [ ... ]
>
> > @@ -136,13 +140,13 @@ extern void complete_all(struct completion *);
> >
> > #define wait_for_completion(x) \
> > ({ \
> > - sdt_might_sleep_start_timeout(NULL, -1L); \
> > + sdt_might_sleep_start_timeout((x)->dmap, -1L); \
> > __wait_for_completion(x); \
> > sdt_might_sleep_end(); \
> > })
>
> [Severity: High]
> Does evaluating the argument x multiple times in this macro lead to issues
> if callers pass an expression with side effects?
>
> For example, if a caller like mlx5_vdpa_exec_async_cmds() does:
>
> while (completed < issued)
> wait_for_completion(&cmds[completed++].cmd_done);
>
> Would completed++ be executed twice per iteration, skipping array elements
> and potentially causing out-of-bounds memory accesses?
Sure. I will fix it.
> [ ... ]
>
> [Severity: Medium]
> Do wait event macros such as ___wait_event() in include/linux/wait.h skip
> calling sdt_might_sleep_end() when interrupted by a signal?
>
> In include/linux/wait.h:___wait_event():
>
> if (___wait_is_interruptible(state) && __int) { \
> __ret = __int; \
> goto __out; \
> }
> ...
> sdt_might_sleep_end(); \
> __out: __ret; \
> })
>
> If the wait is interrupted by a signal, it escapes via goto __out. Because
> __out is placed after sdt_might_sleep_end(), it seems the cleanup is
> skipped.
>
> Does this permanently leak the tracking state and its underlying stack
> objects?
I will fix it.
Byungchul
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=32
^ permalink raw reply
* Re: [PATCH v19 29/40] dept: make dept aware of lockdep_set_lock_cmp_fn() annotation
From: Byungchul Park @ 2026-07-10 6:04 UTC (permalink / raw)
To: sashiko-reviews
Cc: linux-modules, Chuck Lever, Anna Schumaker, dri-devel,
linux-kernel, linux-rt-devel, dlemoal, cassel, tytso, Jeff Layton,
ojeda, linux-ide, kernel_team
In-Reply-To: <20260707073345.A320A1F00A3D@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:45AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] DEPT falsely reports a deadlock when a task sequentially acquires three or more locks of the same class (or interleaves them with other locks), because the `lockdep_cmp_fn` bypass is improperly restricted to only the immediately preceding lock.
> --
>
> commit d03c5b926a945b89f5a90fd98986386e113df5af
> Author: Byungchul Park <byungchul@sk.com>
>
> dept: make dept aware of lockdep_set_lock_cmp_fn() annotation
>
> This commit updates the DEPT (Dependency Tracker) subsystem to respect the
> lockdep_set_lock_cmp_fn() annotation. It allows DEPT to avoid false positive
> deadlock reports by using the custom comparison function when evaluating
> dependencies between locks of the same class.
>
> > diff --git a/kernel/dependency/dept.c b/kernel/dependency/dept.c
> > --- a/kernel/dependency/dept.c
> > +++ b/kernel/dependency/dept.c
>
> [ ... ]
>
> > @@ -1658,8 +1688,13 @@ static void add_wait(struct dept_class *c, unsigned long ip,
> > if (!eh->ecxt)
> > continue;
> >
> > - if (eh->ecxt->class != c || eh->sub_l == sub_l)
> > - add_dep(eh->ecxt, w);
> > + if (eh->ecxt->class == c && eh->sub_l != sub_l)
> > + continue;
> > +
> > + if (i == dt->ecxt_held_pos - 1 && lockdep_cmp_fn(eh->map, m))
> > + continue;
>
> [Severity: Medium]
> Will this condition incorrectly bypass the comparison for earlier locks in
> the chain?
>
> By restricting the lockdep_cmp_fn() check to only the most recently
> acquired lock (i == dt->ecxt_held_pos - 1), it seems we might miss valid
> comparisons for older contexts.
No.
It's how lockdep works and it's intended by lockdep. The temporary code
just follows the rule.
> For example, if a task acquires lock A1, then lock B, and then lock A2
> (where A1 and A2 are the same class), the check against A1 will be skipped
> because A1 is no longer at dt->ecxt_held_pos - 1.
>
> This would result in a call to add_dep(A1, A2), creating a
> self-referential class dependency that triggers a false positive deadlock
> report.
No.
It's a real deadlock.
> Should lockdep_cmp_fn() be allowed to evaluate against any previously held
> lock in the chain, rather than just the immediate predecessor?
No.
Check the lockdep code first. See check_deadlock() and check_prev_add().
Byungchul
> > +
> > + add_dep(eh->ecxt, w);
> > }
> >
> > wg = next_wgen();
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=29
^ permalink raw reply
* Re: [PATCH v19 38/40] mm: percpu: increase PERCPU_DYNAMIC_SIZE_SHIFT on DEPT and large PAGE_SIZE
From: Byungchul Park @ 2026-07-10 5:55 UTC (permalink / raw)
To: sashiko-reviews
Cc: Jeff Layton, ojeda, tytso, Chuck Lever, Anna Schumaker,
linux-kernel, cassel, linux-rt-devel, dri-devel, dlemoal,
linux-ide, linux-modules, kernel_team
In-Reply-To: <20260707073357.1DE2D1F00A3F@smtp.kernel.org>
On Tue, Jul 07, 2026 at 07:33:56AM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The patch addresses a compile-time BUILD_BUG_ON failure that no longer exists, referencing a deleted structure (`struct kmem_cache_cpu`) and function (`alloc_kmem_cache_cpus()`). Both have been removed from the upstream kernel. The only remaining BUILD_BUG_ON checking `PERCPU_DYNAMIC_EARLY_SIZE` in SLUB is in `alloc_kmem_cache_stats()`, which evaluates `sizeof(struct kmem_cache_stats)`. Because `struct kmem_cache_stats` consists solely of an integer array, its size is entirely unaffected by `CONFIG_DEPT` or `local_lock_t`. Thus, this patch needlessly increases the dynamically reserved per-CPU memory based on an obsolete and misleading justification.
> --
>
> commit 279fb8fb2919a407c18ab625ec67d2a3430b7b4d
> Author: Byungchul Park <byungchul@sk.com>
>
> mm: percpu: increase PERCPU_DYNAMIC_SIZE_SHIFT on DEPT and large PAGE_SIZE
>
> This commit increases PERCPU_DYNAMIC_SIZE_SHIFT to 11 when both CONFIG_DEPT
> and a PAGE_SIZE larger than 4KiB are enabled. It aims to prevent a build
> failure related to the size of struct kmem_cache_cpu.
>
> > Yunseong reported a build failure due to the BUILD_BUG_ON() statement in
> > alloc_kmem_cache_cpus(). In the following test:
> >
> > PERCPU_DYNAMIC_EARLY_SIZE < NR_KMALLOC_TYPES * KMALLOC_SHIFT_HIGH * sizeof(struct kmem_cache_cpu)
>
> [Severity: Medium]
> Are we referencing structures and functions that have been removed from the
> upstream kernel?
>
> Both alloc_kmem_cache_cpus() and struct kmem_cache_cpu no longer exist
> upstream. The only remaining BUILD_BUG_ON() checking PERCPU_DYNAMIC_EARLY_SIZE
> in SLUB is in alloc_kmem_cache_stats():
>
> mm/slub.c:alloc_kmem_cache_stats() {
> BUILD_BUG_ON(PERCPU_DYNAMIC_EARLY_SIZE <
> NR_KMALLOC_TYPES * KMALLOC_SHIFT_HIGH *
> sizeof(struct kmem_cache_stats));
> }
>
> Since struct kmem_cache_stats consists solely of an integer array, its size
> is entirely unaffected by CONFIG_DEPT or local_lock_t. Does this mean the
> increase in dynamically reserved per-CPU memory is no longer needed?
Agree. It seems no longer needed. I will fix it.
Byungchul
> > diff --git a/include/linux/percpu.h b/include/linux/percpu.h
> > index 85bf8dd9f0874..dd74321d4bbd0 100644
> > --- a/include/linux/percpu.h
> > +++ b/include/linux/percpu.h
> > @@ -43,7 +43,11 @@
> > # define PERCPU_DYNAMIC_SIZE_SHIFT 12
> > #endif /* LOCKDEP and PAGE_SIZE > 4KiB */
> > #else
> > +#if defined(CONFIG_DEPT) && !defined(CONFIG_PAGE_SIZE_4KB)
> > +#define PERCPU_DYNAMIC_SIZE_SHIFT 11
> > +#else
> > #define PERCPU_DYNAMIC_SIZE_SHIFT 10
> > +#endif /* DEPT and PAGE_SIZE > 4KiB */
> > #endif
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20260706061928.66713-1-byungchul@sk.com?part=38
^ permalink raw reply
* Re: [PATCH v19 31/40] dept: assign unique dept_key to each distinct wait_for_completion() caller
From: Byungchul Park @ 2026-07-10 5:53 UTC (permalink / raw)
To: Gary Guo
Cc: linux-kernel, max.byungchul.park, kernel_team, torvalds,
damien.lemoal, linux-ide, adilger.kernel, linux-ext4, mingo,
peterz, will, tglx, rostedt, joel, sashal, daniel.vetter,
duyuyang, johannes.berg, tj, tytso, willy, david, amir73il,
gregkh, kernel-team, linux-mm, akpm, mhocko, minchan, hannes,
vdavydov.dev, sj, jglisse, dennis, cl, penberg, rientjes, vbabka,
ngupta, linux-block, josef, linux-fsdevel, jack, jlayton,
dan.j.williams, hch, djwong, dri-devel, rodrigosiqueiramelo,
melissa.srw, hamohammed.sa, harry.yoo, chris.p.wilson,
gwan-gyeong.mun, boqun.feng, longman, yunseong.kim, ysk,
yeoreum.yun, netdev, matthew.brost, her0gyugyu, corbet,
catalin.marinas, bp, x86, hpa, luto, sumit.semwal, gustavo,
christian.koenig, andi.shyti, arnd, lorenzo.stoakes, Liam.Howlett,
rppt, surenb, mcgrof, petr.pavlu, da.gomez, samitolvanen, paulmck,
frederic, neeraj.upadhyay, joelagnelf, josh, urezki,
mathieu.desnoyers, jiangshanlai, qiang.zhang, juri.lelli,
vincent.guittot, dietmar.eggemann, bsegall, mgorman, vschneid,
chuck.lever, neil, okorniev, Dai.Ngo, tom, trondmy, anna, kees,
bigeasy, clrkwllms, mark.rutland, ada.coupriediaz,
kristina.martsenko, wangkefeng.wang, broonie, kevin.brodsky, dwmw,
shakeel.butt, ast, ziy, yuzhao, baolin.wang, usamaarif642,
joel.granados, richard.weiyang, geert+renesas, tim.c.chen, linux,
alexander.shishkin, lillian, chenhuacai, francesco,
guoweikang.kernel, link, jpoimboe, masahiroy, brauner,
thomas.weissschuh, oleg, mjguzik, andrii, wangfushuai, linux-doc,
linux-arm-kernel, linux-media, linaro-mm-sig, linux-i2c,
linux-arch, linux-modules, rcu, linux-nfs, linux-rt-devel,
2407018371, dakr, miguel.ojeda.sandonis, neilb, bagasdotme,
wsa+renesas, dave.hansen, geert, ojeda, alex.gaynor, bjorn3_gh,
lossin, a.hindborg, aliceryhl, tmgross, rust-for-linux
In-Reply-To: <DJSEK3KA2ECQ.1512A4KGOBSCV@garyguo.net>
On Tue, Jul 07, 2026 at 03:18:29PM +0100, Gary Guo wrote:
> On Mon Jul 6, 2026 at 7:19 AM BST, Byungchul Park wrote:
> > wait_for_completion() can be used at various points in the code and it's
> > very hard to distinguish wait_for_completion()s between different usages.
> > Using a single dept_key for all the wait_for_completion()s could trigger
> > false positive reports.
> >
> > Assign unique dept_key to each distinct wait_for_completion() caller to
> > avoid false positive reports.
> >
> > While at it, add a rust helper for wait_for_completion() to avoid build
> > errors.
>
> This will cause Rust code to share the same dept_key, so it will have all the
> false positives that the change is trying to avoid.
Thank you for the input.
> In general it is easy to create Rust bindings for static inline C functions
> because it'll be just some computation, while creating bindings for C
> function-like macros that define additional statics can be challenging.
>
> Is dept_key similar to lock_class_key, where only the address matters? If so,
> the approach that I use in
> https://lore.kernel.org/rust-for-linux/DJP0CDOR98N5.29BK8PUFRWRUK@garyguo.net
Yes, dept_key is similar to lock_class_key. IIUC, the way you tried for
lock_class_key can be applied to DEPT too. I will do:
1) add 'key' parameter to sdt_might_sleep_start_timeout()
2) introduce init_completion_dkey() to allow custom keys
3) remove init_completion_dmap() and adjust the existing users
Just in case, it's worth noting that the custom keys must be
well-managed using e.g. dept_key_destroy() when the key gets freed.
Byungchul
> could be used for dept_key as well, then we can keep Rust `wait_for_completion`
> still a function; otherwise we have to turn it into a macro too on the Rust side
> to create such statics, which isn't ideal.
>
> Best,
> Gary
>
> >
> > Signed-off-by: Byungchul Park <byungchul@sk.com>
> > ---
> > include/linux/completion.h | 100 +++++++++++++++++++++++++++++++------
> > kernel/sched/completion.c | 60 +++++++++++-----------
> > rust/helpers/completion.c | 5 ++
> > 3 files changed, 120 insertions(+), 45 deletions(-)
^ permalink raw reply
* Re: [PATCH v7 1/4] kallsyms: embed source file:line info in kernel stack traces
From: sashiko-bot @ 2026-07-09 16:51 UTC (permalink / raw)
To: Sasha Levin; +Cc: linux-modules
In-Reply-To: <20260709163833.3851179-2-sashal@kernel.org>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Use of `snprintf` without checking for truncation leaks uninitialized kernel memory via trace buffers.
- [Medium] The host tool `scripts/gen_lineinfo` will enter an infinite loop during the kernel build if the number of unique source files exceeds 8,192.
- [Medium] Deduplication logic drops valid lineinfo entries with different offsets, breaking symbol boundary checks.
- [Low] The symbol `lineinfo_filenames_size` is missing from the kallsyms ignore list, exposing it while hiding its siblings.
--
--- Patch [1]: [PATCH v7 1/4] kallsyms: embed source file:line info in kernel stack traces ---
commit ca3ea968a16862e3d8b4586ef0bfc722a4a2f652
Author: Sasha Levin <sashal@kernel.org>
kallsyms: embed source file:line info in kernel stack traces
This commit adds a configuration option to embed a compact address-to-line
lookup table in the kernel image. It generates this table at build time from
DWARF debug information using a host tool. Stack traces can then directly
print source file and line number information without external tools.
> diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
> index aec2f06858afd..d3fcf282c33cd 100644
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
[ ... ]
> @@ -467,13 +467,77 @@ static int append_buildid(char *buffer, const char *modname,
>
> #endif /* CONFIG_STACKTRACE_BUILD_ID */
>
> +bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
> + const char **file, unsigned int *line)
> +{
[ ... ]
> + if (low == 0)
> + return false;
> + low--;
> +
> + if (lineinfo_addrs[low] < min_offset)
> + return false;
[Severity: Medium]
Will this boundary check unintentionally reject valid entries for functions
that start on the same source line (e.g. via macro instantiation)?
When deduplicate() in scripts/gen_lineinfo.c processes entries, it drops the
second function's entry because it shares the same file and line. When
querying the second function, kallsyms_lookup_lineinfo() finds the first
function's entry but rejects it here since its offset is earlier than the
second function's sym_start.
> +
> + file_id = lineinfo_file_ids[low];
[ ... ]
> @@ -497,6 +562,23 @@ static int __sprint_symbol(char *buffer, unsigned long address,
> len += sprintf(buffer + len, "]");
> }
>
> + /*
> + * Append "(file:line)" only for stack-backtrace consumers. Plain
> + * sprint_symbol() backs %ps, and many existing format strings tack
> + * literal "()" after %ps to indicate a function call ("foo()
> + * replaced with bar()"); appending lineinfo there would produce a
> + * confusing "foo (file:line)()".
> + */
> + if (add_lineinfo && IS_ENABLED(CONFIG_KALLSYMS_LINEINFO) && !modname) {
> + const char *li_file;
> + unsigned int li_line;
> +
> + 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);
[Severity: High]
Does this unconditionally add snprintf()'s return value to the string length?
If the formatted string is truncated, snprintf() returns the length it
would have written. This makes len larger than the actual bytes written to
the buffer.
Can this cause callers (like fetch_store_sym() in the trace framework) to
expose uninitialized kernel memory to userspace by copying the unwritten
portion of the buffer?
> + }
> +
> return len;
> }
> diff --git a/scripts/gen_lineinfo.c b/scripts/gen_lineinfo.c
> new file mode 100644
> index 0000000000000..699e760178f09
> --- /dev/null
> +++ b/scripts/gen_lineinfo.c
[ ... ]
> +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);
> + }
[Severity: Medium]
Will this open-addressing lookup loop indefinitely if the hash table is fully
saturated?
If a kernel configuration (like allyesconfig) includes 8192 or more unique
source files, the static FILE_HASH_SIZE array becomes completely full.
Any subsequent file lookup will probe all slots without finding a NULL entry
and loop infinitely.
> +
> + if (num_files >= 65535) {
[ ... ]
> +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;
[Severity: Medium]
Does this logic erroneously drop valid lineinfo entries that share the same
file and line but have different address offsets?
As mentioned in kallsyms_lookup_lineinfo(), dropping these entries causes
the lookup to fail its symbol boundary check and reject the previous entry.
> + j++;
> + if (j != i)
> + entries[j] = entries[i];
> + }
> + num_entries = j + 1;
> +}
> 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",
> + };
[Severity: Low]
Is lineinfo_filenames_size intentionally omitted from this ignore list?
Missing this symbol causes it to be exposed in /proc/kallsyms while hiding
its siblings.
> +
> if (type == 'u' || type == 'n')
> return true;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709163833.3851179-1-sashal@kernel.org?part=1
^ permalink raw reply
* Re: [PATCH v7 4/4] kallsyms: add KUnit tests for lineinfo feature
From: sashiko-bot @ 2026-07-09 16:50 UTC (permalink / raw)
To: Sasha Levin; +Cc: linux-modules
In-Reply-To: <20260709163833.3851179-5-sashal@kernel.org>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Unchecked NULL pointer dereference when allocating test symbol buffer.
--
--- Patch [4]: [PATCH v7 4/4] kallsyms: add KUnit tests for lineinfo feature ---
commit aa7a75a3dcd2a5f7456eb96f884496c36509f6d7
Author: Sasha Levin <sashal@kernel.org>
kallsyms: add KUnit tests for lineinfo feature
This commit adds a KUnit test module to verify that the kallsyms lineinfo
feature produces correct annotations in stack traces. It also exports
sprint_backtrace() and sprint_backtrace_build_id() so the test module can
exercise the backtrace APIs.
> diff --git a/lib/tests/lineinfo_kunit.c b/lib/tests/lineinfo_kunit.c
> new file mode 100644
> index 0000000000000..dad1522a44f5a
> --- /dev/null
> +++ b/lib/tests/lineinfo_kunit.c
[ ... ]
> +static char *alloc_sym_buf(struct kunit *test)
> +{
> + return kunit_kzalloc(test, KSYM_SYMBOL_LEN, GFP_KERNEL);
> +}
[ ... ]
> +static void test_normal_function(struct kunit *test)
> +{
> + char *buf = alloc_sym_buf(test);
> + unsigned long addr = target_addr(lineinfo_target_normal);
> +
> + skip_without_module_lineinfo(test);
> +
> + sprint_with_lineinfo(buf, addr);
[Severity: Medium]
Will this crash the kernel if kunit_kzalloc() fails and returns NULL?
If memory allocation fails under pressure, alloc_sym_buf() returns NULL. The
callers like test_normal_function() pass this buffer unchecked into
sprint_with_lineinfo(), which eventually passes it to kallsyms_lookup_buildid()
as the namebuf parameter.
Looking at kallsyms_lookup_buildid() in kernel/kallsyms.c, the buffer is
dereferenced immediately:
kernel/kallsyms.c:kallsyms_lookup_buildid() {
...
namebuf[0] = 0;
...
}
Should alloc_sym_buf() use KUNIT_ASSERT_NOT_NULL(test, buf) to gracefully fail
the test instead of causing a NULL pointer dereference?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709163833.3851179-1-sashal@kernel.org?part=4
^ permalink raw reply
* Re: [PATCH v7 2/4] kallsyms: extend lineinfo to loadable modules
From: sashiko-bot @ 2026-07-09 16:50 UTC (permalink / raw)
To: Sasha Levin; +Cc: linux-modules
In-Reply-To: <20260709163833.3851179-3-sashal@kernel.org>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Unvalidated module blob offsets cause unaligned memory accesses, potentially triggering alignment faults in critical contexts.
- [Medium] Integer overflow in the `apply_one_dl_reloc` bounds check allows out-of-bounds heap access in the `gen_lineinfo` tool.
- [Low] Dynamically allocated memory for `extra_sections` is never freed in the `gen_lineinfo` tool.
--
--- Patch [2]: [PATCH v7 2/4] kallsyms: extend lineinfo to loadable modules ---
commit be00704ef1eba0bdfe61160ba94617348dfdc7dc
Author: Sasha Levin <sashal@kernel.org>
kallsyms: extend lineinfo to loadable modules
This patch extends the CONFIG_KALLSYMS_LINEINFO feature to loadable kernel
modules. It uses scripts/gen-mod-lineinfo.sh to generate .mod_lineinfo
sections containing binary tables of source file and line number mappings.
At runtime, module_lookup_lineinfo() searches these read-only tables to
provide NMI-safe file and line annotations for module stack traces.
> diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
> index 0fc11e45df9b9..4888ce8a7f951 100644
> --- a/kernel/module/kallsyms.c
> +++ b/kernel/module/kallsyms.c
[ ... ]
> @@ -494,3 +494,201 @@ int module_kallsyms_on_each_symbol(const char *modname,
> +static bool module_lookup_lineinfo_section(const void *blob, u32 blob_size,
> + u32 hdr_offset,
> + unsigned int section_offset,
> + unsigned int min_offset,
> + const char **file,
> + unsigned int *line)
> +{
> + const struct mod_lineinfo_header *hdr;
> + const u8 *base;
> + const u32 *addrs, *lines, *file_offsets;
> + const u16 *file_ids;
> + const char *filenames;
> + u32 num_entries, num_files, filenames_size;
> + unsigned int low, high, mid;
> + u16 file_id;
> +
> + if (hdr_offset > blob_size ||
> + blob_size - hdr_offset < sizeof(*hdr))
> + return false;
> +
> + base = (const u8 *)blob + hdr_offset;
> + hdr = (const struct mod_lineinfo_header *)base;
[Severity: High]
Does this code safely handle unaligned memory accesses?
The hdr_offset value is read directly from the binary blob and is never
validated for 4-byte alignment before being cast to struct
mod_lineinfo_header * and subsequently u32 * for the internal arrays.
If a malformed or corrupted .ko file provides an unaligned hdr_offset,
dereferencing these pointers could trigger a hardware alignment trap on
strict-alignment architectures. Since this code is designed to be called
during NMI or panic stack traces, taking an alignment fault here would cause
a recursive fault and hang the system without producing the crash dump.
Could we add an alignment check for hdr_offset before casting it?
[ ... ]
> diff --git a/scripts/gen_lineinfo.c b/scripts/gen_lineinfo.c
> index 699e760178f09..7624098d0cb42 100644
> --- a/scripts/gen_lineinfo.c
> +++ b/scripts/gen_lineinfo.c
[ ... ]
> +static void apply_one_dl_reloc(Elf_Data *dl_data, Elf_Data *sym_data,
> + bool target_le,
> + unsigned int abs32_type, unsigned int abs64_type,
> + GElf_Xword r_info, GElf_Addr r_offset,
> + GElf_Sxword addend, bool has_addend)
> +{
> + GElf_Sym sym;
> + unsigned int r_type = GELF_R_TYPE(r_info);
> + size_t r_sym = GELF_R_SYM(r_info);
> + bool is_abs64;
> + size_t width;
> + uint64_t value;
[ ... ]
> + width = is_abs64 ? 8 : 4;
> +
> + if (r_offset + width > dl_data->d_size)
> + return;
[Severity: Medium]
Can this bounds check overflow?
Because r_offset is a 64-bit integer, a maliciously crafted .ko file fed
to the gen_lineinfo tool could provide an extremely large value (such as
0xFFFFFFFFFFFFFFFE). Adding width to it would wrap around to a small
number, successfully bypassing the bounds check.
This could lead to out-of-bounds pointer arithmetic and a subsequent
out-of-bounds write in elf_write_uint() below, potentially causing a crash
or code execution within the kernel build environment.
[ ... ]
> +static void resolve_covered_sections(Elf *elf,
> + struct covered_section *sections,
> + unsigned int num_sections)
> +{
[ ... ]
> + if (!covered &&
> + (shdr.sh_flags & SHF_EXECINSTR) && (shdr.sh_flags & SHF_ALLOC) &&
> + shdr.sh_size && shdr.sh_size <= UINT_MAX) {
> + struct covered_section *es;
> +
> + extra_sections = realloc(extra_sections,
> + (num_extra_sections + 1) *
> + sizeof(*extra_sections));
[ ... ]
> @@ -552,6 +1387,5 @@ int main(int argc, char *argv[])
> for (unsigned int i = 0; i < num_files; i++)
> free(files[i].name);
> free(files);
> -
> return 0;
> }
[Severity: Low]
Does this code leak the dynamically allocated extra_sections array?
In resolve_covered_sections(), extra_sections is allocated and expanded
via realloc(). However, the cleanup block in main() only frees entries
and files, missing the extra_sections array.
While this is a short-lived userspace tool and the OS will automatically
reclaim the memory upon exit, would it be better to explicitly free
extra_sections to keep the cleanup complete?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260709163833.3851179-1-sashal@kernel.org?part=2
^ permalink raw reply
* [PATCH v7 4/4] kallsyms: add KUnit tests for lineinfo feature
From: Sasha Levin @ 2026-07-09 16:38 UTC (permalink / raw)
To: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley
Cc: Sasha Levin, Jonathan Corbet, Nathan Chancellor, Nicolas Schier,
Petr Pavlu, Daniel Gomez, Greg KH, Petr Mladek, Steven Rostedt,
Kees Cook, Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka,
Helge Deller, Randy Dunlap, Laurent Pinchart, Vivian Wang,
Zhen Lei, Sami Tolvanen, linux-kernel, linux-kbuild,
linux-modules, linux-doc
In-Reply-To: <20260709163833.3851179-1-sashal@kernel.org>
Add a KUnit test module (CONFIG_LINEINFO_KUNIT_TEST) that verifies the
kallsyms lineinfo feature produces correct source file:line annotations
in stack traces.
Export sprint_backtrace() and sprint_backtrace_build_id() as GPL symbols
so the test module can exercise the backtrace APIs.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
kernel/kallsyms.c | 2 +
lib/Kconfig.debug | 10 +
lib/tests/Makefile | 3 +
lib/tests/lineinfo_kunit.c | 973 +++++++++++++++++++++++++++++++++++++
4 files changed, 988 insertions(+)
create mode 100644 lib/tests/lineinfo_kunit.c
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index ffc65bc4cfc925..15cbcf0f9e0847 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -651,6 +651,7 @@ int sprint_backtrace(char *buffer, unsigned long address)
{
return __sprint_symbol(buffer, address, -1, 1, 0, 1);
}
+EXPORT_SYMBOL_GPL(sprint_backtrace);
/**
* sprint_backtrace_build_id - Look up a backtrace symbol and return it in a text buffer
@@ -671,6 +672,7 @@ int sprint_backtrace_build_id(char *buffer, unsigned long address)
{
return __sprint_symbol(buffer, address, -1, 1, 1, 1);
}
+EXPORT_SYMBOL_GPL(sprint_backtrace_build_id);
/* To avoid using get_symbol_offset for every symbol, we carry prefix along. */
struct kallsym_iter {
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 1244dcac2294ad..257b5ca442f327 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3092,6 +3092,16 @@ config LONGEST_SYM_KUNIT_TEST
If unsure, say N.
+config LINEINFO_KUNIT_TEST
+ tristate "KUnit tests for kallsyms lineinfo" if !KUNIT_ALL_TESTS
+ depends on KUNIT && KALLSYMS_LINEINFO
+ default KUNIT_ALL_TESTS
+ help
+ KUnit tests for the kallsyms source line info feature.
+ Verifies that stack traces include correct (file.c:line) annotations.
+
+ If unsure, say N.
+
config HW_BREAKPOINT_KUNIT_TEST
bool "Test hw_breakpoint constraints accounting" if !KUNIT_ALL_TESTS
depends on HAVE_HW_BREAKPOINT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 4ead57602eac46..877416f5f70a3e 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -37,6 +37,9 @@ obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o
CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
+CFLAGS_lineinfo_kunit.o += $(call cc-option,-fno-inline-functions-called-once)
+obj-$(CONFIG_LINEINFO_KUNIT_TEST) += lineinfo_kunit.o
+
obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o
obj-$(CONFIG_MIN_HEAP_KUNIT_TEST) += min_heap_kunit.o
CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare)
diff --git a/lib/tests/lineinfo_kunit.c b/lib/tests/lineinfo_kunit.c
new file mode 100644
index 00000000000000..dad1522a44f5af
--- /dev/null
+++ b/lib/tests/lineinfo_kunit.c
@@ -0,0 +1,973 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for kallsyms lineinfo (CONFIG_KALLSYMS_LINEINFO).
+ *
+ * Copyright (c) 2026 Sasha Levin <sashal@kernel.org>
+ *
+ * Verifies that sprint_symbol() and related APIs append correct
+ * " (file.c:NNN)" annotations to kernel symbol lookups.
+ *
+ * Build with: CONFIG_LINEINFO_KUNIT_TEST=m (or =y)
+ * Run with: ./tools/testing/kunit/kunit.py run lineinfo
+ */
+
+#include <kunit/test.h>
+#include <linux/kallsyms.h>
+#include <linux/module.h>
+#include <asm/sections.h>
+#include <linux/smp.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mod_lineinfo.h>
+
+/* --------------- helpers --------------- */
+
+static char *alloc_sym_buf(struct kunit *test)
+{
+ return kunit_kzalloc(test, KSYM_SYMBOL_LEN, GFP_KERNEL);
+}
+
+/*
+ * Function-pointer casts yield the descriptor address on architectures
+ * with function descriptors (e.g. ppc64 ELFv1, parisc64); resolve to the
+ * code address the lineinfo tables actually cover.
+ */
+static unsigned long target_addr(const void *fn)
+{
+ return (unsigned long)dereference_function_descriptor((void *)fn);
+}
+
+/*
+ * When the test is built as a module but CONFIG_KALLSYMS_LINEINFO_MODULES
+ * is disabled, the test module's own functions have no lineinfo tables, so
+ * every test that expects annotations on local targets must skip rather
+ * than fail.
+ */
+static void skip_without_module_lineinfo(struct kunit *test)
+{
+ if (IS_MODULE(CONFIG_LINEINFO_KUNIT_TEST) &&
+ !IS_ENABLED(CONFIG_KALLSYMS_LINEINFO_MODULES))
+ kunit_skip(test,
+ "KALLSYMS_LINEINFO_MODULES disabled; test module has no lineinfo");
+}
+
+/*
+ * Format a symbol with lineinfo annotation. Lineinfo is appended only
+ * via the sprint_backtrace*() entry points (kernel/kallsyms.c only adds
+ * the "(file:line)" suffix in stack-trace context — sprint_symbol() is
+ * used by %ps and many existing format strings tack literal "()" after
+ * %ps, where the annotation would render as "foo (file:line)()").
+ *
+ * sprint_backtrace() subtracts 1 from the address to handle tail-call
+ * return-address corrections; pass @addr + 1 to recover the original.
+ */
+static int sprint_with_lineinfo(char *buf, unsigned long addr)
+{
+ return sprint_backtrace(buf, addr + 1);
+}
+
+/*
+ * Return true if @buf contains a lineinfo annotation matching
+ * the pattern " (<path>:<digits>)".
+ *
+ * The path may be a full path like "lib/tests/lineinfo_kunit.c" or
+ * a shortened form from module lineinfo (e.g., just a directory name).
+ */
+static bool has_lineinfo(const char *buf)
+{
+ const char *p, *colon, *end;
+
+ p = strstr(buf, " (");
+ if (!p)
+ return false;
+ p += 2; /* skip " (" */
+
+ colon = strchr(p, ':');
+ if (!colon || colon == p)
+ return false;
+
+ /* After colon: one or more digits then ')' */
+ end = colon + 1;
+ if (*end < '0' || *end > '9')
+ return false;
+ while (*end >= '0' && *end <= '9')
+ end++;
+ return *end == ')';
+}
+
+/*
+ * Extract line number from a lineinfo annotation.
+ * Returns 0 if not found.
+ */
+static unsigned int extract_line(const char *buf)
+{
+ const char *p, *colon;
+ unsigned int line = 0;
+
+ p = strstr(buf, " (");
+ if (!p)
+ return 0;
+
+ colon = strchr(p + 2, ':');
+ if (!colon)
+ return 0;
+
+ colon++;
+ while (*colon >= '0' && *colon <= '9') {
+ line = line * 10 + (*colon - '0');
+ colon++;
+ }
+ return line;
+}
+
+/*
+ * Check if the lineinfo annotation contains the given filename substring.
+ */
+static bool lineinfo_contains_file(const char *buf, const char *name)
+{
+ const char *p, *colon;
+
+ p = strstr(buf, " (");
+ if (!p)
+ return false;
+
+ colon = strchr(p + 2, ':');
+ if (!colon)
+ return false;
+
+ /* Search for @name between '(' and ':' */
+ return strnstr(p + 1, name, colon - p - 1) != NULL;
+}
+
+/* --------------- target functions --------------- */
+
+static noinline int lineinfo_target_normal(void)
+{
+ barrier();
+ return 42;
+}
+
+static noinline int lineinfo_target_short(void)
+{
+ barrier();
+ return 1;
+}
+
+static noinline int lineinfo_target_with_arg(int x)
+{
+ barrier();
+ return x + 1;
+}
+
+static noinline int lineinfo_target_many_lines(void)
+{
+ /*
+ * volatile forces a load/store per statement so the function keeps
+ * one line-table row per source line (and a body large enough for
+ * mid-function lookups) at any optimization level; without it the
+ * compiler folds the arithmetic to a single constant.
+ */
+ volatile int a = 0;
+
+ barrier();
+ a += 1;
+ a += 2;
+ a += 3;
+ a += 4;
+ a += 5;
+ a += 6;
+ a += 7;
+ a += 8;
+ a += 9;
+ a += 10;
+ barrier();
+ return a;
+}
+
+static __always_inline int lineinfo_inline_helper(void)
+{
+ return 99;
+}
+
+static noinline int lineinfo_inline_caller(void)
+{
+ barrier();
+ return lineinfo_inline_helper();
+}
+
+/* 10-deep call chain */
+static noinline int lineinfo_chain_10(void) { barrier(); return 10; }
+static noinline int lineinfo_chain_9(void) { barrier(); return lineinfo_chain_10(); }
+static noinline int lineinfo_chain_8(void) { barrier(); return lineinfo_chain_9(); }
+static noinline int lineinfo_chain_7(void) { barrier(); return lineinfo_chain_8(); }
+static noinline int lineinfo_chain_6(void) { barrier(); return lineinfo_chain_7(); }
+static noinline int lineinfo_chain_5(void) { barrier(); return lineinfo_chain_6(); }
+static noinline int lineinfo_chain_4(void) { barrier(); return lineinfo_chain_5(); }
+static noinline int lineinfo_chain_3(void) { barrier(); return lineinfo_chain_4(); }
+static noinline int lineinfo_chain_2(void) { barrier(); return lineinfo_chain_3(); }
+static noinline int lineinfo_chain_1(void) { barrier(); return lineinfo_chain_2(); }
+
+/* --------------- Group A: Basic lineinfo presence --------------- */
+
+static void test_normal_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+ "Wrong file in: %s", buf);
+}
+
+static void test_static_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_short);
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in: %s", buf);
+}
+
+static void test_noinline_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_with_arg);
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in: %s", buf);
+}
+
+static void test_inline_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_inline_caller);
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo for inline caller in: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+ "Wrong file in: %s", buf);
+}
+
+static void test_short_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_short);
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo for short function in: %s", buf);
+}
+
+static void test_many_lines_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_many_lines);
+ unsigned int line;
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in: %s", buf);
+ line = extract_line(buf);
+ KUNIT_EXPECT_GT_MSG(test, line, (unsigned int)0,
+ "Line number should be > 0 in: %s", buf);
+}
+
+/* --------------- Group B: Deep call chain --------------- */
+
+typedef int (*chain_fn_t)(void);
+
+static void test_deep_call_chain(struct kunit *test)
+{
+ static const chain_fn_t chain_fns[] = {
+ lineinfo_chain_1, lineinfo_chain_2,
+ lineinfo_chain_3, lineinfo_chain_4,
+ lineinfo_chain_5, lineinfo_chain_6,
+ lineinfo_chain_7, lineinfo_chain_8,
+ lineinfo_chain_9, lineinfo_chain_10,
+ };
+ char *buf = alloc_sym_buf(test);
+ int i, found = 0;
+
+ skip_without_module_lineinfo(test);
+
+ /* Call chain to prevent dead-code elimination */
+ KUNIT_ASSERT_EQ(test, lineinfo_chain_1(), 10);
+
+ for (i = 0; i < ARRAY_SIZE(chain_fns); i++) {
+ unsigned long addr = target_addr(chain_fns[i]);
+
+ sprint_with_lineinfo(buf, addr);
+ if (has_lineinfo(buf))
+ found++;
+ }
+
+ /*
+ * Not every tiny function gets DWARF line info (compiler may
+ * omit it for very small stubs), but at least some should.
+ */
+ KUNIT_EXPECT_GT_MSG(test, found, 0,
+ "None of the 10 chain functions had lineinfo");
+}
+
+/* --------------- Group C: sprint_symbol API variants --------------- */
+
+static void test_sprint_symbol_format(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ sprint_symbol(buf, addr);
+
+ /* Should contain +0x and /0x for offset/size */
+ KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "+0x"),
+ "Missing offset in: %s", buf);
+ KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "/0x"),
+ "Missing size in: %s", buf);
+ /*
+ * sprint_symbol() backs %ps, which existing format strings combine
+ * with literal "()" to indicate function calls; the lineinfo suffix
+ * is intentionally omitted there to avoid "foo (file:line)()".
+ */
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo in sprint_symbol output: %s",
+ buf);
+}
+
+static void test_sprint_backtrace(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ skip_without_module_lineinfo(test);
+
+ /* sprint_backtrace subtracts 1 internally to handle tail calls */
+ sprint_backtrace(buf, addr + 1);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in backtrace: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+ "Wrong file in backtrace: %s", buf);
+}
+
+static void test_sprint_backtrace_build_id(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ skip_without_module_lineinfo(test);
+
+ sprint_backtrace_build_id(buf, addr + 1);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in backtrace_build_id: %s", buf);
+}
+
+static void test_sprint_symbol_no_offset(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ sprint_symbol_no_offset(buf, addr);
+ /* No "+0x" in output */
+ KUNIT_EXPECT_NULL_MSG(test, strstr(buf, "+0x"),
+ "Unexpected offset in no_offset: %s", buf);
+ /* sprint_symbol_no_offset is a sprint_symbol() variant; lineinfo is
+ * intentionally only appended in sprint_backtrace*() context.
+ */
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo in no_offset: %s", buf);
+}
+
+/* --------------- Group D: printk format specifiers --------------- */
+
+static void test_pS_format(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ void *addr = lineinfo_target_normal;
+
+ snprintf(buf, KSYM_SYMBOL_LEN, "%pS", addr);
+ /*
+ * %pS uses sprint_symbol(), which intentionally omits the lineinfo
+ * suffix (see kernel/kallsyms.c::__sprint_symbol). Lineinfo is only
+ * added via the sprint_backtrace*() entry points, which back %pBb.
+ */
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo in %%pS: %s", buf);
+}
+
+static void test_pBb_format(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ /*
+ * %pBb uses sprint_backtrace_build_id which subtracts 1 from the
+ * address, so pass addr+1 to resolve back to the function.
+ */
+ void *addr = (void *)(target_addr(lineinfo_target_normal) + 1);
+
+ skip_without_module_lineinfo(test);
+
+ snprintf(buf, KSYM_SYMBOL_LEN, "%pBb", addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo in %%pBb: %s", buf);
+}
+
+static void test_pSR_format(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ void *addr = lineinfo_target_normal;
+
+ snprintf(buf, KSYM_SYMBOL_LEN, "%pSR", addr);
+ /* %pSR is a sprint_symbol() variant; same rationale as %pS. */
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo in %%pSR: %s", buf);
+}
+
+/* --------------- Group E: Address edge cases --------------- */
+
+static void test_symbol_start_addr(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ skip_without_module_lineinfo(test);
+
+ /*
+ * sprint_backtrace() subtracts 1 from the input and reports offset
+ * relative to the (decremented) address, so an exact "+0x0/" can't
+ * be expected here. Verify the symbol resolves and carries lineinfo.
+ */
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ strnstr(buf, "lineinfo_target_normal",
+ KSYM_SYMBOL_LEN) != NULL,
+ "Didn't resolve to expected function: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo at function start: %s", buf);
+}
+
+static void test_symbol_nonzero_offset(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ skip_without_module_lineinfo(test);
+
+ /*
+ * sprint_backtrace subtracts 1 internally.
+ * Passing addr+2 resolves to addr+1 which is inside the function
+ * at a non-zero offset.
+ */
+ sprint_backtrace(buf, addr + 2);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ strnstr(buf, "lineinfo_target_normal",
+ KSYM_SYMBOL_LEN) != NULL,
+ "Didn't resolve to expected function: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo at non-zero offset: %s", buf);
+}
+
+static void test_unknown_address(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+
+ sprint_symbol(buf, 1UL);
+ /* Should be "0x1" with no lineinfo */
+ KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "0x1"),
+ "Expected hex address for bogus addr: %s", buf);
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo for bogus addr: %s", buf);
+}
+
+static void test_kernel_function_lineinfo(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(sprint_symbol);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo for sprint_symbol: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ lineinfo_contains_file(buf, "kallsyms.c"),
+ "Expected kallsyms.c in: %s", buf);
+}
+
+static void test_assembly_no_lineinfo(struct kunit *test)
+{
+#if IS_BUILTIN(CONFIG_LINEINFO_KUNIT_TEST)
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = (unsigned long)_text;
+
+ sprint_with_lineinfo(buf, addr);
+ /*
+ * _text is typically an asm entry point with no DWARF line info.
+ * If it has lineinfo, it's a C-based entry — skip in that case.
+ */
+ if (has_lineinfo(buf))
+ kunit_skip(test, "_text has lineinfo (C entry?): %s", buf);
+
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo for asm symbol: %s", buf);
+#else
+ kunit_skip(test, "_text not accessible from modules");
+#endif
+}
+
+static void test_init_text_no_stale_lineinfo(struct kunit *test)
+{
+#if IS_BUILTIN(CONFIG_LINEINFO_KUNIT_TEST)
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = (unsigned long)_sinittext + 16;
+
+ /*
+ * .init.text lies past the _etext cap of the vmlinux lineinfo
+ * table, so a lookup there must not inherit the annotation of
+ * the last covered .text entry.
+ */
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Stale lineinfo for init-text address: %s", buf);
+#else
+ kunit_skip(test, "_sinittext not accessible from modules");
+#endif
+}
+
+/* --------------- Group F: Module path --------------- */
+
+static void test_module_function_lineinfo(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ skip_without_module_lineinfo(test);
+
+ if (!IS_MODULE(CONFIG_LINEINFO_KUNIT_TEST)) {
+ kunit_skip(test, "Test only meaningful when built as module");
+ return;
+ }
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_EXPECT_NOT_NULL_MSG(test,
+ strstr(buf, "[lineinfo_kunit"),
+ "Missing module name in: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+ "No lineinfo for module function: %s", buf);
+ KUNIT_EXPECT_TRUE_MSG(test,
+ lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+ "Wrong file for module function: %s", buf);
+}
+
+/* --------------- Group G: Stress --------------- */
+
+struct lineinfo_stress_data {
+ unsigned long addr;
+ atomic_t failures;
+};
+
+static void lineinfo_stress_fn(void *info)
+{
+ struct lineinfo_stress_data *data = info;
+ char buf[KSYM_SYMBOL_LEN];
+ int i;
+
+ for (i = 0; i < 100; i++) {
+ sprint_with_lineinfo(buf, data->addr);
+ if (!has_lineinfo(buf))
+ atomic_inc(&data->failures);
+ }
+}
+
+static void test_concurrent_sprint_symbol(struct kunit *test)
+{
+ struct lineinfo_stress_data data;
+
+ skip_without_module_lineinfo(test);
+
+ data.addr = target_addr(lineinfo_target_normal);
+ atomic_set(&data.failures, 0);
+
+ on_each_cpu(lineinfo_stress_fn, &data, 1);
+
+ KUNIT_EXPECT_EQ_MSG(test, atomic_read(&data.failures), 0,
+ "Concurrent lineinfo failures detected");
+}
+
+static void test_rapid_sprint_symbol(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+ int i, failures = 0;
+
+ skip_without_module_lineinfo(test);
+
+ for (i = 0; i < 1000; i++) {
+ sprint_with_lineinfo(buf, addr);
+ if (!has_lineinfo(buf))
+ failures++;
+ }
+
+ KUNIT_EXPECT_EQ_MSG(test, failures, 0,
+ "Rapid sprint_symbol failures: %d/1000", failures);
+}
+
+/* --------------- Group H: Safety and plausibility --------------- */
+
+static void test_line_number_plausible(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+ unsigned int line;
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_ASSERT_TRUE(test, has_lineinfo(buf));
+
+ line = extract_line(buf);
+ KUNIT_EXPECT_GT_MSG(test, line, (unsigned int)0,
+ "Line number should be > 0");
+ KUNIT_EXPECT_LT_MSG(test, line, (unsigned int)10000,
+ "Line number %u implausibly large for this file",
+ line);
+}
+
+static void test_buffer_no_overflow(struct kunit *test)
+{
+ const size_t canary_size = 16;
+ char *buf;
+ int i;
+
+ buf = kunit_kzalloc(test, KSYM_SYMBOL_LEN + canary_size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, buf);
+
+ /* Fill canary area past KSYM_SYMBOL_LEN with 0xAA */
+ memset(buf + KSYM_SYMBOL_LEN, 0xAA, canary_size);
+
+ sprint_with_lineinfo(buf, target_addr(lineinfo_target_normal));
+
+ /* Verify canary bytes are untouched */
+ for (i = 0; i < canary_size; i++) {
+ KUNIT_EXPECT_EQ_MSG(test,
+ (unsigned char)buf[KSYM_SYMBOL_LEN + i],
+ (unsigned char)0xAA,
+ "Buffer overflow at offset %d past KSYM_SYMBOL_LEN",
+ i);
+ }
+}
+
+static void test_dump_stack_no_crash(struct kunit *test)
+{
+ /* Just verify dump_stack() completes without panic */
+ dump_stack();
+ KUNIT_SUCCEED(test);
+}
+
+static void test_sprint_symbol_build_id(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+
+ sprint_symbol_build_id(buf, addr);
+ /* Lineinfo is appended only via sprint_backtrace*(); the symbol
+ * variants intentionally omit it to avoid clashing with format
+ * strings that already wrap %ps in literal "()".
+ */
+ KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+ "Unexpected lineinfo in sprint_symbol_build_id: %s",
+ buf);
+}
+
+static void test_sleb128_edge_cases(struct kunit *test)
+{
+ u32 pos;
+ int32_t result;
+
+ /* Value 0: single byte 0x00 */
+ {
+ static const u8 data[] = { 0x00 };
+
+ pos = 0;
+ result = lineinfo_read_sleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)0);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value -1: single byte 0x7F */
+ {
+ static const u8 data[] = { 0x7f };
+
+ pos = 0;
+ result = lineinfo_read_sleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)-1);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value 1: single byte 0x01 */
+ {
+ static const u8 data[] = { 0x01 };
+
+ pos = 0;
+ result = lineinfo_read_sleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)1);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value -64: single byte 0x40 */
+ {
+ static const u8 data[] = { 0x40 };
+
+ pos = 0;
+ result = lineinfo_read_sleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)-64);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value 63: single byte 0x3F */
+ {
+ static const u8 data[] = { 0x3f };
+
+ pos = 0;
+ result = lineinfo_read_sleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)63);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value -128: two bytes 0x80 0x7F */
+ {
+ static const u8 data[] = { 0x80, 0x7f };
+
+ pos = 0;
+ result = lineinfo_read_sleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)-128);
+ KUNIT_EXPECT_EQ(test, pos, (u32)2);
+ }
+
+ /* Value INT32_MIN: five bytes, high bits land at shift 28 */
+ {
+ static const u8 data[] = { 0x80, 0x80, 0x80, 0x80, 0x78 };
+ const u8 *p = data;
+
+ /* Defeat constant folding so the runtime decoder runs. */
+ OPTIMIZER_HIDE_VAR(p);
+ pos = 0;
+ result = lineinfo_read_sleb128(p, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)INT_MIN);
+ KUNIT_EXPECT_EQ(test, pos, (u32)5);
+ }
+
+ /* Value INT32_MAX: five bytes */
+ {
+ static const u8 data[] = { 0xff, 0xff, 0xff, 0xff, 0x07 };
+ const u8 *p = data;
+
+ OPTIMIZER_HIDE_VAR(p);
+ pos = 0;
+ result = lineinfo_read_sleb128(p, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (int32_t)INT_MAX);
+ KUNIT_EXPECT_EQ(test, pos, (u32)5);
+ }
+}
+
+static void test_uleb128_edge_cases(struct kunit *test)
+{
+ u32 pos, result;
+
+ /* Value 0: single byte 0x00 */
+ {
+ static const u8 data[] = { 0x00 };
+
+ pos = 0;
+ result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (u32)0);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value 127: single byte 0x7F */
+ {
+ static const u8 data[] = { 0x7F };
+
+ pos = 0;
+ result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (u32)127);
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+
+ /* Value 128: two bytes 0x80 0x01 */
+ {
+ static const u8 data[] = { 0x80, 0x01 };
+
+ pos = 0;
+ result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (u32)128);
+ KUNIT_EXPECT_EQ(test, pos, (u32)2);
+ }
+
+ /* Max u32 0xFFFFFFFF: 5 bytes */
+ {
+ static const u8 data[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F };
+
+ pos = 0;
+ result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+ KUNIT_EXPECT_EQ(test, result, (u32)0xFFFFFFFF);
+ KUNIT_EXPECT_EQ(test, pos, (u32)5);
+ }
+
+ /* Truncated input: pos >= end returns 0 */
+ {
+ static const u8 data[] = { 0x80 };
+
+ pos = 0;
+ result = lineinfo_read_uleb128(data, &pos, 0);
+ KUNIT_EXPECT_EQ_MSG(test, result, (u32)0,
+ "Expected 0 for empty input");
+ }
+
+ /* Truncated mid-varint: continuation byte but end reached */
+ {
+ static const u8 data[] = { 0x80 };
+
+ pos = 0;
+ result = lineinfo_read_uleb128(data, &pos, 1);
+ KUNIT_EXPECT_EQ_MSG(test, result, (u32)0,
+ "Expected 0 for truncated varint");
+ KUNIT_EXPECT_EQ(test, pos, (u32)1);
+ }
+}
+
+static void test_line_number_accuracy(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_normal);
+ unsigned int line;
+
+ skip_without_module_lineinfo(test);
+
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_ASSERT_TRUE(test, has_lineinfo(buf));
+
+ line = extract_line(buf);
+
+ /*
+ * lineinfo_target_normal is defined around line 103-107.
+ * Allow wide range: KASAN instrumentation and module lineinfo
+ * address mapping can shift the reported line significantly.
+ */
+ KUNIT_EXPECT_GE_MSG(test, line, (unsigned int)50,
+ "Line %u too low for lineinfo_target_normal", line);
+ KUNIT_EXPECT_LE_MSG(test, line, (unsigned int)300,
+ "Line %u too high for lineinfo_target_normal", line);
+}
+
+static void test_many_lines_mid_function(struct kunit *test)
+{
+ char *buf = alloc_sym_buf(test);
+ unsigned long addr = target_addr(lineinfo_target_many_lines);
+ unsigned int line;
+ unsigned long mid_addr;
+
+ skip_without_module_lineinfo(test);
+
+ /* Get function size from sprint_with_lineinfo output */
+ sprint_with_lineinfo(buf, addr);
+ KUNIT_ASSERT_TRUE(test, has_lineinfo(buf));
+
+ /*
+ * Try an address 8 bytes into the function (past prologue). Hide
+ * the base from the compiler so this does not become a static
+ * mid-function relocation (sym+8), which objtool flags as a
+ * relocation to !ENDBR with CONFIG_X86_KERNEL_IBT.
+ */
+ OPTIMIZER_HIDE_VAR(addr);
+ mid_addr = addr + 8;
+ sprint_with_lineinfo(buf, mid_addr);
+
+ /*
+ * Should still resolve to lineinfo_target_many_lines.
+ * Lineinfo should be present with a plausible line number.
+ */
+ KUNIT_EXPECT_TRUE_MSG(test,
+ strnstr(buf, "lineinfo_target_many_lines",
+ KSYM_SYMBOL_LEN) != NULL,
+ "Mid-function addr resolved to wrong symbol: %s",
+ buf);
+ if (has_lineinfo(buf)) {
+ line = extract_line(buf);
+ KUNIT_EXPECT_GE_MSG(test, line, (unsigned int)50,
+ "Line %u too low for mid-function", line);
+ KUNIT_EXPECT_LE_MSG(test, line, (unsigned int)700,
+ "Line %u too high for mid-function", line);
+ }
+}
+
+/* --------------- Suite registration --------------- */
+
+static struct kunit_case lineinfo_test_cases[] = {
+ /* Group A: Basic lineinfo presence */
+ KUNIT_CASE(test_normal_function),
+ KUNIT_CASE(test_static_function),
+ KUNIT_CASE(test_noinline_function),
+ KUNIT_CASE(test_inline_function),
+ KUNIT_CASE(test_short_function),
+ KUNIT_CASE(test_many_lines_function),
+ /* Group B: Deep call chain */
+ KUNIT_CASE(test_deep_call_chain),
+ /* Group C: sprint_symbol API variants */
+ KUNIT_CASE(test_sprint_symbol_format),
+ KUNIT_CASE(test_sprint_backtrace),
+ KUNIT_CASE(test_sprint_backtrace_build_id),
+ KUNIT_CASE(test_sprint_symbol_no_offset),
+ /* Group D: printk format specifiers */
+ KUNIT_CASE(test_pS_format),
+ KUNIT_CASE(test_pBb_format),
+ KUNIT_CASE(test_pSR_format),
+ /* Group E: Address edge cases */
+ KUNIT_CASE(test_symbol_start_addr),
+ KUNIT_CASE(test_symbol_nonzero_offset),
+ KUNIT_CASE(test_unknown_address),
+ KUNIT_CASE(test_kernel_function_lineinfo),
+ KUNIT_CASE(test_assembly_no_lineinfo),
+ KUNIT_CASE(test_init_text_no_stale_lineinfo),
+ /* Group F: Module path */
+ KUNIT_CASE(test_module_function_lineinfo),
+ /* Group G: Stress */
+ KUNIT_CASE_SLOW(test_concurrent_sprint_symbol),
+ KUNIT_CASE_SLOW(test_rapid_sprint_symbol),
+ /* Group H: Safety and plausibility */
+ KUNIT_CASE(test_line_number_plausible),
+ KUNIT_CASE(test_buffer_no_overflow),
+ KUNIT_CASE(test_dump_stack_no_crash),
+ KUNIT_CASE(test_sprint_symbol_build_id),
+ /* Group I: Encoding/decoding and accuracy */
+ KUNIT_CASE(test_sleb128_edge_cases),
+ KUNIT_CASE(test_uleb128_edge_cases),
+ KUNIT_CASE(test_line_number_accuracy),
+ KUNIT_CASE(test_many_lines_mid_function),
+ {}
+};
+
+static struct kunit_suite lineinfo_test_suite = {
+ .name = "lineinfo",
+ .test_cases = lineinfo_test_cases,
+};
+kunit_test_suites(&lineinfo_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests for kallsyms lineinfo");
+MODULE_AUTHOR("Sasha Levin");
--
2.53.0
^ permalink raw reply related
* [PATCH v7 3/4] kallsyms: delta-compress lineinfo tables for ~2.7x size reduction
From: Sasha Levin @ 2026-07-09 16:38 UTC (permalink / raw)
To: Andrew Morton, Masahiro Yamada, Luis Chamberlain, Linus Torvalds,
Richard Weinberger, Juergen Gross, Geert Uytterhoeven,
James Bottomley
Cc: Sasha Levin, Jonathan Corbet, Nathan Chancellor, Nicolas Schier,
Petr Pavlu, Daniel Gomez, Greg KH, Petr Mladek, Steven Rostedt,
Kees Cook, Peter Zijlstra, Thorsten Leemhuis, Vlastimil Babka,
Helge Deller, Randy Dunlap, Laurent Pinchart, Vivian Wang,
Zhen Lei, Sami Tolvanen, linux-kernel, linux-kbuild,
linux-modules, linux-doc
In-Reply-To: <20260709163833.3851179-1-sashal@kernel.org>
Replace the flat uncompressed parallel arrays (lineinfo_addrs[],
lineinfo_file_ids[], lineinfo_lines[]) with a block-indexed,
delta-encoded, ULEB128 varint compressed format.
The sorted address array has small deltas between consecutive entries
(typically 1-50 bytes), file IDs have high locality (delta often 0,
same file), and line numbers change slowly. Delta-encoding followed
by ULEB128 varint compression shrinks most values from 4 bytes to 1.
Entries are grouped into blocks of 64. A small uncompressed block
index (first addr + byte offset per block) enables O(log(N/64)) binary
search, followed by sequential decode of at most 64 varints within the
matching block. All decode state lives on the stack -- zero
allocations, still safe for NMI/panic context.
Measured on a defconfig+debug x86_64 build (3,017,154 entries, 4,822
source files, 47,144 blocks):
Before (flat arrays):
lineinfo_addrs[] 12,068,616 bytes (u32 x 3.0M)
lineinfo_file_ids[] 6,034,308 bytes (u16 x 3.0M)
lineinfo_lines[] 12,068,616 bytes (u32 x 3.0M)
Total: 30,171,540 bytes (28.8 MiB, 10.0 bytes/entry)
After (block-indexed delta + ULEB128):
lineinfo_block_addrs[] 188,576 bytes (184 KiB)
lineinfo_block_offsets[] 188,576 bytes (184 KiB)
lineinfo_data[] 10,926,128 bytes (10.4 MiB)
Total: 11,303,280 bytes (10.8 MiB, 3.7 bytes/entry)
Savings: 18.0 MiB (2.7x reduction)
Booted in QEMU and verified with SysRq-l that annotations still work:
default_idle+0x9/0x10 (arch/x86/kernel/process.c:767)
default_idle_call+0x6c/0xb0 (kernel/sched/idle.c:122)
do_idle+0x335/0x490 (kernel/sched/idle.c:191)
cpu_startup_entry+0x4e/0x60 (kernel/sched/idle.c:429)
rest_init+0x1aa/0x1b0 (init/main.c:760)
Suggested-by: Juergen Gross <jgross@suse.com>
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
include/linux/mod_lineinfo.h | 250 ++++++++++++++++++++++++++++++-----
kernel/kallsyms.c | 55 +++-----
kernel/kallsyms_internal.h | 8 +-
kernel/module/kallsyms.c | 110 +++++++--------
scripts/empty_lineinfo.S | 20 ++-
scripts/gen_lineinfo.c | 185 +++++++++++++++++---------
scripts/kallsyms.c | 7 +-
7 files changed, 436 insertions(+), 199 deletions(-)
diff --git a/include/linux/mod_lineinfo.h b/include/linux/mod_lineinfo.h
index 75d0bfba6ce57b..47277f9d7e4ff5 100644
--- a/include/linux/mod_lineinfo.h
+++ b/include/linux/mod_lineinfo.h
@@ -3,9 +3,9 @@
* mod_lineinfo.h - Binary format for per-module source line information
*
* This header defines the layout of the .mod_lineinfo and
- * .init.mod_lineinfo sections embedded in loadable kernel modules. It
- * is dual-use: included from both the kernel and the userspace
- * gen_lineinfo tool.
+ * .init.mod_lineinfo sections embedded in loadable kernel modules. It is
+ * dual-use: included from both the kernel and the userspace gen_lineinfo
+ * tool.
*
* Top-level layout (all values in target-native endianness):
*
@@ -20,16 +20,27 @@
* If the relocation fails to resolve (e.g. unknown reloc type), .anchor
* stays zero and lookups silently degrade to "no annotation".
*
- * Each per-section sub-table is laid out as a stand-alone
- * mod_lineinfo_header followed by parallel arrays:
+ * Each per-section sub-table is laid out exactly as a stand-alone
+ * mod_lineinfo_header followed by its arrays:
*
- * struct mod_lineinfo_header (16 bytes)
- * u32 addrs[num_entries] -- offsets from this section's 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
+ * struct mod_lineinfo_header
+ * u32 block_addrs[num_blocks] -- first addr per block, for binary search
+ * u32 block_offsets[num_blocks] -- byte offset into compressed data stream
+ * u8 data[data_size] -- LEB128 delta-compressed entries
* u32 file_offsets[num_files] -- byte offset into filenames[]
* char filenames[filenames_size] -- concatenated NUL-terminated strings
+ *
+ * Each sub-array is located by an explicit (offset, size) pair in the
+ * header, similar to a flattened devicetree. All offsets in the per-section
+ * header are relative to that header itself, so a sub-table is fully
+ * self-describing.
+ *
+ * Compressed stream format (per block of LINEINFO_BLOCK_ENTRIES entries):
+ * Entry 0: file_id (ULEB128), line (ULEB128)
+ * addr is in block_addrs[]
+ * Entry 1..N: addr_delta (ULEB128),
+ * file_id_delta (SLEB128),
+ * line_delta (SLEB128)
*/
#ifndef _LINUX_MOD_LINEINFO_H
#define _LINUX_MOD_LINEINFO_H
@@ -44,6 +55,7 @@
#include <stdint.h>
typedef uint32_t u32;
typedef uint16_t u16;
+typedef uint8_t u8;
typedef uint64_t u64;
#ifndef __aligned
#define __aligned(x) __attribute__((__aligned__(x)))
@@ -53,6 +65,8 @@ typedef uint64_t u64;
#endif
#endif
+#define LINEINFO_BLOCK_ENTRIES 64
+
/*
* Per-section descriptor. One entry per ELF text section covered by the
* blob (.text, .exit.text, .init.text, ...).
@@ -84,39 +98,215 @@ static_assert(sizeof(struct mod_lineinfo_section) == 16,
struct mod_lineinfo_header {
u32 num_entries;
+ u32 num_blocks;
u32 num_files;
- u32 filenames_size; /* total bytes of concatenated filenames */
+ u32 blocks_offset; /* offset to block_addrs[] from this header */
+ u32 blocks_size; /* bytes: num_blocks * 2 * sizeof(u32) */
+ u32 data_offset; /* offset to compressed stream */
+ u32 data_size; /* bytes of compressed data */
+ u32 files_offset; /* offset to file_offsets[] */
+ u32 files_size; /* bytes: num_files * sizeof(u32) */
+ u32 filenames_offset;
+ u32 filenames_size;
};
-/* Offset helpers: compute byte offset from the per-section header to each array. */
+/*
+ * Descriptor for a lineinfo table, used by the shared lookup function.
+ * Callers populate this from either linker globals (vmlinux) or a
+ * validated mod_lineinfo_header (modules).
+ */
+struct lineinfo_table {
+ const u32 *blk_addrs;
+ const u32 *blk_offsets;
+ const u8 *data;
+ u32 data_size;
+ const u32 *file_offsets;
+ const char *filenames;
+ u32 num_entries;
+ u32 num_blocks;
+ u32 num_files;
+ u32 filenames_size;
+};
-static inline u32 mod_lineinfo_addrs_off(void)
+/*
+ * Read a ULEB128 varint from a byte stream.
+ * Returns the decoded value and advances *pos past the encoded bytes.
+ * If *pos would exceed 'end', returns 0 and sets *pos = end (safe for
+ * NMI/panic context: no crash, just a missed annotation).
+ */
+static inline u32 lineinfo_read_uleb128(const u8 *data, u32 *pos, u32 end)
{
- return sizeof(struct mod_lineinfo_header);
-}
+ u32 result = 0;
+ unsigned int shift = 0;
-static inline u32 mod_lineinfo_file_ids_off(u32 num_entries)
-{
- return mod_lineinfo_addrs_off() + num_entries * sizeof(u32);
+ while (*pos < end) {
+ u8 byte = data[*pos];
+ (*pos)++;
+ result |= (u32)(byte & 0x7f) << shift;
+ if (!(byte & 0x80))
+ return result;
+ shift += 7;
+ if (shift >= 32) {
+ /* Malformed: skip remaining continuation bytes */
+ while (*pos < end && (data[*pos] & 0x80))
+ (*pos)++;
+ if (*pos < end)
+ (*pos)++;
+ return result;
+ }
+ }
+ return result;
}
-static inline u32 mod_lineinfo_lines_off(u32 num_entries)
+/* Read an SLEB128 varint. Same safety guarantees as above. */
+static inline int32_t lineinfo_read_sleb128(const u8 *data, u32 *pos, u32 end)
{
- /* 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;
-}
+ int32_t result = 0;
+ unsigned int shift = 0;
+ u8 byte = 0;
-static inline u32 mod_lineinfo_file_offsets_off(u32 num_entries)
-{
- return mod_lineinfo_lines_off(num_entries) + num_entries * sizeof(u32);
+ while (*pos < end) {
+ byte = data[*pos];
+ (*pos)++;
+ result |= (int32_t)((u32)(byte & 0x7f) << shift);
+ shift += 7;
+ if (!(byte & 0x80))
+ break;
+ if (shift >= 32) {
+ while (*pos < end && (data[*pos] & 0x80))
+ (*pos)++;
+ if (*pos < end)
+ (*pos)++;
+ return result;
+ }
+ }
+
+ /* Sign-extend if the high bit of the last byte was set */
+ if (shift < 32 && (byte & 0x40))
+ result |= -(1 << shift);
+
+ return result;
}
-static inline u32 mod_lineinfo_filenames_off(u32 num_entries, u32 num_files)
+/*
+ * Search a lineinfo table for the source file and line corresponding to a
+ * given offset (from _text for vmlinux, from the covered section's base for
+ * modules). @min_offset is the containing symbol's start in the same offset
+ * space: entries below it belong to a preceding symbol and are rejected.
+ *
+ * Safe for NMI and panic context: no locks, no allocations, all state on stack.
+ * Returns true and sets @file and @line on success; false on any failure.
+ */
+static inline bool lineinfo_search(const struct lineinfo_table *tbl,
+ unsigned int offset,
+ unsigned int min_offset,
+ const char **file, unsigned int *line)
{
- return mod_lineinfo_file_offsets_off(num_entries) +
- num_files * sizeof(u32);
+ unsigned int low, high, mid, block;
+ unsigned int cur_addr, cur_file_id, cur_line;
+ unsigned int best_addr = 0, best_file_id = 0, best_line = 0;
+ unsigned int block_entries, data_end;
+ bool found = false;
+ u32 pos;
+
+ if (!tbl->num_entries || !tbl->num_blocks)
+ return false;
+
+ /* Binary search on blk_addrs[] to find the right block */
+ low = 0;
+ high = tbl->num_blocks;
+ while (low < high) {
+ mid = low + (high - low) / 2;
+ if (tbl->blk_addrs[mid] <= offset)
+ low = mid + 1;
+ else
+ high = mid;
+ }
+
+ if (low == 0)
+ return false;
+ block = low - 1;
+
+ /* How many entries in this block? */
+ block_entries = LINEINFO_BLOCK_ENTRIES;
+ if (block == tbl->num_blocks - 1) {
+ unsigned int remaining = tbl->num_entries -
+ block * LINEINFO_BLOCK_ENTRIES;
+
+ if (remaining < block_entries)
+ block_entries = remaining;
+ }
+
+ /* Determine end of this block's data in the compressed stream */
+ if (block + 1 < tbl->num_blocks)
+ data_end = tbl->blk_offsets[block + 1];
+ else
+ data_end = tbl->data_size;
+
+ /* Clamp data_end to actual data size */
+ if (data_end > tbl->data_size)
+ data_end = tbl->data_size;
+
+ /* Decode entry 0: addr from blk_addrs, file_id and line from stream */
+ pos = tbl->blk_offsets[block];
+ if (pos >= data_end)
+ return false;
+
+ cur_addr = tbl->blk_addrs[block];
+ cur_file_id = lineinfo_read_uleb128(tbl->data, &pos, data_end);
+ cur_line = lineinfo_read_uleb128(tbl->data, &pos, data_end);
+
+ /* Check entry 0 */
+ if (cur_addr <= offset) {
+ best_addr = cur_addr;
+ best_file_id = cur_file_id;
+ best_line = cur_line;
+ found = true;
+ }
+
+ /* Decode entries 1..N */
+ for (unsigned int i = 1; i < block_entries; i++) {
+ unsigned int addr_delta;
+ int32_t file_delta, line_delta;
+
+ addr_delta = lineinfo_read_uleb128(tbl->data, &pos, data_end);
+ file_delta = lineinfo_read_sleb128(tbl->data, &pos, data_end);
+ line_delta = lineinfo_read_sleb128(tbl->data, &pos, data_end);
+
+ cur_addr += addr_delta;
+ cur_file_id = (unsigned int)((int32_t)cur_file_id + file_delta);
+ cur_line = (unsigned int)((int32_t)cur_line + line_delta);
+
+ if (cur_addr > offset)
+ break;
+
+ best_addr = cur_addr;
+ best_file_id = cur_file_id;
+ best_line = cur_line;
+ found = true;
+ }
+
+ if (!found)
+ return false;
+
+ /*
+ * The best entry is the closest one at or below @offset; reject it
+ * if it lies below the resolved symbol's start, so a symbol without
+ * line entries of its own does not inherit the preceding symbol's
+ * annotation.
+ */
+ if (best_addr < min_offset)
+ return false;
+
+ if (best_file_id >= tbl->num_files)
+ return false;
+
+ if (tbl->file_offsets[best_file_id] >= tbl->filenames_size)
+ return false;
+
+ *file = &tbl->filenames[tbl->file_offsets[best_file_id]];
+ *line = best_line;
+ return true;
}
#endif /* _LINUX_MOD_LINEINFO_H */
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index 55aa56c9d07b16..ffc65bc4cfc925 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -467,13 +467,17 @@ static int append_buildid(char *buffer, const char *modname,
#endif /* CONFIG_STACKTRACE_BUILD_ID */
+#include <linux/mod_lineinfo.h>
+
bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
const char **file, unsigned int *line)
{
+ struct lineinfo_table tbl;
unsigned long long raw_offset;
- unsigned int offset, min_offset = 0, low, high, mid, file_id;
+ unsigned int min_offset = 0;
- if (!IS_ENABLED(CONFIG_KALLSYMS_LINEINFO) || !lineinfo_num_entries)
+ if (!IS_ENABLED(CONFIG_KALLSYMS_LINEINFO) ||
+ !lineinfo_num_entries || !lineinfo_num_blocks)
return false;
/* Compute offset from _text */
@@ -483,11 +487,10 @@ bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
raw_offset = addr - (unsigned long)_text;
if (raw_offset > UINT_MAX)
return false;
- offset = (unsigned int)raw_offset;
/*
- * The search below returns the closest entry at or below @offset, so
- * a symbol without line entries of its own (assembly without debug
+ * The search returns the closest entry at or below the offset, so a
+ * symbol without line entries of its own (assembly without debug
* info, or anything past the _etext cap like .init.text) would
* inherit the last entry of whatever precedes it. Bound the result
* to entries at or above the resolved symbol's start.
@@ -499,35 +502,19 @@ bool kallsyms_lookup_lineinfo(unsigned long addr, unsigned long sym_start,
min_offset = (unsigned int)raw_min;
}
- /* 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--;
-
- if (lineinfo_addrs[low] < min_offset)
- return false;
-
- 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;
+ tbl.blk_addrs = lineinfo_block_addrs;
+ tbl.blk_offsets = lineinfo_block_offsets;
+ tbl.data = lineinfo_data;
+ tbl.data_size = lineinfo_data_size;
+ tbl.file_offsets = lineinfo_file_offsets;
+ tbl.filenames = lineinfo_filenames;
+ tbl.num_entries = lineinfo_num_entries;
+ tbl.num_blocks = lineinfo_num_blocks;
+ tbl.num_files = lineinfo_num_files;
+ tbl.filenames_size = lineinfo_filenames_size;
+
+ return lineinfo_search(&tbl, (unsigned int)raw_offset, min_offset,
+ file, line);
}
/* Look up a kernel symbol and return it in a text buffer. */
diff --git a/kernel/kallsyms_internal.h b/kernel/kallsyms_internal.h
index d7374ce444d811..ffe4c658067ecc 100644
--- a/kernel/kallsyms_internal.h
+++ b/kernel/kallsyms_internal.h
@@ -16,10 +16,12 @@ extern const unsigned int kallsyms_markers[];
extern const u8 kallsyms_seqs_of_names[];
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_num_blocks;
+extern const u32 lineinfo_block_addrs[];
+extern const u32 lineinfo_block_offsets[];
+extern const u32 lineinfo_data_size;
+extern const u8 lineinfo_data[];
extern const u32 lineinfo_file_offsets[];
extern const u32 lineinfo_filenames_size;
extern const char lineinfo_filenames[];
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
index 4888ce8a7f951d..154eeb3738ee6c 100644
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -498,9 +498,9 @@ int module_kallsyms_on_each_symbol(const char *modname,
#include <linux/mod_lineinfo.h>
/*
- * Search one per-section sub-table for @section_offset using flat parallel
- * arrays. @hdr is the per-section header at byte offset @hdr_offset within
- * @blob. Returns true on hit and populates @file / @line.
+ * Search one per-section sub-table for @section_offset.
+ * @hdr is the per-section header at byte offset @hdr_offset within @blob.
+ * Returns true on hit and populates @file / @line.
*/
static bool module_lookup_lineinfo_section(const void *blob, u32 blob_size,
u32 hdr_offset,
@@ -510,93 +510,71 @@ static bool module_lookup_lineinfo_section(const void *blob, u32 blob_size,
unsigned int *line)
{
const struct mod_lineinfo_header *hdr;
- const u8 *base;
- const u32 *addrs, *lines, *file_offsets;
- const u16 *file_ids;
- const char *filenames;
- u32 num_entries, num_files, filenames_size;
- unsigned int low, high, mid;
- u16 file_id;
+ struct lineinfo_table tbl;
+ const void *base;
if (hdr_offset > blob_size ||
blob_size - hdr_offset < sizeof(*hdr))
return false;
base = (const u8 *)blob + hdr_offset;
- hdr = (const struct mod_lineinfo_header *)base;
- num_entries = hdr->num_entries;
- num_files = hdr->num_files;
- filenames_size = hdr->filenames_size;
+ hdr = base;
- if (num_entries == 0)
+ if (hdr->num_entries == 0 || hdr->num_blocks == 0)
return false;
- /*
- * Validate counts before multiplying — sizing arithmetic could
- * otherwise overflow on 32-bit with a malformed blob. Each entry
- * contributes one u32 (addrs), one u16 (file_ids), and one u32
- * (lines); each file contributes one u32 (file_offsets).
- */
+ /* Validate each sub-array fits within the remaining blob bytes */
{
u32 avail = blob_size - hdr_offset;
- u32 needed = mod_lineinfo_filenames_off(num_entries, num_files);
- if (num_entries > U32_MAX / sizeof(u32))
+ if (hdr->blocks_offset > avail ||
+ hdr->blocks_size > avail - hdr->blocks_offset)
+ return false;
+ if (hdr->data_offset > avail ||
+ hdr->data_size > avail - hdr->data_offset)
return false;
- if (num_files > U32_MAX / sizeof(u32))
+ if (hdr->files_offset > avail ||
+ hdr->files_size > avail - hdr->files_offset)
return false;
- if (needed > avail || filenames_size > avail - needed)
+ if (hdr->filenames_offset > avail ||
+ hdr->filenames_size > avail - hdr->filenames_offset)
return false;
}
/*
- * Filenames are read as NUL-terminated C strings. Require the blob
- * to end in NUL so a malformed file_offsets entry can never lead the
- * later "%s" consumer past the end of the section.
+ * Validate counts before multiplying by element size — multiplication
+ * could otherwise overflow on 32-bit builds with a malformed blob.
+ * num_blocks contributes (addr,offset) u32 pairs; num_files contributes
+ * one u32 each.
*/
- if (filenames_size == 0 ||
- base[mod_lineinfo_filenames_off(num_entries, num_files) +
- filenames_size - 1] != 0)
+ if (hdr->num_blocks > hdr->blocks_size / (2 * sizeof(u32)))
return false;
-
- addrs = (const u32 *)(base + mod_lineinfo_addrs_off());
- file_ids = (const u16 *)(base + mod_lineinfo_file_ids_off(num_entries));
- lines = (const u32 *)(base + mod_lineinfo_lines_off(num_entries));
- file_offsets = (const u32 *)(base + mod_lineinfo_file_offsets_off(num_entries));
- filenames = (const char *)(base + mod_lineinfo_filenames_off(num_entries, num_files));
-
- /* Binary search for largest entry <= section_offset. */
- low = 0;
- high = num_entries;
- while (low < high) {
- mid = low + (high - low) / 2;
- if (addrs[mid] <= section_offset)
- low = mid + 1;
- else
- high = mid;
- }
-
- if (low == 0)
+ if (hdr->num_files > hdr->files_size / sizeof(u32))
return false;
- low--;
/*
- * Reject entries below the resolved symbol's start so a symbol
- * without line entries of its own does not inherit the preceding
- * symbol's annotation.
+ * Filenames are read as NUL-terminated C strings. Require the blob
+ * to end in NUL so a malformed file_offsets entry can never lead the
+ * later "%s" consumer past the end of the section.
*/
- if (addrs[low] < min_offset)
+ if (hdr->filenames_size == 0 ||
+ ((const u8 *)base)[hdr->filenames_offset +
+ hdr->filenames_size - 1] != 0)
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;
+ tbl.blk_addrs = base + hdr->blocks_offset;
+ tbl.blk_offsets = base + hdr->blocks_offset +
+ hdr->num_blocks * sizeof(u32);
+ tbl.data = base + hdr->data_offset;
+ tbl.data_size = hdr->data_size;
+ tbl.file_offsets = base + hdr->files_offset;
+ tbl.filenames = base + hdr->filenames_offset;
+ tbl.num_entries = hdr->num_entries;
+ tbl.num_blocks = hdr->num_blocks;
+ tbl.num_files = hdr->num_files;
+ tbl.filenames_size = hdr->filenames_size;
+
+ return lineinfo_search(&tbl, section_offset, min_offset, file, line);
}
/*
@@ -619,6 +597,7 @@ static bool module_lookup_lineinfo_blob(const void *blob, u32 blob_size,
if (root->num_sections == 0)
return false;
+ /* Validate sections[] array fits within the blob */
if (root->num_sections > U32_MAX / sizeof(struct mod_lineinfo_section))
return false;
sections_end = sizeof(*root) +
@@ -656,6 +635,9 @@ static bool module_lookup_lineinfo_blob(const void *blob, u32 blob_size,
/*
* Look up source file:line for an address within a loaded module.
+ * Uses the .mod_lineinfo / .init.mod_lineinfo sections embedded in the .ko
+ * at build time. Each section contains one or more per-section sub-tables
+ * keyed by an ELF-relocation-resolved anchor.
*
* Safe in NMI/panic context: no locks, no allocations.
* Caller must hold RCU read lock (or be in a context where the module
diff --git a/scripts/empty_lineinfo.S b/scripts/empty_lineinfo.S
index e058c411371237..edd5b1092f0503 100644
--- a/scripts/empty_lineinfo.S
+++ b/scripts/empty_lineinfo.S
@@ -14,12 +14,20 @@ lineinfo_num_entries:
.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_num_blocks
+ .balign 4
+lineinfo_num_blocks:
+ .long 0
+ .globl lineinfo_block_addrs
+lineinfo_block_addrs:
+ .globl lineinfo_block_offsets
+lineinfo_block_offsets:
+ .globl lineinfo_data_size
+ .balign 4
+lineinfo_data_size:
+ .long 0
+ .globl lineinfo_data
+lineinfo_data:
.globl lineinfo_file_offsets
lineinfo_file_offsets:
.globl lineinfo_filenames_size
diff --git a/scripts/gen_lineinfo.c b/scripts/gen_lineinfo.c
index 7624098d0cb426..6b69738e45933a 100644
--- a/scripts/gen_lineinfo.c
+++ b/scripts/gen_lineinfo.c
@@ -956,6 +956,45 @@ static void deduplicate(struct covered_section *sections,
}
}
+/*
+ * Emit the LEB128 delta-compressed data stream for one block.
+ * @base is the absolute index of the first entry, @count is the number of
+ * entries in this block (<= LINEINFO_BLOCK_ENTRIES). Used by both vmlinux
+ * mode (one section, full entries[]) and module mode (per-section ranges).
+ */
+static void emit_block_data_range(unsigned int base, unsigned int count)
+{
+ if (!count)
+ return;
+
+ /* Entry 0: file_id, line (both unsigned) */
+ printf("\t.uleb128 %u\n", entries[base].file_id);
+ printf("\t.uleb128 %u\n", entries[base].line);
+
+ /* Entries 1..N: addr_delta (unsigned), file/line deltas (signed) */
+ for (unsigned int i = 1; i < count; i++) {
+ unsigned int idx = base + i;
+
+ printf("\t.uleb128 %u\n",
+ entries[idx].offset - entries[idx - 1].offset);
+ printf("\t.sleb128 %d\n",
+ (int)entries[idx].file_id - (int)entries[idx - 1].file_id);
+ printf("\t.sleb128 %d\n",
+ (int)entries[idx].line - (int)entries[idx - 1].line);
+ }
+}
+
+/* Vmlinux-mode wrapper: pick block index out of the global entries[]. */
+static void emit_block_data(unsigned int block)
+{
+ unsigned int base = block * LINEINFO_BLOCK_ENTRIES;
+ unsigned int count = num_entries - base;
+
+ if (count > LINEINFO_BLOCK_ENTRIES)
+ count = LINEINFO_BLOCK_ENTRIES;
+ emit_block_data_range(base, count);
+}
+
static void compute_file_offsets(void)
{
unsigned int offset = 0;
@@ -979,6 +1018,11 @@ static void print_escaped_asciz(const char *s)
static void output_assembly(void)
{
+ unsigned int num_blocks;
+
+ num_blocks = num_entries ?
+ (num_entries + LINEINFO_BLOCK_ENTRIES - 1) / LINEINFO_BLOCK_ENTRIES : 0;
+
printf("/* SPDX-License-Identifier: GPL-2.0 */\n");
printf("/*\n");
printf(" * Automatically generated by scripts/gen_lineinfo\n");
@@ -999,29 +1043,40 @@ static void output_assembly(void)
printf("lineinfo_num_files:\n");
printf("\t.long %u\n\n", num_files);
- /* Sorted address offsets from _text */
- printf("\t.globl lineinfo_addrs\n");
+ /* Number of blocks */
+ printf("\t.globl lineinfo_num_blocks\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");
+ printf("lineinfo_num_blocks:\n");
+ printf("\t.long %u\n\n", num_blocks);
- /* 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");
+ /* Block first-addresses for binary search */
+ printf("\t.globl lineinfo_block_addrs\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_block_addrs:\n");
+ for (unsigned int i = 0; i < num_blocks; i++)
+ printf("\t.long 0x%x\n", entries[i * LINEINFO_BLOCK_ENTRIES].offset);
- /* Line numbers, parallel to addrs */
- printf("\t.globl lineinfo_lines\n");
+ /* Block byte offsets into compressed stream */
+ printf("\t.globl lineinfo_block_offsets\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");
+ printf("lineinfo_block_offsets:\n");
+ for (unsigned int i = 0; i < num_blocks; i++)
+ printf("\t.long .Lblock_%u - lineinfo_data\n", i);
+
+ /* Compressed data size */
+ printf("\t.globl lineinfo_data_size\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_data_size:\n");
+ printf("\t.long .Ldata_end - lineinfo_data\n\n");
+
+ /* Compressed data stream */
+ printf("\t.globl lineinfo_data\n");
+ printf("lineinfo_data:\n");
+ for (unsigned int i = 0; i < num_blocks; i++) {
+ printf(".Lblock_%u:\n", i);
+ emit_block_data(i);
+ }
+ printf(".Ldata_end:\n\n");
/* File string offset table */
printf("\t.globl lineinfo_file_offsets\n");
@@ -1029,71 +1084,81 @@ static void output_assembly(void)
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);
- }
+ printf("\t.globl lineinfo_filenames_size\n");
+ printf("\t.balign 4\n");
+ printf("lineinfo_filenames_size:\n");
+ printf("\t.long .Lfilenames_end - lineinfo_filenames\n\n");
/* 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");
+ printf(".Lfilenames_end:\n");
}
/*
- * Emit one per-section table in the simple flat-array layout:
+ * Emit one per-section table. @suffix uniquifies the local labels so
+ * multiple tables can coexist in a single output blob; @blob_root_label
+ * is the symbol for the start of the enclosing blob (used for
+ * table_offset = .Lhdr - .Lroot).
*
- * mod_lineinfo_header
- * addrs[count] (u32, sorted)
- * file_ids[count] (u16) + 2-byte pad if count is odd
- * lines[count] (u32)
- * file_offsets[] (u32)
- * filenames[]
- *
- * @suffix uniquifies labels so multiple tables can coexist in one blob.
- * Caller has sorted entries[] so this section's entries occupy [first,
- * first + count).
+ * Caller has already sorted entries[] so this section's entries occupy
+ * the contiguous range [first, first + count). This function emits
+ * block-relative addresses computed from entries[first + N].offset.
*/
static void emit_section_table(unsigned int first, unsigned int count,
const char *suffix)
{
+ unsigned int num_blocks;
+
+ num_blocks = count ?
+ (count + LINEINFO_BLOCK_ENTRIES - 1) / LINEINFO_BLOCK_ENTRIES : 0;
+
printf(".Lhdr%s:\n", suffix);
printf("\t.balign 4\n");
printf("\t.long %u\t\t/* num_entries */\n", count);
+ printf("\t.long %u\t\t/* num_blocks */\n", num_blocks);
printf("\t.long %u\t\t/* num_files */\n", num_files);
+ printf("\t.long .Lblk_addrs%s - .Lhdr%s\n", suffix, suffix);
+ printf("\t.long .Lblk_offsets_end%s - .Lblk_addrs%s\n", suffix, suffix);
+ printf("\t.long .Ldata%s - .Lhdr%s\n", suffix, suffix);
+ printf("\t.long .Ldata_end%s - .Ldata%s\n", suffix, suffix);
+ printf("\t.long .Lfile_offsets%s - .Lhdr%s\n", suffix, suffix);
+ printf("\t.long .Lfile_offsets_end%s - .Lfile_offsets%s\n", suffix, suffix);
+ printf("\t.long .Lfilenames%s - .Lhdr%s\n", suffix, suffix);
printf("\t.long .Lfilenames_end%s - .Lfilenames%s\n\n", suffix, suffix);
- /* addrs[] */
- for (unsigned int i = 0; i < count; i++)
- printf("\t.long 0x%x\n", entries[first + i].offset);
-
- /* file_ids[] */
- for (unsigned int i = 0; i < count; i++)
- printf("\t.short %u\n", entries[first + i].file_id);
- if (count & 1)
- printf("\t.short 0\t\t/* pad to align lines[] */\n");
-
- /* lines[] */
- for (unsigned int i = 0; i < count; i++)
- printf("\t.long %u\n", entries[first + i].line);
+ printf(".Lblk_addrs%s:\n", suffix);
+ for (unsigned int i = 0; i < num_blocks; i++)
+ printf("\t.long 0x%x\n",
+ entries[first + i * LINEINFO_BLOCK_ENTRIES].offset);
+
+ printf(".Lblk_offsets%s:\n", suffix);
+ for (unsigned int i = 0; i < num_blocks; i++)
+ printf("\t.long .Lblock%s_%u - .Ldata%s\n", suffix, i, suffix);
+ printf(".Lblk_offsets_end%s:\n\n", suffix);
+
+ printf(".Ldata%s:\n", suffix);
+ for (unsigned int i = 0; i < num_blocks; i++) {
+ unsigned int base = first + i * LINEINFO_BLOCK_ENTRIES;
+ unsigned int n = count - i * LINEINFO_BLOCK_ENTRIES;
+
+ if (n > LINEINFO_BLOCK_ENTRIES)
+ n = LINEINFO_BLOCK_ENTRIES;
+ printf(".Lblock%s_%u:\n", suffix, i);
+ emit_block_data_range(base, n);
+ }
+ printf(".Ldata_end%s:\n", suffix);
- /* file_offsets[] */
printf("\t.balign 4\n");
+ printf(".Lfile_offsets%s:\n", suffix);
for (unsigned int i = 0; i < num_files; i++)
printf("\t.long %u\n", files[i].str_offset);
+ printf(".Lfile_offsets_end%s:\n\n", suffix);
- /* filenames[] */
printf(".Lfilenames%s:\n", suffix);
for (unsigned int i = 0; i < num_files; i++)
print_escaped_asciz(files[i].name);
@@ -1372,8 +1437,10 @@ int main(int argc, char *argv[])
deduplicate(NULL, 0);
compute_file_offsets();
- fprintf(stderr, "lineinfo: %u entries, %u files\n",
- num_entries, num_files);
+ fprintf(stderr, "lineinfo: %u entries, %u files, %u blocks\n",
+ num_entries, num_files,
+ num_entries ?
+ (num_entries + LINEINFO_BLOCK_ENTRIES - 1) / LINEINFO_BLOCK_ENTRIES : 0);
output_assembly();
}
diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c
index 42662c4fbc6c94..94fbdad3df7c6e 100644
--- a/scripts/kallsyms.c
+++ b/scripts/kallsyms.c
@@ -80,11 +80,12 @@ 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_block_addrs",
+ "lineinfo_block_offsets",
+ "lineinfo_data",
"lineinfo_file_offsets",
"lineinfo_filenames",
- "lineinfo_lines",
+ "lineinfo_num_blocks",
"lineinfo_num_entries",
"lineinfo_num_files",
};
--
2.53.0
^ permalink raw reply related
page: next (older)
- 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