Skip to content

Commit 4b71b37

Browse files
[BOLT] DataAggregator support for binaries with multiple text segments (#92815)
When a binary has multiple text segments, the Size is computed as the difference of the last address of these segments from the BaseAddress. The base addresses of all text segments must be the same. Introduces flag 'perf-script-events' for testing. It allows passing perf events without BOLT having to parse them using 'perf script'. The flag is used to pass a mock perf profile that has two memory mappings for a mock binary that has two text segments. The size of the mapping is updated as this change `parseMMapEvents` processes all text segments.
1 parent b5a11d3 commit 4b71b37

File tree

5 files changed

+183
-14
lines changed

5 files changed

+183
-14
lines changed

bolt/include/bolt/Profile/DataAggregator.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ class DataAggregator : public DataReader {
170170
std::string BuildIDBinaryName;
171171

172172
/// Memory map info for a single file as recorded in perf.data
173+
/// When a binary has multiple text segments, the Size is computed as the
174+
/// difference of the last address of these segments from the BaseAddress.
175+
/// The base addresses of all text segments must be the same.
173176
struct MMapInfo {
174177
uint64_t BaseAddress{0}; /// Base address of the mapped binary.
175178
uint64_t MMapAddress{0}; /// Address of the executable segment.
@@ -493,6 +496,11 @@ class DataAggregator : public DataReader {
493496
/// and return a file name matching a given \p FileBuildID.
494497
std::optional<StringRef> getFileNameForBuildID(StringRef FileBuildID);
495498

499+
/// Get a constant reference to the parsed binary mmap entries.
500+
const std::unordered_map<uint64_t, MMapInfo> &getBinaryMMapInfo() {
501+
return BinaryMMapInfo;
502+
}
503+
496504
friend class YAMLProfileWriter;
497505
};
498506
} // namespace bolt

bolt/lib/Profile/DataAggregator.cpp

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ cl::opt<bool> ReadPreAggregated(
9595
"pa", cl::desc("skip perf and read data from a pre-aggregated file format"),
9696
cl::cat(AggregatorCategory));
9797

98+
cl::opt<std::string>
99+
ReadPerfEvents("perf-script-events",
100+
cl::desc("skip perf event collection by supplying a "
101+
"perf-script output in a textual format"),
102+
cl::ReallyHidden, cl::init(""), cl::cat(AggregatorCategory));
103+
98104
static cl::opt<bool>
99105
TimeAggregator("time-aggr",
100106
cl::desc("time BOLT aggregator"),
@@ -167,8 +173,9 @@ void DataAggregator::findPerfExecutable() {
167173
void DataAggregator::start() {
168174
outs() << "PERF2BOLT: Starting data aggregation job for " << Filename << "\n";
169175

170-
// Don't launch perf for pre-aggregated files
171-
if (opts::ReadPreAggregated)
176+
// Don't launch perf for pre-aggregated files or when perf input is specified
177+
// by the user.
178+
if (opts::ReadPreAggregated || !opts::ReadPerfEvents.empty())
172179
return;
173180

174181
findPerfExecutable();
@@ -464,6 +471,13 @@ void DataAggregator::filterBinaryMMapInfo() {
464471

465472
int DataAggregator::prepareToParse(StringRef Name, PerfProcessInfo &Process,
466473
PerfProcessErrorCallbackTy Callback) {
474+
if (!opts::ReadPerfEvents.empty()) {
475+
outs() << "PERF2BOLT: using pre-processed perf events for '" << Name
476+
<< "' (perf-script-events)\n";
477+
ParsingBuf = opts::ReadPerfEvents;
478+
return 0;
479+
}
480+
467481
std::string Error;
468482
outs() << "PERF2BOLT: waiting for perf " << Name
469483
<< " collection to finish...\n";
@@ -2056,15 +2070,6 @@ std::error_code DataAggregator::parseMMapEvents() {
20562070
if (FileMMapInfo.first == "(deleted)")
20572071
continue;
20582072

2059-
// Consider only the first mapping of the file for any given PID
2060-
auto Range = GlobalMMapInfo.equal_range(FileMMapInfo.first);
2061-
bool PIDExists = llvm::any_of(make_range(Range), [&](const auto &MI) {
2062-
return MI.second.PID == FileMMapInfo.second.PID;
2063-
});
2064-
2065-
if (PIDExists)
2066-
continue;
2067-
20682073
GlobalMMapInfo.insert(FileMMapInfo);
20692074
}
20702075

@@ -2116,12 +2121,22 @@ std::error_code DataAggregator::parseMMapEvents() {
21162121
<< " using file offset 0x" << Twine::utohexstr(MMapInfo.Offset)
21172122
<< ". Ignoring profile data for this mapping\n";
21182123
continue;
2119-
} else {
2120-
MMapInfo.BaseAddress = *BaseAddress;
21212124
}
2125+
MMapInfo.BaseAddress = *BaseAddress;
21222126
}
21232127

2124-
BinaryMMapInfo.insert(std::make_pair(MMapInfo.PID, MMapInfo));
2128+
// Try to add MMapInfo to the map and update its size. Large binaries may
2129+
// span to multiple text segments, so the mapping is inserted only on the
2130+
// first occurrence.
2131+
if (!BinaryMMapInfo.insert(std::make_pair(MMapInfo.PID, MMapInfo)).second)
2132+
assert(MMapInfo.BaseAddress == BinaryMMapInfo[MMapInfo.PID].BaseAddress &&
2133+
"Base address on multiple segment mappings should match");
2134+
2135+
// Update mapping size.
2136+
const uint64_t EndAddress = MMapInfo.MMapAddress + MMapInfo.Size;
2137+
const uint64_t Size = EndAddress - BinaryMMapInfo[MMapInfo.PID].BaseAddress;
2138+
if (Size > BinaryMMapInfo[MMapInfo.PID].Size)
2139+
BinaryMMapInfo[MMapInfo.PID].Size = Size;
21252140
}
21262141

21272142
if (BinaryMMapInfo.empty()) {

bolt/unittests/Core/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
88
add_bolt_unittest(CoreTests
99
BinaryContext.cpp
1010
MCPlusBuilder.cpp
11+
MemoryMaps.cpp
1112
DynoStats.cpp
1213

1314
DISABLE_LLVM_LINK_LLVM_DYLIB
@@ -17,6 +18,8 @@ target_link_libraries(CoreTests
1718
PRIVATE
1819
LLVMBOLTCore
1920
LLVMBOLTRewrite
21+
LLVMBOLTProfile
22+
LLVMTestingSupport
2023
)
2124

2225
foreach (tgt ${BOLT_TARGETS_TO_BUILD})

bolt/unittests/Core/MemoryMaps.cpp

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//===- bolt/unittest/Core/MemoryMaps.cpp ----------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "bolt/Core/BinaryContext.h"
10+
#include "bolt/Profile/DataAggregator.h"
11+
#include "llvm/BinaryFormat/ELF.h"
12+
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
13+
#include "llvm/Support/CommandLine.h"
14+
#include "llvm/Support/TargetSelect.h"
15+
#include "llvm/Testing/Support/Error.h"
16+
#include "gtest/gtest.h"
17+
18+
using namespace llvm;
19+
using namespace llvm::object;
20+
using namespace llvm::ELF;
21+
using namespace bolt;
22+
23+
namespace opts {
24+
extern cl::opt<std::string> ReadPerfEvents;
25+
} // namespace opts
26+
27+
namespace {
28+
29+
/// Perform checks on memory map events normally captured in perf. Tests use
30+
/// the 'opts::ReadPerfEvents' flag to emulate these events, passing a custom
31+
/// 'perf script' output to DataAggregator.
32+
struct MemoryMapsTester : public testing::TestWithParam<Triple::ArchType> {
33+
void SetUp() override {
34+
initalizeLLVM();
35+
prepareElf();
36+
initializeBOLT();
37+
}
38+
39+
protected:
40+
void initalizeLLVM() {
41+
llvm::InitializeAllTargetInfos();
42+
llvm::InitializeAllTargetMCs();
43+
llvm::InitializeAllAsmParsers();
44+
llvm::InitializeAllDisassemblers();
45+
llvm::InitializeAllTargets();
46+
llvm::InitializeAllAsmPrinters();
47+
}
48+
49+
void prepareElf() {
50+
memcpy(ElfBuf, "\177ELF", 4);
51+
ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf);
52+
EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64;
53+
EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB;
54+
EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64;
55+
MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF");
56+
ObjFile = cantFail(ObjectFile::createObjectFile(Source));
57+
}
58+
59+
void initializeBOLT() {
60+
Relocation::Arch = ObjFile->makeTriple().getArch();
61+
BC = cantFail(BinaryContext::createBinaryContext(
62+
ObjFile->makeTriple(), ObjFile->getFileName(), nullptr, true,
63+
DWARFContext::create(*ObjFile.get()), {llvm::outs(), llvm::errs()}));
64+
ASSERT_FALSE(!BC);
65+
}
66+
67+
char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {};
68+
std::unique_ptr<ObjectFile> ObjFile;
69+
std::unique_ptr<BinaryContext> BC;
70+
};
71+
} // namespace
72+
73+
#ifdef X86_AVAILABLE
74+
75+
INSTANTIATE_TEST_SUITE_P(X86, MemoryMapsTester,
76+
::testing::Values(Triple::x86_64));
77+
78+
#endif
79+
80+
#ifdef AARCH64_AVAILABLE
81+
82+
INSTANTIATE_TEST_SUITE_P(AArch64, MemoryMapsTester,
83+
::testing::Values(Triple::aarch64));
84+
85+
#endif
86+
87+
/// Check that the correct mmap size is computed when we have multiple text
88+
/// segment mappings.
89+
TEST_P(MemoryMapsTester, ParseMultipleSegments) {
90+
const int Pid = 1234;
91+
StringRef Filename = "BINARY";
92+
opts::ReadPerfEvents = formatv(
93+
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
94+
"[0xabc0000000(0x1000000) @ 0x11c0000 103:01 1573523 0]: r-xp {1}\n"
95+
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
96+
"[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n",
97+
Pid, Filename);
98+
99+
BC->SegmentMapInfo[0x11da000] =
100+
SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true};
101+
BC->SegmentMapInfo[0x31d0000] =
102+
SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0000, 0x3000000, 0x200000, true};
103+
104+
DataAggregator DA("");
105+
BC->setFilename(Filename);
106+
Error Err = DA.preprocessProfile(*BC);
107+
108+
// Ignore errors from perf2bolt when parsing memory events later on.
109+
ASSERT_THAT_ERROR(std::move(Err), Succeeded());
110+
111+
auto &BinaryMMapInfo = DA.getBinaryMMapInfo();
112+
auto El = BinaryMMapInfo.find(Pid);
113+
// Check that memory mapping is present and has the expected size.
114+
ASSERT_NE(El, BinaryMMapInfo.end());
115+
ASSERT_EQ(El->second.Size, static_cast<uint64_t>(0xb1d0000));
116+
}
117+
118+
/// Check that DataAggregator aborts when pre-processing an input binary
119+
/// with multiple text segments that have different base addresses.
120+
TEST_P(MemoryMapsTester, MultipleSegmentsMismatchedBaseAddress) {
121+
const int Pid = 1234;
122+
StringRef Filename = "BINARY";
123+
opts::ReadPerfEvents = formatv(
124+
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
125+
"[0xabc0000000(0x1000000) @ 0x11c0000 103:01 1573523 0]: r-xp {1}\n"
126+
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
127+
"[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n",
128+
Pid, Filename);
129+
130+
BC->SegmentMapInfo[0x11da000] =
131+
SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true};
132+
// Using '0x31d0fff' FileOffset which triggers a different base address
133+
// for this second text segment.
134+
BC->SegmentMapInfo[0x31d0000] =
135+
SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0fff, 0x3000000, 0x200000, true};
136+
137+
DataAggregator DA("");
138+
BC->setFilename(Filename);
139+
ASSERT_DEATH(
140+
{ Error Err = DA.preprocessProfile(*BC); },
141+
"Base address on multiple segment mappings should match");
142+
}

llvm/utils/gn/secondary/bolt/unittests/Core/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ unittest("CoreTests") {
1515
"BinaryContext.cpp",
1616
"DynoStats.cpp",
1717
"MCPlusBuilder.cpp",
18+
"MemoryMaps.cpp",
1819
]
1920

2021
defines = []

0 commit comments

Comments
 (0)