[llvm] [llvm-profgen] Harden perf script invocation (PR #212253)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 27 06:44:54 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-binary-utilities
Author: Sergey Shcherbinin (SergeyShch01)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/212253.diff
4 Files Affected:
- (modified) llvm/docs/CommandGuide/llvm-profgen.rst (+5)
- (added) llvm/test/tools/llvm-profgen/perf-binary-error.test (+48)
- (added) llvm/test/tools/llvm-profgen/perfdata-redirect.test (+126)
- (modified) llvm/tools/llvm-profgen/PerfReader.cpp (+56-7)
``````````diff
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/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
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};
``````````
</details>
https://github.com/llvm/llvm-project/pull/212253
More information about the llvm-commits
mailing list