From nobody Thu Oct 2 02:15:09 2025 Received: from mail-pj1-f74.google.com (mail-pj1-f74.google.com [209.85.216.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9B16229C343 for ; Mon, 29 Sep 2025 19:09:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.74 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759172965; cv=none; b=Ua+w6pJIWzOw9mGHj4nN61D++ih7UaQgVh4FsqpWeoMdk9+VgaRzLIbhGLJOsU9kiuVpRaf/A2KdYpG97c7tbDZ39hn/2zJM/5BMgWpreSDGnWLLtQE1f5oFTbE2QVLPUFkMflKhYhUvD5eQ/cYdtE+e/9ZUc8vpbLSQSdx+yDg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759172965; c=relaxed/simple; bh=1iLYUYb8ouvZnn/UKIENKYunRTLk9SlEBZxwfkYQBNQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Content-Type; b=lBXDRxAGHrvVEfS9tV0FExtrjBBiLSt+N9f9GxGEDKeEF7jUrxCNg8PcoZQ8HUiPZ+x3g3bN911gYWLb5w+Tt5ziSmHtCfnQUn95JFIDIEjEBdMrTp5wYcrF/qm9UdtmKIJBsYtIUyG49G3AQKVqdjteLsV0lnQEUWNEKiK/xAA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=0KemWLKi; arc=none smtp.client-ip=209.85.216.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="0KemWLKi" Received: by mail-pj1-f74.google.com with SMTP id 98e67ed59e1d1-334b0876195so5077260a91.1 for ; Mon, 29 Sep 2025 12:09:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1759172962; x=1759777762; darn=vger.kernel.org; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :from:to:cc:subject:date:message-id:reply-to; bh=mk8LHZnikQ82ewf8h3KEcwFqyB8K5toDN0BOHpgdtJs=; b=0KemWLKi0hPuQBG/NPKjY38Kb97RgcZvHdfad0x6wLRCTkfhgJVsci+x37+iT4vB6D k0hx25opDeUbPx2e2tHU+PzxkEvwaCFNWQktSC7l9EfNL7kzEKBoT9aDstnLlQyJdqIO WK/Rtu4K4xA+TW533kbawGpY5SpLzOTNU0JO0L5roFTkdVKi3tc7T53dA+OQYMigXB8X beHlN9qbImGSOXcR5ohU23oSyfTbwyDfWiCkv8KLjQ6eJJNOfpixIKdo9B+kTS0mI1nR 1e467HDJxneB/D77CKAKXkJdCH1EXbDOl+fgLEmS6r1uGJgHeAXf4dPySWg0qefylgGB yjLg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1759172962; x=1759777762; h=to:from:subject:message-id:references:mime-version:in-reply-to:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=mk8LHZnikQ82ewf8h3KEcwFqyB8K5toDN0BOHpgdtJs=; b=ABSydZD4wfKHSycFkKNwCygVSLuaWTwWswRqF6wZ3EYFBLw86P1Tt6ua94pPr0EyLc ZtqQ0wvMoPfbV8oKKTvcHm8REFQSBeWg/ONa5hEAYrrcR6j46N/dbBlgae9uW+KBNo/j t9zTWfaDvw1U7I37JpkOXye4s0tEF5b0+/XCcw5kEVot/pwbM78tlwsOEl/kpi616UFg IyhnWw55Sha4aNROxnXPn7eKXBi2Zuy4wa7k7Ihxr8KzXYGriaPyYlsVaeuGLBWH7131 AT4TpmcZxkqsxWhM7qM1oIat2MQdTKPOfZGlhKTHXESPLQmiy6Q96rki3M/k9rUncZH9 24kg== X-Forwarded-Encrypted: i=1; AJvYcCUEeDUsdfSdc5vwDLelCK3hywhjk3kYvN1ILJBysBCtB6X2D34+HRklTjxQqziBgzAObv1qCp+OH4mH5qU=@vger.kernel.org X-Gm-Message-State: AOJu0Yyn0YirhUrcdw3et2jDloMlcpsxQlEcRJw3ZLeiLA/RP9Wmtwen Pfvg88B4Kj57hrSuHp/4jPSuxggsy2ymqwzXpwKoxtVZEKZJhUhFjMOl10KQ7JNRSDLmlEIdbPF gx6dl4xVGNQ== X-Google-Smtp-Source: AGHT+IFJsPmVvCx4+OEHNEgB278DZ/E7yNqKaqoP/lPuYE5QqWPfRjE57/jvUw93LiCw7cWJPDB3rHYUkAZ3 X-Received: from pjbcx11.prod.google.com ([2002:a17:90a:fd8b:b0:327:c20a:364]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a17:90b:384e:b0:332:6356:86b6 with SMTP id 98e67ed59e1d1-3342a28ab34mr18926159a91.9.1759172961933; Mon, 29 Sep 2025 12:09:21 -0700 (PDT) Date: Mon, 29 Sep 2025 12:08:04 -0700 In-Reply-To: <20250929190805.201446-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20250929190805.201446-1-irogers@google.com> X-Mailer: git-send-email 2.51.0.570.gb178f27e6d-goog Message-ID: <20250929190805.201446-15-irogers@google.com> Subject: [PATCH v6 14/15] perf srcline: Fallback between addr2line implementations From: Ian Rogers To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Namhyung Kim , Mark Rutland , Alexander Shishkin , Jiri Olsa , Ian Rogers , Adrian Hunter , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt , Charlie Jenkins , Eric Biggers , "Masami Hiramatsu (Google)" , James Clark , Collin Funk , "Dr. David Alan Gilbert" , Li Huafei , Athira Rajeev , Stephen Brennan , Dmitry Vyukov , Alexandre Ghiti , Haibo Xu , Andi Kleen , linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, bpf@vger.kernel.org, llvm@lists.linux.dev, Song Liu Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Factor the addr2line function implementation into separate source files (addr2line.[ch]) and rename the addr2line function cmd__addr2line. In srcline replace the ifdef-ed addr2line implementations with one that first tries the llvm__addr2line implementation, then the deprecated libbfd__addr2line function and on failure uses cmd__addr2line. If HAVE_LIBLLVM_SUPPORT is enabled the llvm__addr2line will execute against the libLLVM.so it is linked against. If HAVE_LIBLLVM_DYNAMIC is enabled then libperf-llvm.so (that links against libLLVM.so) will be dlopened. If the dlopen succeeds then the behavior should match HAVE_LIBLLVM_SUPPORT. On failure cmd__addr2line is used. The dlopen is only tried once. If HAVE_LIBLLVM_DYNAMIC isn't enabled then llvm__addr2line immediately fails and cmd__addr2line is used. Clean up the dso__free_a2l logic, which is only needed in the non-LLVM version and moved to addr2line.c. Signed-off-by: Ian Rogers --- tools/perf/util/Build | 1 + tools/perf/util/addr2line.c | 439 ++++++++++++++++++++++++++++++++ tools/perf/util/addr2line.h | 20 ++ tools/perf/util/config.c | 2 +- tools/perf/util/llvm.c | 5 - tools/perf/util/llvm.h | 3 - tools/perf/util/srcline.c | 495 ++---------------------------------- tools/perf/util/srcline.h | 1 - 8 files changed, 485 insertions(+), 481 deletions(-) create mode 100644 tools/perf/util/addr2line.c create mode 100644 tools/perf/util/addr2line.h diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 98d0a673fb53..684556986e64 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -2,6 +2,7 @@ include $(srctree)/tools/scripts/Makefile.include include $(srctree)/tools/scripts/utilities.mak =20 perf-util-y +=3D arm64-frame-pointer-unwind-support.o +perf-util-y +=3D addr2line.o perf-util-y +=3D addr_location.o perf-util-y +=3D annotate.o perf-util-y +=3D block-info.o diff --git a/tools/perf/util/addr2line.c b/tools/perf/util/addr2line.c new file mode 100644 index 000000000000..f2d94a3272d7 --- /dev/null +++ b/tools/perf/util/addr2line.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "addr2line.h" +#include "debug.h" +#include "dso.h" +#include "string2.h" +#include "srcline.h" +#include "symbol.h" +#include "symbol_conf.h" + +#include +#include +#include + +#include +#include +#include +#include + +#define MAX_INLINE_NEST 1024 + +/* If addr2line doesn't return data for 1 second then timeout. */ +int addr2line_timeout_ms =3D 1 * 1000; + +static int filename_split(char *filename, unsigned int *line_nr) +{ + char *sep; + + sep =3D strchr(filename, '\n'); + if (sep) + *sep =3D '\0'; + + if (!strcmp(filename, "??:0")) + return 0; + + sep =3D strchr(filename, ':'); + if (sep) { + *sep++ =3D '\0'; + *line_nr =3D strtoul(sep, NULL, 0); + return 1; + } + pr_debug("addr2line missing ':' in filename split\n"); + return 0; +} + +static void addr2line_subprocess_cleanup(struct child_process *a2l) +{ + if (a2l->pid !=3D -1) { + kill(a2l->pid, SIGKILL); + finish_command(a2l); /* ignore result, we don't care */ + a2l->pid =3D -1; + close(a2l->in); + close(a2l->out); + } + + free(a2l); +} + +static struct child_process *addr2line_subprocess_init(const char *addr2li= ne_path, + const char *binary_path) +{ + const char *argv[] =3D { + addr2line_path ?: "addr2line", + "-e", binary_path, + "-a", "-i", "-f", NULL + }; + struct child_process *a2l =3D zalloc(sizeof(*a2l)); + int start_command_status =3D 0; + + if (a2l =3D=3D NULL) { + pr_err("Failed to allocate memory for addr2line"); + return NULL; + } + + a2l->pid =3D -1; + a2l->in =3D -1; + a2l->out =3D -1; + a2l->no_stderr =3D 1; + + a2l->argv =3D argv; + start_command_status =3D start_command(a2l); + a2l->argv =3D NULL; /* it's not used after start_command; avoid dangling = pointers */ + + if (start_command_status !=3D 0) { + pr_warning("could not start addr2line (%s) for %s: start_command return = code %d\n", + addr2line_path, binary_path, start_command_status); + addr2line_subprocess_cleanup(a2l); + return NULL; + } + + return a2l; +} + +enum a2l_style { + BROKEN, + GNU_BINUTILS, + LLVM, +}; + +static enum a2l_style addr2line_configure(struct child_process *a2l, const= char *dso_name) +{ + static bool cached; + static enum a2l_style style; + + if (!cached) { + char buf[128]; + struct io io; + int ch; + int lines; + + if (write(a2l->in, ",\n", 2) !=3D 2) + return BROKEN; + + io__init(&io, a2l->out, buf, sizeof(buf)); + ch =3D io__get_char(&io); + if (ch =3D=3D ',') { + style =3D LLVM; + cached =3D true; + lines =3D 1; + pr_debug3("Detected LLVM addr2line style\n"); + } else if (ch =3D=3D '0') { + style =3D GNU_BINUTILS; + cached =3D true; + lines =3D 3; + pr_debug3("Detected binutils addr2line style\n"); + } else { + if (!symbol_conf.disable_add2line_warn) { + char *output =3D NULL; + size_t output_len; + + io__getline(&io, &output, &output_len); + pr_warning("%s %s: addr2line configuration failed\n", + __func__, dso_name); + pr_warning("\t%c%s", ch, output); + } + pr_debug("Unknown/broken addr2line style\n"); + return BROKEN; + } + while (lines) { + ch =3D io__get_char(&io); + if (ch <=3D 0) + break; + if (ch =3D=3D '\n') + lines--; + } + /* Ignore SIGPIPE in the event addr2line exits. */ + signal(SIGPIPE, SIG_IGN); + } + return style; +} + +static int read_addr2line_record(struct io *io, + enum a2l_style style, + const char *dso_name, + u64 addr, + bool first, + char **function, + char **filename, + unsigned int *line_nr) +{ + /* + * Returns: + * -1 =3D=3D> error + * 0 =3D=3D> sentinel (or other ill-formed) record read + * 1 =3D=3D> a genuine record read + */ + char *line =3D NULL; + size_t line_len =3D 0; + unsigned int dummy_line_nr =3D 0; + int ret =3D -1; + + if (function !=3D NULL) + zfree(function); + + if (filename !=3D NULL) + zfree(filename); + + if (line_nr !=3D NULL) + *line_nr =3D 0; + + /* + * Read the first line. Without an error this will be: + * - for the first line an address like 0x1234, + * - the binutils sentinel 0x0000000000000000, + * - the llvm-addr2line the sentinel ',' character, + * - the function name line for an inlined function. + */ + if (io__getline(io, &line, &line_len) < 0 || !line_len) + goto error; + + pr_debug3("%s %s: addr2line read address for sentinel: %s", __func__, dso= _name, line); + if (style =3D=3D LLVM && line_len =3D=3D 2 && line[0] =3D=3D ',') { + /* Found the llvm-addr2line sentinel character. */ + zfree(&line); + return 0; + } else if (style =3D=3D GNU_BINUTILS && (!first || addr !=3D 0)) { + int zero_count =3D 0, non_zero_count =3D 0; + /* + * Check for binutils sentinel ignoring it for the case the + * requested address is 0. + */ + + /* A given address should always start 0x. */ + if (line_len >=3D 2 || line[0] !=3D '0' || line[1] !=3D 'x') { + for (size_t i =3D 2; i < line_len; i++) { + if (line[i] =3D=3D '0') + zero_count++; + else if (line[i] !=3D '\n') + non_zero_count++; + } + if (!non_zero_count) { + int ch; + + if (first && !zero_count) { + /* Line was erroneous just '0x'. */ + goto error; + } + /* + * Line was 0x0..0, the sentinel for binutils. Remove + * the function and filename lines. + */ + zfree(&line); + do { + ch =3D io__get_char(io); + } while (ch > 0 && ch !=3D '\n'); + do { + ch =3D io__get_char(io); + } while (ch > 0 && ch !=3D '\n'); + return 0; + } + } + } + /* Read the second function name line (if inline data then this is the fi= rst line). */ + if (first && (io__getline(io, &line, &line_len) < 0 || !line_len)) + goto error; + + pr_debug3("%s %s: addr2line read line: %s", __func__, dso_name, line); + if (function !=3D NULL) + *function =3D strdup(strim(line)); + + zfree(&line); + line_len =3D 0; + + /* Read the third filename and line number line. */ + if (io__getline(io, &line, &line_len) < 0 || !line_len) + goto error; + + pr_debug3("%s %s: addr2line filename:number : %s", __func__, dso_name, li= ne); + if (filename_split(line, line_nr =3D=3D NULL ? &dummy_line_nr : line_nr) = =3D=3D 0 && + style =3D=3D GNU_BINUTILS) { + ret =3D 0; + goto error; + } + + if (filename !=3D NULL) + *filename =3D strdup(line); + + zfree(&line); + line_len =3D 0; + + return 1; + +error: + free(line); + if (function !=3D NULL) + zfree(function); + if (filename !=3D NULL) + zfree(filename); + return ret; +} + +static int inline_list__append_record(struct dso *dso, + struct inline_node *node, + struct symbol *sym, + const char *function, + const char *filename, + unsigned int line_nr) +{ + struct symbol *inline_sym =3D new_inline_sym(dso, sym, function); + + return inline_list__append(inline_sym, srcline_from_fileline(filename, li= ne_nr), node); +} + +int cmd__addr2line(const char *dso_name, u64 addr, + char **file, unsigned int *line_nr, + struct dso *dso, + bool unwind_inlines, + struct inline_node *node, + struct symbol *sym __maybe_unused) +{ + struct child_process *a2l =3D dso__a2l(dso); + char *record_function =3D NULL; + char *record_filename =3D NULL; + unsigned int record_line_nr =3D 0; + int record_status =3D -1; + int ret =3D 0; + size_t inline_count =3D 0; + int len; + char buf[128]; + ssize_t written; + struct io io =3D { .eof =3D false }; + enum a2l_style a2l_style; + + if (!a2l) { + if (!filename__has_section(dso_name, ".debug_line")) + goto out; + + dso__set_a2l(dso, + addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name)); + a2l =3D dso__a2l(dso); + } + + if (a2l =3D=3D NULL) { + if (!symbol_conf.disable_add2line_warn) + pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_n= ame); + goto out; + } + a2l_style =3D addr2line_configure(a2l, dso_name); + if (a2l_style =3D=3D BROKEN) + goto out; + + /* + * Send our request and then *deliberately* send something that can't be + * interpreted as a valid address to ask addr2line about (namely, + * ","). This causes addr2line to first write out the answer to our + * request, in an unbounded/unknown number of records, and then to write + * out the lines "0x0...0", "??" and "??:0", for GNU binutils, or "," + * for llvm-addr2line, so that we can detect when it has finished giving + * us anything useful. + */ + len =3D snprintf(buf, sizeof(buf), "%016"PRIx64"\n,\n", addr); + written =3D len > 0 ? write(a2l->in, buf, len) : -1; + if (written !=3D len) { + if (!symbol_conf.disable_add2line_warn) + pr_warning("%s %s: could not send request\n", __func__, dso_name); + goto out; + } + io__init(&io, a2l->out, buf, sizeof(buf)); + io.timeout_ms =3D addr2line_timeout_ms; + switch (read_addr2line_record(&io, a2l_style, dso_name, addr, /*first=3D*= /true, + &record_function, &record_filename, &record_line_nr)) { + case -1: + if (!symbol_conf.disable_add2line_warn) + pr_warning("%s %s: could not read first record\n", __func__, dso_name); + goto out; + case 0: + /* + * The first record was invalid, so return failure, but first + * read another record, since we sent a sentinel ',' for the + * sake of detected the last inlined function. Treat this as the + * first of a record as the ',' generates a new start with GNU + * binutils, also force a non-zero address as we're no longer + * reading that record. + */ + switch (read_addr2line_record(&io, a2l_style, dso_name, + /*addr=3D*/1, /*first=3D*/true, + NULL, NULL, NULL)) { + case -1: + if (!symbol_conf.disable_add2line_warn) + pr_warning("%s %s: could not read sentinel record\n", + __func__, dso_name); + break; + case 0: + /* The sentinel as expected. */ + break; + default: + if (!symbol_conf.disable_add2line_warn) + pr_warning("%s %s: unexpected record instead of sentinel", + __func__, dso_name); + break; + } + goto out; + default: + /* First record as expected. */ + break; + } + + if (file) { + *file =3D strdup(record_filename); + ret =3D 1; + } + if (line_nr) + *line_nr =3D record_line_nr; + + if (unwind_inlines) { + if (node && inline_list__append_record(dso, node, sym, + record_function, + record_filename, + record_line_nr)) { + ret =3D 0; + goto out; + } + } + + /* + * We have to read the records even if we don't care about the inline + * info. This isn't the first record and force the address to non-zero + * as we're reading records beyond the first. + */ + while ((record_status =3D read_addr2line_record(&io, + a2l_style, + dso_name, + /*addr=3D*/1, + /*first=3D*/false, + &record_function, + &record_filename, + &record_line_nr)) =3D=3D 1) { + if (unwind_inlines && node && inline_count++ < MAX_INLINE_NEST) { + if (inline_list__append_record(dso, node, sym, + record_function, + record_filename, + record_line_nr)) { + ret =3D 0; + goto out; + } + ret =3D 1; /* found at least one inline frame */ + } + } + +out: + free(record_function); + free(record_filename); + if (io.eof) { + dso__set_a2l(dso, NULL); + addr2line_subprocess_cleanup(a2l); + } + return ret; +} + +void dso__free_a2l(struct dso *dso) +{ + struct child_process *a2l =3D dso__a2l(dso); + + if (!a2l) + return; + + addr2line_subprocess_cleanup(a2l); + + dso__set_a2l(dso, NULL); +} diff --git a/tools/perf/util/addr2line.h b/tools/perf/util/addr2line.h new file mode 100644 index 000000000000..d35a47ba8dab --- /dev/null +++ b/tools/perf/util/addr2line.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PERF_ADDR2LINE_H +#define __PERF_ADDR2LINE_H + +#include + +struct dso; +struct inline_node; +struct symbol; + +extern int addr2line_timeout_ms; + +int cmd__addr2line(const char *dso_name, u64 addr, + char **file, unsigned int *line_nr, + struct dso *dso, + bool unwind_inlines, + struct inline_node *node, + struct symbol *sym); + +#endif /* __PERF_ADDR2LINE_H */ diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index ae72b66b6ded..6f914620c6ff 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c @@ -19,7 +19,7 @@ #include "util/hist.h" /* perf_hist_config */ #include "util/stat.h" /* perf_stat__set_big_num */ #include "util/evsel.h" /* evsel__hw_names, evsel__use_bpf_counters */ -#include "util/srcline.h" /* addr2line_timeout_ms */ +#include "util/addr2line.h" /* addr2line_timeout_ms */ #include "build-id.h" #include "debug.h" #include "config.h" diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c index 1607364ee736..cacb510c6814 100644 --- a/tools/perf/util/llvm.c +++ b/tools/perf/util/llvm.c @@ -303,11 +303,6 @@ int llvm__addr2line(const char *dso_name __maybe_unuse= d, u64 addr __maybe_unused return num_frames; } =20 -void dso__free_a2l_llvm(struct dso *dso __maybe_unused) -{ - /* Nothing to free. */ -} - /* * Whenever LLVM wants to resolve an address into a symbol, it calls this * callback. We don't ever actually _return_ anything (in particular, beca= use diff --git a/tools/perf/util/llvm.h b/tools/perf/util/llvm.h index 8aa19bb6b068..57f6bafb24bb 100644 --- a/tools/perf/util/llvm.h +++ b/tools/perf/util/llvm.h @@ -15,9 +15,6 @@ int llvm__addr2line(const char *dso_name, u64 addr, bool unwind_inlines, struct inline_node *node, struct symbol *sym); =20 - -void dso__free_a2l_llvm(struct dso *dso); - int symbol__disassemble_llvm(const char *filename, struct symbol *sym, struct annotate_args *args); =20 diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c index 23b942d4729e..27c0966611ab 100644 --- a/tools/perf/util/srcline.c +++ b/tools/perf/util/srcline.c @@ -1,30 +1,15 @@ // SPDX-License-Identifier: GPL-2.0 -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "util/dso.h" -#include "util/debug.h" -#include "util/callchain.h" -#include "util/symbol_conf.h" -#include "llvm.h" #include "srcline.h" -#include "string2.h" +#include "addr2line.h" +#include "dso.h" +#include "callchain.h" +#include "libbfd.h" +#include "llvm.h" #include "symbol.h" -#include "subcmd/run-command.h" =20 -/* If addr2line doesn't return data for 1 second then timeout. */ -int addr2line_timeout_ms =3D 1 * 1000; +#include +#include + bool srcline_full_filename; =20 char *srcline__unknown =3D (char *)"??:0"; @@ -129,458 +114,23 @@ struct symbol *new_inline_sym(struct dso *dso, return inline_sym; } =20 -#ifdef HAVE_LIBLLVM_SUPPORT -#include "llvm.h" - -static int addr2line(const char *dso_name, u64 addr, - char **file, unsigned int *line, struct dso *dso, - bool unwind_inlines, struct inline_node *node, - struct symbol *sym) -{ - return llvm__addr2line(dso_name, addr, file, line, dso, unwind_inlines, n= ode, sym); -} - -void dso__free_a2l(struct dso *dso) -{ - dso__free_a2l_llvm(dso); -} -#elif defined(HAVE_LIBBFD_SUPPORT) -#include "libbfd.h" - -static int addr2line(const char *dso_name, u64 addr, - char **file, unsigned int *line, struct dso *dso, - bool unwind_inlines, struct inline_node *node, +static int addr2line(const char *dso_name, u64 addr, char **file, unsigned= int *line_nr, + struct dso *dso, bool unwind_inlines, struct inline_node *node, struct symbol *sym) { - return libbfd__addr2line(dso_name, addr, file, line, dso, unwind_inlines,= node, sym); -} - -void dso__free_a2l(struct dso *dso) -{ - dso__free_a2l_libbfd(dso); -} - -#else /* HAVE_LIBBFD_SUPPORT */ - -static int filename_split(char *filename, unsigned int *line_nr) -{ - char *sep; - - sep =3D strchr(filename, '\n'); - if (sep) - *sep =3D '\0'; - - if (!strcmp(filename, "??:0")) - return 0; - - sep =3D strchr(filename, ':'); - if (sep) { - *sep++ =3D '\0'; - *line_nr =3D strtoul(sep, NULL, 0); - return 1; - } - pr_debug("addr2line missing ':' in filename split\n"); - return 0; -} - -static void addr2line_subprocess_cleanup(struct child_process *a2l) -{ - if (a2l->pid !=3D -1) { - kill(a2l->pid, SIGKILL); - finish_command(a2l); /* ignore result, we don't care */ - a2l->pid =3D -1; - close(a2l->in); - close(a2l->out); - } - - free(a2l); -} - -static struct child_process *addr2line_subprocess_init(const char *addr2li= ne_path, - const char *binary_path) -{ - const char *argv[] =3D { - addr2line_path ?: "addr2line", - "-e", binary_path, - "-a", "-i", "-f", NULL - }; - struct child_process *a2l =3D zalloc(sizeof(*a2l)); - int start_command_status =3D 0; - - if (a2l =3D=3D NULL) { - pr_err("Failed to allocate memory for addr2line"); - return NULL; - } - - a2l->pid =3D -1; - a2l->in =3D -1; - a2l->out =3D -1; - a2l->no_stderr =3D 1; - - a2l->argv =3D argv; - start_command_status =3D start_command(a2l); - a2l->argv =3D NULL; /* it's not used after start_command; avoid dangling = pointers */ - - if (start_command_status !=3D 0) { - pr_warning("could not start addr2line (%s) for %s: start_command return = code %d\n", - addr2line_path, binary_path, start_command_status); - addr2line_subprocess_cleanup(a2l); - return NULL; - } - - return a2l; -} - -enum a2l_style { - BROKEN, - GNU_BINUTILS, - LLVM, -}; - -static enum a2l_style addr2line_configure(struct child_process *a2l, const= char *dso_name) -{ - static bool cached; - static enum a2l_style style; - - if (!cached) { - char buf[128]; - struct io io; - int ch; - int lines; - - if (write(a2l->in, ",\n", 2) !=3D 2) - return BROKEN; - - io__init(&io, a2l->out, buf, sizeof(buf)); - ch =3D io__get_char(&io); - if (ch =3D=3D ',') { - style =3D LLVM; - cached =3D true; - lines =3D 1; - pr_debug3("Detected LLVM addr2line style\n"); - } else if (ch =3D=3D '0') { - style =3D GNU_BINUTILS; - cached =3D true; - lines =3D 3; - pr_debug3("Detected binutils addr2line style\n"); - } else { - if (!symbol_conf.disable_add2line_warn) { - char *output =3D NULL; - size_t output_len; - - io__getline(&io, &output, &output_len); - pr_warning("%s %s: addr2line configuration failed\n", - __func__, dso_name); - pr_warning("\t%c%s", ch, output); - } - pr_debug("Unknown/broken addr2line style\n"); - return BROKEN; - } - while (lines) { - ch =3D io__get_char(&io); - if (ch <=3D 0) - break; - if (ch =3D=3D '\n') - lines--; - } - /* Ignore SIGPIPE in the event addr2line exits. */ - signal(SIGPIPE, SIG_IGN); - } - return style; -} - -static int read_addr2line_record(struct io *io, - enum a2l_style style, - const char *dso_name, - u64 addr, - bool first, - char **function, - char **filename, - unsigned int *line_nr) -{ - /* - * Returns: - * -1 =3D=3D> error - * 0 =3D=3D> sentinel (or other ill-formed) record read - * 1 =3D=3D> a genuine record read - */ - char *line =3D NULL; - size_t line_len =3D 0; - unsigned int dummy_line_nr =3D 0; - int ret =3D -1; - - if (function !=3D NULL) - zfree(function); - - if (filename !=3D NULL) - zfree(filename); - - if (line_nr !=3D NULL) - *line_nr =3D 0; - - /* - * Read the first line. Without an error this will be: - * - for the first line an address like 0x1234, - * - the binutils sentinel 0x0000000000000000, - * - the llvm-addr2line the sentinel ',' character, - * - the function name line for an inlined function. - */ - if (io__getline(io, &line, &line_len) < 0 || !line_len) - goto error; - - pr_debug3("%s %s: addr2line read address for sentinel: %s", __func__, dso= _name, line); - if (style =3D=3D LLVM && line_len =3D=3D 2 && line[0] =3D=3D ',') { - /* Found the llvm-addr2line sentinel character. */ - zfree(&line); - return 0; - } else if (style =3D=3D GNU_BINUTILS && (!first || addr !=3D 0)) { - int zero_count =3D 0, non_zero_count =3D 0; - /* - * Check for binutils sentinel ignoring it for the case the - * requested address is 0. - */ - - /* A given address should always start 0x. */ - if (line_len >=3D 2 || line[0] !=3D '0' || line[1] !=3D 'x') { - for (size_t i =3D 2; i < line_len; i++) { - if (line[i] =3D=3D '0') - zero_count++; - else if (line[i] !=3D '\n') - non_zero_count++; - } - if (!non_zero_count) { - int ch; - - if (first && !zero_count) { - /* Line was erroneous just '0x'. */ - goto error; - } - /* - * Line was 0x0..0, the sentinel for binutils. Remove - * the function and filename lines. - */ - zfree(&line); - do { - ch =3D io__get_char(io); - } while (ch > 0 && ch !=3D '\n'); - do { - ch =3D io__get_char(io); - } while (ch > 0 && ch !=3D '\n'); - return 0; - } - } - } - /* Read the second function name line (if inline data then this is the fi= rst line). */ - if (first && (io__getline(io, &line, &line_len) < 0 || !line_len)) - goto error; - - pr_debug3("%s %s: addr2line read line: %s", __func__, dso_name, line); - if (function !=3D NULL) - *function =3D strdup(strim(line)); - - zfree(&line); - line_len =3D 0; - - /* Read the third filename and line number line. */ - if (io__getline(io, &line, &line_len) < 0 || !line_len) - goto error; - - pr_debug3("%s %s: addr2line filename:number : %s", __func__, dso_name, li= ne); - if (filename_split(line, line_nr =3D=3D NULL ? &dummy_line_nr : line_nr) = =3D=3D 0 && - style =3D=3D GNU_BINUTILS) { - ret =3D 0; - goto error; - } - - if (filename !=3D NULL) - *filename =3D strdup(line); - - zfree(&line); - line_len =3D 0; - - return 1; - -error: - free(line); - if (function !=3D NULL) - zfree(function); - if (filename !=3D NULL) - zfree(filename); - return ret; -} - -static int inline_list__append_record(struct dso *dso, - struct inline_node *node, - struct symbol *sym, - const char *function, - const char *filename, - unsigned int line_nr) -{ - struct symbol *inline_sym =3D new_inline_sym(dso, sym, function); - - return inline_list__append(inline_sym, srcline_from_fileline(filename, li= ne_nr), node); -} - -static int addr2line(const char *dso_name, u64 addr, - char **file, unsigned int *line_nr, - struct dso *dso, - bool unwind_inlines, - struct inline_node *node, - struct symbol *sym __maybe_unused) -{ - struct child_process *a2l =3D dso__a2l(dso); - char *record_function =3D NULL; - char *record_filename =3D NULL; - unsigned int record_line_nr =3D 0; - int record_status =3D -1; - int ret =3D 0; - size_t inline_count =3D 0; - int len; - char buf[128]; - ssize_t written; - struct io io =3D { .eof =3D false }; - enum a2l_style a2l_style; - - if (!a2l) { - if (!filename__has_section(dso_name, ".debug_line")) - goto out; - - dso__set_a2l(dso, - addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name)); - a2l =3D dso__a2l(dso); - } - - if (a2l =3D=3D NULL) { - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_n= ame); - goto out; - } - a2l_style =3D addr2line_configure(a2l, dso_name); - if (a2l_style =3D=3D BROKEN) - goto out; - - /* - * Send our request and then *deliberately* send something that can't be - * interpreted as a valid address to ask addr2line about (namely, - * ","). This causes addr2line to first write out the answer to our - * request, in an unbounded/unknown number of records, and then to write - * out the lines "0x0...0", "??" and "??:0", for GNU binutils, or "," - * for llvm-addr2line, so that we can detect when it has finished giving - * us anything useful. - */ - len =3D snprintf(buf, sizeof(buf), "%016"PRIx64"\n,\n", addr); - written =3D len > 0 ? write(a2l->in, buf, len) : -1; - if (written !=3D len) { - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: could not send request\n", __func__, dso_name); - goto out; - } - io__init(&io, a2l->out, buf, sizeof(buf)); - io.timeout_ms =3D addr2line_timeout_ms; - switch (read_addr2line_record(&io, a2l_style, dso_name, addr, /*first=3D*= /true, - &record_function, &record_filename, &record_line_nr)) { - case -1: - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: could not read first record\n", __func__, dso_name); - goto out; - case 0: - /* - * The first record was invalid, so return failure, but first - * read another record, since we sent a sentinel ',' for the - * sake of detected the last inlined function. Treat this as the - * first of a record as the ',' generates a new start with GNU - * binutils, also force a non-zero address as we're no longer - * reading that record. - */ - switch (read_addr2line_record(&io, a2l_style, dso_name, - /*addr=3D*/1, /*first=3D*/true, - NULL, NULL, NULL)) { - case -1: - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: could not read sentinel record\n", - __func__, dso_name); - break; - case 0: - /* The sentinel as expected. */ - break; - default: - if (!symbol_conf.disable_add2line_warn) - pr_warning("%s %s: unexpected record instead of sentinel", - __func__, dso_name); - break; - } - goto out; - default: - /* First record as expected. */ - break; - } - - if (file) { - *file =3D strdup(record_filename); - ret =3D 1; - } - if (line_nr) - *line_nr =3D record_line_nr; - - if (unwind_inlines) { - if (node && inline_list__append_record(dso, node, sym, - record_function, - record_filename, - record_line_nr)) { - ret =3D 0; - goto out; - } - } - - /* - * We have to read the records even if we don't care about the inline - * info. This isn't the first record and force the address to non-zero - * as we're reading records beyond the first. - */ - while ((record_status =3D read_addr2line_record(&io, - a2l_style, - dso_name, - /*addr=3D*/1, - /*first=3D*/false, - &record_function, - &record_filename, - &record_line_nr)) =3D=3D 1) { - if (unwind_inlines && node && inline_count++ < MAX_INLINE_NEST) { - if (inline_list__append_record(dso, node, sym, - record_function, - record_filename, - record_line_nr)) { - ret =3D 0; - goto out; - } - ret =3D 1; /* found at least one inline frame */ - } - } - -out: - free(record_function); - free(record_filename); - if (io.eof) { - dso__set_a2l(dso, NULL); - addr2line_subprocess_cleanup(a2l); - } - return ret; -} - -void dso__free_a2l(struct dso *dso) -{ - struct child_process *a2l =3D dso__a2l(dso); + int ret; =20 - if (!a2l) - return; + ret =3D llvm__addr2line(dso_name, addr, file, line_nr, dso, unwind_inline= s, node, sym); + if (ret > 0) + return ret; =20 - addr2line_subprocess_cleanup(a2l); + ret =3D libbfd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inli= nes, node, sym); + if (ret > 0) + return ret; =20 - dso__set_a2l(dso, NULL); + return cmd__addr2line(dso_name, addr, file, line_nr, dso, unwind_inlines,= node, sym); } =20 -#endif /* HAVE_LIBBFD_SUPPORT */ - static struct inline_node *addr2inlines(const char *dso_name, u64 addr, struct dso *dso, struct symbol *sym) { @@ -595,7 +145,9 @@ static struct inline_node *addr2inlines(const char *dso= _name, u64 addr, INIT_LIST_HEAD(&node->val); node->addr =3D addr; =20 - addr2line(dso_name, addr, NULL, NULL, dso, true, node, sym); + addr2line(dso_name, addr, /*file=3D*/NULL, /*line_nr=3D*/NULL, dso, + /*unwind_inlines=3D*/true, node, sym); + return node; } =20 @@ -622,7 +174,7 @@ char *__get_srcline(struct dso *dso, u64 addr, struct s= ymbol *sym, goto out_err; =20 if (!addr2line(dso_name, addr, &file, &line, dso, - unwind_inlines, NULL, sym)) + unwind_inlines, /*node=3D*/NULL, sym)) goto out_err; =20 srcline =3D srcline_from_fileline(file, line); @@ -668,7 +220,8 @@ char *get_srcline_split(struct dso *dso, u64 addr, unsi= gned *line) if (dso_name =3D=3D NULL) goto out_err; =20 - if (!addr2line(dso_name, addr, &file, line, dso, true, NULL, NULL)) + if (!addr2line(dso_name, addr, &file, line, dso, /*unwind_inlines=3D*/tru= e, + /*node=3D*/NULL, /*sym=3D*/NULL)) goto out_err; =20 dso__set_a2l_fails(dso, 0); diff --git a/tools/perf/util/srcline.h b/tools/perf/util/srcline.h index 6e66ddbcc879..c36f573cd339 100644 --- a/tools/perf/util/srcline.h +++ b/tools/perf/util/srcline.h @@ -9,7 +9,6 @@ struct dso; struct symbol; =20 -extern int addr2line_timeout_ms; extern bool srcline_full_filename; char *get_srcline(struct dso *dso, u64 addr, struct symbol *sym, bool show_sym, bool show_addr, u64 ip); --=20 2.51.0.570.gb178f27e6d-goog