From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 43DC44DDB53; Thu, 17 Sep 2026 15:55:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660544; cv=none; b=STEYzUHx8qoQW3ncJm9nkRclEVo8l7EJ8e4dd5fbzmS3yR7+Rf25Amn2TKbEYaF3JK8Jp8N3TIW3onYnCaO6awwuP52HDd6/3v20c8wqVWa85weg9izCljBsX479BI6QuTGjdyXGY/BpB+3ldRX3pJmEAVeql/P/A1qHC9yPsDA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660544; c=relaxed/simple; bh=LcaxSLwQW+MnUlLGg9DL0JduOXTswwEvhzP6ZF4ikEw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HwQvCOu8x23W8/lzLCoRX5ghIMFYgQqH8ps0gLsP/hJjagbfwGPsCj6r53BmCcx/JOqPMw9La1uieoVXTJwIx62pEhCaQ7mPZpWS0eWQ0As7QKayfQ4EuK7sZGEc/6flUuEA4cntNwbHmbIzdoZ6uyRH1KC+1QQsStuaTICely0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=dWX6POYR; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="dWX6POYR" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 181721F000FF; Thu, 17 Sep 2026 15:55:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660542; bh=Mhq6KSeI3LcSTb54cw0+mjWtq32mDhF8Ko8lwr5Y3dY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=dWX6POYRQQOjr90vH6iMlMFDlxveT8zKF8A2/EzRFcLqL0g8l2/SyZyE7CnMpjnDw w8Jf+U0tWypFTB4gHIaKSPnaHnIT4vVuE2Au3rOowQA4hUb02+8CRtpEuI2nNYQ700 MNFqCEzXyfO3GlxLmZ/pRZA2ABIxQKMPskeyXdnx+9lZf/yTIh6Cezy2HtLMevRqqm ph7VJ7/L8TJ/k9VUVGYfPJQdqQDArXSt9A/G7P4b/qzQS8AqnZnfUekh0lhg2pX05t SwRkvEPUo1d1KL8MM7W1ZSYFhFl7A1Y4y6gYx+IEqIp549+iidFT5oSrZTRH1XNa5r eoRdUVwh/q6Bg== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 01/15] perf debuginfo: Fetch debuginfo keyed by build ID using debuginfod Date: Thu, 17 Sep 2026 12:55:12 -0300 Message-ID: <20260917155528.62607-2-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo Add debuginfo__find_build_id(), which uses the debuginfod client to locate a debuginfo file keyed by the build ID - checking its local cache first and then the servers in DEBUGINFOD_URLS - and debuginfo__new_build_id(), which opens the DWARF in the file it finds. dso__debuginfo() falls back to it for DSOs whose debuginfo is not installed locally, e.g. the vmlinux of a kernel profiled on another machine or upgraded since, doing the fetch outside dso__lock. Querying servers, possibly third party ones, sends the build IDs of the binaries being analysed off-box, and a fetch can take a while, so this is opt-out: --no-debuginfod, core.debuginfod=3Dfalse, report.debuginfod/top.debuginfod, and off also when the build-id cache is disabled. Fetches are serialized and misses are remembered, as both the lookup state and the fetch interaction are process global. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-annotate.txt | 8 + tools/perf/Documentation/perf-config.txt | 19 ++ tools/perf/Documentation/perf-report.txt | 12 + tools/perf/Documentation/perf-top.txt | 11 + tools/perf/builtin-annotate.c | 3 + tools/perf/builtin-report.c | 7 + tools/perf/builtin-top.c | 7 + tools/perf/util/config.c | 3 + tools/perf/util/debuginfo.c | 327 +++++++++++++++++++++ tools/perf/util/debuginfo.h | 35 +++ tools/perf/util/dso.c | 10 + tools/perf/util/symbol.c | 2 + tools/perf/util/symbol_conf.h | 1 + 13 files changed, 445 insertions(+) diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Docume= ntation/perf-annotate.txt index 1a90b09a12d5abb1..12edc3337b86f099 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -58,6 +58,14 @@ OPTIONS --ignore-vmlinux:: Ignore vmlinux files. =20 +--debuginfod:: +--no-debuginfod:: + Fetch debuginfo keyed by build ID from the debuginfod servers + configured in DEBUGINFOD_URLS, checking the local debuginfod + client cache first, when it is not available locally, on for + these commands by default. See the --debuginfod option of + 'perf report' for how to turn it off. + --itrace:: Options for decoding instruction tracing data. The options are: =20 diff --git a/tools/perf/Documentation/perf-config.txt b/tools/perf/Document= ation/perf-config.txt index 9b223f8928299945..688306abe847a0df 100644 --- a/tools/perf/Documentation/perf-config.txt +++ b/tools/perf/Documentation/perf-config.txt @@ -216,6 +216,14 @@ core.*:: addr2line-timeout:: Sets a timeout (in milliseconds) for parsing 'addr2line' output. The default timeout is 5s. + debuginfod:: + When set to 'false', disable fetching debuginfo keyed by + build ID from the debuginfod servers configured in + DEBUGINFOD_URLS. It is on by default, can be overridden per + tool with the 'report.debuginfod' and 'top.debuginfod' + options and per invocation with --no-debuginfod; it is off + too when the local build-id cache is disabled, e.g. + 'buildid.dir' set to /dev/null. =20 tui.*, gtk.*:: Subcommands that can be configured here are 'top', 'report' and 'annotate= '. @@ -562,6 +570,14 @@ report.*:: This option can change default stat behavior with empty results. If it's set true, 'perf report --stat' will not show 0 stats. =20 + report.debuginfod:: + Fetch debuginfo keyed by build ID from the debuginfod + servers configured in DEBUGINFOD_URLS, checking the local + debuginfod client cache first, when it is not available + locally. On by default, set to 'false' to disable it for + 'perf report', globally with 'core.debuginfod=3Dfalse' or per + invocation with --no-debuginfod. + top.*:: top.children:: Same as 'report.children'. So if it is enabled, the output of 'top' @@ -569,6 +585,9 @@ top.*:: column by default. The default is 'true'. =20 + top.debuginfod:: + Same as 'report.debuginfod', for 'perf top'. + top.call-graph:: This is identical to 'call-graph.record-mode', except it is applicable only for 'top' subcommand. This option ONLY setup diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Document= ation/perf-report.txt index 1a4706329c6cef1d..2e11c0a97a016853 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -366,6 +366,18 @@ OPTIONS --ignore-vmlinux:: Ignore vmlinux files. =20 +--debuginfod:: +--no-debuginfod:: + Fetch debuginfo keyed by build ID from the debuginfod servers + configured in DEBUGINFOD_URLS, checking the local debuginfod + client cache first, when it is not available locally, on for + these commands by default. It can be turned off per invocation + with --no-debuginfod, per tool with the "report.debuginfod" + config option or globally with "core.debuginfod" set to false. + It is disabled as well when the local build-id cache is turned + off, e.g. "buildid.dir" set to /dev/null, as that asks for + fetched files not to be kept on the box. + --kallsyms=3D:: kallsyms pathname =20 diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentati= on/perf-top.txt index 2da2a16bbf260685..345dd9ee9b6bab88 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -83,6 +83,17 @@ Default is to monitor all CPUS. --ignore-vmlinux:: Ignore vmlinux files. =20 +--debuginfod:: +--no-debuginfod:: + Fetch debuginfo keyed by build ID from the debuginfod servers + configured in DEBUGINFOD_URLS, checking the local debuginfod + client cache first, when it is not available locally, on for + these commands by default. Turn it off per invocation with + --no-debuginfod, with the "top.debuginfod" config option or + globally with "core.debuginfod" set to false. Disabled as well + when the build-id cache is off, e.g. "buildid.dir" set to + /dev/null. + --kallsyms=3D:: kallsyms pathname =20 diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 850fc72fa75fe7df..d6a201c1866d363f 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -733,6 +733,9 @@ int cmd_annotate(int argc, const char **argv) OPT_BOOLEAN(0, "stdio2", &annotate.use_stdio2, "Use the stdio interface"), OPT_BOOLEAN(0, "ignore-vmlinux", &symbol_conf.ignore_vmlinux, "don't load vmlinux even if found"), + OPT_BOOLEAN(0, "debuginfod", &symbol_conf.debuginfod, + "fetch debuginfo keyed by build ID from the debuginfod " + "servers, on by default, use --no-debuginfod to turn off"), OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 279e61c2366cb294..442c0822e614197f 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -134,6 +134,10 @@ static int report__config(const char *var, const char = *value, void *cb) symbol_conf.event_group =3D perf_config_bool(var, value); return 0; } + if (!strcmp(var, "report.debuginfod")) { + symbol_conf.debuginfod =3D perf_config_bool(var, value); + return 0; + } if (!strcmp(var, "report.percent-limit")) { double pcnt =3D strtof(value, NULL); =20 @@ -1346,6 +1350,9 @@ int cmd_report(int argc, const char **argv) "file", "vmlinux pathname"), OPT_BOOLEAN(0, "ignore-vmlinux", &symbol_conf.ignore_vmlinux, "don't load vmlinux even if found"), + OPT_BOOLEAN(0, "debuginfod", &symbol_conf.debuginfod, + "fetch debuginfo keyed by build ID from the debuginfod " + "servers, on by default, use --no-debuginfod to turn off"), OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, "file", "kallsyms pathname"), OPT_BOOLEAN('f', "force", &symbol_conf.force, "don't complain, do it"), diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index c2562d49be46a9a1..2be6859fd4aee141 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -1436,6 +1436,10 @@ static int perf_top_config(const char *var, const ch= ar *value, void *cb __maybe_ symbol_conf.cumulate_callchain =3D perf_config_bool(var, value); return 0; } + if (!strcmp(var, "top.debuginfod")) { + symbol_conf.debuginfod =3D perf_config_bool(var, value); + return 0; + } =20 return 0; } @@ -1508,6 +1512,9 @@ int cmd_top(int argc, const char **argv) "file", "vmlinux pathname"), OPT_BOOLEAN(0, "ignore-vmlinux", &symbol_conf.ignore_vmlinux, "don't load vmlinux even if found"), + OPT_BOOLEAN(0, "debuginfod", &symbol_conf.debuginfod, + "fetch debuginfo keyed by build ID from the debuginfod " + "servers, on by default, use --no-debuginfod to turn off"), OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, "file", "kallsyms pathname"), OPT_BOOLEAN('K', "hide_kernel_symbols", &top.hide_kernel_symbols, diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index b2972c35c1eca68c..31c6618d3b3daf22 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -470,6 +470,9 @@ static int perf_default_core_config(const char *var, co= nst char *value) if (!strcmp(var, "core.addr2line-disable-warn")) symbol_conf.addr2line_disable_warn =3D perf_config_bool(var, value); =20 + if (!strcmp(var, "core.debuginfod")) + symbol_conf.debuginfod =3D perf_config_bool(var, value); + /* Add other config variables here. */ return 0; } diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c index 84a78b30ceac1066..be5882d9c2204797 100644 --- a/tools/perf/util/debuginfo.c +++ b/tools/perf/util/debuginfo.c @@ -7,16 +7,22 @@ =20 #include #include +#include +#include +#include #include #include #include #include +#include #include +#include =20 #include "build-id.h" #include "dso.h" #include "debug.h" #include "debuginfo.h" +#include "mutex.h" #include "symbol.h" =20 #ifdef HAVE_DEBUGINFOD_SUPPORT @@ -139,6 +145,327 @@ struct debuginfo *debuginfo__new(const char *path) return __debuginfo__new(buf); } =20 +#ifdef HAVE_DEBUGINFOD_SUPPORT +/* + * Users can disable the local build-id/.debug cache by setting + * buildid.dir to /dev/null, meaning they don't want fetched + * binaries/debuginfo stored on the box; the debuginfod client keeps + * its own cache in ~/.cache/debuginfod_client, so honour that intent + * and don't fetch at all in that case. + */ +static bool debuginfod__cache_disabled(void) +{ + return !strcmp(buildid_dir, "/dev/null"); +} + +/* + * Build IDs that shouldn't be searched for again in this session: the + * ones already searched for on the debuginfod servers without success, + * so that callers that see the same DSO over and over, such as the data + * type profiler switching between DSOs on every hist entry, don't pay a + * server round trip again for each miss. The cache of successes is the + * debuginfod client's own, in the local filesystem. + * + * Guarded by debuginfod__fetch_lock: it is only read by the lookups + * below, that run with that lock held, and written by fetches, that run + * with it held too. + */ +struct debuginfod_miss { + struct list_head node; + struct build_id bid; +}; + +static LIST_HEAD(debuginfod__misses); + +/* + * Was the search for this build ID already made and settled, i.e. the + * servers had nothing for it? + */ +static bool debuginfod__missed(const struct build_id *bid) +{ + struct debuginfod_miss *miss; + + list_for_each_entry(miss, &debuginfod__misses, node) { + if (miss->bid.size =3D=3D bid->size && + !memcmp(miss->bid.data, bid->data, bid->size)) + return true; + } + + return false; +} + +static void debuginfod__miss_add(const struct build_id *bid) +{ + struct debuginfod_miss *miss =3D zalloc(sizeof(*miss)); + + if (miss =3D=3D NULL) + return; + + miss->bid =3D *bid; + + list_add(&miss->node, &debuginfod__misses); +} + +/* + * One fetch at a time. + * + * The lookup state below is process global, so two concurrent fetches, + * which dso__debuginfo() makes possible by taking the fetch out of + * dso__lock, would race for it: the second one could answer from a list + * the first one is concurrently updating, and put the same build ID on + * the misses list twice. + * + * Serializing also means a second request for a build ID that is being + * fetched waits here for the fetch to finish, instead of starting a second + * download of the same file, and is then answered from the entry the fetch + * published, or from the misses list, with no client at all. Should the + * fetches ever run in parallel, that wait has to come back explicitly, wi= th + * this lock split in two: one only for the lookup state, that the waiters + * sleep on, and one held around each fetch, with the thread that finds a + * fetch in progress waiting on the former for the entry to be published. + * + * What that costs is that a fetch for one build ID blocks a fetch for + * another one, and it is what parallel downloads would fix. Worth doing + * only if the wait turns out to be long, because it mostly is not: the + * lookups below answer the second and later requests for a build ID from + * memory, so after the first pass over the build IDs of a workload, which + * is the only time anything is fetched at all, the serialization has + * nothing left to serialize. Start there if a profile with many DSOs to + * fetch shows up in a profile of perf itself. + */ +static struct mutex debuginfod__fetch_lock; + +static void debuginfod__fetch_lock_setup(void) +{ + mutex_init(&debuginfod__fetch_lock); +} + +static void debuginfod__fetch_lock_init(void) +{ + static pthread_once_t once =3D PTHREAD_ONCE_INIT; + + pthread_once(&once, debuginfod__fetch_lock_setup); +} + +/* + * The build IDs already fetched in this session, and the path of the file + * that came back for each, so that the repeated requests for the same bui= ld + * ID, dso__debuginfo() is called per symbol annotated, are answered with a + * strdup() instead of another client: the file is in the debuginfod client + * cache already and its path checked before being handed out, in case that + * cache is cleaned from under us. + * + * Guarded by debuginfod__fetch_lock. Only a fetch that brought a file ba= ck + * gets an entry: one that didn't is recorded in debuginfod__misses as a m= iss, + * and that is what keeps the rest of the session from asking for it again. + * Like debuginfod__misses this grows with the number of build IDs in the + * workload, one small entry each, and is not trimmed. + */ +struct debuginfo_lookup { + struct list_head node; + struct build_id bid; + char *path; +}; + +static LIST_HEAD(debuginfo_lookups); + +static bool build_id__equal(const struct build_id *a, const struct build_i= d *b) +{ + return a->size =3D=3D b->size && memcmp(a->data, b->data, a->size) =3D=3D= 0; +} + +static struct debuginfo_lookup *debuginfo_lookup__find(const struct build_= id *bid) +{ + struct debuginfo_lookup *lookup; + + list_for_each_entry(lookup, &debuginfo_lookups, node) { + if (build_id__equal(&lookup->bid, bid)) + return lookup; + } + + return NULL; +} + +static void debuginfo_lookup__delete(struct debuginfo_lookup *lookup) +{ + list_del(&lookup->node); + zfree(&lookup->path); + free(lookup); +} + +/* + * Remember that this build ID was fetched, with the file at @path, so that + * the next request for it is answered from memory. Called with + * debuginfod__fetch_lock held. Out of memory just means not sharing this + * one, the file is fetched and the caller has its path. + */ +static void debuginfo_lookup__add(const struct build_id *bid, const char *= path) +{ + struct debuginfo_lookup *lookup =3D zalloc(sizeof(*lookup)); + + if (lookup =3D=3D NULL) + return; + + lookup->bid =3D *bid; + lookup->path =3D strdup(path); + if (lookup->path =3D=3D NULL) { + free(lookup); + return; + } + + list_add(&lookup->node, &debuginfo_lookups); +} + +/* + * The fetch itself. Called with debuginfod__fetch_lock held for the + * whole of it, see the comment there. + */ +static int debuginfod__fetch(const struct build_id *bid, char **path) +{ + char sbuild_id[SBUILD_ID_SIZE]; + debuginfod_client *c; + int fd; + + c =3D debuginfod_begin(); + if (c =3D=3D NULL) + return -1; + + fd =3D debuginfod_find_debuginfo(c, bid->data, bid->size, path); + + debuginfod_end(c); + + if (fd < 0) { + build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id)); + pr_debug("No debuginfo found for build ID %s in debuginfod\n", + sbuild_id); + debuginfod__miss_add(bid); + return -1; + } + + close(fd); + + return 0; +} + +/* + * Look the build ID up in the files already fetched in this session, + * fetching it if it isn't there yet. Called, and left, with + * debuginfod__fetch_lock held, which also means that no fetch for this + * build ID can be running anywhere else: a second request for a build ID + * being fetched waits for the lock and is answered from the entry the fet= ch + * published, or from the misses list, with no client at all. + */ +static int debuginfo_lookup__find_build_id(const struct build_id *bid, cha= r **path) +{ + struct debuginfo_lookup *lookup =3D debuginfo_lookup__find(bid); + + if (lookup !=3D NULL) { + /* + * The file stays in the debuginfod client cache, but that + * cache can be cleaned from under us, so check that it is + * still there before handing its path out. If it isn't, + * forget the entry and look for the file again below, + * remembering the new answer the same way the first fetch + * does, so that the next lookup shares it instead of + * fetching it a third time. + */ + if (access(lookup->path, R_OK) =3D=3D 0) { + *path =3D strdup(lookup->path); + return *path !=3D NULL ? 0 : -1; + } + + debuginfo_lookup__delete(lookup); + } + + if (debuginfod__fetch(bid, path) < 0) + return -1; + + debuginfo_lookup__add(bid, *path); + + return 0; +} + +/* + * Find a debuginfo file keyed by the build ID, using the debuginfod + * client, which checks its local cache first and then queries the + * servers in DEBUGINFOD_URLS. Used when the debuginfo is not available + * locally under the name the DSO was opened with, for instance the + * vmlinux for the kernel the profile was recorded on, when processing + * the profile on another machine or after the kernel or its debuginfo + * package got upgraded in between. + * + * Querying servers, possibly third party, sends the build IDs of the + * binaries being analysed off the box, so this is opt-out: on by + * default, switchable off with --no-debuginfod, with + * core.debuginfod=3Dfalse, with the per-tool + * report.debuginfod/top.debuginfod, and it is off too when the user + * disabled the local build-id/.debug cache, e.g. with + * buildid.dir =3D /dev/null, as is the case for users that don't want + * any of this stored locally. + * + * On success the path is stored in *@path and must be freed by the + * caller, the file remains available in the debuginfod client cache. + */ +int debuginfo__find_build_id(const struct build_id *bid, char **path) +{ + int err =3D -1; + + *path =3D NULL; + + if (!build_id__is_defined(bid)) + return -1; + + /* + * The checks below have to be made with the lock held, as they look + * at the state the fetch changes: a build ID the fetch in progress + * just settled as a miss is settled for whoever is waiting for the + * lock as well. Deciding here and fetching there would repeat a + * fetch that was already made, and put the same build ID on the + * misses list twice. + */ + debuginfod__fetch_lock_init(); + mutex_lock(&debuginfod__fetch_lock); + + if (symbol_conf.debuginfod) { + if (debuginfod__cache_disabled()) { + pr_debug("Build-id cache disabled (buildid dir is '%s'), not using debu= ginfod\n", + buildid_dir); + } else if (debuginfod__missed(bid)) { + char sbuild_id[SBUILD_ID_SIZE]; + + build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id)); + pr_debug("Not searching build ID %s in debuginfod again, it was a miss = earlier\n", + sbuild_id); + } else { + err =3D debuginfo_lookup__find_build_id(bid, path); + } + } + + mutex_unlock(&debuginfod__fetch_lock); + + return err; +} + +struct debuginfo *debuginfo__new_build_id(const struct build_id *bid) +{ + char sbuild_id[SBUILD_ID_SIZE]; + char *path =3D NULL; + struct debuginfo *dbg; + + if (debuginfo__find_build_id(bid, &path)) + return NULL; + + dbg =3D __debuginfo__new(path); + if (dbg =3D=3D NULL) { + build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id)); + pr_debug("Failed to open DWARF in debuginfo fetched for build ID %s: %s\= n", + sbuild_id, path); + } + free(path); + return dbg; +} +#endif /* HAVE_DEBUGINFOD_SUPPORT */ + void debuginfo__delete(struct debuginfo *dbg) { if (dbg) { diff --git a/tools/perf/util/debuginfo.h b/tools/perf/util/debuginfo.h index a52d69932815cd72..ef44a7557a9d6192 100644 --- a/tools/perf/util/debuginfo.h +++ b/tools/perf/util/debuginfo.h @@ -5,6 +5,8 @@ #include #include =20 +struct build_id; + #ifdef HAVE_LIBDW_SUPPORT =20 #include "dwarf-aux.h" @@ -54,6 +56,27 @@ static inline int debuginfo__get_text_offset(struct debu= ginfo *dbg __maybe_unuse #ifdef HAVE_DEBUGINFOD_SUPPORT int get_source_from_debuginfod(const char *raw_path, const char *sbuild_id, char **new_path); + +/* + * These need libdw to open the DWARF, so they live in debuginfo.o, + * built only with CONFIG_LIBDW. + */ +#ifdef HAVE_LIBDW_SUPPORT +int debuginfo__find_build_id(const struct build_id *bid, char **path); +struct debuginfo *debuginfo__new_build_id(const struct build_id *bid); +#else +static inline int debuginfo__find_build_id(const struct build_id *bid __ma= ybe_unused, + char **path __maybe_unused) +{ + return -ENOTSUP; +} + +static inline struct debuginfo * +debuginfo__new_build_id(const struct build_id *bid __maybe_unused) +{ + return NULL; +} +#endif /* HAVE_LIBDW_SUPPORT */ #else /* HAVE_DEBUGINFOD_SUPPORT */ static inline int get_source_from_debuginfod(const char *raw_path __maybe_= unused, const char *sbuild_id __maybe_unused, @@ -61,6 +84,18 @@ static inline int get_source_from_debuginfod(const char = *raw_path __maybe_unused { return -ENOTSUP; } + +static inline int debuginfo__find_build_id(const struct build_id *bid __ma= ybe_unused, + char **path __maybe_unused) +{ + return -ENOTSUP; +} + +static inline struct debuginfo * +debuginfo__new_build_id(const struct build_id *bid __maybe_unused) +{ + return NULL; +} #endif /* HAVE_DEBUGINFOD_SUPPORT */ =20 #endif /* _PERF_DEBUGINFO_H */ diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index 42bfe30a3b518e80..b0079ef17c597011 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -32,6 +32,7 @@ #include "string2.h" #include "vdso.h" #include "annotate-data.h" +#include "debuginfo.h" #include "libdw.h" =20 static const char * const debuglink_paths[] =3D { @@ -2073,5 +2074,14 @@ struct debuginfo *dso__debuginfo(struct dso *dso) =20 mutex_unlock(dso__lock(dso)); free(name); + + /* + * The debuginfo for a DSO in the profile may not be installed locally, + * fall back to fetching it keyed by the build ID recorded in perf.data. + * Do it outside dso__lock, a fetch can take a while. + */ + if (dinfo =3D=3D NULL) + dinfo =3D debuginfo__new_build_id(dso__bid(dso)); + return dinfo; } diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 3587ad243159074f..b1a2684c813c5d8d 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -76,6 +76,8 @@ struct symbol_conf symbol_conf =3D { .inline_name =3D true, .res_sample =3D 0, .addr2line_timeout_ms =3D 5 * 1000, + /* Fetching debuginfo by build ID, off via --no-debuginfod, etc */ + .debuginfod =3D true, }; =20 struct map_list_node { diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h index 71f60081a85bb18d..a56b1d2d843b9ff1 100644 --- a/tools/perf/util/symbol_conf.h +++ b/tools/perf/util/symbol_conf.h @@ -45,6 +45,7 @@ struct symbol_conf { force, ignore_vmlinux, ignore_vmlinux_buildid, + debuginfod, show_kernel_path, use_modules, allow_aliases, --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A7A8B4E2F08; Thu, 17 Sep 2026 15:55:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660548; cv=none; b=iqNYe+tw9rRGddEtpSSpGiv9kCCJHACi4Ih7HQA+iDwL36VtJQYfIdBxx68p7sR8uMadE80u4refnwMcJEFGs42DPmahrHls/9uCNQbodpLBXBn5Q4/yji22U+OKXVgp+WbeOREfMYOiascQxIgMgEFGz70xS69HCQm9sYlfRdM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660548; c=relaxed/simple; bh=Vgv5m697j2LtDwHwK/f7vJZZ5y6Op6J/VZBiDNrsd0M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=rBfQttg1VaKOMfhuKQDsCe965FhCePdnys2sJy+SM0LmcURZFTspekx/qBz3nZuHFir9phClG9GhsuHeztSugkTfTyFPiI6QzahSpH9O3FQJTKdmcedLDfXso3zitSm58j5Zsz/5z7AWGU3q9AtYG+Evqc5oIO0DGTVl/8Y5Vvg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EWq+fo6/; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="EWq+fo6/" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 92F411F00893; Thu, 17 Sep 2026 15:55:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660546; bh=veEtliTXE+qi5c1dPvGcmoseCgN+WEJW4Sh9bMMveps=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=EWq+fo6/GGIDhJCspORXUGjcMG3EBmFOFamFaQHyc+cSpNUGtF1/+WLwbQCwmtQYp luGbaAFsjF7wXGRBWWDfdtBVqvHapxvDIjXbR7hB/3Bfebz0bObI9vIVNNIfkt+T0P ybASAWRcsBzvFi42BUkLqYKUwmGbRrBYG7B3Mw59BKnD0RPuW31/hKhg9rcJCWtJay 2OgtEqAQwAZUND/k4cn0rPooFxfsvcVG6fpK73Vc8qxELkRzyzX79Jhv82xeJ/tHK+ ZKdhcO438p0zJLy+SsLVjzJ5FbjblziOQUUaZCp1rkf3wpXBPd5g0v4RyFzBUnMIDh JkTwOVAm2+TcQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 02/15] perf debuginfo: Set DEBUGINFOD_URLS from /etc/debuginfod when unset Date: Thu, 17 Sep 2026 12:55:13 -0300 Message-ID: <20260917155528.62607-3-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo The debuginfod client fails when DEBUGINFOD_URLS isn't set even when what it wants is in its local cache, and the distro setup scripts that populate it from the files in /etc/debuginfod don't reach cron jobs, systemd services and CI, so set it from those .urls files from symbol__init(), before any thread that can call getenv() is started: setenv() is not thread safe and libdebuginfod reads the variable in every debuginfod_begin(). Install it via putenv() as a string perf owns, so that it can be emptied in place mid-session when the user disables debuginfod, and when debuginfod is off, with --no-debuginfod, core.debuginfod=3Dfalse or by disabling the build-id cache, set the empty string, that both libdebuginfod and libdwfl's own client read as an opt-out, instead of exporting the variable for libdwfl to fetch behind perf's back. Tools that manage DEBUGINFOD_URLS themselves are left alone. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/symbol.c | 7 ++ tools/perf/util/util.c | 151 +++++++++++++++++++++++++++++++++++++-- tools/perf/util/util.h | 9 +++ 3 files changed, 161 insertions(+), 6 deletions(-) diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index b1a2684c813c5d8d..fbad770cb96f8294 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -2536,6 +2536,13 @@ int symbol__init(struct perf_env *env) if (symbol_conf.initialized) return 0; =20 + /* + * Set DEBUGINFOD_URLS from the distro .urls files before any thread + * that getenv()s it is started: setenv() is not thread safe and + * libdebuginfod reads it in every debuginfod_begin(). + */ + debuginfod__setup_urls_env(); + symbol_conf.priv_size =3D PERF_ALIGN(symbol_conf.priv_size, sizeof(u64)); =20 symbol__elf_init(); diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 8f7cd32f524dc10e..d502fd867f38196b 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -21,9 +21,11 @@ #include #include #include +#include "build-id.h" #include "cap.h" #include "strlist.h" #include "string2.h" +#include "symbol_conf.h" =20 /* * XXX We need to find a better place for these things... @@ -431,19 +433,66 @@ char *perf_exe(char *buf, int len) return strcpy(buf, "perf"); } =20 +static bool debuginfod__urls_set_by_tool; + +#define DEBUGINFOD_URLS_ENV "DEBUGINFOD_URLS" + +/* + * The DEBUGINFOD_URLS string perf owns: it has to change mid-session + * when the user disables debuginfod, and setenv() then could race with + * another thread's getenv(). With the string owned by perf, disabling + * it is just writing a NUL over its first byte. The string is never + * freed: from putenv() on it is the environment's own storage. + */ +static char *debuginfod_urls_env; + +static void debuginfod__set_urls_env(const char *urls) +{ + size_t len =3D strlen(DEBUGINFOD_URLS_ENV "=3D") + strlen(urls) + 1; + char *env =3D malloc(len); + + if (env =3D=3D NULL) + return; + + snprintf(env, len, DEBUGINFOD_URLS_ENV "=3D%s", urls); + debuginfod_urls_env =3D env; + putenv(env); +} + +/* + * Empty the copy of DEBUGINFOD_URLS installed by + * debuginfod__set_urls_env(), which both libdebuginfod and libdwfl's + * own client read as an opt-out. No environ array write and no free(), + * so a racing getenv() sees a valid string. + */ +void debuginfod__disable_urls_env(void) +{ + if (debuginfod_urls_env !=3D NULL) + debuginfod_urls_env[sizeof(DEBUGINFOD_URLS_ENV "=3D") - 1] =3D '\0'; +} + void perf_debuginfod_setup(struct perf_debuginfod *di) { /* * By default '!di->set' we clear DEBUGINFOD_URLS, so debuginfod * processing is not triggered, otherwise we set it to 'di->urls' - * value. If 'di->urls' is "system" we keep DEBUGINFOD_URLS value. + * value. If 'di->urls' is "system" we keep DEBUGINFOD_URLS value, + * but as a copy perf owns, so that it can still be emptied + * mid-session, see debuginfod__disable_urls_env(). */ - if (!di->set) - setenv("DEBUGINFOD_URLS", "", 1); - else if (di->urls && strcmp(di->urls, "system")) - setenv("DEBUGINFOD_URLS", di->urls, 1); + if (!di->set) { + debuginfod__set_urls_env(""); + } else if (di->urls =3D=3D NULL || !strcmp(di->urls, "system")) { + const char *urls =3D getenv(DEBUGINFOD_URLS_ENV); + + if (urls !=3D NULL) + debuginfod__set_urls_env(urls); + } else { + debuginfod__set_urls_env(di->urls); + } =20 - pr_debug("DEBUGINFOD_URLS=3D%s\n", getenv("DEBUGINFOD_URLS")); + debuginfod__urls_set_by_tool =3D true; + pr_debug("DEBUGINFOD_URLS=3D%s\n", getenv(DEBUGINFOD_URLS_ENV)); =20 #ifndef HAVE_DEBUGINFOD_SUPPORT if (di->set) @@ -451,6 +500,96 @@ void perf_debuginfod_setup(struct perf_debuginfod *di) #endif } =20 +#ifdef HAVE_DEBUGINFOD_SUPPORT +/* + * The debuginfod client checks its local cache only as part of the + * server query flow, so with no servers configured it fails even when + * the file is cached; the distro setup scripts that export + * DEBUGINFOD_URLS from /etc/debuginfod don't reach cron jobs, systemd + * services and CI, so do it here when the variable isn't set. An + * explicitly empty DEBUGINFOD_URLS is an opt-out and is left alone. + * + * Done from symbol__init(), on the single-threaded setup: setenv() is + * not thread safe and libdebuginfod reads it in every + * debuginfod_begin(). When debuginfod is off, set the empty opt-out + * instead of exporting it, so that libdwfl's own client doesn't fetch. + * The value is a copy owned by perf, see debuginfod__set_urls_env(). + */ +void debuginfod__setup_urls_env(void) +{ + const char *env_urls; + char *urls =3D NULL; + DIR *dir; + struct dirent *dent; + + /* + * Tools that set DEBUGINFOD_URLS themselves already made their choice, + * leave it alone. + */ + if (debuginfod__urls_set_by_tool) + return; + + if (!symbol_conf.debuginfod || !strcmp(buildid_dir, "/dev/null")) { + debuginfod__set_urls_env(""); + pr_debug("DEBUGINFOD_URLS cleared, debuginfod is disabled\n"); + return; + } + + env_urls =3D getenv(DEBUGINFOD_URLS_ENV); + if (env_urls !=3D NULL) { + /* + * Take ownership of the value, so that it can be emptied mid-session + * with the 'd' key. + */ + debuginfod__set_urls_env(env_urls); + return; + } + + dir =3D opendir("/etc/debuginfod"); + if (dir =3D=3D NULL) + return; + + while ((dent =3D readdir(dir)) !=3D NULL) { + char *content =3D NULL; + char *new_urls; + char path[PATH_MAX]; + size_t len =3D strlen(dent->d_name), i, size; + int n; + + if (len < 5 || strcmp(dent->d_name + len - 5, ".urls")) + continue; + + snprintf(path, sizeof(path), "/etc/debuginfod/%s", dent->d_name); + if (filename__read_str(path, &content, &size) < 0) + continue; + + for (i =3D 0; i < size; i++) + if (content[i] =3D=3D '\n' || content[i] =3D=3D '\r') + content[i] =3D ' '; + + if (urls =3D=3D NULL) { + urls =3D strdup(content); + } else { + n =3D asprintf(&new_urls, "%s %s", urls, content); + if (n < 0) { + free(content); + continue; + } + free(urls); + urls =3D new_urls; + } + free(content); + } + closedir(dir); + + if (urls !=3D NULL) { + debuginfod__set_urls_env(urls); + pr_debug("Set DEBUGINFOD_URLS from /etc/debuginfod: %s\n", urls); + } + free(urls); +} +#endif /* HAVE_DEBUGINFOD_SUPPORT */ + /* * Return a new filename prepended with task's root directory if it's in * a chroot. Callers should free the returned string. diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index f8f1ff603c728086..42feccf18f0d163f 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -81,6 +81,15 @@ struct perf_debuginfod { bool set; }; void perf_debuginfod_setup(struct perf_debuginfod *di); +void debuginfod__disable_urls_env(void); + +#ifdef HAVE_DEBUGINFOD_SUPPORT +void debuginfod__setup_urls_env(void); +#else +static inline void debuginfod__setup_urls_env(void) +{ +} +#endif =20 const char *perf_basename(const char *path); =20 --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8ECE54E77EE; Thu, 17 Sep 2026 15:55:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660553; cv=none; b=vAZNyh+UCVEgG7MInikCyC0JVKlGW3Q8G6gJkvcy6Da0Nuo5AVYUTVwVc5MflW7UT++mlfLip7pgnA5OmFiHRHdfue0zbj1Shz5/T7si+MS5MhuQRN4300SuQVL40EqmCabG6mSlyvpSPC2ktPWVbnNIcXjUgT0kvWe0vLug/xY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660553; c=relaxed/simple; bh=LdEw/QTInOJ563vNeGfjQlo5LNId6hRlzp7M1iLSQbs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=W5OSdVRe+BN8Wf/8ttWuwf6AJY3EjUmClGnijDadCR5cboBNE+O028lNzeLvvu1OsfAuTpKRne2pQyTP/z8uil8vfXdm7deUVL/vaj0HztKujGKqh+URWnVd+fef0472W7RgAUPToZzH0JNCoQjd7Iqwuk88qNAyFxdfBALIOUM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=cTGJgbjp; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="cTGJgbjp" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 170911F00898; Thu, 17 Sep 2026 15:55:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660551; bh=hzNAh/dZy3Dg85TJnOnNpH7opqOlcT8YZFzDZsnsJUY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=cTGJgbjpdsdMrMh0pwcb6cM1YZNXZgEYIv2eCyRZJdeWU2mCSxwKG4o93DV4XjRni iZKsgRDPJBb9bfc4qsnmTXoYrgIwnTSuRq/OXy1t+UWAOZix3cgy/EwY/Y7iZVBVn8 HxcJl08EO3SCPq2HkABhR8le694KHhvC8lryrPOIBTvNglfGZWUDMSeG2p09uk1J22 XQs+z55ug6MpD7HJzo6sYEiBGkJNFBrjGG99t1XZzCkmS+M4CUqO+UrYTKLziIOaNo 3MNCRqcLN+Dptn1538B5oT5dQG+SG3L8NlnVtpAOrkL11iOZr7c02x6cBXM8fHYmtE fzKtONzovqSnw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 03/15] perf config: Move perf_config__set_variable() to util/config.c Date: Thu, 17 Sep 2026 12:55:14 -0300 Message-ID: <20260917155528.62607-4-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo Move perf_config__set_variable() out of the 'perf config' builtin so that util/debuginfo.c can use it to write core.debuginfod=3Dfalse when the user disables debuginfod for the rest of the session. The set_config() body becomes perf_config_set__write(), with the system_config choice as an argument, as the builtin's use_system_config/use_user_config statics are not available outside it, and it has to honour PERF_CONFIG and keep the entries marked as coming from the system config when rewriting the system wide file. All config file access shares the static parser state and can now run on more than one thread, with the debuginfod fetch writing the config while perf top's display thread reads it, so serialize parsing and rewriting with a mutex. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/builtin-config.c | 70 +-------------------------- tools/perf/util/config.c | 96 ++++++++++++++++++++++++++++++++++++- tools/perf/util/config.h | 2 + 3 files changed, 98 insertions(+), 70 deletions(-) diff --git a/tools/perf/builtin-config.c b/tools/perf/builtin-config.c index cefd042e4f853466..3b074aca8d344539 100644 --- a/tools/perf/builtin-config.c +++ b/tools/perf/builtin-config.c @@ -41,37 +41,7 @@ static struct option config_options[] =3D { =20 static int set_config(struct perf_config_set *set, const char *file_name) { - struct perf_config_section *section =3D NULL; - struct perf_config_item *item =3D NULL; - const char *first_line =3D "# this file is auto-generated."; - FILE *fp; - - if (set =3D=3D NULL) - return -1; - - fp =3D fopen(file_name, "w"); - if (!fp) - return -1; - - fprintf(fp, "%s\n", first_line); - - /* overwrite configvariables */ - perf_config_items__for_each_entry(&set->sections, section) { - if (!use_system_config && section->from_system_config) - continue; - fprintf(fp, "[%s]\n", section->name); - - perf_config_items__for_each_entry(§ion->items, item) { - if (!use_system_config && item->from_system_config) - continue; - if (item->value) - fprintf(fp, "\t%s =3D %s\n", - item->name, item->value); - } - } - fclose(fp); - - return 0; + return perf_config_set__write(set, file_name, use_system_config); } =20 static int show_spec_config(struct perf_config_set *set, const char *var) @@ -158,44 +128,6 @@ static int parse_config_arg(char *arg, char **var, cha= r **value) return 0; } =20 -int perf_config__set_variable(const char *var, const char *value) -{ - char path[PATH_MAX]; - char *user_config =3D mkpath(path, sizeof(path), "%s/.perfconfig", getenv= ("HOME")); - const char *config_filename; - struct perf_config_set *set; - int ret =3D -1; - - if (use_system_config) - config_exclusive_filename =3D perf_etc_perfconfig(); - else if (use_user_config) - config_exclusive_filename =3D user_config; - - if (!config_exclusive_filename) - config_filename =3D user_config; - else - config_filename =3D config_exclusive_filename; - - set =3D perf_config_set__new(); - if (!set) - goto out_err; - - if (perf_config_set__collect(set, config_filename, var, value) < 0) { - pr_err("Failed to add '%s=3D%s'\n", var, value); - goto out_err; - } - - if (set_config(set, config_filename) < 0) { - pr_err("Failed to set the configs on %s\n", config_filename); - goto out_err; - } - - ret =3D 0; -out_err: - perf_config_set__delete(set); - return ret; -} - int cmd_config(int argc, const char **argv) { int i, ret =3D -1; diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index 31c6618d3b3daf22..d926360bccfd23a0 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -12,6 +12,8 @@ #include "config.h" =20 #include +#include +#include #include #include #include @@ -542,11 +544,20 @@ int perf_default_config(const char *var, const char *= value, return 0; } =20 +/* + * Serialize config file access: parsing and rewriting share the static + * parser state and can run on more than one thread, with the debuginfod + * fetch writing core.debuginfod=3Dfalse while perf top's display thread + * calls perf_config(). + */ +static pthread_mutex_t config_mutex =3D PTHREAD_MUTEX_INITIALIZER; + static int perf_config_from_file(config_fn_t fn, const char *filename, voi= d *data) { int ret; FILE *f =3D fopen(filename, "r"); =20 + pthread_mutex_lock(&config_mutex); ret =3D -1; if (f) { config_file =3D f; @@ -557,6 +568,7 @@ static int perf_config_from_file(config_fn_t fn, const = char *filename, void *dat fclose(f); config_file_name =3D NULL; } + pthread_mutex_unlock(&config_mutex); return ret; } =20 @@ -776,8 +788,13 @@ static int collect_config(const char *var, const char = *value, int perf_config_set__collect(struct perf_config_set *set, const char *file= _name, const char *var, const char *value) { + int ret; + + pthread_mutex_lock(&config_mutex); config_file_name =3D file_name; - return collect_config(var, value, set); + ret =3D collect_config(var, value, set); + pthread_mutex_unlock(&config_mutex); + return ret; } =20 static int perf_config_set__init(struct perf_config_set *set) @@ -876,6 +893,83 @@ void perf_config__exit(void) config_set =3D NULL; } =20 +int perf_config_set__write(struct perf_config_set *set, + const char *file_name, bool system_config) +{ + struct perf_config_section *section =3D NULL; + struct perf_config_item *item =3D NULL; + FILE *fp; + + pthread_mutex_lock(&config_mutex); + fp =3D fopen(file_name, "w"); + if (!fp) { + pthread_mutex_unlock(&config_mutex); + return -1; + } + + fprintf(fp, "# this file is auto-generated.\n"); + + /* overwrite configvariables */ + perf_config_sections__for_each_entry(&set->sections, section) { + if (!system_config && section->from_system_config) + continue; + fprintf(fp, "[%s]\n", section->name); + + perf_config_items__for_each_entry(§ion->items, item) { + if (!system_config && item->from_system_config) + continue; + if (item->value) + fprintf(fp, "\t%s =3D %s\n", + item->name, item->value); + } + } + fclose(fp); + pthread_mutex_unlock(&config_mutex); + + return 0; +} + +/* + * Set @var=3D@value in the configuration file perf is using: the user's + * ~/.perfconfig, or the file named by PERF_CONFIG, which makes perf + * read only that file, so writing ~/.perfconfig would replace it with + * the exclusive file's entries. The rewrite is the same 'perf config' + * does, comments are not preserved. + */ +int perf_config__set_variable(const char *var, const char *value) +{ + char path[PATH_MAX]; + char *user_config =3D mkpath(path, sizeof(path), "%s/.perfconfig", getenv= ("HOME")); + const char *config_filename =3D config_exclusive_filename ?: user_config; + /* + * When rewriting the system wide file (e.g. with + * PERF_CONFIG=3D/etc/perfconfig) all entries are marked as coming from it + * and must be kept, or the file would be truncated down to its header. + */ + bool system_config =3D strcmp(config_filename, perf_etc_perfconfig()) =3D= =3D 0; + struct perf_config_set *set; + int ret =3D -1; + + set =3D perf_config_set__new(); + if (!set) + goto out_err; + + if (perf_config_set__collect(set, config_filename, var, value) < 0) { + pr_err("Failed to add '%s=3D%s'\n", var, value); + goto out_err; + } + + if (perf_config_set__write(set, config_filename, system_config) < 0) { + pr_err("Failed to set the configs on %s\n", config_filename); + goto out_err; + } + + ret =3D 0; +out_err: + perf_config_set__delete(set); + return ret; +} + static void perf_config_item__delete(struct perf_config_item *item) { zfree(&item->name); diff --git a/tools/perf/util/config.h b/tools/perf/util/config.h index 987b47cf54c350ba..9098f8a045850c97 100644 --- a/tools/perf/util/config.h +++ b/tools/perf/util/config.h @@ -33,6 +33,8 @@ int perf_config_scan(const char *name, const char *fmt, .= ..) __scanf(2, 3); const char *perf_config_get(const char *name); int perf_config_set(struct perf_config_set *set, config_fn_t fn, void *data); +int perf_config_set__write(struct perf_config_set *set, + const char *file_name, bool system_config); int perf_config_int(int *dest, const char *, const char *); int perf_config_u8(u8 *dest, const char *name, const char *value); int perf_config_u64(u64 *dest, const char *, const char *); --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 716BE4EB85A; Thu, 17 Sep 2026 15:55:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660558; cv=none; b=s34u2LTDSdWHo4Qe2qyvi+qVM4ySou50Q6G2N5CUSsylVyJ7wVEa5sakErHsoqVMDm1t1+3CR12IwHTDZ9FphKDw206h3YcAiW1gJRefS3GzEQXtFdBExjPWpRfC0SuTMIi1Ekx9DzCchq1C+xtCIRlRfnrO84t3f+FsJg3/0KU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660558; c=relaxed/simple; bh=W7FFmi7s9lDF4nFQE1wd+QtNoXhRaYKMGRwk1wAgcY4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HnUkap0HoFf+zLWW0h/YGZ6WAhw4amhDhBZcEiEqrzIIgamU/WfKgP6jFa985TVX0qmV+CtlR5/Oebo7KOIieccgTqap0U/gzv5Ml99Pt8W8Ma72uO+zFQ6VCfeOjzaN912vl589z1fw6rOpJF4yrnm2lKuFx3O4d6Sn4bCrb8c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lZ0AGSsl; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lZ0AGSsl" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0222A1F00893; Thu, 17 Sep 2026 15:55:51 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660556; bh=/Eglm6SlL28MqoVCibvqvIgYslao5w2kLm5zKcmuWQI=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=lZ0AGSslWNSZze58bYci/KDT8kPQN9Dtcios/NgpIM9ycP1bHMc0TAhWDUKWrmruK ePdqALgl0mbHaSKBqVUlXgfXB43ToYL7Ig+LrKDmaLLJsjC4I7//CtF3UMjZQFdbul op3t9BOjvPV0RWrqFhog5EEjfUfS8elo3GxXAd3QyYGEhJnEdiJsJeEJ7klrNOyfGy /eWqVeNOn3Mr5VnVH5jc2OzKGPtetUdFfrjCVY9H7Km3UkVkvSHQMSEpPi084/Ycoj e7xLQ71qxq4ubxlCxdcPP8UyzMPDqKUMnU0I2tF28wvvYWlQME+1fcW67IIvMMxDlY 7dGJv2CG4Ws2w== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 04/15] perf debuginfo: Let the user skip and disable debuginfod fetches Date: Thu, 17 Sep 2026 12:55:15 -0300 Message-ID: <20260917155528.62607-5-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo In the stdio case, while a fetch is in progress: 's' aborts it through the debuginfod client's progress callback and remembers the build ID, so that the rest of the session does not ask for it again; 'd' additionally disables debuginfod for the session, emptying the DEBUGINFOD_URLS copy perf owns (libdwfl's own client reads it in every query) and writing core.debuginfod=3Dfalse to the config file with perf_config__set_variable(). SIGINT, SIGQUIT and SIGTERM are intercepted while the terminal is in raw mode and re-raised after it is restored. The keys are drained with read()s that do not block: the terminal is in raw mode with VMIN and VTIME zero, so read() returns zero when nothing was typed and the callback returns to the client, which carries on with the fetch. In the TUI nothing is shown yet, that comes in the next patch. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-annotate.txt | 4 +- tools/perf/Documentation/perf-report.txt | 11 +- tools/perf/Documentation/perf-top.txt | 10 +- tools/perf/util/debuginfo.c | 375 +++++++++++++++------ 4 files changed, 283 insertions(+), 117 deletions(-) diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Docume= ntation/perf-annotate.txt index 12edc3337b86f099..25af1d166dc4c877 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -64,7 +64,9 @@ OPTIONS configured in DEBUGINFOD_URLS, checking the local debuginfod client cache first, when it is not available locally, on for these commands by default. See the --debuginfod option of - 'perf report' for how to turn it off. + 'perf report' for how to turn it off, including the 's' and + 'd' keys that skip a fetch in progress while 'perf' is + waiting for it. =20 --itrace:: Options for decoding instruction tracing data. The options are: diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Document= ation/perf-report.txt index 2e11c0a97a016853..0a821490d6a0f895 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -374,9 +374,14 @@ OPTIONS these commands by default. It can be turned off per invocation with --no-debuginfod, per tool with the "report.debuginfod" config option or globally with "core.debuginfod" set to false. - It is disabled as well when the local build-id cache is turned - off, e.g. "buildid.dir" set to /dev/null, as that asks for - fetched files not to be kept on the box. + While a fetch is in progress, 's' skips the current fetch and + 'd' skips it and disables debuginfod for the rest of the + session, writing core.debuginfod=3Dfalse to the configuration + file so that it stays disabled in the next runs too; in the + stdio interface the progress is a line on stderr. It is + disabled as well when the local build-id cache is turned off, + e.g. "buildid.dir" set to /dev/null, as that asks for fetched + files not to be kept on the box. =20 --kallsyms=3D:: kallsyms pathname diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentati= on/perf-top.txt index 345dd9ee9b6bab88..f38b54267125cb08 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -90,9 +90,13 @@ Default is to monitor all CPUS. client cache first, when it is not available locally, on for these commands by default. Turn it off per invocation with --no-debuginfod, with the "top.debuginfod" config option or - globally with "core.debuginfod" set to false. Disabled as well - when the build-id cache is off, e.g. "buildid.dir" set to - /dev/null. + globally with "core.debuginfod" set to false. While a fetch is + in progress, 's' skips the current fetch and 'd' skips it and + disables debuginfod for the rest of the session, writing + core.debuginfod=3Dfalse to the configuration file so that it + stays disabled in the next runs too; in the stdio interface the + progress is a line on stderr. Disabled as well when the + build-id cache is off, e.g. "buildid.dir" set to /dev/null. =20 --kallsyms=3D:: kallsyms pathname diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c index be5882d9c2204797..0ec11e1ad7508edc 100644 --- a/tools/perf/util/debuginfo.c +++ b/tools/perf/util/debuginfo.c @@ -9,21 +9,25 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include =20 #include "build-id.h" +#include "config.h" #include "dso.h" #include "debug.h" #include "debuginfo.h" #include "mutex.h" #include "symbol.h" +#include "term.h" =20 #ifdef HAVE_DEBUGINFOD_SUPPORT #include @@ -147,11 +151,137 @@ struct debuginfo *debuginfo__new(const char *path) =20 #ifdef HAVE_DEBUGINFOD_SUPPORT /* - * Users can disable the local build-id/.debug cache by setting - * buildid.dir to /dev/null, meaning they don't want fetched - * binaries/debuginfo stored on the box; the debuginfod client keeps - * its own cache in ~/.cache/debuginfod_client, so honour that intent - * and don't fetch at all in that case. + * use_browser tells whether a full screen UI, the TUI for now, owns + * the terminal and its input queue: the fetch progress and the + * skip/disable keys below are stdio only when it doesn't. + */ +#include "ui/ui.h" + +static bool debuginfod_progress_started; +static bool debuginfod_fetch_cancelled; + +/* + * While stdin is in raw mode, SIGINT, SIGQUIT and SIGTERM are only + * recorded by the handler; the callback aborts the query and + * debuginfod__fetch() restores the terminal and re-raises the signal. + */ +static volatile sig_atomic_t debuginfod_signal; + +static void debuginfod_signal_handler(int sig) +{ + debuginfod_signal =3D sig; +} + +/* + * Say that the fetch in progress was skipped, and where disabling it + * lands. + */ +static void debuginfod__skipped(const char *msg) +{ + fprintf(stderr, "\n%s\n", msg); +} + +/* + * 's': skip this fetch, and remember the build ID so that the rest of + * the session doesn't ask for it again, the query is aborted by + * returning a non-zero value from the progress callback, as the + * debuginfod client docs prescribe. 'd': also disable debuginfod for + * the rest of the session, emptying the copy of DEBUGINFOD_URLS that + * util/util.c installed at setup, so that libdwfl's own client, that + * reads it in every query, stops fetching too, without a setenv() here + * racing other threads' getenv()s, and + * write core.debuginfod=3Dfalse to the configuration file, the same + * rewrite 'perf config' does, the comments are not preserved as the + * config set carries just the key-value pairs, pointing at + * 'perf config' when that rewrite can't be done. + */ +static void debuginfod__cancel_key(int key) +{ + if (key =3D=3D 's' || key =3D=3D 'S') { + debuginfod_fetch_cancelled =3D true; + debuginfod__skipped("Skipping this debuginfod fetch, this build ID will = not be fetched again in this session, press 'd' to also disable it for the = other ones"); + } else if (key =3D=3D 'd' || key =3D=3D 'D') { + debuginfod_fetch_cancelled =3D true; + symbol_conf.debuginfod =3D false; + /* + * libdwfl's own client reads DEBUGINFOD_URLS in every query, clear the + * perf-owned copy to stop it too. + */ + debuginfod__disable_urls_env(); + if (perf_config__set_variable("core.debuginfod", "false")) + debuginfod__skipped("Skipping this debuginfod fetch and disabling debug= infod for this session, run 'perf config core.debuginfod=3Dfalse' to also d= isable it permanently"); + else if (config_exclusive_filename) + debuginfod__skipped("Skipping this debuginfod fetch and disabling debug= infod for this session and in the configuration file"); + else + debuginfod__skipped("Skipping this debuginfod fetch and disabling debug= infod for this session and in ~/.perfconfig"); + } +} + +/* + * The terminal is in raw mode with VMIN and VTIME zero, so read() + * returns zero when nothing was typed: this drains the keys typed so + * far and returns, it doesn't wait for a keypress. + */ +static void debuginfod__poll_cancel_keys(void) +{ + char ch; + + while (read(STDIN_FILENO, &ch, 1) =3D=3D 1) + debuginfod__cancel_key(ch); +} + +/* + * Print a warning and a progress indicator when the debuginfod client + * ends up fetching a file, which can be big, such as the vmlinux for a + * kernel profiled on another machine or before it got upgraded, so that + * users know perf is not stuck, and let them bail out: 's' skips this + * fetch and remembers the build ID, so that the rest of the session + * doesn't ask for it again, 'd' also disables debuginfod for the rest + * of the session. + * + * The client invokes this both while fetching, where 'a' is the number + * of bytes transferred so far and 'b' the total size, zero when it + * doesn't know it yet, and, before committing to a server, from the + * cache cleanup, that scans the debuginfod client cache, with 'a' being + * the number of cache files scanned so far and 'b' zero. + * + * In the stdio case the progress goes to stderr, a \r terminated line, + * the keys are drained from stdin, that debuginfod__fetch() put in raw + * mode. + */ +static int debuginfod_progress_fn(debuginfod_client *c __maybe_unused, + long a, long b) +{ + if (debuginfod_signal) + return 1; + + if (!isatty(STDERR_FILENO) || use_browser) + return 0; + + if (isatty(STDIN_FILENO)) { + debuginfod__poll_cancel_keys(); + if (debuginfod_fetch_cancelled) + return 1; + } + + if (!debuginfod_progress_started) { + fprintf(stderr, "Fetching debuginfo by build ID from the debuginfod serv= ers, this may take a while for large files such as the vmlinux, press 's' t= o skip, 'd' to skip and disable\n"); + debuginfod_progress_started =3D true; + } + + if (a >=3D 0) { + if (b > 0) + fprintf(stderr, " %ld/%ld MiB fetched\r", a >> 20, b >> 20); + else + fprintf(stderr, " %ld MiB fetched\r", a >> 20); + } + + return 0; +} + +/* + * Setting buildid.dir to /dev/null means no fetched files stored + * locally, honour it and don't fetch at all. */ static bool debuginfod__cache_disabled(void) { @@ -159,42 +289,39 @@ static bool debuginfod__cache_disabled(void) } =20 /* - * Build IDs that shouldn't be searched for again in this session: the - * ones already searched for on the debuginfod servers without success, - * so that callers that see the same DSO over and over, such as the data - * type profiler switching between DSOs on every hist entry, don't pay a - * server round trip again for each miss. The cache of successes is the - * debuginfod client's own, in the local filesystem. - * - * Guarded by debuginfod__fetch_lock: it is only read by the lookups - * below, that run with that lock held, and written by fetches, that run - * with it held too. + * Build IDs already searched for without success or cancelled by the + * user, so that callers revisiting DSOs don't pay a server round trip + * per miss. Guarded by debuginfod__fetch_lock. */ struct debuginfod_miss { struct list_head node; struct build_id bid; + bool cancelled; }; =20 static LIST_HEAD(debuginfod__misses); =20 -/* - * Was the search for this build ID already made and settled, i.e. the - * servers had nothing for it? - */ -static bool debuginfod__missed(const struct build_id *bid) +/* Was the search for this build ID already settled, or cancelled? */ +static bool debuginfod__missed(const struct build_id *bid, bool *cancelled) { struct debuginfod_miss *miss; + bool found =3D false; + + *cancelled =3D false; =20 list_for_each_entry(miss, &debuginfod__misses, node) { if (miss->bid.size =3D=3D bid->size && - !memcmp(miss->bid.data, bid->data, bid->size)) - return true; + !memcmp(miss->bid.data, bid->data, bid->size)) { + found =3D true; + *cancelled =3D miss->cancelled; + break; + } } =20 - return false; + return found; } =20 -static void debuginfod__miss_add(const struct build_id *bid) +static void debuginfod__miss_add(const struct build_id *bid, bool cancelle= d) { struct debuginfod_miss *miss =3D zalloc(sizeof(*miss)); =20 @@ -202,36 +329,18 @@ static void debuginfod__miss_add(const struct build_i= d *bid) return; =20 miss->bid =3D *bid; + miss->cancelled =3D cancelled; =20 list_add(&miss->node, &debuginfod__misses); } =20 /* - * One fetch at a time. - * - * The lookup state below is process global, so two concurrent fetches, - * which dso__debuginfo() makes possible by taking the fetch out of - * dso__lock, would race for it: the second one could answer from a list - * the first one is concurrently updating, and put the same build ID on - * the misses list twice. - * - * Serializing also means a second request for a build ID that is being - * fetched waits here for the fetch to finish, instead of starting a second - * download of the same file, and is then answered from the entry the fetch - * published, or from the misses list, with no client at all. Should the - * fetches ever run in parallel, that wait has to come back explicitly, wi= th - * this lock split in two: one only for the lookup state, that the waiters - * sleep on, and one held around each fetch, with the thread that finds a - * fetch in progress waiting on the former for the entry to be published. - * - * What that costs is that a fetch for one build ID blocks a fetch for - * another one, and it is what parallel downloads would fix. Worth doing - * only if the wait turns out to be long, because it mostly is not: the - * lookups below answer the second and later requests for a build ID from - * memory, so after the first pass over the build IDs of a workload, which - * is the only time anything is fetched at all, the serialization has - * nothing left to serialize. Start there if a profile with many DSOs to - * fetch shows up in a profile of perf itself. + * One fetch at a time: the lookup state below and the interaction state + * (terminal mode, signal dispositions, progress line and keys) are + * process global, so serializing avoids two fetches racing for them. + * A request for a build ID being fetched waits here and is answered + * from the published entry, or from the misses list, with no second + * download. */ static struct mutex debuginfod__fetch_lock; =20 @@ -248,18 +357,9 @@ static void debuginfod__fetch_lock_init(void) } =20 /* - * The build IDs already fetched in this session, and the path of the file - * that came back for each, so that the repeated requests for the same bui= ld - * ID, dso__debuginfo() is called per symbol annotated, are answered with a - * strdup() instead of another client: the file is in the debuginfod client - * cache already and its path checked before being handed out, in case that - * cache is cleaned from under us. - * - * Guarded by debuginfod__fetch_lock. Only a fetch that brought a file ba= ck - * gets an entry: one that didn't is recorded in debuginfod__misses as a m= iss, - * and that is what keeps the rest of the session from asking for it again. - * Like debuginfod__misses this grows with the number of build IDs in the - * workload, one small entry each, and is not trimmed. + * Build IDs fetched in this session and the path of the file that came + * back, so repeated requests are answered with a strdup() instead of + * another client. Guarded by debuginfod__fetch_lock. */ struct debuginfo_lookup { struct list_head node; @@ -294,10 +394,9 @@ static void debuginfo_lookup__delete(struct debuginfo_= lookup *lookup) } =20 /* - * Remember that this build ID was fetched, with the file at @path, so that - * the next request for it is answered from memory. Called with - * debuginfod__fetch_lock held. Out of memory just means not sharing this - * one, the file is fetched and the caller has its path. + * Remember that this build ID was fetched, with the file at @path, so + * that the next request is answered from memory. Called with + * debuginfod__fetch_lock held. */ static void debuginfo_lookup__add(const struct build_id *bid, const char *= path) { @@ -317,12 +416,16 @@ static void debuginfo_lookup__add(const struct build_= id *bid, const char *path) } =20 /* - * The fetch itself. Called with debuginfod__fetch_lock held for the - * whole of it, see the comment there. + * The fetch itself, and, in the stdio case, the terminal in raw mode: + * the caller has to hold debuginfod__fetch_lock for the whole of it. */ static int debuginfod__fetch(const struct build_id *bid, char **path) { char sbuild_id[SBUILD_ID_SIZE]; + struct termios orig_termios; + struct sigaction sa, orig_sigint, orig_sigquit, orig_sigterm; + bool term_set =3D false, sigint_set =3D false, sigquit_set =3D false; + bool sigterm_set =3D false; debuginfod_client *c; int fd; =20 @@ -330,30 +433,102 @@ static int debuginfod__fetch(const struct build_id *= bid, char **path) if (c =3D=3D NULL) return -1; =20 + debuginfod_set_progressfn(c, debuginfod_progress_fn); + + debuginfod_fetch_cancelled =3D false; + debuginfod_signal =3D 0; + + /* + * Make stdin deliver keypresses without waiting for a newline, + * the progress callback above polls it for the 's'/'d' keys, + * only in the stdio case with both stdin and stderr being a + * terminal: the pipe cases have no business being poked here, + * and in the TUI the terminal and its input queue are the + * browser's own. Intercept SIGINT, SIGQUIT and SIGTERM so that + * the terminal is restored before the process dies, the handler + * only records the signal and the callback aborts the query. + * The handlers go in before the terminal mode changes, so that a + * signal landing in between is caught and the raw mode is + * restored. + */ + if (isatty(STDIN_FILENO) && isatty(STDERR_FILENO) && !use_browser) { + memset(&sa, 0, sizeof(sa)); + sa.sa_handler =3D debuginfod_signal_handler; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGINT, &sa, &orig_sigint) =3D=3D 0) + sigint_set =3D true; + if (sigaction(SIGQUIT, &sa, &orig_sigquit) =3D=3D 0) + sigquit_set =3D true; + if (sigaction(SIGTERM, &sa, &orig_sigterm) =3D=3D 0) + sigterm_set =3D true; + + set_term_quiet_input(&orig_termios); + term_set =3D true; + } + fd =3D debuginfod_find_debuginfo(c, bid->data, bid->size, path); =20 - debuginfod_end(c); + if (term_set) + tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios); + if (sigint_set) + sigaction(SIGINT, &orig_sigint, NULL); + if (sigquit_set) + sigaction(SIGQUIT, &orig_sigquit, NULL); + if (sigterm_set) + sigaction(SIGTERM, &orig_sigterm, NULL); =20 + debuginfod_end(c); + if (debuginfod_progress_started) { + fputc('\n', stderr); + debuginfod_progress_started =3D false; + } if (fd < 0) { build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id)); + if (debuginfod_fetch_cancelled || debuginfod_signal) { + pr_debug("debuginfod search for build ID %s cancelled by the user\n", + sbuild_id); + /* + * Remember it so that the rest of the session doesn't + * ask for the same file again: the user may have + * skipped it for being too big. + */ + debuginfod__miss_add(bid, true); + /* + * The terminal is restored, die as the user asked; + * the original dispositions are back in place. + */ + if (debuginfod_signal) + raise(debuginfod_signal); + return -1; + } pr_debug("No debuginfo found for build ID %s in debuginfod\n", sbuild_id); - debuginfod__miss_add(bid); + debuginfod__miss_add(bid, false); return -1; } =20 close(fd); =20 + /* + * The interrupt can land after the file is already here, in which + * case there is no failure to report, but the user still asked for + * perf to stop, and the terminal and the signal dispositions are + * back to what they were, so honour it here as well instead of + * swallowing it and going on. + */ + if (debuginfod_signal) { + build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id)); + pr_debug("debuginfod found the debuginfo for build ID %s, but the search= was interrupted, exiting\n", + sbuild_id); + raise(debuginfod_signal); + } + return 0; } =20 /* - * Look the build ID up in the files already fetched in this session, - * fetching it if it isn't there yet. Called, and left, with - * debuginfod__fetch_lock held, which also means that no fetch for this - * build ID can be running anywhere else: a second request for a build ID - * being fetched waits for the lock and is answered from the entry the fet= ch - * published, or from the misses list, with no client at all. + * Look the build ID up in the files already fetched, fetching it if it + * isn't there yet. Called, and left, with debuginfod__fetch_lock held. */ static int debuginfo_lookup__find_build_id(const struct build_id *bid, cha= r **path) { @@ -361,13 +536,8 @@ static int debuginfo_lookup__find_build_id(const struc= t build_id *bid, char **pa =20 if (lookup !=3D NULL) { /* - * The file stays in the debuginfod client cache, but that - * cache can be cleaned from under us, so check that it is - * still there before handing its path out. If it isn't, - * forget the entry and look for the file again below, - * remembering the new answer the same way the first fetch - * does, so that the next lookup shares it instead of - * fetching it a third time. + * The file may have been cleaned from the debuginfod client cache: + * if it is gone, forget the entry and fetch it again below. */ if (access(lookup->path, R_OK) =3D=3D 0) { *path =3D strdup(lookup->path); @@ -386,25 +556,10 @@ static int debuginfo_lookup__find_build_id(const stru= ct build_id *bid, char **pa } =20 /* - * Find a debuginfo file keyed by the build ID, using the debuginfod - * client, which checks its local cache first and then queries the - * servers in DEBUGINFOD_URLS. Used when the debuginfo is not available - * locally under the name the DSO was opened with, for instance the - * vmlinux for the kernel the profile was recorded on, when processing - * the profile on another machine or after the kernel or its debuginfo - * package got upgraded in between. - * - * Querying servers, possibly third party, sends the build IDs of the - * binaries being analysed off the box, so this is opt-out: on by - * default, switchable off with --no-debuginfod, with - * core.debuginfod=3Dfalse, with the per-tool - * report.debuginfod/top.debuginfod, and it is off too when the user - * disabled the local build-id/.debug cache, e.g. with - * buildid.dir =3D /dev/null, as is the case for users that don't want - * any of this stored locally. - * - * On success the path is stored in *@path and must be freed by the - * caller, the file remains available in the debuginfod client cache. + * Find a debuginfo file keyed by the build ID using the debuginfod + * client, which checks its local cache first and then the servers in + * DEBUGINFOD_URLS. On success the path is stored in *@path and must be + * freed by the caller. */ int debuginfo__find_build_id(const struct build_id *bid, char **path) { @@ -416,26 +571,26 @@ int debuginfo__find_build_id(const struct build_id *b= id, char **path) return -1; =20 /* - * The checks below have to be made with the lock held, as they look - * at the state the fetch changes: a build ID the fetch in progress - * just settled as a miss is settled for whoever is waiting for the - * lock as well. Deciding here and fetching there would repeat a - * fetch that was already made, and put the same build ID on the - * misses list twice. + * These check the state the fetch changes, so they have to run with + * the lock held. */ debuginfod__fetch_lock_init(); mutex_lock(&debuginfod__fetch_lock); =20 if (symbol_conf.debuginfod) { + bool cancelled; + if (debuginfod__cache_disabled()) { pr_debug("Build-id cache disabled (buildid dir is '%s'), not using debu= ginfod\n", buildid_dir); - } else if (debuginfod__missed(bid)) { + } else if (debuginfod__missed(bid, &cancelled)) { char sbuild_id[SBUILD_ID_SIZE]; =20 build_id__snprintf(bid, sbuild_id, sizeof(sbuild_id)); - pr_debug("Not searching build ID %s in debuginfod again, it was a miss = earlier\n", - sbuild_id); + pr_debug("Not searching build ID %s in debuginfod again, %s\n", + sbuild_id, + cancelled ? "the user cancelled the search earlier" : + "it was a miss earlier"); } else { err =3D debuginfo_lookup__find_build_id(bid, path); } --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 40989390230; Thu, 17 Sep 2026 15:56:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660563; cv=none; b=EyYq9ZL92ZHXbjbmtM10zEqsNijDpHtXfACuLErlU4rFyeVdLdiVOTTZPRTPa+EmFngSPp7B8Gziumce5q/dTknx2FIIT/qvfSmA8dNGdUzuSkx87EA1rMNUn2/tcLFAr86IDy/W04/eoAu4WpBXt+eEh9S1gw+jI6wgETYfM3Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660563; c=relaxed/simple; bh=DcvHzGl5oesIU+AQPLi07d/9HB5IrBx/tN1+lTD6ZIk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ViIgAhTiC03u/evTQYBsS6YbxyUlGckLZeNqyHdJd1K+Z3XjWhzAJ3Epbraw6VVqI4+9FW1QnQ+h/VoAKuDin+aegSdqVxuLa/0fktoqJoBBEfleeqZrwsQrB68EDfnrwVJHnT/QkaDiH1KQwWeSM5GNDPKn/kF2IGfYd4jiTW4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AtvIY+gj; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="AtvIY+gj" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C9B451F00898; Thu, 17 Sep 2026 15:55:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660561; bh=lcDq6R3zqPhqcnAMX7wVGS6Ayltg6JZIho0w+u15AzM=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=AtvIY+gjXAIUSoXO0RJgmgNmZh72hxZB4i1Vhjvve/CQMQCLFp0zmbM22wXs7U8gl GexBA0Y+op5vc+pvgVBxnKyaA0Zn12/GV1LKBhKrwd5UmXVUC6IyNkBWk4TgCWS2ew 9n0Qw8Fnr5Eja2whSAZGnc1PhUm+GzNvttnpfo+s3vTwEo77QIGgMfbY+eusq07Ds6 CFn7V1JAvGurgTqyyZUzCSCFKk3gJGakwKVcvD+SxrosQcfHvebBVsynnVb3+yfzGw p/2gAclXq2iA615KMJgGEL1wlWoNXqBDyuyA/PNoHuwarKmb4rNOelx+hpvFnn24Um ypEkWpjxjDiMw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 05/15] perf debuginfo: Show the debuginfod fetch progress and keys in the TUI Date: Thu, 17 Sep 2026 12:55:16 -0300 Message-ID: <20260917155528.62607-6-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo The stdio only fetch UI left a TUI fetch in progress with nothing on screen, indistinguishable from a stuck perf, with no way out of it. Draw a window over the browser with the fetch progress, and drain the 's'/'d' keys from the TUI input queue, through the new ui__key_pending()/ui__key_read()/ui__progress_window()/ ui__progress_window_end() primitives declared in ui/util.h and implemented in ui/tui/util.c, with no-ops for builds without slang: slang stays behind the ui/ layer and callers stay free of #ifdefs. The text is word wrapped to the available columns so that narrow terminals get the 'd' option hint too, and the width is clamped, never negative. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-report.txt | 9 +- tools/perf/Documentation/perf-top.txt | 5 +- tools/perf/ui/tui/util.c | 171 +++++++++++++++++++++++ tools/perf/ui/util.h | 27 ++++ tools/perf/util/debuginfo.c | 97 +++++++------ 5 files changed, 254 insertions(+), 55 deletions(-) diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Document= ation/perf-report.txt index 0a821490d6a0f895..0a4f61283b9542f7 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -378,10 +378,11 @@ OPTIONS 'd' skips it and disables debuginfod for the rest of the session, writing core.debuginfod=3Dfalse to the configuration file so that it stays disabled in the next runs too; in the - stdio interface the progress is a line on stderr. It is - disabled as well when the local build-id cache is turned off, - e.g. "buildid.dir" set to /dev/null, as that asks for fetched - files not to be kept on the box. + stdio interface the progress is a line on stderr, in the TUI a + window over the browser. It is disabled as well when the local + build-id cache is turned off, e.g. "buildid.dir" set to + /dev/null, as that asks for fetched files not to be kept on the + box. =20 --kallsyms=3D:: kallsyms pathname diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentati= on/perf-top.txt index f38b54267125cb08..3ab8121480175b51 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -95,8 +95,9 @@ Default is to monitor all CPUS. disables debuginfod for the rest of the session, writing core.debuginfod=3Dfalse to the configuration file so that it stays disabled in the next runs too; in the stdio interface the - progress is a line on stderr. Disabled as well when the - build-id cache is off, e.g. "buildid.dir" set to /dev/null. + progress is a line on stderr, in the TUI a window over the + browser. Disabled as well when the build-id cache is off, e.g. + "buildid.dir" set to /dev/null. =20 --kallsyms=3D:: kallsyms pathname diff --git a/tools/perf/ui/tui/util.c b/tools/perf/ui/tui/util.c index e4d322ce0b54cbc6..280865e980ef5491 100644 --- a/tools/perf/ui/tui/util.c +++ b/tools/perf/ui/tui/util.c @@ -4,6 +4,7 @@ #include #include #include +#include =20 #include "../browser.h" #include "../keysyms.h" @@ -11,6 +12,7 @@ #include "../ui.h" #include "../util.h" #include "../libslang.h" +#include "units.h" =20 static void ui_browser__argv_write(struct ui_browser *browser, void *entry, int row) @@ -272,3 +274,172 @@ struct perf_error_ops perf_tui_eops =3D { .error =3D perf_tui__error, .warning =3D perf_tui__warning, }; + +/* + * The debuginfod fetch progress window, drawn over the browser while a + * fetch is in progress: with nothing on screen the browser looks hung. + * Redraw only when the fetched bytes change, the callback is called at + * every write chunk. + */ +#define PROGRESS_WINDOW_MAX_LINES 12 + +static bool progress_window__shown; +static char progress_window__bytes[64]; +static int progress_window__y, progress_window__rows; + +/* + * Word wrap @text, on spaces, into lines of at most @width characters, + * breaking words that don't fit whole, returning the number of lines. + */ +static int progress_window__wrap(const char *text, int width, + char lines[PROGRESS_WINDOW_MAX_LINES][256]) +{ + int nr_lines =3D 0; + const char *p =3D text; + + if (width <=3D 0) + return 0; + + while (*p && nr_lines < PROGRESS_WINDOW_MAX_LINES) { + char *line =3D lines[nr_lines++]; + int len =3D 0; + bool space =3D false; + + line[0] =3D '\0'; + while (*p) { + const char *word; + int wlen, avail; + + while (*p =3D=3D ' ' || *p =3D=3D '\n') + ++p; + if (*p =3D=3D '\0') + break; + word =3D p; + while (*p && *p !=3D ' ' && *p !=3D '\n') + ++p; + wlen =3D p - word; + avail =3D width - len - (space ? 1 : 0); + if (wlen > avail) { + if (len > 0) { + p =3D word; + break; + } + if (avail > 0) { + /* The word doesn't fit whole */ + memcpy(line, word, avail); + len =3D avail; + line[len] =3D '\0'; + p =3D word + avail; + } + break; + } + if (space) + line[len++] =3D ' '; + memcpy(line + len, word, wlen); + len +=3D wlen; + line[len] =3D '\0'; + space =3D true; + } + } + + return nr_lines; +} + +void ui__progress_window(const char *title, const char *text, + u64 fetched, u64 total) +{ + static char lines[PROGRESS_WINDOW_MAX_LINES][256]; + char buf_cur[20], buf_tot[20], bytes[64]; + size_t len; + int y, height, nr_lines, inner, i; + + if (use_browser !=3D 1) + return; + + unit_number__scnprintf(buf_cur, sizeof(buf_cur), fetched); + if (total) { + unit_number__scnprintf(buf_tot, sizeof(buf_tot), total); + scnprintf(bytes, sizeof(bytes), " %s / %s fetched", + buf_cur, buf_tot); + } else { + scnprintf(bytes, sizeof(bytes), + " %s fetched, size unknown", buf_cur); + } + + if (progress_window__shown && !strcmp(bytes, progress_window__bytes)) + return; + + scnprintf(progress_window__bytes, sizeof(progress_window__bytes), + "%s", bytes); + progress_window__shown =3D true; + + ui__refresh_dimensions(false); + mutex_lock(&ui__lock); + /* + * SLsmg_write_nstring() takes the width as an unsigned int, so never + * let a narrow terminal make it negative. Clamp it. + */ + inner =3D SLtt_Screen_Cols - 2; + if (inner < 0) + inner =3D 0; + else if (inner > 255) + inner =3D 255; + nr_lines =3D progress_window__wrap(text, inner, lines); + height =3D nr_lines + 3; + + SLsmg_set_color(0); + if (progress_window__rows) + SLsmg_fill_region(progress_window__y, 0, progress_window__rows, + SLtt_Screen_Cols, ' '); + y =3D (SLtt_Screen_Rows - height) / 2; + if (y < 0) + y =3D 0; + progress_window__y =3D y; + progress_window__rows =3D height; + + SLsmg_draw_box(y, 0, height, SLtt_Screen_Cols); + SLsmg_gotorc(y++, 1); + len =3D strlen(title); + if (len > (size_t)inner) + len =3D inner; + SLsmg_write_nchars(title, len); + for (i =3D 0; i < nr_lines; i++, y++) { + SLsmg_gotorc(y, 1); + SLsmg_write_nstring(lines[i], inner); + } + SLsmg_gotorc(y, 1); + SLsmg_write_nstring(bytes, inner); + SLsmg_refresh(); + mutex_unlock(&ui__lock); +} + +void ui__progress_window_end(void) +{ + if (!progress_window__shown || use_browser !=3D 1) + return; + + progress_window__shown =3D false; + progress_window__bytes[0] =3D '\0'; + + mutex_lock(&ui__lock); + SLsmg_set_color(0); + SLsmg_fill_region(progress_window__y, 0, progress_window__rows, + SLtt_Screen_Cols, ' '); + progress_window__rows =3D 0; + SLsmg_refresh(); + mutex_unlock(&ui__lock); +} + +/* + * The keys typed while the fetch blocked the browser are in the TUI + * input queue: nobody else is reading it, drain them through these. + */ +bool ui__key_pending(void) +{ + return use_browser =3D=3D 1 && SLang_input_pending(0) > 0; +} + +int ui__key_read(void) +{ + return SLang_getkey(); +} diff --git a/tools/perf/ui/util.h b/tools/perf/ui/util.h index e30cea807564f92f..c264a8b2d5016110 100644 --- a/tools/perf/ui/util.h +++ b/tools/perf/ui/util.h @@ -2,9 +2,36 @@ #ifndef _PERF_UI_UTIL_H_ #define _PERF_UI_UTIL_H_ 1 =20 +#include #include +#include +#include =20 int ui__getch(int delay_secs); + +/* + * The TUI owns the terminal and its input queue, so the rest of perf + * asks for the progress display and for pending keys through these, + * keeping slang behind the ui/ layer. Without slang they are no-ops, + * so callers stay free of #ifdefs. + */ +#ifndef HAVE_SLANG_SUPPORT +/* No-ops without the TUI, keeping callers free of #ifdefs. */ +static inline bool ui__key_pending(void) { return false; } +static inline int ui__key_read(void) { return -1; } +static inline void ui__progress_window(const char *title __maybe_unused, + const char *text __maybe_unused, + u64 fetched __maybe_unused, + u64 total __maybe_unused) {} +static inline void ui__progress_window_end(void) {} +#else /* HAVE_SLANG_SUPPORT */ +bool ui__key_pending(void); +int ui__key_read(void); +void ui__progress_window(const char *title, const char *text, + u64 fetched, u64 total); +void ui__progress_window_end(void); +#endif /* HAVE_SLANG_SUPPORT */ + int ui__popup_menu(int argc, char * const argv[], int *keyp); int ui__help_window(const char *text); int ui__dialog_yesno(const char *msg); diff --git a/tools/perf/util/debuginfo.c b/tools/perf/util/debuginfo.c index 0ec11e1ad7508edc..737dd445d42bb72f 100644 --- a/tools/perf/util/debuginfo.c +++ b/tools/perf/util/debuginfo.c @@ -151,11 +151,13 @@ struct debuginfo *debuginfo__new(const char *path) =20 #ifdef HAVE_DEBUGINFOD_SUPPORT /* - * use_browser tells whether a full screen UI, the TUI for now, owns - * the terminal and its input queue: the fetch progress and the - * skip/disable keys below are stdio only when it doesn't. + * The TUI side of the fetch interaction is behind the ui/ layer: + * slang stays in ui/tui/ and callers use the ui__ primitives in + * ui/util.h. */ #include "ui/ui.h" +#include "ui/util.h" +#include "ui/helpline.h" =20 static bool debuginfod_progress_started; static bool debuginfod_fetch_cancelled; @@ -172,28 +174,19 @@ static void debuginfod_signal_handler(int sig) debuginfod_signal =3D sig; } =20 -/* - * Say that the fetch in progress was skipped, and where disabling it - * lands. - */ +/* Say that the fetch in progress was skipped, and where disabling lands. = */ static void debuginfod__skipped(const char *msg) { - fprintf(stderr, "\n%s\n", msg); + if (use_browser > 0) + ui_helpline__puts(msg); + else + fprintf(stderr, "\n%s\n", msg); } =20 /* - * 's': skip this fetch, and remember the build ID so that the rest of - * the session doesn't ask for it again, the query is aborted by - * returning a non-zero value from the progress callback, as the - * debuginfod client docs prescribe. 'd': also disable debuginfod for - * the rest of the session, emptying the copy of DEBUGINFOD_URLS that - * util/util.c installed at setup, so that libdwfl's own client, that - * reads it in every query, stops fetching too, without a setenv() here - * racing other threads' getenv()s, and - * write core.debuginfod=3Dfalse to the configuration file, the same - * rewrite 'perf config' does, the comments are not preserved as the - * config set carries just the key-value pairs, pointing at - * 'perf config' when that rewrite can't be done. + * 's': skip this fetch and remember the build ID, so that the rest of + * the session doesn't ask for it again. 'd': also disable debuginfod + * for the session and write core.debuginfod=3Dfalse to the config file. */ static void debuginfod__cancel_key(int key) { @@ -230,24 +223,22 @@ static void debuginfod__poll_cancel_keys(void) debuginfod__cancel_key(ch); } =20 +/* + * The browser thread is the one doing the fetch, so its input queue has + * the keys typed while it was busy, drain those. + */ +static void debuginfod__tui_poll_cancel_keys(void) +{ + while (ui__key_pending()) + debuginfod__cancel_key(ui__key_read()); +} + /* * Print a warning and a progress indicator when the debuginfod client - * ends up fetching a file, which can be big, such as the vmlinux for a - * kernel profiled on another machine or before it got upgraded, so that - * users know perf is not stuck, and let them bail out: 's' skips this - * fetch and remembers the build ID, so that the rest of the session - * doesn't ask for it again, 'd' also disables debuginfod for the rest - * of the session. - * - * The client invokes this both while fetching, where 'a' is the number - * of bytes transferred so far and 'b' the total size, zero when it - * doesn't know it yet, and, before committing to a server, from the - * cache cleanup, that scans the debuginfod client cache, with 'a' being - * the number of cache files scanned so far and 'b' zero. - * - * In the stdio case the progress goes to stderr, a \r terminated line, - * the keys are drained from stdin, that debuginfod__fetch() put in raw - * mode. + * fetches a file, which can be big, so that users know perf is not + * stuck, and let them bail out with 's' or 'd'. In the stdio case the + * progress goes to stderr and the keys are drained from stdin; in the + * TUI they come from the input queue, see the comment on ui/util.h. */ static int debuginfod_progress_fn(debuginfod_client *c __maybe_unused, long a, long b) @@ -255,7 +246,18 @@ static int debuginfod_progress_fn(debuginfod_client *c= __maybe_unused, if (debuginfod_signal) return 1; =20 - if (!isatty(STDERR_FILENO) || use_browser) + if (use_browser > 0) { + ui__progress_window("Fetching debuginfo by build ID from the debuginfod = servers", + "This may take a while for large files such as the vmlinux, press = 's' to skip, 'd' to skip and disable", + a > 0 ? (u64)a : 0, b > 0 ? (u64)b : 0); + + debuginfod__tui_poll_cancel_keys(); + if (debuginfod_fetch_cancelled) + return 1; + return 0; + } + + if (!isatty(STDERR_FILENO)) return 0; =20 if (isatty(STDIN_FILENO)) { @@ -439,17 +441,12 @@ static int debuginfod__fetch(const struct build_id *b= id, char **path) debuginfod_signal =3D 0; =20 /* - * Make stdin deliver keypresses without waiting for a newline, - * the progress callback above polls it for the 's'/'d' keys, - * only in the stdio case with both stdin and stderr being a - * terminal: the pipe cases have no business being poked here, - * and in the TUI the terminal and its input queue are the - * browser's own. Intercept SIGINT, SIGQUIT and SIGTERM so that - * the terminal is restored before the process dies, the handler - * only records the signal and the callback aborts the query. - * The handlers go in before the terminal mode changes, so that a - * signal landing in between is caught and the raw mode is - * restored. + * In the stdio case, with stdin and stderr a terminal, put stdin in raw + * mode so the 's'/'d' keys are delivered without waiting for a newline, + * and intercept SIGINT, SIGQUIT and SIGTERM so the terminal is restored + * before the process dies; the TUI uses its own primitives and + * handlers. The handlers go in before the terminal mode changes, so a + * signal landing in between still restores it. */ if (isatty(STDIN_FILENO) && isatty(STDERR_FILENO) && !use_browser) { memset(&sa, 0, sizeof(sa)); @@ -478,7 +475,9 @@ static int debuginfod__fetch(const struct build_id *bid= , char **path) sigaction(SIGTERM, &orig_sigterm, NULL); =20 debuginfod_end(c); - if (debuginfod_progress_started) { + if (use_browser > 0) + ui__progress_window_end(); + else if (debuginfod_progress_started) { fputc('\n', stderr); debuginfod_progress_started =3D false; } --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E8E9E4C10F6; Thu, 17 Sep 2026 15:56:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660566; cv=none; b=nQdqnPM6Vy8ae3/OhBqe2FdjpN+dN0ldWY8JKhIjJU7tC7PeyRxBWG5lrcoMq8TaXgxcXsOd6Zr4t/ysyj0AmksMmttegrIH4ws2i7xhV5lkELa+EO8/jp9W11XBOmL7Izn7WPJA9Rn+lGHlDiZJdw8R3Bo17QczhcRhRORaA8Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660566; c=relaxed/simple; bh=NRkWhoThg/1YMiSC8JAjOIJcJIDl81K4OIG6pDn95lE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nOYJlD4ZOrZsp4NO8eIIslaTCLviM4OAAEUVFEoyeymp6WJ97v+NdRduNtYM2o4CWHG57YbbZykZd5y/OSlSKskkneN8s7rw6Cuy8xIms2ZlRHzkX8KfVyAWiwENmJmdhKCJoUIS9pRVI37o+lwV8Bv5iLGEWluSt6450xw0rY0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=T2XiU9CB; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="T2XiU9CB" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C26A01F00893; Thu, 17 Sep 2026 15:56:01 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660564; bh=ENMlp3U8o8HPaksuZXdnV+kA24oe4O+hYit9U0xWJVw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=T2XiU9CBFhjuuZHgPOu6yIoSJ/+bb69Mfenf1ho8zA4JBCOO7d4wPcV5GZg3fX/p6 av2rGBfKu+mwntJpC6+v41fs3SViEZ0lNYBtNRAk8ZRCEooZ21SH9UUVQzGmv10VmK YLAJYiCLFz+luUVyfTpfwgKW/9YWXLrB2x6pIV3pmNc9naC5N/AxlQG7zjm3ekQtZ0 3vwIlPYPbX9cnhH0/yHtFvDr8cgFe2VXI8/z2Fon/51oNS5OL7eHR3E5hGkhgDra/w ridj2lDionKBAX8mPqa7DyJDN0gar1O6X4Nn9XC19Un7uinr4qFUfg0HaNcHhL56CB NwPHvfKtqw6PQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 06/15] perf symbol: Fall back to fetching the vmlinux by build ID Date: Thu, 17 Sep 2026 12:55:17 -0300 Message-ID: <20260917155528.62607-7-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo A profile recorded on a kernel that is no longer installed, e.g. one processed on another machine or after a kernel upgrade, cannot have its kernel symbols resolved: /proc/kallsyms does not match it and the build-id cache may carry only a kallsyms copy with zeroed addresses. Fetch the vmlinux keyed by the build ID recorded in perf.data, using debuginfod, as a last resort when no local source was found, honoring symbol_conf.ignore_vmlinux and ignore_vmlinux_buildid like the other vmlinux sources. The fetch runs with dso->lock dropped, like dso__debuginfo(), and the file is loaded with the lock held again, only if no other thread got the symbols meanwhile (dso__has_symbols(), not dso__loaded(): the latter is set even for failed attempts). With the lock dropped two threads can map the x86-64 entry trampolines at once, so that mapping is now idempotent. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/machine.c | 11 ++++++-- tools/perf/util/symbol.c | 55 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index a1288fbed8330a23..8aa9f5f12d3ea4bf 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -1139,12 +1139,19 @@ static int machine__map_x86_64_entry_trampolines_cb= (struct map *map, void *data) if (!kmap || !is_entry_trampoline(kmap->name)) return 0; =20 + args->found =3D true; + + /* + * pgoff is a virtual address when the trampoline maps are created and + * a vmlinux offset once mapped, so a second pass, e.g. a concurrent + * dso__load() of the kernel dso, finds no map and has nothing to + * translate: leave the map alone, never dereference a NULL dest_map. + */ dest_map =3D maps__find(args->kmaps, map__pgoff(map)); - if (RC_CHK_ACCESS(dest_map) !=3D RC_CHK_ACCESS(map)) + if (dest_map !=3D NULL && RC_CHK_ACCESS(dest_map) !=3D RC_CHK_ACCESS(map)) map__set_pgoff(map, map__map_ip(dest_map, map__pgoff(map))); =20 map__put(dest_map); - args->found =3D true; return 0; } =20 diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index fbad770cb96f8294..7a84dc8420975ab6 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -20,6 +20,7 @@ #include "cap.h" #include "cpumap.h" #include "debug.h" +#include "debuginfo.h" #include "demangle-cxx.h" #include "demangle-java.h" #include "demangle-ocaml.h" @@ -2191,12 +2192,28 @@ static char *dso__find_kallsyms(struct dso *dso, st= ruct map *map) return strdup(path); } =20 +/* + * Last resort when the symbols for the kernel the profile was recorded + * on can't be found locally: fetch the vmlinux keyed by the build ID + * recorded in perf.data, e.g. when processing the profile on another + * machine or after the kernel got upgraded in between. + */ +/* Called by dso__load_kernel_sym() with dso->lock dropped, see there. */ +static int dso__fetch_vmlinux_build_id(struct dso *dso, char **path) +{ + if (!dso__has_build_id(dso)) + return -1; + + return debuginfo__find_build_id(dso__bid(dso), path); +} + static int dso__load_kernel_sym(struct dso *dso, struct map *map) { int err; const char *kallsyms_filename =3D NULL; char *kallsyms_allocated_filename =3D NULL; char *filename =3D NULL; + bool user_kallsyms =3D false; =20 /* * Step 1: if the user specified a kallsyms or vmlinux filename, use @@ -2215,6 +2232,7 @@ static int dso__load_kernel_sym(struct dso *dso, stru= ct map *map) */ if (symbol_conf.kallsyms_name !=3D NULL) { kallsyms_filename =3D symbol_conf.kallsyms_name; + user_kallsyms =3D true; goto do_kallsyms; } =20 @@ -2257,7 +2275,42 @@ static int dso__load_kernel_sym(struct dso *dso, str= uct map *map) pr_debug("Using %s for symbols\n", kallsyms_filename); free(kallsyms_allocated_filename); =20 - if (err > 0 && !dso__is_kcore(dso)) { + /* + * The kallsyms may be unavailable or restricted, try to fetch the + * vmlinux keyed by the build ID using debuginfod as a last resort, + * honoring ignore_vmlinux/ignore_vmlinux_buildid like the sources above. + */ + if (err <=3D 0 && !user_kallsyms && + !symbol_conf.ignore_vmlinux && + !symbol_conf.ignore_vmlinux_buildid) { + char *fetched_path =3D NULL; + + /* + * dso__load() holds dso->lock while it calls us, and the fetch below + * can block for a long time: do it with the lock dropped, like + * dso__debuginfo(), and load the symbols with the lock held again. + */ + mutex_unlock(dso__lock(dso)); + err =3D dso__fetch_vmlinux_build_id(dso, &fetched_path); + mutex_lock(dso__lock(dso)); + + if (err) { + zfree(&fetched_path); + } else if (dso__has_symbols(dso)) { + /* + * Somebody else got the symbols while the lock was dropped for the + * fetch, use those instead. dso__has_symbols() and not dso__loaded(): + * the latter is set even for failed attempts. + */ + pr_debug("%s got its symbols while its vmlinux was being fetched, using= them\n", + dso__name(dso)); + zfree(&fetched_path); + err =3D 1; + } else { + /* Takes ownership of 'fetched_path' even when it fails */ + err =3D dso__load_vmlinux(dso, map, fetched_path, true); + } + } else if (err > 0 && !dso__is_kcore(dso)) { struct maps *kmaps =3D map__kmaps(map); =20 dso__set_binary_type(dso, DSO_BINARY_TYPE__KALLSYMS); --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0F64F4ED1B2; Thu, 17 Sep 2026 15:56:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660569; cv=none; b=Ufy21fcU3tP1JS0drAbQfUtpOt6/7Me5PFgDYhfuWG13x6dZxO5JaR4WlBO0DtXmfui7GUP3TX7TWOm70CCepLKhXUYwdiULaXqgaJR235RTD4a+T+bThZCkINQZOmEunB5rjVoizQNRH0bkGAw6ZWQo69IiPlIvoG9d9Eg2hio= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660569; c=relaxed/simple; bh=sQ6irpPy9qg+KwS9ppQ29XA+Btr2M5iA8huAws5TNto=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JqZgEBJqoSYp+oMEFepqPwDHTtSMi0MU3yvLPMZGpXrS0yV+WQu6peDAT2yrZMeu8J6JppdPrRzxWiSryQOLSp670IBUOA/CKmm5GycPhVnxg2JUWf3Kg4IGBdhxiSq700wnzrbOhNI1hbFSy3hQPEKVdmZJXari8zuSGEC+C38= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Jzt1EoJW; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Jzt1EoJW" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 37D081F00898; Thu, 17 Sep 2026 15:56:05 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660567; bh=yiamfbmklyaY8g3wHJHvEs97pXS5G54zTVCv5jS7mCY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Jzt1EoJWf4PiB9Bv8x1KPBhFOp1qewaXQi7VfW3/a2Yd0Svc1j1qsyghjQ8/tQVNU ttrREQHilktgLKd8hHYEA0QknrB6YW85n1CoTv0jn1sVDM9Z/5/u3Xm8GfZ5hEFp1e 5NsshGs6tnEE6gYT5MLSjDpJr/MoFuQYGa8p7xQL7K06wIn+wz+jwEpe+Xu2ETeYJO rTdLCVURb2nVaamGQk4LSJQIXTE2YRB5w0OphVTKzZg33BqCYP459+d1/qIQMCFbpD IGErRMrkNINduEk4MA2tPTujiX9lpFMjcdswQT0h7L6568yibv/WSUJPeIUW8yF0/3 RC2PgG6tLnSLg== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 07/15] perf annotate-data: Show the sample count in the data-type browser Date: Thu, 17 Sep 2026 12:55:18 -0300 Message-ID: <20260917155528.62607-8-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo The samples view (-n, annotate.show_nr_samples) prints a local nr_samples variable that is initialized to zero and never updated, so every member is listed as having no samples while the period and percent columns for the same entry are filled in. Print the histogram entry's own count instead. Fixes: d001c7a7f4736743 ("perf annotate-data: Add hist_entry__annotate_data= _tui()") Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/ui/browsers/annotate-data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/perf/ui/browsers/annotate-data.c b/tools/perf/ui/browser= s/annotate-data.c index c6e07a9b64089ab5..a15608f6ee53335f 100644 --- a/tools/perf/ui/browsers/annotate-data.c +++ b/tools/perf/ui/browsers/annotate-data.c @@ -374,7 +374,7 @@ static void browser__write_overhead(struct ui_browser *= uib, u64 period =3D hist->period; double percent =3D total->period ? (100.0 * period / total->period) : 0; bool current =3D ui_browser__is_current_entry(uib, row); - int nr_samples =3D 0; + int nr_samples =3D hist->nr_samples; =20 ui_browser__set_percent_color(uib, percent, current); =20 --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E928B4EFFD3; Thu, 17 Sep 2026 15:56:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660574; cv=none; b=e+9mhD1WaMvIjjYUhHM9Ua3jqqSps1KdM+AE4YDkbd9BbH2M5P2Qy15intMCkrR5fq5b0ZuGi6r2pJ+44vDWg4qruCky/Gc/kmVG7KFNr3ppIdfOD4D4+PbWJg7IwMZ60DxNSb8CV/Gzke8IZKfKO4RlcyjqhNBSntbLyg47oB8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660574; c=relaxed/simple; bh=RwxGyUEIXeI/KZP34Ek5mK23N/262nE3m5eS8V2Qu9E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NOrApqmWhwFPeJyK/zgMfhq5CP9e2EHFr+P3uPSX9H1sZfukqSuCmHGtM3rvDHwjZAd+ZSqS0aaKdBA/NeWOkeBvRa+cvnJGlWH5SPGY/zRGfhlXJlrvbeqMGTBbI2UyC9wMCfYJWC11j/wfThFrW7G5/iRDD/Q/Op9lrxUv3Rc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=byxWZt8n; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="byxWZt8n" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7A9161F0089D; Thu, 17 Sep 2026 15:56:08 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660572; bh=FEu2/W+m42i/8SvhGUC3KqnIA4UUPB2rylJaFT79elc=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=byxWZt8nXQSsCRtttaRW3bvT8c8qri6lj9LOci6QtAyR+uzpHHlmcgHSG0O1h0iSu YWxyDGgL+tIb2h1NmapX7XNWLLPJbSESMJS+ScdChzSBGYqfXyxMPy05LiYw4L1vmA qd4q6auVrFYCUfIjnh3yhtgJ8yZoGpRECFcz7F8VLGAwTlEtEInxB2QGbxpvhUHk9x M/ebTfIZXZDKkAM1jvxDkEuk9yHOJJ+raezhvD/ei8m01NxeiwH5llI1AhGJzPJnKs Sl1mneDjQG04/SqxxRYwm5xhhZhHgnewWDHMtZKYNtkxKmAtrDq2K/c15hhs+2atph M/Cd4IQekeuIQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 08/15] perf report: Add --progress option Date: Thu, 17 Sep 2026 12:55:19 -0300 Message-ID: <20260917155528.62607-9-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo Processing a large session with stdio output gives no feedback about which phase perf is in or how far along it is: the ui_progress updates are only shown by the TUI. Add --progress, installing a stdio backend for ui_progress (ui/stdio/progress.c) that prints the phase title, the percentage and the current/total counts to stderr: Processing events... [ 42.3%] 317M / 746M Merging related events... [ 61.0%] 309026 / 506686 Phases can be nested, so the backend keeps track of the ones started so far to complete the right one on ui_progress__finish(). That requires init()/finish() to be paired, and the paths that returned early without the finish, in ordered-events.c and in the pipe and directory event processing, are fixed. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-report.txt | 13 ++ tools/perf/builtin-report.c | 10 ++ tools/perf/ui/Build | 1 + tools/perf/ui/progress.h | 2 + tools/perf/ui/stdio/progress.c | 163 +++++++++++++++++++++++ tools/perf/util/ordered-events.c | 16 ++- tools/perf/util/session.c | 12 +- 7 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 tools/perf/ui/stdio/progress.c diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Document= ation/perf-report.txt index 0a4f61283b9542f7..8fc1d5d56d54f698 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -29,6 +29,19 @@ OPTIONS --quiet:: Do not show any warnings or messages. (Suppress -v) =20 +--progress:: + Show progress for each of the processing phases, printing the + percentage done and the current/total counts: the first phase + counts the bytes of the perf.data file processed so far, the + merge and sort phases count hist entries, e.g.: + + Processing events... [ 42.3%] 4G / 10G + Merging related events... [ 7.1%] 1024 / 14387 + Sorting events for output... [ 98.2%] 14132 / 14387 + + It is a no-op when using the TUI or GTK browsers, that already + present progress information. + -n:: --show-nr-samples:: Show the number of samples for each symbol diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 442c0822e614197f..e3ac2f15c9fd5c4f 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -87,6 +87,7 @@ struct report { bool use_gtk; #endif bool use_stdio; + bool progress; bool show_full_info; bool show_threads; bool inverted_callchain; @@ -1374,6 +1375,8 @@ int cmd_report(int argc, const char **argv) "Use the stdio interface"), OPT_BOOLEAN(0, "weights", &symbol_conf.annotate_weight, "Show or hide weight columns in annotation. Default show if non-zero."), + OPT_BOOLEAN(0, "progress", &report.progress, + "Show progress while processing the perf.data file"), OPT_BOOLEAN(0, "header", &report.header, "Show data header."), OPT_BOOLEAN(0, "header-only", &report.header_only, "Show only data header."), @@ -1766,6 +1769,13 @@ int cmd_report(int argc, const char **argv) else use_browser =3D 0; =20 + /* + * For the stdio case: print the percentage of the perf.data file + * processed so far for each processing phase. + */ + if (report.progress && use_browser =3D=3D 0) + stdio_progress__init(); + if (report.data_type && use_browser =3D=3D 1) { symbol_conf.annotate_data_member =3D true; symbol_conf.annotate_data_sample =3D true; diff --git a/tools/perf/ui/Build b/tools/perf/ui/Build index 6005f813c9e3990c..a7b1740d51c80f23 100644 --- a/tools/perf/ui/Build +++ b/tools/perf/ui/Build @@ -4,6 +4,7 @@ perf-ui-y +=3D progress.o perf-ui-y +=3D util.o perf-ui-y +=3D hist.o perf-ui-y +=3D stdio/hist.o +perf-ui-y +=3D stdio/progress.o =20 CFLAGS_setup.o +=3D -DLIBDIR=3D"BUILD_STR($(LIBDIR))" =20 diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h index 4f52c37b2f099a82..03f1a8bb260ba076 100644 --- a/tools/perf/ui/progress.h +++ b/tools/perf/ui/progress.h @@ -23,6 +23,8 @@ void __ui_progress__init(struct ui_progress *p, u64 total, =20 void ui_progress__update(struct ui_progress *p, u64 adv); =20 +void stdio_progress__init(void); + struct ui_progress_ops { void (*init)(struct ui_progress *p); void (*update)(struct ui_progress *p); diff --git a/tools/perf/ui/stdio/progress.c b/tools/perf/ui/stdio/progress.c new file mode 100644 index 0000000000000000..d8465e13ad50e4f6 --- /dev/null +++ b/tools/perf/ui/stdio/progress.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Progress feedback for the stdio (non-TUI/GTK) case, enabled with + * 'perf report --progress'. + */ +#include +#include +#include +#include +#include "../../util/debug.h" +#include "../../util/units.h" +#include "../progress.h" + +/* + * Phases can be nested, so keep track of the ones started so far to be + * able to complete the right one on ui_progress__finish(), which gets + * no arguments. + */ +#define STDIO_PROGRESS__MAX_DEPTH 8 + +struct stdio_progress_phase { + struct ui_progress *p; + u64 last_printed; + size_t last_len; +}; + +static struct stdio_progress_phase stdio_progress__stack[STDIO_PROGRESS__M= AX_DEPTH]; +static int stdio_progress__depth; +static bool stdio_progress__is_tty; +/* Phases that didn't fit on the stack are not shown. */ +static int stdio_progress__dropped; + +static void stdio_progress__print_phase(struct stdio_progress_phase *phase, + u64 curr) +{ + struct ui_progress *p =3D phase->p; + char buf_cur[20], buf_tot[20], buf[128]; + double percent =3D p->total ? 100.0 * (double)curr / (double)p->total : 0= .0; + size_t len; + + /* + * Only the completion line shows 100.0%: a 99.99% progress would round + * up to it and look like a duplicate at finish time. + */ + if (curr < p->total && percent > 99.9) + percent =3D 99.9; + + if (p->size) { + unit_number__scnprintf(buf_cur, sizeof(buf_cur), curr); + unit_number__scnprintf(buf_tot, sizeof(buf_tot), p->total); + len =3D scnprintf(buf, sizeof(buf), "%s [%5.1f%%] %s / %s", + p->title, percent, buf_cur, buf_tot); + } else { + len =3D scnprintf(buf, sizeof(buf), "%s [%5.1f%%] %" PRIu64 " / %" PRIu6= 4, + p->title, percent, curr, p->total); + } + + if (!stdio_progress__is_tty) { + fprintf(stderr, "%s\n", buf); + goto out; + } + + /* Pad to the length of the previous line to erase its leftovers. */ + fprintf(stderr, "\r%s%*s", buf, + (int)(len < phase->last_len ? phase->last_len - len : 0), ""); + phase->last_len =3D len; +out: + phase->last_printed =3D curr; + fflush(stderr); +} + +static void __stdio_progress__init(struct ui_progress *p) +{ + /* The default step is meant for the TUI bar, use 1% steps for stdio. */ + p->next =3D p->step =3D p->total / 100 ?: 1; + + if (stdio_progress__depth =3D=3D STDIO_PROGRESS__MAX_DEPTH) { + /* + * Out of room: don't start this phase, its finish() is swallowed below + * and its updates are ignored, so it doesn't complete the phase that + * encloses it. + */ + pr_warning("progress phases nested deeper than %d, not showing progress = for %s\n", + STDIO_PROGRESS__MAX_DEPTH, p->title); + stdio_progress__dropped++; + return; + } + + /* Start a nested phase in a line of its own. */ + if (stdio_progress__depth && stdio_progress__is_tty) + fputc('\n', stderr); + + stdio_progress__stack[stdio_progress__depth++] =3D + (struct stdio_progress_phase) { + .p =3D p, + .last_printed =3D 0, + .last_len =3D 0, + }; + + stdio_progress__print_phase(&stdio_progress__stack[stdio_progress__depth = - 1], + p->curr); +} + +static void stdio_progress__update(struct ui_progress *p) +{ + /* + * An update that doesn't match the innermost running phase means + * something got out of sync: print nothing rather than another phase's + * numbers, or read past the stack. + */ + if (!stdio_progress__depth || + stdio_progress__stack[stdio_progress__depth - 1].p !=3D p) + return; + + stdio_progress__print_phase(&stdio_progress__stack[stdio_progress__depth = - 1], + p->curr); +} + +static void stdio_progress__finish(void) +{ + struct stdio_progress_phase *phase; + + /* + * Being the innermost phase, its finish() comes first: swallow it, or + * it would complete the phase that encloses it. + */ + if (stdio_progress__dropped) { + stdio_progress__dropped--; + return; + } + + if (!stdio_progress__depth) + return; + + phase =3D &stdio_progress__stack[--stdio_progress__depth]; + + /* + * The last line may have stopped short of the total, close this phase + * showing it as complete unless that was already printed. + */ + if (phase->last_printed !=3D phase->p->total) + stdio_progress__print_phase(phase, phase->p->total); + + phase->last_printed =3D 0; + phase->last_len =3D 0; + + if (stdio_progress__is_tty) + fputc('\n', stderr); + + fflush(stderr); +} + +static struct ui_progress_ops stdio_progress__ops =3D { + .init =3D __stdio_progress__init, + .update =3D stdio_progress__update, + .finish =3D stdio_progress__finish, +}; + +void stdio_progress__init(void) +{ + stdio_progress__is_tty =3D isatty(STDERR_FILENO) =3D=3D 1; + ui_progress__ops =3D &stdio_progress__ops; +} diff --git a/tools/perf/util/ordered-events.c b/tools/perf/util/ordered-eve= nts.c index a5857f9f5af2d3de..54c85663e733be4d 100644 --- a/tools/perf/util/ordered-events.c +++ b/tools/perf/util/ordered-events.c @@ -237,14 +237,16 @@ static int do_flush(struct ordered_events *oe, bool s= how_progress) ui_progress__init(&prog, oe->nr_events, "Processing time ordered events.= .."); =20 list_for_each_entry_safe(iter, tmp, head, list) { - if (session_done()) - return 0; + if (session_done()) { + ret =3D 0; + goto out_progress; + } =20 if (iter->timestamp > limit) break; ret =3D oe->deliver(oe, iter); if (ret < 0) - return ret; + goto out_progress; =20 ordered_events__delete(oe, iter); oe->last_flush =3D iter->timestamp; @@ -258,10 +260,16 @@ static int do_flush(struct ordered_events *oe, bool s= how_progress) else if (last_ts <=3D limit) oe->last =3D list_entry(head->prev, struct ordered_event, list); =20 + ret =3D 0; +out_progress: + /* + * Always pair ui_progress__init() with ui_progress__finish(), the + * stdio backend tracks the phases on a stack. + */ if (show_progress) ui_progress__finish(); =20 - return 0; + return ret; } =20 static int __ordered_events__flush(struct ordered_events *oe, enum oe_flus= h how, diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index a5b596cd14be6186..50f3e1d6c17d3147 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -3248,8 +3248,12 @@ static int __perf_session__process_pipe_events(struc= t perf_session *session) cur_size =3D sizeof(union perf_event); =20 buf =3D malloc(cur_size); - if (!buf) - return -errno; + if (!buf) { + err =3D -errno; + if (update_prog) + ui_progress__finish(); + return err; + } ordered_events__set_copy_on_queue(oe, true); more: event =3D buf; @@ -3748,8 +3752,10 @@ static int __perf_session__process_dir_events(struct= perf_session *session) } =20 rd =3D calloc(nr_readers, sizeof(struct reader)); - if (!rd) + if (!rd) { + ui_progress__finish(); return -ENOMEM; + } =20 rd[0] =3D (struct reader) { .fd =3D perf_data__fd(session->data), --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CB0284F390B; Thu, 17 Sep 2026 15:56:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660578; cv=none; b=p1j1O8uVXM4sfxfGQCmSkFrfspWJZbPyKjIV4X3rP1sC2xZf9Dsh3MZBwDkBFrBK9/w/tG9HFD7bn80kxtlQX73oXXDlEtJwLwV+RRnev//xlDHWuoUcUI2LnK/zD3apusWjDqvbyipyKlINY3KFSU4HAlU5e0MkNWGghVDkG/I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660578; c=relaxed/simple; bh=unHMDA6KRZrstIS/HTSWwWgJshWRk/EjX5+RW3S6zpM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mQTyb7TTqji+6l26KZ+eXwLyn2tXF6jkWlfhlh2SJVMKSP3xEAWP63GWQgKRNSlcrs0mE/QgNGYLeug3GlJwdAxavAhGGLJpn9s7k0payj1vddg/wjUPMj4VUIa20+X8mrb4tAK6GmJAvy3eqOL/S0PBM5ZjOF3OtHRCjF3/eJ8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XpMh5Syc; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="XpMh5Syc" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3A23D1F00898; Thu, 17 Sep 2026 15:56:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660576; bh=yZyKzwWOsWZxYQQmjedx7QzFFbJe3mzTpcqQ6wLwGEc=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=XpMh5SycIDWPp3J8SVY8bWfg+vSdQVsSsbv5DHRSi+N5ZjhNxCG4svdpVGrltbYQg kT/n8Xu4v/2g9LZPZLEP5jIKBSKhFDHyWCi85yYmJhEND+wUfLXp1DqkOCIaXHMwSJ 3FuVRBc0qacrht46FnCJxe5wNeYpKSdXasMsLo20ljwSFHwOM7dg3yYc93G4YmzKZy 1C5rOuE/22TerpdfMu09jcybsz9ljVHN8MjQTbhmHW2RIv3aj4QcQk7NP46iNcR3Bl WssDQVUXHUT5GWvOF9PADEZo7U7QleCU3ZdK0Vu60H9SLSks3OypwzG8z8w/7U65Mt qYkEQbSrbKKFQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 09/15] perf scripts: Add perf-stuck, to tell where a running perf is stuck Date: Thu, 17 Sep 2026 12:55:20 -0300 Message-ID: <20260917155528.62607-10-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo A perf that takes forever is hard to tell apart from one stuck in a loop, and when it is stuck there is no way to see where without attaching gdb and looking around. perf-stuck.sh samples a running process' /proc entries and its progress line at a fixed interval, and with -g runs gdb (perf-stuck.gdb, adding the perf-die-chain command) when no progress is made across two samples, printing the DIE chain a DWARF type chase is walking when it is stuck in one of those. It is a prototype: the plan is to turn it into a first class 'perf stuck' command sampling a process from inside perf. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/scripts/perf-stuck.gdb | 104 ++++++++++++++++ tools/perf/scripts/perf-stuck.sh | 194 ++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 tools/perf/scripts/perf-stuck.gdb create mode 100755 tools/perf/scripts/perf-stuck.sh diff --git a/tools/perf/scripts/perf-stuck.gdb b/tools/perf/scripts/perf-st= uck.gdb new file mode 100644 index 0000000000000000..53e9019828b6ef62 --- /dev/null +++ b/tools/perf/scripts/perf-stuck.gdb @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# gdb commands for a perf that is stuck, used by perf-stuck.sh -g and usab= le +# directly: +# +# gdb -p $(pgrep -x perf) -batch -x perf-stuck.gdb -ex bt +# +# PROTOTYPE: part of the perf-stuck.sh stopgap, see the note at the start = of +# that script: this wants to move into a first class 'perf stuck' command, +# which would print these DIE chains by itself, without gdb. +# +# The settings are the ones that keep a batch attach from stopping to ask +# questions (debuginfod, pagination) and that make the output readable. +# +# The commands are for the DWARF type chasers in util/dwarf-aux.c, the +# functions a data type profiling 'perf report -s type' spins in when a +# debug info file has a type chain that got into a cycle: +# +# perf-die-chain [iterations] +# perf-die-chain-all [iterations] +# perf-dso +# +# For each iteration of the chasing loop they print the DIE address, the +# CU it came from, its offset in the debug file, its tag and its name: a +# cycle shows up as the same handful of (addr, cu) pairs repeating, and a +# CU that changes from one iteration to the next means the chase is +# hopping between a debug file and its dwz common file. + +set pagination off +set confirm off +set debuginfod enabled off +set print pretty on +set height 0 +set width 0 + +define perf-die-chain + if $argc < 2 + printf "usage: perf-die-chain [iterations]\n" + else + frame function $arg0 + if $argc =3D=3D 3 + set $perf_die_chain_n =3D $arg2 + else + set $perf_die_chain_n =3D 10 + end + set $perf_die_chain_head =3D $pc + set $perf_die_chain_i =3D 0 + while $perf_die_chain_i < $perf_die_chain_n + printf "chain[%d] die=3D%p addr=3D%p cu=3D%p off=3D0x%lx tag=3D%d na= me=3D%s\n", $perf_die_chain_i, $arg1, $arg1->addr, $arg1->cu, ((Dwarf_Off) = dwarf_dieoffset($arg1)), ((int) dwarf_tag($arg1)), ((char *) dwarf_diename(= $arg1)) + until *$perf_die_chain_head + set $perf_die_chain_i =3D $perf_die_chain_i + 1 + end + end +end + +document perf-die-chain +Print the DIE chain being walked by a DWARF type chasing loop. +usage: perf-die-chain [iterations] + perf-die-chain die_get_pointer_type type_die + perf-die-chain __die_get_real_type vr_die + perf-die-chain die_get_real_type vr_die +end + +define perf-die-chain-all + if $argc =3D=3D 0 + set $perf_die_chain_n =3D 10 + else + set $perf_die_chain_n =3D $arg0 + end + if $_any_caller_is("die_get_pointer_type", 20) + printf "stuck in die_get_pointer_type():\n" + perf-die-chain die_get_pointer_type type_die $perf_die_chain_n + else + if $_any_caller_is("__die_get_real_type", 20) + printf "stuck in __die_get_real_type():\n" + perf-die-chain __die_get_real_type vr_die $perf_die_chain_n + else + if $_any_caller_is("die_get_real_type", 20) + printf "stuck in die_get_real_type():\n" + perf-die-chain die_get_real_type vr_die $perf_die_chain_n + else + printf "not in a DWARF type chaser, try: bt\n" + end + end + end +end + +document perf-die-chain-all +Find which DWARF type chaser the process is in and print the DIE chain. +usage: perf-die-chain-all [iterations] +end + +define perf-dso + if $_any_caller_is("find_data_type", 20) + frame function find_data_type + printf "dso=3D%s ip=3D0x%lx sym=3D%s\n", dloc->ms->map->dso->name, dlo= c->ip, dloc->ms->sym->name + else + printf "not in find_data_type()\n" + end +end + +document perf-dso +Print the dso, ip and symbol of the data location being resolved. +end diff --git a/tools/perf/scripts/perf-stuck.sh b/tools/perf/scripts/perf-stu= ck.sh new file mode 100755 index 0000000000000000..70e59192b1d9658e --- /dev/null +++ b/tools/perf/scripts/perf-stuck.sh @@ -0,0 +1,194 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# perf-stuck - tell a spinning perf apart from a blocked or recursing one +# +# Arnaldo Carvalho de Melo +# +# PROTOTYPE: this wants to become a first class 'perf stuck' command, that +# samples a running perf, or any other process, from inside perf, with the +# knowledge of the phases perf goes through and of the DWARF type chasing +# loops built in, instead of this shell script poking at /proc and shelling +# out to gdb. It is here as a stopgap, to be able to tell where a perf is +# stuck while looking at hangs such as the one 'perf report -s type' hits +# on dwz compressed debug info. +# +# Samples /proc/ at a fixed interval and prints, for each sample: +# +# the CPU time used since the previous sample, so a process burning a +# full interval's worth of ticks is spinning, while one using none is +# blocked +# +# the [stack] mapping start, which moves down as the stack grows, the +# giveaway for runaway recursion, together with its size +# +# the last line of a progress log, when one is given, e.g. the stderr +# of 'perf report --progress', to see which phase is stuck +# +# A process that burns CPU with a constant stack and no progress is in an +# unbounded loop, e.g. a die_get_pointer_type() chain that got into a +# cycle, while one whose [stack] start keeps moving down is recursing. +# +# With -g it runs gdb, using the perf-stuck.gdb that sits next to this +# script, when no progress is made for two consecutive samples, which for +# a perf in a DWARF type chasing loop prints the DIE chain it is walking. +# +# usage: perf-stuck.sh [options] + +set -u + +usage() { + cat <<-EOF + usage: perf-stuck.sh [options] + + -i sampling interval (default: 10) + -n stop after this many samples (default: watch till it exits) + -l progress log, its last line is printed with every sample + -g run gdb with perf-stuck.gdb when no progress is made for + two consecutive samples, writing the output to a temp file + -x use this gdb command file instead of perf-stuck.gdb + -h this help + EOF + exit "${1:-0}" +} + +interval=3D10 +count=3D0 +progress_log=3D +use_gdb=3D +gdb_cmds=3D + +while getopts "i:n:l:gx:h" opt; do + case "$opt" in + i) interval=3D$OPTARG ;; + n) count=3D$OPTARG ;; + l) progress_log=3D$OPTARG ;; + g) use_gdb=3D1 ;; + x) gdb_cmds=3D$OPTARG ;; + h) usage 0 ;; + *) usage 1 ;; + esac +done +shift $((OPTIND - 1)) + +[ $# -eq 1 ] || usage 1 + +if [[ "$1" =3D~ ^[0-9]+$ ]]; then + pid=3D$1 +else + pid=3D$(pgrep -x "$1" | head -1) + [ -n "$pid" ] || { echo "no process named '$1'"; exit 1; } +fi + +[ -d /proc/"$pid" ] || { echo "no process $pid"; exit 1; } + +if [ -n "$use_gdb" ] && [ -z "$gdb_cmds" ]; then + gdb_cmds=3D$(dirname "$0")/perf-stuck.gdb + [ -r "$gdb_cmds" ] || { echo "cannot read $gdb_cmds"; exit 1; } +fi + +hz=3D$(getconf CLK_TCK) +psz=3D$(getconf PAGESIZE) +prev_cpu=3D +prev_stack=3D +prev_progress=3D +stuck=3D0 +gdb_done=3D +nsample=3D0 + +# The command line is whatever the process was started with, so drop the +# control characters from it: a process started with escape sequences in +# its arguments, e.g. one replaying a log line, would otherwise get them +# replayed on the terminal of whoever runs this. +cmdline=3D$(tr '\0' ' ' < /proc/"$pid"/cmdline | tr -d '[:cntrl:]') + +echo "watching $pid ($cmdline) every ${interval}s" + +while :; do + if [ ! -d /proc/"$pid" ]; then + echo "$(date +%T) process gone" + break + fi + + # Field 2, the command name, is in parentheses and can contain + # spaces, so drop it together with the pid before splitting: the + # fields after it then line up, with the state, the utime+stime + # pair and the RSS landing where they are read below. Printing the + # CPU time with %d instead of relying on awk's default output format + # keeps it out of scientific notation, that bash arithmetic cannot + # parse, once it goes past six digits, i.e. some 16 minutes of CPU + # at 100 Hz. + if ! stat_line=3D$(awk '{ sub(/^[^ ]+ \(.*\) /, ""); + printf "%s %d %d\n", $1, $12 + $13, $22 }' \ + /proc/"$pid"/stat 2>/dev/null); then + echo "$(date +%T) process gone" + break + fi + + # The process can be gone between the check above and this read, in + # which case there is nothing to report: 'set -u' would otherwise + # turn the unbound fields into an aborted script. + if [ -z "$stat_line" ]; then + echo "$(date +%T) process gone" + break + fi + + stat=3D($stat_line) + state=3D${stat[0]} + cpu=3D${stat[1]} + # field 24 of /proc//stat, the resident set size in pages + rss=3D$(( stat[2] * psz / 1024 )) + + stack=3D$(awk '/\[stack\]/{print $1; exit}' /proc/"$pid"/maps 2>/dev/null) + if [ -n "$stack" ]; then + stack_start=3D0x${stack%-*} + stack_size=3D$(( 0x${stack#*-} - stack_start )) + stack_txt=3D"$stack size=3D$((stack_size / 1024))kB" + else + stack_start=3D + stack_txt=3D"-" + fi + + progress=3D + [ -n "$progress_log" ] && [ -s "$progress_log" ] && progress=3D$(tail -1 = "$progress_log") + + if [ -n "$prev_cpu" ]; then + cpu_delta=3D$(( cpu - prev_cpu )) + # With a progress log, count the samples that show no progress, + # without one there is no progress to look at, so count them all: + # -g then looks at where the process is after two intervals. + if [ -z "$progress_log" ] || + { [ -n "$progress" ] && [ "$progress" =3D "$prev_progress" ]; }; then + stuck=3D$((stuck + 1)) + else + stuck=3D0 + fi + stuck_txt=3D"stuck=3D${stuck}" + [ "$stack_start" !=3D "$prev_stack" ] && stuck_txt=3D"$stuck_txt STACK" + else + cpu_delta=3D0 + stuck_txt=3D"" + fi + + printf '%s state=3D%s cpu=3D+%d (%d.%02ds) rss=3D%dkB stack=3D%s %s %s\n'= \ + "$(date +%T)" "$state" "$cpu_delta" \ + $(( cpu_delta / hz )) $(( (cpu_delta % hz) * 100 / hz )) \ + "$rss" "$stack_txt" "$stuck_txt" "${progress:-(no progress log)}" + + if [ -n "$use_gdb" ] && [ -z "$gdb_done" ] && [ "$stuck" -ge 2 ]; then + gdb_log=3D$(mktemp /tmp/perf-stuck-gdb.XXXXXX) + gdb -p "$pid" -batch -x "$gdb_cmds" -ex bt \ + -ex 'perf-die-chain-all' -ex perf-dso -ex detach > "$gdb_log" 2>&1 + gdb_done=3D1 + echo "... gdb output of $pid in $gdb_log" + fi + + prev_cpu=3D$cpu + prev_stack=3D$stack_start + prev_progress=3D$progress + + nsample=3D$((nsample + 1)) + [ "$count" -gt 0 ] && [ "$nsample" -ge "$count" ] && break + + sleep "$interval" +done --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8BFD750EBE1; Thu, 17 Sep 2026 15:56:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660582; cv=none; b=C3z1i4jfYtiFypXj8n799d3JM8LosYWZMu2gK7W+PVk0KlJSxf6tpOQ7uk3HQDfj9bHOvLGmwi2lVIlGpDnTbsLarPdPIwhZU2LN1GXWUSBtdl2TOymZVMdJ5YHsf9QuXjfC1CeSIuVHNW4gWOj7xkyqpjFZNOFY5jqWe5Wu3hc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660582; c=relaxed/simple; bh=Rfu25ECYoXI+/kV6W/6MswO7S3WKPtf1KRF1KE0xcMM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PN4tGstdGx0oqF6/0FGnzlCJ5+Z+hk039E2MNTrqrpCUJ4z7yPz+5bRK80AhDcg+b8Z64WArpTgQeOUo5lq9s73GI4Jp4bnaX1kmJ5eKVvj3M/nw/iFAK5Aev2zv6akcL3LYXHfprmGD5LCfN2hZZKCVsiYt0Tz1I9SENPutQkw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=eHa+HiIe; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="eHa+HiIe" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 49DDA1F00893; Thu, 17 Sep 2026 15:56:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660581; bh=/o0j4IyBit4x0M7siaLpgTimP2fXGHf326McwkQdspI=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=eHa+HiIeb9PIvN8Ls+lH/lrgTCy51XY8jdeCsF3KROtOHZMunYLMnG6coilKFKSYw lbl5awVcHyHhy1gbwrV7rgp9dSXNvRVWDGYy0k9m4YwdT3LT0tKrTp2doIWYOcIsnF XBkKwW0OvQItY8VcFxiAcbS9bltwpc7cXVpaFGuEL0WZ9M6YggrkfGpF9CBK0CuTEq AEsIN9a7Y5aryo6uRmOcwPigOiF74/Gr/x1ld4yBBwSsb2Tc2s9GSjTj9Lww/zhHhs EqCDCFmLT1dpCjMsxVRZdrlKZweiIM8BEVaCYd3E7v8aC0H8g3pRQCfuLxyGBSA03s uXb5o62WjxsfA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 10/15] perf dwarf-aux: Bound the type chases for broken debug info Date: Thu, 17 Sep 2026 12:55:21 -0300 Message-ID: <20260917155528.62607-11-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo A DIE that is not what it looks like, e.g. one parsed at an offset that is not the start of a DIE, can have a DW_AT_type that refers back to itself, making the typedef/qualifier chases in die_get_real_type() and die_get_pointer_type() spin forever, and the same for the type name recursion in die_get_typename_from_type(); 'perf report -s type' did exactly that on the dwz compressed debug info of zlib-ng (libz.so.1). No sane chain of typedefs and qualifiers is 32 DIEs long, so give up on the type with a pr_debug instead of hanging. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/dwarf-aux.c | 88 ++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 20 deletions(-) diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index d7160f87ac7d7ab3..b5ffeea54446408d 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -266,16 +266,29 @@ Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die = *die_mem) return NULL; } =20 +/* + * A DIE that is not what it looks like, e.g. one parsed at an offset + * that is not the start of a DIE, can have a DW_AT_type that refers + * back to itself, making these chases spin forever: bound them and + * report, instead of hanging. + */ +#define MAX_TYPE_CHASE 32 + /* Get a type die, but skip qualifiers */ Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) { - int tag; + int tag, chase =3D 0; =20 do { vr_die =3D die_get_type(vr_die, die_mem); if (!vr_die) - break; + return NULL; tag =3D dwarf_tag(vr_die); + if (++chase > MAX_TYPE_CHASE) { + pr_debug("DWARF: qualifier chase limit reached at DIE 0x%lx\n", + (unsigned long)dwarf_dieoffset(vr_die)); + return NULL; + } } while (tag =3D=3D DW_TAG_const_type || tag =3D=3D DW_TAG_restrict_type || tag =3D=3D DW_TAG_volatile_type || @@ -296,8 +309,15 @@ Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwar= f_Die *die_mem) */ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem) { + int chase =3D 0; + do { vr_die =3D __die_get_real_type(vr_die, die_mem); + if (++chase > MAX_TYPE_CHASE) { + pr_debug("DWARF: typedef chase limit reached at DIE 0x%lx\n", + vr_die ? (unsigned long)dwarf_dieoffset(vr_die) : 0); + return NULL; + } } while (vr_die && dwarf_tag(vr_die) =3D=3D DW_TAG_typedef); =20 return vr_die; @@ -314,7 +334,7 @@ Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_D= ie *die_mem) */ Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, Dwarf_Die *die_mem) { - int tag; + int tag, chase =3D 0; =20 do { tag =3D dwarf_tag(type_die); @@ -324,6 +344,11 @@ Dwarf_Die *die_get_pointer_type(Dwarf_Die *type_die, D= warf_Die *die_mem) tag !=3D DW_TAG_restrict_type && tag !=3D DW_TAG_volatile_type && tag !=3D DW_TAG_shared_type) return NULL; + if (++chase > MAX_TYPE_CHASE) { + pr_debug("DWARF: pointer type chase limit reached at DIE 0x%lx\n", + (unsigned long)dwarf_dieoffset(type_die)); + return NULL; + } type_die =3D die_get_type(type_die, die_mem); } while (type_die); =20 @@ -1118,17 +1143,25 @@ Dwarf_Die *die_find_member(Dwarf_Die *st_die, const= char *name, die_mem); } =20 -/** - * die_get_typename_from_type - Get the name of given type DIE - * @type_die: a type DIE - * @buf: a strbuf for result type name - * - * Get the name of @type_die and stores it to @buf. Return 0 if succeeded. - * and Return -ENOENT if failed to find type name. - * Note that the result will stores typedef name if possible, and stores - * "*(function_type)" if the type is a function pointer. +/* + * The name follows DW_AT_type, so a self-referring DIE makes this + * recurse forever: bound it like the chases above. */ -int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) +static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf= *buf, + int depth); + +static int __die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf, int d= epth) +{ + Dwarf_Die type; + + if (__die_get_real_type(vr_die, &type) =3D=3D NULL) + return -ENOENT; + + return __die_get_typename_from_type(&type, buf, depth); +} + +static int __die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf= *buf, + int depth) { int tag, ret; const char *tmp =3D ""; @@ -1155,7 +1188,12 @@ int die_get_typename_from_type(Dwarf_Die *type_die, = struct strbuf *buf) /* Write a base name */ return strbuf_addf(buf, "%s%s", tmp, name ?: ""); } - ret =3D die_get_typename(type_die, buf); + if (depth >=3D MAX_TYPE_CHASE) { + pr_debug("DWARF: type name recursion limit reached at DIE 0x%lx\n", + (unsigned long)dwarf_dieoffset(type_die)); + return -ENOENT; + } + ret =3D __die_get_typename(type_die, buf, depth + 1); if (ret < 0) { /* void pointer has no type attribute */ if (tag =3D=3D DW_TAG_pointer_type && ret =3D=3D -ENOENT) @@ -1166,6 +1204,21 @@ int die_get_typename_from_type(Dwarf_Die *type_die, = struct strbuf *buf) return strbuf_addstr(buf, tmp); } =20 +/** + * die_get_typename_from_type - Get the name of given type DIE + * @type_die: a type DIE + * @buf: a strbuf for result type name + * + * Get the name of @type_die and stores it to @buf. Return 0 if succeeded. + * and Return -ENOENT if failed to find type name. + * Note that the result will stores typedef name if possible, and stores + * "*(function_type)" if the type is a function pointer. + */ +int die_get_typename_from_type(Dwarf_Die *type_die, struct strbuf *buf) +{ + return __die_get_typename_from_type(type_die, buf, 0); +} + /** * die_get_typename - Get the name of given variable DIE * @vr_die: a variable DIE @@ -1178,12 +1231,7 @@ int die_get_typename_from_type(Dwarf_Die *type_die, = struct strbuf *buf) */ int die_get_typename(Dwarf_Die *vr_die, struct strbuf *buf) { - Dwarf_Die type; - - if (__die_get_real_type(vr_die, &type) =3D=3D NULL) - return -ENOENT; - - return die_get_typename_from_type(&type, buf); + return __die_get_typename(vr_die, buf, 0); } =20 /** --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 273D45293F4; Thu, 17 Sep 2026 15:56:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660587; cv=none; b=ilZPPfrMZ2bwmilRn1QTnWn9wEFP/iZWhdc4Qtke29YwAn/pQQrYXMoTfFNhyPrTpEuQUSJSdo05TKm4Ej26VawlCXgJ4+CM6nfZOSHENkg9xNLXbFueGJCRNuIyX2yAzAr7qpiI+2FySKGDKgeEpjioyxSU04EHV9BrgYYXHuA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660587; c=relaxed/simple; bh=iLULfbRqUOyy8sxLo0T8y0j5XmaveAsYQ7eAm5INT10=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aC6RXNkjTZoqeViZJM4sTnJ9pWv6elY2yWdYpcgHf8lW97o+u/qfNghs5LI1EIO4VvuTbuTQTuLIuGLT3ShJGM+3lE/AxHRSeBWKxlgDhb9uEtxeapHStsIxF9Xgy5FhwpX/bHSWlPvytE8A1xKllxBvHsLKrJIgQ1jPo80cdUo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=GAsrrf2K; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="GAsrrf2K" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0391D1F000FF; Thu, 17 Sep 2026 15:56:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660586; bh=tlvNEhVstLx2sb9EmNRJfJzlsNgRVw9d8I//uWSkWn8=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=GAsrrf2KSNfUfrtjJLYovrUuBYd579NmleID6r/5esI+DPx6iSA5ves8nctTIsJvK d/Q4wUdcmScSl2/wDtwY4lpje1OXJe8rm6coOxsa4+LNogurkkOy8V7ejLPPqZxI5Y 98PFWkM9p1PoibaCtYPllFYMZT9tiHESU05hncRN9jrSaczGdHZjAfpWYzHQfRCUIs us1EqZPudFBwuQmbuzTJzLZsqlVdt9jNjZ6KHmgO0KZpDlT3R9hnM+QypSEvx21xBZ p1AE0hDl5aqjzK6evFLIQG4nHABNt+X3B69QK3wI/8bdwzHvuB+h/vqGsZ5u40PIX4 WHfoImPnFtGtA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 11/15] perf dwarf-aux: Add die_same_file() and die_get_type_die() Date: Thu, 17 Sep 2026 12:55:22 -0300 Message-ID: <20260917155528.62607-12-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo dwarf_dieoffset() is relative to the file the DIE is in, so an offset saved by die_collect_vars()/die_collect_global_vars() is only meaningful in that file: add die_same_file(), comparing the Dwarf each DIE's CU belongs to, to record which file that is, and die_get_type_die() to resolve the offset in it, with the saved tag as a sanity check. Resolving the offset in the main file instead parses whatever is at it when it came from the dwz common file. dwarf_cu_getdwarf() is new in elfutils 0.160, so the libdw feature test probes for it and Makefile.config says 0.160. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/build/feature/test-libdw.c | 13 ++++++- tools/perf/Makefile.config | 2 +- tools/perf/util/dwarf-aux.c | 62 ++++++++++++++++++++++++++++++-- tools/perf/util/dwarf-aux.h | 17 +++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/tools/build/feature/test-libdw.c b/tools/build/feature/test-li= bdw.c index aabd63ca76b4d7e6..0ebe7bb4adf9d0bc 100644 --- a/tools/build/feature/test-libdw.c +++ b/tools/build/feature/test-libdw.c @@ -49,8 +49,19 @@ int test_elfutils(void) return 0; } =20 +/* + * dwarf_cu_getdwarf() needs elfutils 0.160: take its address only, so + * that older versions fail the probe instead of the link. + */ +int test_libdw_cu_getdwarf(void) +{ + void *sym =3D (void *)dwarf_cu_getdwarf; + + return sym =3D=3D NULL; +} + int main(void) { return test_libdw() + test_libdw_unwind() + test_libdw_getlocations() + - test_libdw_getcfi() + test_elfutils(); + test_libdw_getcfi() + test_libdw_cu_getdwarf() + test_elfutils(); } diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 4ee7393a39f91d85..0585d71e3182c6dd 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -470,7 +470,7 @@ else else ifneq ($(feature-libdw), 1) ifndef NO_LIBDW - $(warning No libdw.h found or old libdw.h found or elfutils is old= er than 0.157, disables dwarf support. Please install new elfutils-devel/li= bdw-dev) + $(warning No libdw.h found or old libdw.h found or elfutils is old= er than 0.160, disables dwarf support. Please install new elfutils-devel/li= bdw-dev) NO_LIBDW :=3D 1 endif endif # Dwarf support diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c index b5ffeea54446408d..de14d18a9d2f87d8 100644 --- a/tools/perf/util/dwarf-aux.c +++ b/tools/perf/util/dwarf-aux.c @@ -1680,6 +1680,16 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_d= ie, Dwarf_Addr addr, return result; } =20 +/* + * Whether two DIEs live in the same debug file, i.e. whether their + * offsets have to be resolved in the same file: dwarf_dieoffset() is + * relative to the file the DIE is in. + */ +bool die_same_file(Dwarf_Die *die_a, Dwarf_Die *die_b) +{ + return dwarf_cu_getdwarf(die_a->cu) =3D=3D dwarf_cu_getdwarf(die_b->cu); +} + static int __die_collect_vars_cb(Dwarf_Die *die_mem, void *arg) { struct die_var_type **var_types =3D arg; @@ -1724,6 +1734,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, = void *arg) vt->is_reg_var_addr =3D true; =20 vt->die_off =3D dwarf_dieoffset(&type_die); + vt->die_tag =3D dwarf_tag(&type_die); + vt->from_alt =3D !die_same_file(die_mem, &type_die); vt->addr =3D start; vt->end =3D end; vt->has_range =3D (end !=3D 0 || start !=3D 0); @@ -1743,7 +1755,8 @@ static int __die_collect_vars_cb(Dwarf_Die *die_mem, = void *arg) * * Save all variables and parameters in the @sc_die and save them to @var_= types. * The @var_types is a singly-linked list containing type and location inf= o. - * Actual type can be retrieved using dwarf_offdie() with 'die_off' later. + * Actual type can be retrieved using die_get_type_die() with 'die_off', + * 'die_tag' and 'from_alt' later. * * Callers should free @var_types. */ @@ -1789,6 +1802,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *di= e_mem, void *arg) return DIE_FIND_CB_END; =20 vt->die_off =3D dwarf_dieoffset(&type_die); + vt->die_tag =3D dwarf_tag(&type_die); + vt->from_alt =3D !die_same_file(die_mem, &type_die); vt->addr =3D ops->number; vt->end =3D 0; vt->has_range =3D false; @@ -1800,6 +1815,48 @@ static int __die_collect_global_vars_cb(Dwarf_Die *d= ie_mem, void *arg) return DIE_FIND_CB_SIBLING; } =20 +/** + * die_get_type_die - Get a type DIE saved by die_collect_vars() + * @dbg: the main debug info + * @die_off: offset of the type DIE, from dwarf_dieoffset() + * @die_tag: tag that DIE had when the offset was saved + * @from_alt: whether the type DIE is in the dwz alt file + * @die_mem: where to store the resulting DIE + * + * Resolve @die_off in the file it was recorded as belonging to: the + * offset is only meaningful there, and there is deliberately no + * fallback, as resolving an alt file offset in the main file parses + * whatever is at it. @die_tag is a sanity check. + */ +Dwarf_Die *die_get_type_die(Dwarf *dbg, u64 die_off, int die_tag, bool fro= m_alt, + Dwarf_Die *die_mem) +{ + Dwarf *target =3D dbg; + Dwarf_Die die; + + if (from_alt) { + /* + * No fallback to the main file: resolving an alt file offset in it + * does not fail, it parses whatever is there as a DIE. + */ + target =3D dwarf_getalt(dbg); + if (target =3D=3D NULL) { + pr_debug("DWARF: no alt (dwz) debug file to resolve the type DIE at off= set 0x%lx in\n", + (unsigned long)die_off); + return NULL; + } + } + + if (dwarf_offdie(target, die_off, &die) && dwarf_tag(&die) =3D=3D die_tag= ) { + *die_mem =3D die; + return die_mem; + } + + pr_debug("DWARF: no DIE with tag %d at offset 0x%lx in the %s debug file\= n", + die_tag, (unsigned long)die_off, from_alt ? "alt" : "main"); + return NULL; +} + /** * die_collect_global_vars - Save all global variables * @cu_die: a CU DIE @@ -1807,7 +1864,8 @@ static int __die_collect_global_vars_cb(Dwarf_Die *di= e_mem, void *arg) * * Save all global variables in the @cu_die and save them to @var_types. * The @var_types is a singly-linked list containing type and location inf= o. - * Actual type can be retrieved using dwarf_offdie() with 'die_off' later. + * Actual type can be retrieved using die_get_type_die() with 'die_off', + * 'die_tag' and 'from_alt' later. * * Callers should free @var_types. */ diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h index 161f0bf980b6ee6a..299ffddab0358baa 100644 --- a/tools/perf/util/dwarf-aux.h +++ b/tools/perf/util/dwarf-aux.h @@ -152,6 +152,8 @@ int die_get_scopes(Dwarf_Die *cu_die, Dwarf_Addr pc, Dw= arf_Die **scopes); struct die_var_type { struct die_var_type *next; u64 die_off; + int die_tag; + bool from_alt; /* die_off is relative to the alt (dwz) file */ u64 addr; u64 end; /* end address of location range */ int reg; @@ -183,6 +185,21 @@ Dwarf_Die *die_find_variable_by_addr(Dwarf_Die *sc_die= , Dwarf_Addr addr, /* Save all variables and parameters in this scope */ void die_collect_vars(Dwarf_Die *sc_die, struct die_var_type **var_types); =20 +/* + * Get the type DIE saved by die_collect_vars()/die_collect_global_vars(). + * + * Those save the dwarf_dieoffset() of the type DIE, which is relative + * to the file it lives in, so @from_alt, recorded when the offset was + * saved, says which file to resolve it in; @die_tag is a sanity check. + * Resolving an alt file offset in the main file parses whatever is at + * it, which is what hung 'perf report -s type'. + */ +Dwarf_Die *die_get_type_die(Dwarf *dbg, u64 die_off, int die_tag, bool fro= m_alt, + Dwarf_Die *die_mem); + +/* Whether two DIEs live in the same debug file */ +bool die_same_file(Dwarf_Die *die_a, Dwarf_Die *die_b); + /* Save all global variables in this CU */ void die_collect_global_vars(Dwarf_Die *cu_die, struct die_var_type **var_= types); =20 --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 13CD54F392D; Thu, 17 Sep 2026 15:56:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660593; cv=none; b=LPiduQJ/GV/79qQgr6/q5wBuZa52IUMhPGG8moTJzjxnO/DWF+pjk0cpZO5NkxkZEobkIaE5E82HgPz6UInuAZB44zl4uFSCbvRkB+lZzc1pA2jJB3exl1MpnzpMmaJ+cUEcx7hkVU6aeut2hDHtnDFcJXBxFHAWMZJSYc+tDcs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660593; c=relaxed/simple; bh=xxiNI9/2opfYfVGzSxTkvlXp/rdC3Wlpw3sG77B3pCI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RZ1axjL8FhgY1ENbmCGWw1DvA/b9tPAdIm/RO3/ZFwMp1EuhYoFoZvyYtcIjDXxmoM+jhU6KC3t8FyvwmABRrNZohRNYw9/dgM49MEsbFjOvhf4913YQk9700KqXCdkK0SBpSm1wnhc6/EYbNbtFrYTchpc59k+dY8NneEJB9aI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=eme4qT0L; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="eme4qT0L" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 98CDD1F00893; Thu, 17 Sep 2026 15:56:26 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660590; bh=aPv8hIl8pd1TTEMUwVRyvbk3noeKznEU2ge1GAQyBvY=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=eme4qT0L4RJshJfXk6SdVgfEkig58ZKG5XtvBKQX+A1YX00m2C9b1rF0BrCYDgA6W 33otxNfhQRBJv2IDeWMNuVptFbp07exybudPchfFmEsyFoowIKpiQEvFNQ2fer/fSR QHIVwXyRf3nBso/KGz70RrNepjo/1SGJV+0h8zDDTMEY86cV6Lu8+FmywBGVt6A9fC xvm0E0Re7NdnOVgPtj5DvwmxfdvepFDwTO/gbGpZc7jthdZFKo+n0Nt35OgFoK5fIL y7SgCbKKBRcAzi9nxxSH1eHI8zS5EzSEEHPkiABhac2yJrfqUVSmdVdFeB0iz+EKeQ gEN/P4s8Zm7pA== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 12/15] perf annotate-data: Resolve type DIEs in the debug file they came from Date: Thu, 17 Sep 2026 12:55:23 -0300 Message-ID: <20260917155528.62607-13-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo 'perf report -s type' hangs, burning all of a CPU with no output, when resolving a hist entry on the dwz compressed debug info of libz.so.1 (zlib-ng): die_collect_vars() saves the dwarf_dieoffset() of the type DIE, which is relative to the file it lives in - the dwz alt file for the types shared by more than one CU - and update_var_state() resolves that offset in the main debug file, where it parses whatever is at that offset, here a typedef whose DW_AT_type refers to itself, making the type chase spin forever. Record whether the type DIE came from the alt file and resolve the offset with die_get_type_die() in that file, with no fallback: resolving an alt file offset in the main file is exactly the misparse above. Fixes: 06b2ce75386df04b ("perf annotate-data: Maintain variable type info") Fixes: 55ee3d005d62279d ("perf annotate-data: Add a cache for global variab= le types") Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/annotate-data.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-dat= a.c index aff60a630fd05b01..2ad6d012e069c522 100644 --- a/tools/perf/util/annotate-data.c +++ b/tools/perf/util/annotate-data.c @@ -645,6 +645,8 @@ struct global_var_entry { u64 start; u64 end; u64 die_offset; + int die_tag; + bool from_alt; /* die_offset is relative to the alt (dwz) file */ }; =20 static int global_var_cmp(const void *_key, const struct rb_node *node) @@ -682,7 +684,7 @@ static struct global_var_entry *global_var__find(struct= data_loc_info *dloc, u64 } =20 static bool global_var__add(struct data_loc_info *dloc, u64 addr, - const char *name, Dwarf_Die *type_die) + const char *name, Dwarf_Die *type_die, bool from_alt) { struct dso *dso =3D map__dso(dloc->ms->map); struct global_var_entry *gvar; @@ -704,6 +706,8 @@ static bool global_var__add(struct data_loc_info *dloc,= u64 addr, gvar->start =3D addr; gvar->end =3D addr + size; gvar->die_offset =3D dwarf_dieoffset(type_die); + gvar->die_tag =3D dwarf_tag(type_die); + gvar->from_alt =3D from_alt; =20 rb_add(&gvar->node, dso__global_vars(dso), global_var_less); return true; @@ -778,12 +782,14 @@ static void global_var__collect(struct data_loc_info = *dloc) if (pos->reg !=3D -1) continue; =20 - if (!dwarf_offdie(dwarf, pos->die_off, &type_die)) + if (!die_get_type_die(dwarf, pos->die_off, pos->die_tag, + pos->from_alt, &type_die)) continue; =20 get_global_var_info(dloc, pos->addr, &var_name, &var_offset); =20 - global_var__add(dloc, pos->addr, var_name, &type_die); + global_var__add(dloc, pos->addr, var_name, &type_die, + pos->from_alt); } =20 delete_var_types(var_types); @@ -808,7 +814,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data= _loc_info *dloc, =20 gvar =3D global_var__find(dloc, var_addr); if (gvar) { - if (!dwarf_offdie(dloc->di->dbg, gvar->die_offset, type_die)) + if (!die_get_type_die(dloc->di->dbg, gvar->die_offset, + gvar->die_tag, gvar->from_alt, type_die)) return false; =20 *var_offset =3D var_addr - gvar->start; @@ -838,7 +845,8 @@ bool get_global_var_type(Dwarf_Die *cu_die, struct data= _loc_info *dloc, =20 ok: /* The address should point to the start of the variable */ - global_var__add(dloc, var_addr - *var_offset, var_name, type_die); + global_var__add(dloc, var_addr - *var_offset, var_name, type_die, + !die_same_file(cu_die, type_die)); return true; } =20 @@ -893,7 +901,8 @@ static void update_var_state(struct type_state *state, = struct data_loc_info *dlo continue; } /* Get the type DIE using the offset */ - if (!dwarf_offdie(dloc->di->dbg, var->die_off, &mem_die)) + if (!die_get_type_die(dloc->di->dbg, var->die_off, + var->die_tag, var->from_alt, &mem_die)) continue; =20 if (var->reg =3D=3D DWARF_REG_FB || var->reg =3D=3D fbreg || var->reg = =3D=3D state->stack_reg) { --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 458B5501F20; Thu, 17 Sep 2026 15:56:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660596; cv=none; b=gi/r6duVQZRQdjGeW0fSn6uoFfBY4VI9jCdMve87gd/lQiViCeoFQ8HeOYELlMEaT8fWXgxxv9tEwGpQoeDfB+/o89MsFi3lXa9A8LjulTSLPoU9rdIOsTBS2EgJWDg+AmZIzl2UvWg2nbpYKHF+0XcsgXDvgnqoTjn6FviU+iU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660596; c=relaxed/simple; bh=UYWT+oVqyny2eST03r7b17ygFPjoKUMwy/JevqymIrw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=OQcwzVRM85X6ZEdiefA01ggWLUryS/OIDlADHYW3Cb6kBp9A7T9bb8fjNoop4zylDEc41dxgnkE9AhuDRBUZ4/0gVCb1zNWRbxYtKawKrRC+m/1nV2B6CuCLMaD338F51+aCgorT0dMW8mRQYXgyaWD4cRwl4rtn41U/vIAiADw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Lf0RGRlA; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Lf0RGRlA" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 9C3381F00898; Thu, 17 Sep 2026 15:56:31 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660595; bh=oW+hSXQLm1tqoxb9Q6xu+I/QyZiQd7wLo2aepouAabg=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Lf0RGRlAwbxPAZWjFXhc9zFwxyp7RVMcTd4KTGO/vdOVIdkURMgePrx9BKpFJa28j ZWoZs9Hjth0EdU9A0hNPfK7W1PMlDNYkCwj/IqKDoQ3uv8fiJrRaUj+0Bw6uBjH/ts z435iQNUv4nSAtzw+3h8hDkbUuWrscoiWQpxGzQZqS4i8WkE1eR7HWBMzUYaZjF/d6 YFQv7lO1lXiJPGDpbM76cIyHgZgbCKeVKgphdeVvhjy+LzRHpUP9/3mT+UbhXXR3Da re5cdp2Oq/5M0M4/Y5N9u6BAmGiqvaPk1U85FsDPlVjRc4KNNbhjfb2UldWaEZp/l6 P0eVZzVSztvmg== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 13/15] perf annotate-data: Bound the member nesting recursion Date: Thu, 17 Sep 2026 12:55:24 -0300 Message-ID: <20260917155528.62607-14-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo Members are added recursively, and the same kind of broken DIE can make a member's type point back at one of its own ancestors, recursing until the stack is gone; nothing usable comes out of nesting members 8 deep anyway, so stop there, marking the member as truncated (reported by the JSON exporter added in a later series) and giving up on member types that don't resolve. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/util/annotate-data.c | 32 ++++++++++++++++++++++++++------ tools/perf/util/annotate-data.h | 3 +++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-dat= a.c index 2ad6d012e069c522..c67bb6005e23c516 100644 --- a/tools/perf/util/annotate-data.c +++ b/tools/perf/util/annotate-data.c @@ -221,6 +221,13 @@ static bool data_type_less(struct rb_node *node_a, con= st struct rb_node *node_b) return strcmp(a->self.type_name, b->self.type_name) < 0; } =20 +/* + * Members are added recursively; bound the nesting so that a broken + * type that points back at one of its own ancestors doesn't recurse + * until the stack is gone. + */ +#define MAX_MEMBER_DEPTH 8 + /* Recursively add new members for struct/union */ static int __add_member_cb(Dwarf_Die *die, void *arg) { @@ -235,6 +242,16 @@ static int __add_member_cb(Dwarf_Die *die, void *arg) if (dwarf_tag(die) !=3D DW_TAG_member) return DIE_FIND_CB_SIBLING; =20 + if (__die_get_real_type(die, &member_type) =3D=3D NULL) + return DIE_FIND_CB_SIBLING; + + if (dwarf_tag(&member_type) =3D=3D DW_TAG_typedef) { + if (die_get_real_type(&member_type, &die_mem) =3D=3D NULL) + return DIE_FIND_CB_SIBLING; + } else { + die_mem =3D member_type; + } + member =3D zalloc(sizeof(*member)); if (member =3D=3D NULL) return DIE_FIND_CB_END; @@ -242,12 +259,6 @@ static int __add_member_cb(Dwarf_Die *die, void *arg) strbuf_init(&sb, 32); die_get_typename(die, &sb); =20 - __die_get_real_type(die, &member_type); - if (dwarf_tag(&member_type) =3D=3D DW_TAG_typedef) - die_get_real_type(&member_type, &die_mem); - else - die_mem =3D member_type; - if (dwarf_aggregate_size(&die_mem, &size) < 0) size =3D 0; =20 @@ -289,10 +300,19 @@ static int __add_member_cb(Dwarf_Die *die, void *arg) } member->size =3D size; member->offset =3D loc + parent->offset; + member->depth =3D parent->depth + 1; INIT_LIST_HEAD(&member->children); list_add_tail(&member->node, &parent->children); =20 tag =3D dwarf_tag(&die_mem); + if (member->depth >=3D MAX_MEMBER_DEPTH) { + /* Reported by the JSON exporter so consumers can tell a truncated tree.= */ + member->truncated =3D true; + pr_debug_dtp("member nesting limit reached at %s\n", + member->type_name ?: "(unknown type)"); + return DIE_FIND_CB_SIBLING; + } + switch (tag) { case DW_TAG_structure_type: case DW_TAG_union_type: diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-dat= a.h index ca2096a9ee62cbfe..cc576232f55b5fb0 100644 --- a/tools/perf/util/annotate-data.h +++ b/tools/perf/util/annotate-data.h @@ -57,6 +57,9 @@ struct annotated_member { char *var_name; int offset; int size; + unsigned int depth; + /* Children not expanded because the nesting limit was reached */ + bool truncated; }; =20 /** --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 65BAF5678F2; Thu, 17 Sep 2026 15:56:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660599; cv=none; b=VqjbAQXsDOTjEJx1pLrVJTcTGGWKhFOv7OZa1tMnrrQY2t/Gfse9HjEVOlMKSwe2VJZ+gfGeodYXlxS9Op5OvOPJOKMoX9owaBTeR0rrGedq3Wld/pediWz7dF1ECH2Lu1GGYoxfRm+qR/lpEQJWadqIJYov7crcQabQdaZ/9b8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660599; c=relaxed/simple; bh=wxs2XxPjV4TsQRFaOZYnOMvLj8n/AHgTau/C8WqoWYA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YXfYVOYo4TOx3GZSXU7Li1bwcJTaa7Vt3t4r2MJkdMwet73R79Ee3A9IqYl4jARigQR5iPi6RT7dy8AletLpmKqjJtOrO8xKiwjQEWy/oRPteHsz4qW7rXzvufOOgXUBfKarpvHSd8OeTxnCZEFznuz4Dflr6v1qvY8j9MgaAw0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=TVV3uzih; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="TVV3uzih" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 8FF1A1F00893; Thu, 17 Sep 2026 15:56:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660598; bh=eLkJrvnAeHJo+bmYbI04lAs1t48D/OuOuWuJv/uf5x0=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=TVV3uzihdfQU8zcgpPMxe9ZwKQc9F6ev4ugXva2nvMCXinbZT7jarO2yuihcLSlDu gDvPcZ0ixlWBY0Ur04jjML//2e08ejGwImkAtTmxMxDEzYP+A98/BOLfSlHyluJTDh oy93A/zoymxeJmjMWNbsc5VHohEWfUrmA3Jwn+h7UP4hAgTKSRvoF689sFIEbzqKf7 atQkQqTAlsRgiDnYq+AtMFBPRGQBUELfZmlBrl8/FBF3yH0toFgRgIiavSlaLhlUw6 FTmPTzMWv2FTc2KpfNexjChimNf+2YkZoIR0WT9PsXMKaIWdh4ZeZ6lo33zrzeZHuX LSkDXej2+Mcaw== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 14/15] perf mem record: Request PERF_SAMPLE_CPU by default Date: Thu, 17 Sep 2026 12:55:25 -0300 Message-ID: <20260917155528.62607-15-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo The data type profiling per-sample stream keys cross-CPU contention on sample->cpu; without PERF_SAMPLE_CPU that field is the (u32)-1 "no CPU info" sentinel, so same-instance accesses from different cores are indistinguishable from same-CPU traffic. 'perf mem record' already passes -d (addr) and -W (weight) explicitly to the record parser, add --sample-cpu as well and document it in perf-mem(1). The rec_argv array only had room for nine arguments per PMU plus the user arguments, not counting the up to eight __cmd_record() adds itself, which a new argument would overflow on PMUs with separate load and store events; reserve space for the fixed arguments too. Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-mem.txt | 4 ++++ tools/perf/builtin-mem.c | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tools/perf/Documentation/perf-mem.txt b/tools/perf/Documentati= on/perf-mem.txt index 4d164836d0943119..fe51c5e3333dc4a0 100644 --- a/tools/perf/Documentation/perf-mem.txt +++ b/tools/perf/Documentation/perf-mem.txt @@ -14,6 +14,10 @@ DESCRIPTION ----------- "perf mem record" runs a command and gathers memory operation data from it, into perf.data. Perf record options are accepted and are passed t= hrough. +It also requests the address (-d), the weight (-W, where supported) and the +CPU id (--sample-cpu) of every sampled access by default; the CPU id is wh= at +lets per-sample analysis tell reads and writes to the same data from +different cores apart from same-CPU traffic. =20 "perf mem report" displays the result. It invokes perf report with the right set of options to display a memory access profile. By default, loads diff --git a/tools/perf/builtin-mem.c b/tools/perf/builtin-mem.c index 6101a26b3a781e69..f25592bc52728655 100644 --- a/tools/perf/builtin-mem.c +++ b/tools/perf/builtin-mem.c @@ -99,8 +99,8 @@ static int __cmd_record(int argc, const char **argv, stru= ct perf_mem *mem, argc =3D parse_options(argc, argv, options, record_usage, PARSE_OPT_KEEP_UNKNOWN); =20 - /* Max number of arguments multiplied by number of PMUs that can support = them. */ - rec_argc =3D argc + 9 * (perf_pmu__mem_events_num_mem_pmus(pmu) + 1); + /* Max number of arguments per PMU plus the fixed ones added below. */ + rec_argc =3D argc + 8 + 9 * (perf_pmu__mem_events_num_mem_pmus(pmu) + 1); =20 if (mem->cpu_list) rec_argc +=3D 2; @@ -135,6 +135,13 @@ static int __cmd_record(int argc, const char **argv, s= truct perf_mem *mem, =20 rec_argv[i++] =3D "-d"; =20 + /* + * The data type profiling per-sample stream keys cross-CPU contention + * on sample->cpu; without PERF_SAMPLE_CPU it is the (u32)-1 'no CPU + * info' sentinel. + */ + rec_argv[i++] =3D "--sample-cpu"; + if (mem->phys_addr) rec_argv[i++] =3D "--phys-data"; =20 --=20 2.55.0 From nobody Fri Sep 25 02:08:28 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7C331568FA0; Thu, 17 Sep 2026 15:56:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660602; cv=none; b=A2AjlV1OU9J7ersiRF+osXt6M1OGKoGrTUthkm34C4ZT6jzFqU/0OBeEaUUZj4L0N6LSAV/ttcR1oDfYeLKzDKvWhqbe2/+faIIkS+9wcddSJA0WEw/tkjSTEju0aSJv73ReH3biTyxxvD/tuHd+mVdD6+Y6gWW2fKQSmXMxdRA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789660602; c=relaxed/simple; bh=zHcxKbrunYVsvNyJ+cuY8XMv04mlJthI8lf4qJuxCvM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=etBROGwHzIDU6KOvz6Dp+scKHOzAyBVah8LHjhX5i7n+YFloQINdU9CrFp43At5t15D9ChI6qPIA6Fh3PXjrM0hEr7jlwuTBZBYPWj/5hV3Q4o8YTUvIw+MZPW52cLrcR3jS5sa8ulPK9Nu7we7vPpgycgaV9KES55WNFReM4sU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Ohd1aqAk; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Ohd1aqAk" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B3EE21F000FF; Thu, 17 Sep 2026 15:56:38 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1789660601; bh=T9/PygOUc5Sp8CyqFmmxdPTgMyswLof5Pol5Pa1EloE=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Ohd1aqAkQutlsIlLBI33fHp+tBCwNkBgf9MFHK3lQ5hMGgax2rGUkU3DKpDOjfKef aMbHigxeFWcBcmgPTwKcAYxlGJpD/9zfgquXi/0LnOYzYj8dlJvtsGdZW2oMrwzXJ1 T5AkSeQrKcj3+OkUkSiTVuYBpcKsifFOE34fYqea/G4J8hME9MCpmg58dDSA5GcOZ0 pFDX85ublTv0fiVhFnjDktp6pW1LZSI+FrLbcPPxf5R6n7mydOwB8z8mfqR2Nf76a3 83JmRdsfPMtXwlg8DHauMijqxzHhk0zm1Z17ae+QeLkVRVD39sJieFGqtJma0u3bEB 1xVyJ+w9XFgTQ== From: Arnaldo Carvalho de Melo To: Namhyung Kim Cc: Ingo Molnar , Thomas Gleixner , James Clark , Jiri Olsa , Ian Rogers , Adrian Hunter , Clark Williams , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, Arnaldo Carvalho de Melo Subject: [PATCH 15/15] perf mem record: Use the IBS swfilt filter when available Date: Thu, 17 Sep 2026 12:55:26 -0300 Message-ID: <20260917155528.62607-16-acme@kernel.org> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260917155528.62607-1-acme@kernel.org> References: <20260917155528.62607-1-acme@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Arnaldo Carvalho de Melo IBS events with exclude_{user,kernel} bits, as used for per-thread recording when kernel samples are not allowed, are rejected by the kernel on hardware without the privilege filter, so per-thread 'perf mem record' fails on AMD: $ perf mem record -o /dev/null -- true Error: Failure to open event 'ibs_op/ldlat=3D0/u' on PMU 'ibs_op' which will be = removed. Invalid event (ibs_op/ldlat=3D0/u) in per-thread mode, enable system wide= with '-a'. Kernel v6.14 added swfilt, a software privilege filter exposed as the 'swfilt' format term, making those events usable per-thread: $ perf record -e ibs_op/ldlat=3D0,swfilt=3D1/ -- true Give the ibs_op memory events a name variant with the swfilt term, used when the PMU exposes it, even when the record ends up without exclude bits: 'perf record' adds those bits itself when the first open fails, after the name was built, and that retry needs the term. This makes the per-thread 'perf mem record' the data type profiling shell test does work on AMD kernels with swfilt, and the test's event regex is adjusted for the added term. Suggested-by: Namhyung Kim Assisted-by: LLM Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/arch/x86/util/mem-events.c | 14 +++++++++++--- tools/perf/tests/shell/test_data_symbol.sh | 6 ++++-- tools/perf/util/mem-events.c | 18 ++++++++++++++---- tools/perf/util/mem-events.h | 2 ++ 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/tools/perf/arch/x86/util/mem-events.c b/tools/perf/arch/x86/ut= il/mem-events.c index b38f519020ff8c6f..a12d410247dfd47e 100644 --- a/tools/perf/arch/x86/util/mem-events.c +++ b/tools/perf/arch/x86/util/mem-events.c @@ -7,7 +7,10 @@ =20 #define MEM_LOADS_AUX 0x8203 =20 -#define E(t, n, s, l, a) { .tag =3D t, .name =3D n, .event_name =3D s, .ld= lat =3D l, .aux_event =3D a } +#define E_INIT(t, n, s, l, a, sf) { \ + .tag =3D t, .name =3D n, .event_name =3D s, .swfilt_name =3D sf, \ + .ldlat =3D l, .aux_event =3D a } +#define E(t, n, s, l, a) E_INIT(t, n, s, l, a, NULL) =20 struct perf_mem_event perf_mem_events_intel[PERF_MEM_EVENTS__MAX] =3D { E("ldlat-loads", "%s/mem-loads,ldlat=3D%u/P", "mem-loads", true, 0), @@ -21,14 +24,19 @@ struct perf_mem_event perf_mem_events_intel_aux[PERF_ME= M_EVENTS__MAX] =3D { E(NULL, NULL, NULL, false, 0), }; =20 +/* + * IBS events with exclude bits, as used for per-thread recording, are + * rejected without the swfilt software filter, so carry a name variant + * with the term, used when the PMU exposes it. + */ struct perf_mem_event perf_mem_events_amd[PERF_MEM_EVENTS__MAX] =3D { E(NULL, NULL, NULL, false, 0), E(NULL, NULL, NULL, false, 0), - E("mem-ldst", "%s//", NULL, false, 0), + E_INIT("mem-ldst", "%s//", NULL, false, 0, "%s/swfilt=3D1/"), }; =20 struct perf_mem_event perf_mem_events_amd_ldlat[PERF_MEM_EVENTS__MAX] =3D { E(NULL, NULL, NULL, false, 0), E(NULL, NULL, NULL, false, 0), - E("mem-ldst", "%s/ldlat=3D%u/", NULL, true, 0), + E_INIT("mem-ldst", "%s/ldlat=3D%u/", NULL, true, 0, "%s/ldlat=3D%u,swfilt= =3D1/"), }; diff --git a/tools/perf/tests/shell/test_data_symbol.sh b/tools/perf/tests/= shell/test_data_symbol.sh index d61b5659a46d9a77..52c837fddb639595 100755 --- a/tools/perf/tests/shell/test_data_symbol.sh +++ b/tools/perf/tests/shell/test_data_symbol.sh @@ -65,15 +65,17 @@ if (($is_amd >=3D 1)); then # --ldlat on AMD: # o Zen4 and earlier uarch does not support ldlat # o Even on supported platforms, it's disabled (--ldlat=3D0) by default. + # o Kernels with the swfilt term add it even when ldlat is not + # supported, so only check ldlat when the term is present. ldlat=3D${BASH_REMATCH[1]} - if [[ -n $ldlat ]]; then + if [[ $ldlat =3D=3D *ldlat=3D* ]]; then if ! [[ "$ldlat" =3D~ ldlat=3D0 ]]; then echo "ERROR: ldlat not initialized to 0?" exit 1 fi =20 mem_events=3D"$(perf mem record -v --ldlat=3D150 -e list 2>&1)" - if ! [[ "$mem_events" =3D~ ^mem-ldst.*ibs_op/ldlat=3D150/.*available ]];= then + if ! [[ "$mem_events" =3D~ ^mem-ldst.*ibs_op/ldlat=3D150[,/].*available = ]]; then echo "ERROR: --ldlat not honored?" exit 1 fi diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c index 0b49fce251fcc184..f45598aa89825d79 100644 --- a/tools/perf/util/mem-events.c +++ b/tools/perf/util/mem-events.c @@ -82,6 +82,7 @@ static const char *perf_pmu__mem_events_name(struct perf_= pmu *pmu, int i, char *buf, size_t buf_size) { struct perf_mem_event *e; + const char *name; =20 if (i >=3D PERF_MEM_EVENTS__MAX || !pmu) return NULL; @@ -90,24 +91,33 @@ static const char *perf_pmu__mem_events_name(struct per= f_pmu *pmu, int i, if (!e || !e->name) return NULL; =20 + /* + * Use the swfilt variant when the PMU exposes the term: 'perf record' + * adds exclude bits itself when the first open fails, after this name + * was built, and that retry needs the term in the name. + */ + name =3D e->name; + if (e->swfilt_name && perf_pmu__has_format(pmu, "swfilt")) + name =3D e->swfilt_name; + if (i =3D=3D PERF_MEM_EVENTS__LOAD || i =3D=3D PERF_MEM_EVENTS__LOAD_STOR= E) { if (e->ldlat) { if (!e->aux_event) { /* ARM and Most of Intel */ scnprintf(buf, buf_size, - e->name, pmu->name, + name, pmu->name, perf_mem_events__loads_ldlat); } else { /* Intel with mem-loads-aux event */ scnprintf(buf, buf_size, - e->name, pmu->name, pmu->name, + name, pmu->name, pmu->name, perf_mem_events__loads_ldlat); } } else { if (!e->aux_event) { /* AMD and POWER */ scnprintf(buf, buf_size, - e->name, pmu->name); + name, pmu->name); } else { return NULL; } @@ -117,7 +127,7 @@ static const char *perf_pmu__mem_events_name(struct per= f_pmu *pmu, int i, =20 if (i =3D=3D PERF_MEM_EVENTS__STORE) { scnprintf(buf, buf_size, - e->name, pmu->name); + name, pmu->name); return buf; } =20 diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h index 5b98076904b0b689..41f628fad10709b9 100644 --- a/tools/perf/util/mem-events.h +++ b/tools/perf/util/mem-events.h @@ -11,6 +11,8 @@ struct perf_mem_event { u32 aux_event; const char *tag; const char *name; + /* Name with the swfilt software privilege filter, when supported. */ + const char *swfilt_name; const char *event_name; }; =20 --=20 2.55.0