[llvm] [llvm-profgen] Harden perf script invocation (PR #212253)
Sergey Shcherbinin via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 27 06:44:16 PDT 2026
https://github.com/SergeyShch01 created https://github.com/llvm/llvm-project/pull/212253
Add --perf-binary to select the perf executable used for --perfdata conversion. Report launch/exit failures, clear redirect files between the two perf script invocations so stale stdout/stderr cannot leak, and coverboth with lit tests.
>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/2] [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/2] [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
More information about the llvm-commits
mailing list