From: sashiko-bot@kernel.org
To: "Matthew Wood" <thepacketgeek@gmail.com>
Cc: linux-perf-users@vger.kernel.org
Subject: Re: [PATCH v2 2/3] perf unwind: Find separate debuginfo without loading symbols
Date: Sat, 17 Oct 2026 00:18:23 +0000 [thread overview]
Message-ID: <sashiko-outbox-175062@kernel.org> (raw)
In-Reply-To: <20261017000158.1178269-3-thepacketgeek@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] perf dso: dso__find_debuginfo() returns unverified first regular file
- [Medium] perf dso: dso__find_debuginfo() ignores symfs when reading build-id
- [Medium] perf dso: dso__find_debuginfo() skips mountns and chroot lookup
- [Medium] perf unwind: __find_debuginfo() skips fallback when symsrc is main binary
--
commit c3719700a5c64b73424eb58a041d6711fca9b8d4
Author: Matthew Wood <thepacketgeek@gmail.com>
perf unwind: Find separate debuginfo without loading symbols
This commit adds dso__find_debuginfo() to locate separate debuginfo files
for user space DSOs without loading symbol tables, and wires it into the
libdw and libunwind DWARF unwinders.
> diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
> index e03e2cb9212b1..36f5ab94c7e70 100644
> --- a/tools/perf/util/dso.c
> +++ b/tools/perf/util/dso.c
> @@ -598,6 +598,37 @@ int dso__read_build_id(struct dso *dso)
> return dso__has_build_id(dso) ? 0 : -1;
> }
>
> +/*
> + * Look for a separate debuginfo file for a user space DSO in the places
> + * dso__load() would, without loading its symbols. Returns an allocated path,
> + * or NULL if none was found.
> + */
> +char *dso__find_debuginfo(struct dso *dso)
> +{
> + static const enum dso_binary_type debuginfo_types[] = {
> + DSO_BINARY_TYPE__DEBUGLINK,
> + DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO,
> + DSO_BINARY_TYPE__FEDORA_DEBUGINFO,
> + DSO_BINARY_TYPE__UBUNTU_DEBUGINFO,
> + DSO_BINARY_TYPE__BUILDID_DEBUGINFO,
> + DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO,
> + DSO_BINARY_TYPE__MIXEDUP_UBUNTU_DEBUGINFO,
> + };
> + char filename[PATH_MAX];
> +
> + /* The build-id cache and /usr/lib/debug/.build-id need the build-id. */
> + dso__read_build_id(dso);
[Severity: Medium]
Does calling dso__read_build_id() here ignore symbol_conf.symfs when
--symfs is used and the DSO does not yet have a recorded build-id?
In tools/perf/util/symbol.c:dso__load(), perf joins symfs before reading
the build-id:
if (!dso__has_build_id(dso)) {
struct build_id bid = { .size = 0, };
__symbol__join_symfs(name, PATH_MAX, dso__long_name(dso));
if (filename__read_build_id(name, &bid) > 0)
dso__set_build_id(dso, &bid);
}
In contrast, tools/perf/util/dso.c:dso__read_build_id() passes
dso__long_name(dso) directly to filename__read_build_id() without
__symbol__join_symfs():
if (filename__read_build_id(dso__long_name(dso), &bid) > 0)
dso__set_build_id(dso, &bid);
If the binary only exists under symfs, dso__read_build_id() fails to read
the build-id. If a different version of the binary exists at
dso__long_name(dso) on the host root filesystem, dso__read_build_id()
populates the DSO with the host binary's build-id, causing subsequent
dso__load() calls to reject the symfs binary with
DSO_LOAD_ERRNO__MISMATCHING_BUILDID.
> +
> + for (size_t i = 0; i < ARRAY_SIZE(debuginfo_types); i++) {
> + if (dso__read_binary_type_filename(dso, debuginfo_types[i], /*root_dir=*/"",
> + filename, sizeof(filename)))
> + continue;
> + if (is_regular_file(filename))
> + return strdup(filename);
> + }
[Severity: Medium]
Can this loop return the stripped DSO itself or a file with a mismatched
build-id before reaching the remaining candidates in debuginfo_types[]?
When checking DSO_BINARY_TYPE__DEBUGLINK,
tools/perf/util/dso.c:dso__read_binary_type_filename() tests
debuglink_paths[] using is_regular_file():
for (i = 0; i < ARRAY_SIZE(debuglink_paths); i++) {
snprintf(filename, size,
debuglink_paths[i], dso_dir, symfile);
if (is_regular_file(filename)) {
ret = 0;
break;
}
}
When .gnu_debuglink has the same basename as the DSO itself (such as on
Debian/Ubuntu where /usr/lib/libfoo.so has separate debuginfo at
/usr/lib/debug/usr/lib/libfoo.so), debuglink_paths[1] ("%s/%s") resolves to
the stripped DSO binary itself. Similarly, for
DSO_BINARY_TYPE__BUILD_ID_CACHE_DEBUGINFO, __dso__build_id_filename()
ignores is_debug=true for vDSOs or flat build-id cache links and returns
the cached main binary.
In dso__load(), each candidate is opened via symsrc__init(), which checks
that the candidate's build-id matches the DSO:
if (dso__has_build_id(dso) && !symbol_conf.ignore_vmlinux_buildid) {
...
if (!dso__build_id_equal(dso, &bid)) {
pr_debug("%s: build id mismatch for %s.\n", __func__, name);
*dso__load_errno(dso) = DSO_LOAD_ERRNO__MISMATCHING_BUILDID;
goto out_elf_end;
}
}
and verifies symsrc__has_symtab() before stopping, allowing the loop to
continue to BUILD_ID_CACHE_DEBUGINFO, UBUNTU_DEBUGINFO, and
BUILDID_DEBUGINFO.
Because dso__find_debuginfo() returns the very first regular file without
checking if it is distinct from the DSO, if its build-id matches, or if it
contains debug/unwind sections:
- In __find_debuginfo(), if the returned path equals file_name, it is freed
and libdw receives no debuginfo without trying later debuginfo_types[]
entries.
- If a path-based candidate (DEBUGLINK, FEDORA_DEBUGINFO, UBUNTU_DEBUGINFO)
on the host has a mismatched build-id while the matching debuginfo is in
BUILD_ID_CACHE_DEBUGINFO or BUILDID_DEBUGINFO, libdwfl rejects the
mismatched file and never calls __find_debuginfo() again for that module,
while libunwind's read_unwind_spec_debug_frame() reads .debug_frame from
the mismatched file and overwrites dso->symsrc_filename with it.
[Severity: Medium]
Also in this loop, how does dso__find_debuginfo() find debuginfo files
located inside a target process's mount namespace or chroot?
In tools/perf/util/symbol.c:dso__load(), perf enters dso__nsinfo(dso)'s
mount namespace (exiting for the build-id cache types) and falls back to
dso__filename_with_chroot() when is_regular_file() fails with ENOENT:
if (nsexit)
nsinfo__mountns_exit(&nsc);
is_reg = is_regular_file(name);
if (!is_reg && errno == ENOENT && dso__nsinfo(dso)) {
char *new_name = dso__filename_with_chroot(dso, name);
if (new_name) {
is_reg = is_regular_file(new_name);
strlcpy(name, new_name, PATH_MAX);
free(new_name);
}
}
Here, dso__read_build_id(dso) enters and immediately exits the mount
namespace, so the debuginfo_types[] loop runs in perf's own mount namespace
without mountns or chroot handling.
> + return NULL;
> +}
> +
> static char *dso__get_filename(struct dso *dso, const char *root_dir,
> bool *decomp)
> {
[ ... ]
> diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c
> index 63a5c2253174f..a2e70e4f206f7 100644
> --- a/tools/perf/util/unwind-libdw.c
> +++ b/tools/perf/util/unwind-libdw.c
[ ... ]
> @@ -39,11 +40,20 @@ static int __find_debuginfo(Dwfl_Module *mod __maybe_unused, void **userdata,
> const char *file_name, const char *debuglink_file __maybe_unused,
> GElf_Word debuglink_crc __maybe_unused, char **debuginfo_file_name)
> {
> - const struct dso *dso = *userdata;
> + struct dso *dso = *userdata;
>
> assert(dso);
> - if (dso__symsrc_filename(dso) && strcmp(file_name, dso__symsrc_filename(dso)))
> - *debuginfo_file_name = strdup(dso__symsrc_filename(dso));
> + if (dso__symsrc_filename(dso)) {
> + if (strcmp(file_name, dso__symsrc_filename(dso)))
> + *debuginfo_file_name = strdup(dso__symsrc_filename(dso));
> + } else {
> + char *debuginfo = dso__find_debuginfo(dso);
> +
> + if (debuginfo && strcmp(file_name, debuginfo))
> + *debuginfo_file_name = debuginfo;
> + else
> + free(debuginfo);
> + }
> return -1;
> }
[Severity: Medium]
Should __find_debuginfo() also fall back to dso__find_debuginfo(dso) when
dso__symsrc_filename(dso) is equal to file_name?
If symbols were loaded from the main binary itself (for example, when the
binary retains a .symtab after strip --strip-debug, has .gnu_debugdata
MiniDebugInfo, or matched DSO_BINARY_TYPE__BUILD_ID_CACHE in dso__load()),
dso__symsrc_filename(dso) is non-NULL and equal to file_name while
.debug_frame is in a separate debuginfo file.
With the if/else structure above, strcmp(file_name, dso__symsrc_filename(dso))
is 0, so *debuginfo_file_name is not set and the else branch calling
dso__find_debuginfo(dso) is skipped.
[ ... ]
> diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c
> index 73d191ce51a55..d92f1d4852851 100644
> --- a/tools/perf/util/unwind-libunwind.c
> +++ b/tools/perf/util/unwind-libunwind.c
[ ... ]
> @@ -301,19 +302,10 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
> }
>
> if (ofs <= 0) {
> - char *debuglink = malloc(PATH_MAX);
> - int ret = 0;
> -
> - if (debuglink == NULL) {
> - pr_err("unwind: Can't read unwind spec debug frame.\n");
> - return -ENOMEM;
> - }
> + char *debuginfo = dso__find_debuginfo(dso);
>
> - ret = dso__read_binary_type_filename(
> - dso, DSO_BINARY_TYPE__DEBUGLINK,
> - machine->root_dir, debuglink, PATH_MAX);
> - if (!ret) {
> - fd = open(debuglink, O_RDONLY);
> + if (debuginfo) {
> + fd = open(debuginfo, O_RDONLY);
> if (fd >= 0) {
> ofs = elf_section_offset(fd,
> ".debug_frame");
> @@ -326,12 +318,12 @@ static int read_unwind_spec_debug_frame(struct dso *dso,
> "%s: overwrite symsrc(%s,%s)\n",
> __func__,
> dso__symsrc_filename(dso),
> - debuglink);
> + debuginfo);
> dso__free_symsrc_filename(dso);
> }
> - dso__set_symsrc_filename(dso, debuglink);
> + dso__set_symsrc_filename(dso, debuginfo);
> } else {
> - free(debuglink);
> + free(debuginfo);
> }
> }
>
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261017000158.1178269-1-thepacketgeek@gmail.com?part=2
next prev parent reply other threads:[~2026-10-17 0:18 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-17 0:01 [PATCH v2 0/3] perf unwind: Don't load symbols when the caller doesn't need them Matthew Wood
2026-10-17 0:01 ` [PATCH v2 1/3] perf dso: Move dso__read_build_id() from perf inject to dso.c Matthew Wood
2026-10-17 0:05 ` sashiko-bot
2026-10-17 0:01 ` [PATCH v2 2/3] perf unwind: Find separate debuginfo without loading symbols Matthew Wood
2026-10-17 0:18 ` sashiko-bot [this message]
2026-10-17 0:01 ` [PATCH v2 3/3] perf unwind: Don't look up symbols when the caller doesn't want them Matthew Wood
2026-10-17 0: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-175062@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=thepacketgeek@gmail.com \
/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