From: sashiko-bot@kernel.org
To: "Sasha Levin" <sashal@kernel.org>,
"Andrew Morton" <akpm@linux-foundation.org>,
"Masahiro Yamada" <masahiroy@kernel.org>,
"Luis Chamberlain" <mcgrof@kernel.org>,
"Linus Torvalds" <torvalds@linux-foundation.org>,
"Richard Weinberger" <richard@nod.at>,
"Juergen Gross" <jgross@suse.com>,
"Geert Uytterhoeven" <geert@linux-m68k.org>,
"James Bottomley" <James.Bottomley@HansenPartnership.com>
Cc: "Jonathan Corbet" <corbet@lwn.net>,
"Nathan Chancellor" <nathan@kernel.org>,
"Nicolas Schier" <nsc@kernel.org>,
"Petr Pavlu" <petr.pavlu@suse.com>,
"Daniel Gomez" <da.gomez@kernel.org>,
"Aaron Tomlin" <atomlin@atomlin.com>,
"Greg KH" <gregkh@linuxfoundation.org>,
"Petr Mladek" <pmladek@suse.com>,
"Steven Rostedt" <rostedt@goodmis.org>,
"Kees Cook" <kees@kernel.org>,
"Peter Zijlstra" <peterz@infradead.org>,
"Thorsten Leemhuis" <linux@leemhuis.info>,
"Vlastimil Babka" <vbabka@kernel.org>,
"Helge Deller" <deller@gmx.de>,
"Randy Dunlap" <rdunlap@infradead.org>,
"Laurent Pinchart" <laurent.pinchart@ideasonboard.com>,
"Vivian Wang" <wangruikang@iscas.ac.cn>,
"Zhen Lei" <thunder.leizhen@huawei.com>,
"Sami Tolvanen" <samitolvanen@google.com>,
linux-kbuild@vger.kernel.org, linux-modules@vger.kernel.org
Subject: Re: [PATCH v10 1/4] kallsyms: embed source file:line info in kernel stack traces
Date: Tue, 03 Nov 2026 11:15:24 +0000 [thread overview]
Message-ID: <sashiko-outbox-195130@kernel.org> (raw)
In-Reply-To: <20261102111455.919102-2-sashal@kernel.org>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] kallsyms: gen_lineinfo excludes STT_NOTYPE and unsized symbols in sym_starts
- [Medium] kallsyms: collect_symbol_starts() includes SHN_ABS and mapping symbols
- [Medium] kallsyms: line-0 entries overwrite valid coverage in deduplicate()
- [Low] scripts: missing libelf in pkg-config query for gen_lineinfo in Makefile
--
commit 939ac5438a3c14ba3c356cc9994f3c7b50467285
Author: Sasha Levin <sashal@kernel.org>
kallsyms: embed source file:line info in kernel stack traces
This commit adds CONFIG_KALLSYMS_LINEINFO and a host tool
(scripts/gen_lineinfo) to extract DWARF .debug_line address-to-source-line
mappings into vmlinux .rodata so stack traces include (file:line)
annotations.
> diff --git a/scripts/Makefile b/scripts/Makefile
> index d7fbb7ca098b1..94b09cf7c1839 100644
> --- a/scripts/Makefile
> +++ b/scripts/Makefile
> @@ -4,6 +4,7 @@
> # the kernel for the build process.
>
> hostprogs-always-y += kallsyms
> +hostprogs-always-$(CONFIG_KALLSYMS_LINEINFO) += gen_lineinfo
> hostprogs-always-$(BUILD_C_RECORDMCOUNT) += recordmcount
> hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable
> hostprogs-always-$(CONFIG_ASN1) += asn1_compiler
> @@ -40,6 +41,8 @@ HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include
> HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
> HOSTCFLAGS_sign-file.o += -I$(srctree)/tools/include/uapi/
> HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
> +HOSTCFLAGS_gen_lineinfo.o = $(shell $(HOSTPKG_CONFIG) --cflags libdw 2> /dev/null)
> +HOSTLDLIBS_gen_lineinfo = $(shell $(HOSTPKG_CONFIG) --libs libdw 2> /dev/null || echo -ldw -lelf)
[Severity: Low]
Should HOSTCFLAGS_gen_lineinfo.o and HOSTLDLIBS_gen_lineinfo in
scripts/Makefile query pkg-config for both libdw and libelf?
scripts/gen_lineinfo.c directly includes <elf.h> and <gelf.h> and calls
libelf APIs in main(), collect_symbol_starts(), and find_vmlinux_sym()
(such as elf_version(), elf_begin(), elf_nextscn(), gelf_getshdr(),
elf_getdata(), gelf_getsym(), elf_strptr(), elf_errmsg(), elf_errno(), and
elf_end()).
In elfutils, libdw.pc lists libelf under Requires.private rather than
Requires. When pkg-config is installed on the host, running:
$(HOSTPKG_CONFIG) --libs libdw
succeeds with exit status 0 and outputs only -ldw, which prevents the
"|| echo -ldw -lelf" fallback from running. On toolchains that default to
--no-copy-dt-needed-entries where libdw.so is a symlink to libdw.so.1
rather than a linker script, linking scripts/gen_lineinfo can fail with
undefined references to the libelf symbols.
Would querying "$(HOSTPKG_CONFIG) --cflags libdw libelf" and
"$(HOSTPKG_CONFIG) --libs libdw libelf" avoid this build failure?
> diff --git a/scripts/gen_lineinfo.c b/scripts/gen_lineinfo.c
> new file mode 100644
> index 0000000000000..7562ac81acca5
> --- /dev/null
> +++ b/scripts/gen_lineinfo.c
[ ... ]
> +static void collect_symbol_starts(Elf *elf, unsigned long long text_addr)
> +{
> + Elf_Scn *scn = NULL;
> + GElf_Shdr shdr;
> +
> + while ((scn = elf_nextscn(elf, scn)) != NULL) {
> + Elf_Data *data;
> + size_t nsyms;
> +
> + if (!gelf_getshdr(scn, &shdr))
> + continue;
> + if (shdr.sh_type != SHT_SYMTAB || !shdr.sh_entsize)
> + continue;
> +
> + data = elf_getdata(scn, NULL);
> + if (!data)
> + continue;
> +
> + nsyms = shdr.sh_size / shdr.sh_entsize;
> + for (size_t i = 0; i < nsyms; i++) {
> + GElf_Sym sym;
> + unsigned long long raw;
> +
> + if (!gelf_getsym(data, i, &sym))
> + continue;
> + switch (GELF_ST_TYPE(sym.st_info)) {
> + case STT_FUNC:
> + case STT_NOTYPE:
> + break;
> + default:
> + continue;
> + }
> + if (sym.st_value < text_addr)
> + continue;
> + if (text_end_addr && sym.st_value >= text_end_addr)
> + continue;
> +
> + raw = sym.st_value - text_addr;
> + if (raw > UINT_MAX)
> + continue;
> +
> + if (num_sym_starts >= sym_starts_capacity) {
> + sym_starts_capacity = sym_starts_capacity ?
> + sym_starts_capacity * 2 : 16384;
> + sym_starts = xrealloc(sym_starts,
> + sym_starts_capacity *
> + sizeof(*sym_starts));
> + }
> + if (num_text_starts >= text_starts_capacity) {
> + text_starts_capacity = text_starts_capacity ?
> + text_starts_capacity * 2 : 16384;
> + text_starts = xrealloc(text_starts,
> + text_starts_capacity *
> + sizeof(*text_starts));
> + }
> + text_starts[num_text_starts].offset = (unsigned int)raw;
> + text_starts[num_text_starts].size =
> + sym.st_size > UINT_MAX ? UINT_MAX :
> + (unsigned int)sym.st_size;
> + num_text_starts++;
> +
> + if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
> + continue;
> +
> + sym_starts[num_sym_starts].offset = (unsigned int)raw;
> + sym_starts[num_sym_starts].size =
> + sym.st_size > UINT_MAX ? UINT_MAX :
> + (unsigned int)sym.st_size;
> + num_sym_starts++;
> + }
> + }
> +
> + sort_starts(sym_starts, &num_sym_starts);
> + sort_starts(text_starts, &num_text_starts);
> +
> + /*
> + * An unsized label reaches the next symbol, and a sequence beginning
> + * there cannot run past it.
> + */
> + for (unsigned int i = 1; i < num_text_starts; i++) {
> + if (!text_starts[i - 1].size)
> + text_starts[i - 1].size = text_starts[i - 1].offset ?
> + text_starts[i].offset -
> + text_starts[i - 1].offset : 0;
[Severity: Medium]
Can unfiltered ELF symbols in collect_symbol_starts() in
scripts/gen_lineinfo.c corrupt the symbol extents in text_starts[]?
In collect_symbol_starts(), every STT_FUNC and STT_NOTYPE symbol in
SHT_SYMTAB whose st_value falls in [text_addr, text_end_addr) is added to
text_starts[] without checking sym.st_shndx or the symbol name:
- SHN_ABS symbols (such as __kcfi_typeid_* hashes) whose values happen to
fall in [text_addr, text_end_addr) are included.
- Architecture mapping symbols ($x, $d on arm64/arm/riscv/loongarch) and
assembler local labels (.L*) are included.
When the loop at the end of collect_symbol_starts() backfills sizes for
unsized entries:
for (unsigned int i = 1; i < num_text_starts; i++) {
if (!text_starts[i - 1].size)
text_starts[i - 1].size = text_starts[i].offset -
text_starts[i - 1].offset;
}
an internal .L* or $x label inside an unsized assembly routine truncates
that routine's extent at the first internal label, while an unsized mapping
or SHN_ABS symbol at a sequence boundary is given a non-zero size reaching
to the next symbol, which can cause starts_new_sequence() and
resolve_cu_row_groups() to misclassify DWARF sequence boundaries.
Additionally, why does collect_symbol_starts() exclude STT_NOTYPE and
unsized symbols from sym_starts[]?
At runtime, __sprint_symbol() -> kallsyms_lookup_buildid() resolves symbols
using the kallsyms table, which includes STT_NOTYPE code symbols and
unsized assembly labels (such as Clang .cold subfunctions, SYM_CODE_START
routines, SYM_INNER_LABEL entry points like entry_SYSCALL_64_after_hwframe,
and CONFIG_PROPELLER_CLANG basic-block symbols), and passes sym_start to
kallsyms_lookup_lineinfo():
if (lineinfo_addrs[low] < min_offset)
return false;
Because collect_symbol_starts() skips GELF_ST_TYPE(sym.st_info) != STT_FUNC
when populating sym_starts[] and never backfills zero sizes in
sym_starts[], synthesize_symbol_starts() skips those symbols (or rejects
st_size == 0 at "if (end <= start) continue;") and deduplicate() sets
at_symbol_start = false at their start offsets. Any frame inside those
symbols before a new file:line transition will then fail the min_offset
check in kallsyms_lookup_lineinfo() and lose its annotation.
[ ... ]
> +static void synthesize_symbol_starts(void)
> +{
> + unsigned int base_entries = num_entries;
> + unsigned int cursor = 0;
> +
> + if (!base_entries || !num_sym_starts)
> + return;
> +
> + sort_unique(seq_ends, &num_seq_ends);
> + qsort(asm_spans, num_asm_spans, sizeof(*asm_spans), compare_asm_spans);
> +
> + for (unsigned int i = 0; i < num_sym_starts; i++) {
> + unsigned int start = sym_starts[i].offset;
> + unsigned int end = start + sym_starts[i].size;
> +
> + while (cursor + 1 < base_entries &&
> + entries[cursor + 1].offset <= start)
> + cursor++;
> +
> + if (entries[cursor].offset > start)
> + continue; /* nothing covers this symbol */
> + if (entries[cursor].offset == start)
> + continue; /* already has its own entry */
> + if (seq_end_between(entries[cursor].offset, start))
> + continue; /* coverage stopped before here */
> +
> + if (end <= start)
> + continue; /* size overflowed */
> +
> + /*
> + * A row reaching over a symbol with none inside it is padding
> + * or a split-out fragment in C, carrying the line of whatever
> + * it came from. In assembly it is one macro expanding to a
> + * function per invocation, which the row does describe.
> + */
> + if (!in_asm_span(start) &&
> + (cursor + 1 >= base_entries ||
> + entries[cursor + 1].offset >= end))
> + continue;
> +
> + add_entry(start, entries[cursor].file_id, entries[cursor].line);
> + }
> +}
> +
> +static void deduplicate(void)
> +{
> + unsigned int sym_cursor = 0;
> + 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);
> +
> + synthesize_symbol_starts();
> + qsort(entries, num_entries, sizeof(*entries), compare_entries);
> +
> + /*
> + * Remove duplicate entries:
> + * - Same offset: keep last. Earlier rows at that address cover no
> + * bytes, so the final one is what describes the instruction, and
> + * it is what addr2line reports. For an inlined call that is the
> + * inlined body rather than the call site.
> + * - Same file:line as previous kept entry: redundant for binary
> + * search -- any address between them resolves to the earlier one
> + *
> + * Entries sitting on a symbol start are exempt from the second rule:
> + * they are the only thing standing between that symbol and the
> + * kernel's boundary check.
> + */
> + j = 0;
> + for (i = 1; i < num_entries; i++) {
> + bool at_symbol_start;
> +
> + if (entries[i].offset == entries[j].offset) {
> + /*
> + * Rows from different compile units can share an
> + * address, and coverage is their union: a real
> + * location beats another unit's no-coverage marker.
> + * Within one unit resolve_cu_row_groups() has already
> + * picked the row that describes the address.
> + */
> + if (entries[i].line || !entries[j].line)
> + entries[j] = entries[i];
> + continue;
> + }
> +
> + while (sym_cursor < num_sym_starts &&
> + sym_starts[sym_cursor].offset < entries[i].offset)
> + sym_cursor++;
> + at_symbol_start = sym_cursor < num_sym_starts &&
> + sym_starts[sym_cursor].offset == entries[i].offset;
> +
> + if (!at_symbol_start &&
> + entries[i].file_id == entries[j].file_id &&
> + entries[i].line == entries[j].line)
> + continue;
> +
> + j++;
> + if (j != i)
> + entries[j] = entries[i];
> + }
> + num_entries = j + 1;
> +}
[Severity: Medium]
Can line-0 end_sequence entries overwrite valid source coverage in
synthesize_symbol_starts() and deduplicate() in scripts/gen_lineinfo.c?
There appear to be three ways a line-0 entry can clobber a valid line entry
across compile units:
1. deduplicate() calls synthesize_symbol_starts() before collapsing
same-offset entries across compile units. When an earlier CU emits a valid
row (line > 0) at offset X and a later CU emits an end_sequence row
(line == 0) at the same offset X, the line-0 entry has a higher seq and
sorts last at offset X. In synthesize_symbol_starts(), the cursor loop:
while (cursor + 1 < base_entries &&
entries[cursor + 1].offset <= start)
cursor++;
advances to the last entry at offset X, picking the line-0 entry instead
of the valid line entry.
2. In process_dwarf(), every sequence end passed to record_seq_end() also
emits a line-0 entry into entries[] at that same offset. Because cursor in
synthesize_symbol_starts() stops at the largest entry offset <= start,
there can never be a sequence end strictly between entries[cursor].offset
and start without a corresponding entry in entries[] that would have
advanced cursor to it. Thus seq_end_between(entries[cursor].offset, start)
never triggers, and because synthesize_symbol_starts() does not check
whether entries[cursor].line == 0, a line-0 sequence-end entry at
entries[cursor].offset gets copied to start.
3. In deduplicate(), same-offset union resolution is interleaved with
adjacent (file_id, line) deduplication. Suppose entries[j] is at offset A
with (file_id=1, line=10), and at offset B > A (not a symbol start) CU1 has
(file_id=1, line=10) followed by CU2 having an end_sequence (file_id=0,
line=0) at the same offset B:
- For CU1's entry at offset B, entries[i].offset != entries[j].offset,
and since (file_id, line) matches entries[j], the loop hits continue
without updating entries[j].offset to B.
- For CU2's line-0 entry at offset B, entries[i].offset (B) !=
entries[j].offset (A), so it misses the "if (entries[i].offset ==
entries[j].offset)" union check and is appended as a new entry at
offset B with line 0, cutting off valid coverage from offset B onward.
Would collapsing same-offset entries across CUs in a separate pass before
running synthesize_symbol_starts() and before collapsing adjacent
identical (file_id, line) entries (and checking entries[cursor].line != 0
in synthesize_symbol_starts()) avoid losing coverage here?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102111455.919102-1-sashal@kernel.org?part=1
next prev parent reply other threads:[~2026-11-03 11:15 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 11:14 [PATCH v10 0/4] kallsyms: embed source file:line info in kernel stack traces Sasha Levin
2026-11-02 11:14 ` [PATCH v10 1/4] " Sasha Levin
2026-11-03 11:15 ` sashiko-bot [this message]
2026-11-02 11:14 ` [PATCH v10 2/4] kallsyms: extend lineinfo to loadable modules Sasha Levin
2026-11-03 11:15 ` sashiko-bot
2026-11-02 11:14 ` [PATCH v10 3/4] kallsyms: delta-compress lineinfo tables for ~2.7x size reduction Sasha Levin
2026-11-02 11:14 ` [PATCH v10 4/4] kallsyms: add KUnit tests for lineinfo feature Sasha Levin
2026-11-03 11:15 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-195130@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=James.Bottomley@HansenPartnership.com \
--cc=akpm@linux-foundation.org \
--cc=atomlin@atomlin.com \
--cc=corbet@lwn.net \
--cc=da.gomez@kernel.org \
--cc=deller@gmx.de \
--cc=geert@linux-m68k.org \
--cc=gregkh@linuxfoundation.org \
--cc=jgross@suse.com \
--cc=kees@kernel.org \
--cc=laurent.pinchart@ideasonboard.com \
--cc=linux-kbuild@vger.kernel.org \
--cc=linux-modules@vger.kernel.org \
--cc=linux@leemhuis.info \
--cc=masahiroy@kernel.org \
--cc=mcgrof@kernel.org \
--cc=nathan@kernel.org \
--cc=nsc@kernel.org \
--cc=peterz@infradead.org \
--cc=petr.pavlu@suse.com \
--cc=pmladek@suse.com \
--cc=rdunlap@infradead.org \
--cc=richard@nod.at \
--cc=rostedt@goodmis.org \
--cc=samitolvanen@google.com \
--cc=sashal@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=thunder.leizhen@huawei.com \
--cc=torvalds@linux-foundation.org \
--cc=vbabka@kernel.org \
--cc=wangruikang@iscas.ac.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox