[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