[llvm] [llvm-profgen] Harden perf script invocation (PR #212253)
Sergey Shcherbinin via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 31 08:31:20 PDT 2026
https://github.com/SergeyShch01 updated https://github.com/llvm/llvm-project/pull/212253
>From 29d601579a29436affa2a0585eee4d5713c0d404 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Mon, 27 Jul 2026 16:48:33 +0400
Subject: [PATCH 1/5] [llvm-profgen] Add --perf-binary to select the perf
executable
Allow selecting the perf binary used for --perfdata conversion instead of
always searching PATH. Also report perf invocation failures and clear
redirect files between the two perf script calls so error diagnostics and
tests are reliable.
Assisted by GPT-5
---
llvm/docs/CommandGuide/llvm-profgen.rst | 5 ++
.../tools/llvm-profgen/perf-binary-error.test | 48 ++++++++++++++
llvm/tools/llvm-profgen/PerfReader.cpp | 63 ++++++++++++++++---
3 files changed, 109 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/tools/llvm-profgen/perf-binary-error.test
diff --git a/llvm/docs/CommandGuide/llvm-profgen.rst b/llvm/docs/CommandGuide/llvm-profgen.rst
index 006cdfecf4f88..2b4bfbbd53d3a 100644
--- a/llvm/docs/CommandGuide/llvm-profgen.rst
+++ b/llvm/docs/CommandGuide/llvm-profgen.rst
@@ -71,6 +71,11 @@ OPTIONS
Print mmap events.
+.. option:: --perf-binary=<filename>
+
+ Path to the ``perf`` executable used to convert ``--perfdata`` input. If this
+ option is omitted, ``llvm-profgen`` searches for ``perf`` in ``PATH``.
+
.. option:: --show-disassembly
Print disassembled code.
diff --git a/llvm/test/tools/llvm-profgen/perf-binary-error.test b/llvm/test/tools/llvm-profgen/perf-binary-error.test
new file mode 100644
index 0000000000000..298cdcf069bfc
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/perf-binary-error.test
@@ -0,0 +1,48 @@
+# REQUIRES: x86-registered-target
+
+# RUN: split-file %s %t
+# RUN: yaml2obj %t/binary.yaml -o %t/perf-binary-error.exe
+# RUN: touch %t/perf.data
+
+# RUN: not llvm-profgen --binary=%t/perf-binary-error.exe \
+# RUN: --perfdata=%t/perf.data --perf-binary yaml2obj --output=/dev/null \
+# RUN: 2>&1 | FileCheck %s --check-prefix=EXIT
+# RUN: not llvm-profgen --binary=%t/perf-binary-error.exe \
+# RUN: --perfdata=%t/perf.data --perf-binary=%t/missing-perf \
+# RUN: --output=/dev/null 2>&1 | FileCheck %s --check-prefix=EXEC
+
+# EXIT: error: Perf script failed with exit code 1
+# EXIT: yaml2obj
+# EXEC: error: Failed to execute perf script
+
+#--- binary.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_DYN
+ Machine: EM_X86_64
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ Offset: 0x1000
+ AddressAlign: 0x1
+ ## ret
+ Content: C3
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ Offset: 0
+ VAddr: 0
+ Align: 0x1000
+ FirstSec: .text
+ LastSec: .text
+Symbols:
+ - Name: foo
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x1000
+ Size: 0x1
diff --git a/llvm/tools/llvm-profgen/PerfReader.cpp b/llvm/tools/llvm-profgen/PerfReader.cpp
index 5fb1c6d252915..8ae6a2d5453f3 100644
--- a/llvm/tools/llvm-profgen/PerfReader.cpp
+++ b/llvm/tools/llvm-profgen/PerfReader.cpp
@@ -18,6 +18,7 @@
#include "llvm/Support/Process.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Triple.h"
#define DEBUG_TYPE "perf-reader"
@@ -69,6 +70,11 @@ cl::opt<bool> TimeProfGen("time-profgen", cl::desc("Time llvm-profgen phases"),
static const char *TimerGroupName = "profgen";
static const char *TimerGroupDesc = "llvm-profgen";
+static cl::opt<std::string> PerfPath("perf-binary",
+ cl::desc("Path to perf binary"),
+ cl::value_desc("filename"),
+ cl::cat(ProfGenCategory));
+
namespace sampleprof {
void VirtualUnwinder::unwindCall(UnwindState &State) {
@@ -462,11 +468,13 @@ PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
std::optional<int32_t> PIDFilter) {
StringRef PerfData = File.InputFilePath;
// Run perf script to retrieve PIDs matching binary we're interested in.
- auto PerfExecutable = sys::Process::FindInEnvPath("PATH", "perf");
+ auto PerfExecutable = PerfPath.getNumOccurrences()
+ ? std::optional<std::string>(PerfPath)
+ : sys::Process::FindInEnvPath("PATH", "perf");
if (!PerfExecutable) {
exitWithError("Perf not found.");
}
- std::string PerfPath = *PerfExecutable;
+ std::string PerfExecutablePath = *PerfExecutable;
SmallString<128> PerfTraceFile;
sys::fs::createUniquePath("perf-script-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%.tmp",
PerfTraceFile, /*MakeAbsolute=*/true);
@@ -477,12 +485,53 @@ PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
PerfScriptReader::TempFileCleanups.emplace_back(PerfTraceFile);
PerfScriptReader::TempFileCleanups.emplace_back(ErrorFile);
+ auto RunPerfScript = [&](ArrayRef<StringRef> Args) {
+ // ExecuteAndWait does not truncate redirected output files on Unix. Remove
+ // both files so a shorter invocation cannot retain output from the
+ // previous perf script invocation.
+ for (StringRef Path : {StringRef(PerfTraceFile), StringRef(ErrorFile)}) {
+ if (std::error_code EC = sys::fs::remove(Path))
+ exitWithError(EC, Path);
+ }
+
+ std::string ExecutionError;
+ bool ExecutionFailed = false;
+ int ExitCode =
+ sys::ExecuteAndWait(PerfExecutablePath, Args, std::nullopt, Redirects,
+ /*SecondsToWait=*/0, /*MemoryLimit=*/0,
+ &ExecutionError, &ExecutionFailed);
+ if (!ExecutionFailed && ExitCode == 0)
+ return;
+
+ std::string Message;
+ raw_string_ostream OS(Message);
+ if (ExecutionFailed || ExitCode == -1)
+ OS << "Failed to execute perf script";
+ else if (ExitCode == -2)
+ OS << "Perf script terminated abnormally";
+ else
+ OS << "Perf script failed with exit code " << ExitCode;
+ if (!ExecutionError.empty())
+ OS << ": " << ExecutionError;
+
+ if (auto ErrorBuffer = MemoryBuffer::getFile(ErrorFile)) {
+ StringRef Stderr = ErrorBuffer.get()->getBuffer().trim();
+ if (!Stderr.empty())
+ OS << "\n" << Stderr;
+ }
+ exitWithError(OS.str());
+ };
+
std::string PIDs;
if (!SkipPID) {
- StringRef ScriptMMapArgs[] = {PerfPath, "script", "--show-mmap-events",
- "-F", "comm,pid", "-i",
+ StringRef ScriptMMapArgs[] = {PerfExecutablePath,
+ "script",
+ "--show-mmap-events",
+ "-F",
+ "comm,pid",
+ "-i",
PerfData};
- sys::ExecuteAndWait(PerfPath, ScriptMMapArgs, std::nullopt, Redirects);
+ RunPerfScript(ScriptMMapArgs);
// Collect the PIDs
TraceStream TraceIt(PerfTraceFile);
@@ -509,7 +558,7 @@ PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
// Run perf script again to retrieve events for PIDs collected above
SmallVector<StringRef, 8> ScriptSampleArgs;
- ScriptSampleArgs.push_back(PerfPath);
+ ScriptSampleArgs.push_back(PerfExecutablePath);
ScriptSampleArgs.push_back("script");
ScriptSampleArgs.push_back("--show-mmap-events");
ScriptSampleArgs.push_back("-F");
@@ -520,7 +569,7 @@ PerfScriptReader::convertPerfDataToTrace(ProfiledBinary *Binary, bool SkipPID,
ScriptSampleArgs.push_back("--pid");
ScriptSampleArgs.push_back(PIDs);
}
- sys::ExecuteAndWait(PerfPath, ScriptSampleArgs, std::nullopt, Redirects);
+ RunPerfScript(ScriptSampleArgs);
return {std::string(PerfTraceFile), InputFormat::PerfScript,
PerfContent::UnknownContent};
>From df7258efa7273c26ec19f1daf85f71c666c85e9a Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Mon, 27 Jul 2026 17:35:44 +0400
Subject: [PATCH 2/5] [llvm-profgen] Add perfdata redirect truncation test
Cover stale stdout/stderr between the two perf script invocations used for
--perfdata conversion, using an arch-neutral X86 fixture.
---
.../tools/llvm-profgen/perfdata-redirect.test | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 llvm/test/tools/llvm-profgen/perfdata-redirect.test
diff --git a/llvm/test/tools/llvm-profgen/perfdata-redirect.test b/llvm/test/tools/llvm-profgen/perfdata-redirect.test
new file mode 100644
index 0000000000000..cfd124b696147
--- /dev/null
+++ b/llvm/test/tools/llvm-profgen/perfdata-redirect.test
@@ -0,0 +1,126 @@
+# REQUIRES: system-linux, x86-registered-target
+
+# RUN: split-file %s %t
+# RUN: yaml2obj %t/binary.yaml -o %t/perfdata-redirect.exe
+# RUN: touch %t/perf.data
+# RUN: chmod +x %t/mock-perf %t/mock-perf-fail
+# RUN: llvm-profgen --binary=%t/perfdata-redirect.exe \
+# RUN: --perfdata=%t/perf.data --perf-binary=%t/mock-perf \
+# RUN: --skip-symbolization --use-offset=0 --format=text --output=%t/profile
+# RUN: FileCheck %s --check-prefix=PROFILE --input-file=%t/profile
+# RUN: not llvm-profgen --binary=%t/perfdata-redirect.exe \
+# RUN: --perfdata=%t/perf.data --perf-binary=%t/mock-perf-fail \
+# RUN: --output=/dev/null 2>&1 \
+# RUN: | FileCheck %s --check-prefix=SECOND-ERROR
+
+## The first mock invocation deliberately writes the complete second output plus a
+## duplicate sample. Without truncating the shared redirect file, that stale
+## sample survives the second invocation and doubles every profile count.
+# PROFILE: 1
+# PROFILE-NEXT: 1008-100c:1
+# PROFILE-NEXT: 2
+# PROFILE-NEXT: 1000->1008:1
+# PROFILE-NEXT: 100c->1000:1
+
+## The first invocation also leaves a longer stderr message. Verify that an
+## error from the second invocation contains only its own stderr.
+# SECOND-ERROR: error: Perf script failed with exit code 7
+# SECOND-ERROR-NEXT: second invocation failed
+# SECOND-ERROR-NOT: STALE-FIRST-STDERR
+
+#--- mock-perf
+#!/bin/sh
+
+if [ "$1" != "script" ] || [ "$2" != "--show-mmap-events" ] || \
+ [ "$3" != "-F" ] || [ "$5" != "-i" ] || [ ! -f "$6" ]; then
+ echo "unexpected perf arguments: $*" >&2
+ exit 2
+fi
+
+emit_sample() {
+ printf '%s\n' 'PERF_RECORD_MMAP2 1/1: [0x700000000000(0x2000) @ 0 00:00 0 0]: r-xp /tmp/perfdata-redirect.exe'
+ printf '%s\n' '70000000100c 0x70000000100c/0x700000001000/P/-/-/0 0x700000001000/0x700000001008/P/-/-/0'
+}
+
+case "$4" in
+ comm,pid)
+ if [ "$#" -ne 6 ]; then
+ echo "unexpected mmap arguments: $*" >&2
+ exit 2
+ fi
+ emit_sample
+ printf '%s\n' '70000000100c 0x70000000100c/0x700000001000/P/-/-/0 0x700000001000/0x700000001008/P/-/-/0'
+ ;;
+ ip,brstack)
+ if [ "$#" -ne 8 ] || [ "$7" != "--pid" ] || [ "$8" != "1" ]; then
+ echo "unexpected sample arguments: $*" >&2
+ exit 2
+ fi
+ emit_sample
+ ;;
+ *)
+ echo "unexpected perf fields: $4" >&2
+ exit 2
+ ;;
+esac
+
+#--- mock-perf-fail
+#!/bin/sh
+
+if [ "$1" != "script" ] || [ "$2" != "--show-mmap-events" ] || \
+ [ "$3" != "-F" ] || [ "$5" != "-i" ] || [ ! -f "$6" ]; then
+ echo "unexpected perf arguments: $*" >&2
+ exit 2
+fi
+
+case "$4" in
+ comm,pid)
+ printf '%s\n' 'PERF_RECORD_MMAP2 1/1: [0x700000000000(0x2000) @ 0 00:00 0 0]: r-xp /tmp/perfdata-redirect.exe'
+ printf '%s\n' 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx STALE-FIRST-STDERR' >&2
+ ;;
+ ip,brstack)
+ printf '%s\n' 'second invocation failed' >&2
+ exit 7
+ ;;
+ *)
+ echo "unexpected perf fields: $4" >&2
+ exit 2
+ ;;
+esac
+
+#--- binary.yaml
+--- !ELF
+FileHeader:
+ Class: ELFCLASS64
+ Data: ELFDATA2LSB
+ Type: ET_DYN
+ Machine: EM_X86_64
+ Entry: 0x1000
+Sections:
+ - Name: .text
+ Type: SHT_PROGBITS
+ Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
+ Address: 0x1000
+ Offset: 0x1000
+ AddressAlign: 0x1
+ ## 1000: jmp 0x1008
+ ## 1002: nop*6
+ ## 1008: nop*4
+ ## 100c: jmp 0x1000
+ ## 100e: ret
+ Content: EB0690909090909090909090EBF2C3
+ProgramHeaders:
+ - Type: PT_LOAD
+ Flags: [ PF_X, PF_R ]
+ Offset: 0
+ VAddr: 0
+ FileSize: 0x100F
+ MemSize: 0x100F
+ Align: 0x1000
+Symbols:
+ - Name: foo
+ Type: STT_FUNC
+ Section: .text
+ Binding: STB_GLOBAL
+ Value: 0x1000
+ Size: 0xF
>From 2033c33f6c938ed4f028be6db371b33742eba570 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Mon, 27 Jul 2026 17:50:14 +0400
Subject: [PATCH 3/5] [llvm-profgen] Move perf harness tests under X86/
Place the new lit tests with the other X86 fixtures and rely on
X86/lit.local.cfg instead of an explicit x86-registered-target requirement.
---
llvm/test/tools/llvm-profgen/{ => X86}/perf-binary-error.test | 2 --
llvm/test/tools/llvm-profgen/{ => X86}/perfdata-redirect.test | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
rename llvm/test/tools/llvm-profgen/{ => X86}/perf-binary-error.test (97%)
rename llvm/test/tools/llvm-profgen/{ => X86}/perfdata-redirect.test (98%)
diff --git a/llvm/test/tools/llvm-profgen/perf-binary-error.test b/llvm/test/tools/llvm-profgen/X86/perf-binary-error.test
similarity index 97%
rename from llvm/test/tools/llvm-profgen/perf-binary-error.test
rename to llvm/test/tools/llvm-profgen/X86/perf-binary-error.test
index 298cdcf069bfc..b7465432c8a4f 100644
--- a/llvm/test/tools/llvm-profgen/perf-binary-error.test
+++ b/llvm/test/tools/llvm-profgen/X86/perf-binary-error.test
@@ -1,5 +1,3 @@
-# REQUIRES: x86-registered-target
-
# RUN: split-file %s %t
# RUN: yaml2obj %t/binary.yaml -o %t/perf-binary-error.exe
# RUN: touch %t/perf.data
diff --git a/llvm/test/tools/llvm-profgen/perfdata-redirect.test b/llvm/test/tools/llvm-profgen/X86/perfdata-redirect.test
similarity index 98%
rename from llvm/test/tools/llvm-profgen/perfdata-redirect.test
rename to llvm/test/tools/llvm-profgen/X86/perfdata-redirect.test
index cfd124b696147..27c8b3fad9771 100644
--- a/llvm/test/tools/llvm-profgen/perfdata-redirect.test
+++ b/llvm/test/tools/llvm-profgen/X86/perfdata-redirect.test
@@ -1,4 +1,4 @@
-# REQUIRES: system-linux, x86-registered-target
+# REQUIRES: system-linux
# RUN: split-file %s %t
# RUN: yaml2obj %t/binary.yaml -o %t/perfdata-redirect.exe
>From c1332a90896b5487bad6119a75391012db79fdec Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Fri, 31 Jul 2026 17:15:20 +0400
Subject: [PATCH 4/5] [llvm-profgen][test] Explain yaml2obj error fixture
Document why yaml2obj provides a stable nonzero-exit test input.
---
llvm/test/tools/llvm-profgen/X86/perf-binary-error.test | 2 ++
1 file changed, 2 insertions(+)
diff --git a/llvm/test/tools/llvm-profgen/X86/perf-binary-error.test b/llvm/test/tools/llvm-profgen/X86/perf-binary-error.test
index b7465432c8a4f..a6f98bc3459fa 100644
--- a/llvm/test/tools/llvm-profgen/X86/perf-binary-error.test
+++ b/llvm/test/tools/llvm-profgen/X86/perf-binary-error.test
@@ -2,6 +2,8 @@
# RUN: yaml2obj %t/binary.yaml -o %t/perf-binary-error.exe
# RUN: touch %t/perf.data
+## yaml2obj is consistently available in LLVM test environments and returns a
+## nonzero status when invoked with perf script arguments.
# RUN: not llvm-profgen --binary=%t/perf-binary-error.exe \
# RUN: --perfdata=%t/perf.data --perf-binary yaml2obj --output=/dev/null \
# RUN: 2>&1 | FileCheck %s --check-prefix=EXIT
>From 2177ca1a0189b7ee812eac9c9043c2beb914d645 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Fri, 31 Jul 2026 19:31:05 +0400
Subject: [PATCH 5/5] [llvm-profgen] Retrigger CI after network timeout
More information about the llvm-commits
mailing list