1 //===- RawMemProfReader.cpp - Instrumented memory profiling reader --------===//
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 // This file contains support for reading MemProf profiling data.
10 //
11 //===----------------------------------------------------------------------===//
12
13 #include <cstdint>
14 #include <memory>
15 #include <type_traits>
16
17 #include "llvm/ADT/ArrayRef.h"
18 #include "llvm/ADT/DenseMap.h"
19 #include "llvm/ADT/SetVector.h"
20 #include "llvm/ADT/SmallSet.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/ADT/StringExtras.h"
23 #include "llvm/ADT/Twine.h"
24 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
25 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
26 #include "llvm/DebugInfo/Symbolize/SymbolizableObjectFile.h"
27 #include "llvm/Object/Binary.h"
28 #include "llvm/Object/BuildID.h"
29 #include "llvm/Object/ELFObjectFile.h"
30 #include "llvm/Object/ObjectFile.h"
31 #include "llvm/ProfileData/InstrProf.h"
32 #include "llvm/ProfileData/MemProf.h"
33 #include "llvm/ProfileData/MemProfData.inc"
34 #include "llvm/ProfileData/MemProfReader.h"
35 #include "llvm/ProfileData/MemProfSummaryBuilder.h"
36 #include "llvm/ProfileData/MemProfYAML.h"
37 #include "llvm/ProfileData/SampleProf.h"
38 #include "llvm/Support/Debug.h"
39 #include "llvm/Support/Endian.h"
40 #include "llvm/Support/Error.h"
41 #include "llvm/Support/ErrorHandling.h"
42 #include "llvm/Support/MemoryBuffer.h"
43 #include "llvm/Support/Path.h"
44
45 #define DEBUG_TYPE "memprof"
46
47 namespace llvm {
48 namespace memprof {
49 namespace {
alignedRead(const char * Ptr)50 template <class T = uint64_t> inline T alignedRead(const char *Ptr) {
51 static_assert(std::is_integral_v<T>, "Not an integral type");
52 assert(reinterpret_cast<size_t>(Ptr) % sizeof(T) == 0 && "Unaligned Read");
53 return *reinterpret_cast<const T *>(Ptr);
54 }
55
checkBuffer(const MemoryBuffer & Buffer)56 Error checkBuffer(const MemoryBuffer &Buffer) {
57 if (!RawMemProfReader::hasFormat(Buffer))
58 return make_error<InstrProfError>(instrprof_error::bad_magic);
59
60 if (Buffer.getBufferSize() == 0)
61 return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
62
63 if (Buffer.getBufferSize() < sizeof(Header)) {
64 return make_error<InstrProfError>(instrprof_error::truncated);
65 }
66
67 // The size of the buffer can be > header total size since we allow repeated
68 // serialization of memprof profiles to the same file.
69 uint64_t TotalSize = 0;
70 const char *Next = Buffer.getBufferStart();
71 while (Next < Buffer.getBufferEnd()) {
72 const auto *H = reinterpret_cast<const Header *>(Next);
73
74 // Check if the version in header is among the supported versions.
75 bool IsSupported = false;
76 for (auto SupportedVersion : MEMPROF_RAW_SUPPORTED_VERSIONS) {
77 if (H->Version == SupportedVersion)
78 IsSupported = true;
79 }
80 if (!IsSupported) {
81 return make_error<InstrProfError>(instrprof_error::unsupported_version);
82 }
83
84 TotalSize += H->TotalSize;
85 Next += H->TotalSize;
86 }
87
88 if (Buffer.getBufferSize() != TotalSize) {
89 return make_error<InstrProfError>(instrprof_error::malformed);
90 }
91 return Error::success();
92 }
93
readSegmentEntries(const char * Ptr)94 llvm::SmallVector<SegmentEntry> readSegmentEntries(const char *Ptr) {
95 using namespace support;
96
97 const uint64_t NumItemsToRead =
98 endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
99 llvm::SmallVector<SegmentEntry> Items;
100 for (uint64_t I = 0; I < NumItemsToRead; I++) {
101 Items.push_back(*reinterpret_cast<const SegmentEntry *>(
102 Ptr + I * sizeof(SegmentEntry)));
103 }
104 return Items;
105 }
106
107 llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>
readMemInfoBlocksV3(const char * Ptr)108 readMemInfoBlocksV3(const char *Ptr) {
109 using namespace support;
110
111 const uint64_t NumItemsToRead =
112 endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);
113
114 llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;
115 for (uint64_t I = 0; I < NumItemsToRead; I++) {
116 const uint64_t Id =
117 endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);
118
119 // We cheat a bit here and remove the const from cast to set the
120 // Histogram Pointer to newly allocated buffer. We also cheat, since V3 and
121 // V4 do not have the same fields. V3 is missing AccessHistogramSize and
122 // AccessHistogram. This means we read "dirty" data in here, but it should
123 // not segfault, since there will be callstack data placed after this in the
124 // binary format.
125 MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);
126 // Overwrite dirty data.
127 MIB.AccessHistogramSize = 0;
128 MIB.AccessHistogram = 0;
129
130 Items.push_back({Id, MIB});
131 // Only increment by the size of MIB in V3.
132 Ptr += MEMPROF_V3_MIB_SIZE;
133 }
134 return Items;
135 }
136
137 llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>
readMemInfoBlocksV4(const char * Ptr)138 readMemInfoBlocksV4(const char *Ptr) {
139 using namespace support;
140
141 const uint64_t NumItemsToRead =
142 endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);
143
144 llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;
145 for (uint64_t I = 0; I < NumItemsToRead; I++) {
146 const uint64_t Id =
147 endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);
148 // We cheat a bit here and remove the const from cast to set the
149 // Histogram Pointer to newly allocated buffer.
150 MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);
151
152 // Only increment by size of MIB since readNext implicitly increments.
153 Ptr += sizeof(MemInfoBlock);
154
155 if (MIB.AccessHistogramSize > 0) {
156 MIB.AccessHistogram =
157 (uintptr_t)malloc(MIB.AccessHistogramSize * sizeof(uint64_t));
158 }
159
160 for (uint64_t J = 0; J < MIB.AccessHistogramSize; J++) {
161 ((uint64_t *)MIB.AccessHistogram)[J] =
162 endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);
163 }
164 Items.push_back({Id, MIB});
165 }
166 return Items;
167 }
168
readStackInfo(const char * Ptr)169 CallStackMap readStackInfo(const char *Ptr) {
170 using namespace support;
171
172 const uint64_t NumItemsToRead =
173 endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
174 CallStackMap Items;
175
176 for (uint64_t I = 0; I < NumItemsToRead; I++) {
177 const uint64_t StackId =
178 endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
179 const uint64_t NumPCs =
180 endian::readNext<uint64_t, llvm::endianness::little>(Ptr);
181
182 SmallVector<uint64_t> CallStack;
183 CallStack.reserve(NumPCs);
184 for (uint64_t J = 0; J < NumPCs; J++) {
185 CallStack.push_back(
186 endian::readNext<uint64_t, llvm::endianness::little>(Ptr));
187 }
188
189 Items[StackId] = CallStack;
190 }
191 return Items;
192 }
193
194 // Merges the contents of stack information in \p From to \p To. Returns true if
195 // any stack ids observed previously map to a different set of program counter
196 // addresses.
mergeStackMap(const CallStackMap & From,CallStackMap & To)197 bool mergeStackMap(const CallStackMap &From, CallStackMap &To) {
198 for (const auto &[Id, Stack] : From) {
199 auto [It, Inserted] = To.try_emplace(Id, Stack);
200 // Check that the PCs are the same (in order).
201 if (!Inserted && Stack != It->second)
202 return true;
203 }
204 return false;
205 }
206
report(Error E,const StringRef Context)207 Error report(Error E, const StringRef Context) {
208 return joinErrors(createStringError(inconvertibleErrorCode(), Context),
209 std::move(E));
210 }
211
isRuntimePath(const StringRef Path)212 bool isRuntimePath(const StringRef Path) {
213 const StringRef Filename = llvm::sys::path::filename(Path);
214 // This list should be updated in case new files with additional interceptors
215 // are added to the memprof runtime.
216 return Filename == "memprof_malloc_linux.cpp" ||
217 Filename == "memprof_interceptors.cpp" ||
218 Filename == "memprof_new_delete.cpp";
219 }
220
getBuildIdString(const SegmentEntry & Entry)221 std::string getBuildIdString(const SegmentEntry &Entry) {
222 // If the build id is unset print a helpful string instead of all zeros.
223 if (Entry.BuildIdSize == 0)
224 return "<None>";
225
226 std::string Str;
227 raw_string_ostream OS(Str);
228 for (size_t I = 0; I < Entry.BuildIdSize; I++) {
229 OS << format_hex_no_prefix(Entry.BuildId[I], 2);
230 }
231 return OS.str();
232 }
233 } // namespace
234
235 Expected<std::unique_ptr<RawMemProfReader>>
create(const Twine & Path,const StringRef ProfiledBinary,bool KeepName)236 RawMemProfReader::create(const Twine &Path, const StringRef ProfiledBinary,
237 bool KeepName) {
238 auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
239 if (std::error_code EC = BufferOr.getError())
240 return report(errorCodeToError(EC), Path.getSingleStringRef());
241
242 std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
243 return create(std::move(Buffer), ProfiledBinary, KeepName);
244 }
245
246 Expected<std::unique_ptr<RawMemProfReader>>
create(std::unique_ptr<MemoryBuffer> Buffer,const StringRef ProfiledBinary,bool KeepName)247 RawMemProfReader::create(std::unique_ptr<MemoryBuffer> Buffer,
248 const StringRef ProfiledBinary, bool KeepName) {
249 if (Error E = checkBuffer(*Buffer))
250 return report(std::move(E), Buffer->getBufferIdentifier());
251
252 if (ProfiledBinary.empty()) {
253 // Peek the build ids to print a helpful error message.
254 const std::vector<std::string> BuildIds = peekBuildIds(Buffer.get());
255 std::string ErrorMessage(
256 R"(Path to profiled binary is empty, expected binary with one of the following build ids:
257 )");
258 for (const auto &Id : BuildIds) {
259 ErrorMessage += "\n BuildId: ";
260 ErrorMessage += Id;
261 }
262 return report(
263 make_error<StringError>(ErrorMessage, inconvertibleErrorCode()),
264 /*Context=*/"");
265 }
266
267 auto BinaryOr = llvm::object::createBinary(ProfiledBinary);
268 if (!BinaryOr) {
269 return report(BinaryOr.takeError(), ProfiledBinary);
270 }
271
272 // Use new here since constructor is private.
273 std::unique_ptr<RawMemProfReader> Reader(
274 new RawMemProfReader(std::move(BinaryOr.get()), KeepName));
275 if (Error E = Reader->initialize(std::move(Buffer))) {
276 return std::move(E);
277 }
278 return std::move(Reader);
279 }
280
281 // We need to make sure that all leftover MIB histograms that have not been
282 // freed by merge are freed here.
~RawMemProfReader()283 RawMemProfReader::~RawMemProfReader() {
284 for (auto &[_, MIB] : CallstackProfileData) {
285 if (MemprofRawVersion >= 4ULL && MIB.AccessHistogramSize > 0) {
286 free((void *)MIB.AccessHistogram);
287 }
288 }
289 }
290
hasFormat(const StringRef Path)291 bool RawMemProfReader::hasFormat(const StringRef Path) {
292 auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
293 if (!BufferOr)
294 return false;
295
296 std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
297 return hasFormat(*Buffer);
298 }
299
hasFormat(const MemoryBuffer & Buffer)300 bool RawMemProfReader::hasFormat(const MemoryBuffer &Buffer) {
301 if (Buffer.getBufferSize() < sizeof(uint64_t))
302 return false;
303 // Aligned read to sanity check that the buffer was allocated with at least 8b
304 // alignment.
305 const uint64_t Magic = alignedRead(Buffer.getBufferStart());
306 return Magic == MEMPROF_RAW_MAGIC_64;
307 }
308
printYAML(raw_ostream & OS)309 void RawMemProfReader::printYAML(raw_ostream &OS) {
310 MemProfSummaryBuilder MemProfSumBuilder;
311 uint64_t NumAllocFunctions = 0, NumMibInfo = 0;
312 for (const auto &KV : MemProfData.Records) {
313 MemProfSumBuilder.addRecord(KV.second);
314 const size_t NumAllocSites = KV.second.AllocSites.size();
315 if (NumAllocSites > 0) {
316 NumAllocFunctions++;
317 NumMibInfo += NumAllocSites;
318 }
319 }
320
321 // Print the summary first, as it is printed as YAML comments.
322 auto MemProfSum = MemProfSumBuilder.getSummary();
323 MemProfSum->printSummaryYaml(OS);
324
325 OS << "MemprofProfile:\n";
326 OS << " Summary:\n";
327 OS << " Version: " << MemprofRawVersion << "\n";
328 OS << " NumSegments: " << SegmentInfo.size() << "\n";
329 OS << " NumMibInfo: " << NumMibInfo << "\n";
330 OS << " NumAllocFunctions: " << NumAllocFunctions << "\n";
331 OS << " NumStackOffsets: " << StackMap.size() << "\n";
332 // Print out the segment information.
333 OS << " Segments:\n";
334 for (const auto &Entry : SegmentInfo) {
335 OS << " -\n";
336 OS << " BuildId: " << getBuildIdString(Entry) << "\n";
337 OS << " Start: 0x" << llvm::utohexstr(Entry.Start) << "\n";
338 OS << " End: 0x" << llvm::utohexstr(Entry.End) << "\n";
339 OS << " Offset: 0x" << llvm::utohexstr(Entry.Offset) << "\n";
340 }
341 // Print out the merged contents of the profiles.
342 OS << " Records:\n";
343 for (const auto &[GUID, Record] : *this) {
344 OS << " -\n";
345 OS << " FunctionGUID: " << GUID << "\n";
346 Record.print(OS);
347 }
348 }
349
initialize(std::unique_ptr<MemoryBuffer> DataBuffer)350 Error RawMemProfReader::initialize(std::unique_ptr<MemoryBuffer> DataBuffer) {
351 const StringRef FileName = Binary.getBinary()->getFileName();
352
353 auto *ElfObject = dyn_cast<object::ELFObjectFileBase>(Binary.getBinary());
354 if (!ElfObject) {
355 return report(make_error<StringError>(Twine("Not an ELF file: "),
356 inconvertibleErrorCode()),
357 FileName);
358 }
359
360 // Check whether the profiled binary was built with position independent code
361 // (PIC). Perform sanity checks for assumptions we rely on to simplify
362 // symbolization.
363 auto *Elf64LEObject = llvm::cast<llvm::object::ELF64LEObjectFile>(ElfObject);
364 const llvm::object::ELF64LEFile &ElfFile = Elf64LEObject->getELFFile();
365 auto PHdrsOr = ElfFile.program_headers();
366 if (!PHdrsOr)
367 return report(
368 make_error<StringError>(Twine("Could not read program headers: "),
369 inconvertibleErrorCode()),
370 FileName);
371
372 int NumExecutableSegments = 0;
373 for (const auto &Phdr : *PHdrsOr) {
374 if (Phdr.p_type == ELF::PT_LOAD) {
375 if (Phdr.p_flags & ELF::PF_X) {
376 // We assume only one text segment in the main binary for simplicity and
377 // reduce the overhead of checking multiple ranges during symbolization.
378 if (++NumExecutableSegments > 1) {
379 return report(
380 make_error<StringError>(
381 "Expect only one executable load segment in the binary",
382 inconvertibleErrorCode()),
383 FileName);
384 }
385 // Segment will always be loaded at a page boundary, expect it to be
386 // aligned already. Assume 4K pagesize for the machine from which the
387 // profile has been collected. This should be fine for now, in case we
388 // want to support other pagesizes it can be recorded in the raw profile
389 // during collection.
390 PreferredTextSegmentAddress = Phdr.p_vaddr;
391 assert(Phdr.p_vaddr == (Phdr.p_vaddr & ~(0x1000 - 1U)) &&
392 "Expect p_vaddr to always be page aligned");
393 assert(Phdr.p_offset == 0 && "Expect p_offset = 0 for symbolization.");
394 }
395 }
396 }
397
398 auto Triple = ElfObject->makeTriple();
399 if (!Triple.isX86())
400 return report(make_error<StringError>(Twine("Unsupported target: ") +
401 Triple.getArchName(),
402 inconvertibleErrorCode()),
403 FileName);
404
405 // Process the raw profile.
406 if (Error E = readRawProfile(std::move(DataBuffer)))
407 return E;
408
409 if (Error E = setupForSymbolization())
410 return E;
411
412 auto *Object = cast<object::ObjectFile>(Binary.getBinary());
413 std::unique_ptr<DIContext> Context = DWARFContext::create(
414 *Object, DWARFContext::ProcessDebugRelocations::Process);
415
416 auto SOFOr = symbolize::SymbolizableObjectFile::create(
417 Object, std::move(Context), /*UntagAddresses=*/false);
418 if (!SOFOr)
419 return report(SOFOr.takeError(), FileName);
420 auto Symbolizer = std::move(SOFOr.get());
421
422 // The symbolizer ownership is moved into symbolizeAndFilterStackFrames so
423 // that it is freed automatically at the end, when it is no longer used. This
424 // reduces peak memory since it won't be live while also mapping the raw
425 // profile into records afterwards.
426 if (Error E = symbolizeAndFilterStackFrames(std::move(Symbolizer)))
427 return E;
428
429 return mapRawProfileToRecords();
430 }
431
setupForSymbolization()432 Error RawMemProfReader::setupForSymbolization() {
433 auto *Object = cast<object::ObjectFile>(Binary.getBinary());
434 object::BuildIDRef BinaryId = object::getBuildID(Object);
435 if (BinaryId.empty())
436 return make_error<StringError>(Twine("No build id found in binary ") +
437 Binary.getBinary()->getFileName(),
438 inconvertibleErrorCode());
439
440 int NumMatched = 0;
441 for (const auto &Entry : SegmentInfo) {
442 llvm::ArrayRef<uint8_t> SegmentId(Entry.BuildId, Entry.BuildIdSize);
443 if (BinaryId == SegmentId) {
444 // We assume only one text segment in the main binary for simplicity and
445 // reduce the overhead of checking multiple ranges during symbolization.
446 if (++NumMatched > 1) {
447 return make_error<StringError>(
448 "We expect only one executable segment in the profiled binary",
449 inconvertibleErrorCode());
450 }
451 ProfiledTextSegmentStart = Entry.Start;
452 ProfiledTextSegmentEnd = Entry.End;
453 }
454 }
455 if (NumMatched == 0)
456 return make_error<StringError>(
457 Twine("No matching executable segments found in binary ") +
458 Binary.getBinary()->getFileName(),
459 inconvertibleErrorCode());
460 assert((PreferredTextSegmentAddress == 0 ||
461 (PreferredTextSegmentAddress == ProfiledTextSegmentStart)) &&
462 "Expect text segment address to be 0 or equal to profiled text "
463 "segment start.");
464 return Error::success();
465 }
466
mapRawProfileToRecords()467 Error RawMemProfReader::mapRawProfileToRecords() {
468 // Hold a mapping from function to each callsite location we encounter within
469 // it that is part of some dynamic allocation context. The location is stored
470 // as a pointer to a symbolized list of inline frames.
471 using LocationPtr = const llvm::SmallVector<FrameId> *;
472 llvm::MapVector<GlobalValue::GUID, llvm::SetVector<LocationPtr>>
473 PerFunctionCallSites;
474
475 // Convert the raw profile callstack data into memprof records. While doing so
476 // keep track of related contexts so that we can fill these in later.
477 for (const auto &[StackId, MIB] : CallstackProfileData) {
478 auto It = StackMap.find(StackId);
479 if (It == StackMap.end())
480 return make_error<InstrProfError>(
481 instrprof_error::malformed,
482 "memprof callstack record does not contain id: " + Twine(StackId));
483
484 // Construct the symbolized callstack.
485 llvm::SmallVector<FrameId> Callstack;
486 Callstack.reserve(It->getSecond().size());
487
488 llvm::ArrayRef<uint64_t> Addresses = It->getSecond();
489 for (size_t I = 0; I < Addresses.size(); I++) {
490 const uint64_t Address = Addresses[I];
491 assert(SymbolizedFrame.count(Address) > 0 &&
492 "Address not found in SymbolizedFrame map");
493 const SmallVector<FrameId> &Frames = SymbolizedFrame[Address];
494
495 assert(!idToFrame(Frames.back()).IsInlineFrame &&
496 "The last frame should not be inlined");
497
498 // Record the callsites for each function. Skip the first frame of the
499 // first address since it is the allocation site itself that is recorded
500 // as an alloc site.
501 for (size_t J = 0; J < Frames.size(); J++) {
502 if (I == 0 && J == 0)
503 continue;
504 // We attach the entire bottom-up frame here for the callsite even
505 // though we only need the frames up to and including the frame for
506 // Frames[J].Function. This will enable better deduplication for
507 // compression in the future.
508 const GlobalValue::GUID Guid = idToFrame(Frames[J]).Function;
509 PerFunctionCallSites[Guid].insert(&Frames);
510 }
511
512 // Add all the frames to the current allocation callstack.
513 Callstack.append(Frames.begin(), Frames.end());
514 }
515
516 CallStackId CSId = MemProfData.addCallStack(Callstack);
517
518 // We attach the memprof record to each function bottom-up including the
519 // first non-inline frame.
520 for (size_t I = 0; /*Break out using the condition below*/; I++) {
521 const Frame &F = idToFrame(Callstack[I]);
522 IndexedMemProfRecord &Record = MemProfData.Records[F.Function];
523 Record.AllocSites.emplace_back(CSId, MIB);
524
525 if (!F.IsInlineFrame)
526 break;
527 }
528 }
529
530 // Fill in the related callsites per function.
531 for (const auto &[Id, Locs] : PerFunctionCallSites) {
532 // Some functions may have only callsite data and no allocation data. Here
533 // we insert a new entry for callsite data if we need to.
534 IndexedMemProfRecord &Record = MemProfData.Records[Id];
535 for (LocationPtr Loc : Locs)
536 Record.CallSites.emplace_back(MemProfData.addCallStack(*Loc));
537 }
538
539 return Error::success();
540 }
541
symbolizeAndFilterStackFrames(std::unique_ptr<llvm::symbolize::SymbolizableModule> Symbolizer)542 Error RawMemProfReader::symbolizeAndFilterStackFrames(
543 std::unique_ptr<llvm::symbolize::SymbolizableModule> Symbolizer) {
544 // The specifier to use when symbolization is requested.
545 const DILineInfoSpecifier Specifier(
546 DILineInfoSpecifier::FileLineInfoKind::RawValue,
547 DILineInfoSpecifier::FunctionNameKind::LinkageName);
548
549 // For entries where all PCs in the callstack are discarded, we erase the
550 // entry from the stack map.
551 llvm::SmallVector<uint64_t> EntriesToErase;
552 // We keep track of all prior discarded entries so that we can avoid invoking
553 // the symbolizer for such entries.
554 llvm::DenseSet<uint64_t> AllVAddrsToDiscard;
555 for (auto &Entry : StackMap) {
556 for (const uint64_t VAddr : Entry.getSecond()) {
557 // Check if we have already symbolized and cached the result or if we
558 // don't want to attempt symbolization since we know this address is bad.
559 // In this case the address is also removed from the current callstack.
560 if (SymbolizedFrame.count(VAddr) > 0 ||
561 AllVAddrsToDiscard.contains(VAddr))
562 continue;
563
564 Expected<DIInliningInfo> DIOr = Symbolizer->symbolizeInlinedCode(
565 getModuleOffset(VAddr), Specifier, /*UseSymbolTable=*/false);
566 if (!DIOr)
567 return DIOr.takeError();
568 DIInliningInfo DI = DIOr.get();
569
570 // Drop frames which we can't symbolize or if they belong to the runtime.
571 if (DI.getFrame(0).FunctionName == DILineInfo::BadString ||
572 isRuntimePath(DI.getFrame(0).FileName)) {
573 AllVAddrsToDiscard.insert(VAddr);
574 continue;
575 }
576
577 for (size_t I = 0, NumFrames = DI.getNumberOfFrames(); I < NumFrames;
578 I++) {
579 const auto &DIFrame = DI.getFrame(I);
580 const uint64_t Guid = memprof::getGUID(DIFrame.FunctionName);
581 const Frame F(Guid, DIFrame.Line - DIFrame.StartLine, DIFrame.Column,
582 // Only the last entry is not an inlined location.
583 I != NumFrames - 1);
584 // Here we retain a mapping from the GUID to canonical symbol name
585 // instead of adding it to the frame object directly to reduce memory
586 // overhead. This is because there can be many unique frames,
587 // particularly for callsite frames.
588 if (KeepSymbolName) {
589 StringRef CanonicalName =
590 sampleprof::FunctionSamples::getCanonicalFnName(
591 DIFrame.FunctionName);
592 GuidToSymbolName.insert({Guid, CanonicalName.str()});
593 }
594
595 SymbolizedFrame[VAddr].push_back(MemProfData.addFrame(F));
596 }
597 }
598
599 auto &CallStack = Entry.getSecond();
600 llvm::erase_if(CallStack, [&AllVAddrsToDiscard](const uint64_t A) {
601 return AllVAddrsToDiscard.contains(A);
602 });
603 if (CallStack.empty())
604 EntriesToErase.push_back(Entry.getFirst());
605 }
606
607 // Drop the entries where the callstack is empty.
608 for (const uint64_t Id : EntriesToErase) {
609 StackMap.erase(Id);
610 if (auto It = CallstackProfileData.find(Id);
611 It != CallstackProfileData.end()) {
612 if (It->second.AccessHistogramSize > 0)
613 free((void *)It->second.AccessHistogram);
614 CallstackProfileData.erase(It);
615 }
616 }
617
618 if (StackMap.empty())
619 return make_error<InstrProfError>(
620 instrprof_error::malformed,
621 "no entries in callstack map after symbolization");
622
623 return Error::success();
624 }
625
626 std::vector<std::string>
peekBuildIds(MemoryBuffer * DataBuffer)627 RawMemProfReader::peekBuildIds(MemoryBuffer *DataBuffer) {
628 const char *Next = DataBuffer->getBufferStart();
629 // Use a SetVector since a profile file may contain multiple raw profile
630 // dumps, each with segment information. We want them unique and in order they
631 // were stored in the profile; the profiled binary should be the first entry.
632 // The runtime uses dl_iterate_phdr and the "... first object visited by
633 // callback is the main program."
634 // https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
635 llvm::SetVector<std::string, std::vector<std::string>,
636 llvm::SmallSet<std::string, 10>>
637 BuildIds;
638 while (Next < DataBuffer->getBufferEnd()) {
639 const auto *Header = reinterpret_cast<const memprof::Header *>(Next);
640
641 const llvm::SmallVector<SegmentEntry> Entries =
642 readSegmentEntries(Next + Header->SegmentOffset);
643
644 for (const auto &Entry : Entries)
645 BuildIds.insert(getBuildIdString(Entry));
646
647 Next += Header->TotalSize;
648 }
649 return BuildIds.takeVector();
650 }
651
652 // FIXME: Add a schema for serializing similiar to IndexedMemprofReader. This
653 // will help being able to deserialize different versions raw memprof versions
654 // more easily.
655 llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>
readMemInfoBlocks(const char * Ptr)656 RawMemProfReader::readMemInfoBlocks(const char *Ptr) {
657 if (MemprofRawVersion == 3ULL)
658 return readMemInfoBlocksV3(Ptr);
659 if (MemprofRawVersion == 4ULL)
660 return readMemInfoBlocksV4(Ptr);
661 llvm_unreachable(
662 "Panic: Unsupported version number when reading MemInfoBlocks");
663 }
664
readRawProfile(std::unique_ptr<MemoryBuffer> DataBuffer)665 Error RawMemProfReader::readRawProfile(
666 std::unique_ptr<MemoryBuffer> DataBuffer) {
667 const char *Next = DataBuffer->getBufferStart();
668
669 while (Next < DataBuffer->getBufferEnd()) {
670 const auto *Header = reinterpret_cast<const memprof::Header *>(Next);
671
672 // Set Reader version to memprof raw version of profile. Checking if version
673 // is supported is checked before creating the reader.
674 MemprofRawVersion = Header->Version;
675
676 // Read in the segment information, check whether its the same across all
677 // profiles in this binary file.
678 const llvm::SmallVector<SegmentEntry> Entries =
679 readSegmentEntries(Next + Header->SegmentOffset);
680 if (!SegmentInfo.empty() && SegmentInfo != Entries) {
681 // We do not expect segment information to change when deserializing from
682 // the same binary profile file. This can happen if dynamic libraries are
683 // loaded/unloaded between profile dumping.
684 return make_error<InstrProfError>(
685 instrprof_error::malformed,
686 "memprof raw profile has different segment information");
687 }
688 SegmentInfo.assign(Entries.begin(), Entries.end());
689
690 // Read in the MemInfoBlocks. Merge them based on stack id - we assume that
691 // raw profiles in the same binary file are from the same process so the
692 // stackdepot ids are the same.
693 for (const auto &[Id, MIB] : readMemInfoBlocks(Next + Header->MIBOffset)) {
694 if (CallstackProfileData.count(Id)) {
695
696 if (MemprofRawVersion >= 4ULL &&
697 (CallstackProfileData[Id].AccessHistogramSize > 0 ||
698 MIB.AccessHistogramSize > 0)) {
699 uintptr_t ShorterHistogram;
700 if (CallstackProfileData[Id].AccessHistogramSize >
701 MIB.AccessHistogramSize)
702 ShorterHistogram = MIB.AccessHistogram;
703 else
704 ShorterHistogram = CallstackProfileData[Id].AccessHistogram;
705 CallstackProfileData[Id].Merge(MIB);
706 free((void *)ShorterHistogram);
707 } else {
708 CallstackProfileData[Id].Merge(MIB);
709 }
710 } else {
711 CallstackProfileData[Id] = MIB;
712 }
713 }
714
715 // Read in the callstack for each ids. For multiple raw profiles in the same
716 // file, we expect that the callstack is the same for a unique id.
717 const CallStackMap CSM = readStackInfo(Next + Header->StackOffset);
718 if (StackMap.empty()) {
719 StackMap = CSM;
720 } else {
721 if (mergeStackMap(CSM, StackMap))
722 return make_error<InstrProfError>(
723 instrprof_error::malformed,
724 "memprof raw profile got different call stack for same id");
725 }
726
727 Next += Header->TotalSize;
728 }
729
730 return Error::success();
731 }
732
733 object::SectionedAddress
getModuleOffset(const uint64_t VirtualAddress)734 RawMemProfReader::getModuleOffset(const uint64_t VirtualAddress) {
735 if (VirtualAddress > ProfiledTextSegmentStart &&
736 VirtualAddress <= ProfiledTextSegmentEnd) {
737 // For PIE binaries, the preferred address is zero and we adjust the virtual
738 // address by start of the profiled segment assuming that the offset of the
739 // segment in the binary is zero. For non-PIE binaries the preferred and
740 // profiled segment addresses should be equal and this is a no-op.
741 const uint64_t AdjustedAddress =
742 VirtualAddress + PreferredTextSegmentAddress - ProfiledTextSegmentStart;
743 return object::SectionedAddress{AdjustedAddress};
744 }
745 // Addresses which do not originate from the profiled text segment in the
746 // binary are not adjusted. These will fail symbolization and be filtered out
747 // during processing.
748 return object::SectionedAddress{VirtualAddress};
749 }
750
readNextRecord(GuidMemProfRecordPair & GuidRecord,std::function<const Frame (const FrameId)> Callback)751 Error RawMemProfReader::readNextRecord(
752 GuidMemProfRecordPair &GuidRecord,
753 std::function<const Frame(const FrameId)> Callback) {
754 // Create a new callback for the RawMemProfRecord iterator so that we can
755 // provide the symbol name if the reader was initialized with KeepSymbolName =
756 // true. This is useful for debugging and testing.
757 auto IdToFrameCallback = [this](const FrameId Id) {
758 Frame F = this->idToFrame(Id);
759 if (!this->KeepSymbolName)
760 return F;
761 auto Iter = this->GuidToSymbolName.find(F.Function);
762 assert(Iter != this->GuidToSymbolName.end());
763 F.SymbolName = std::make_unique<std::string>(Iter->getSecond());
764 return F;
765 };
766 return MemProfReader::readNextRecord(GuidRecord, IdToFrameCallback);
767 }
768
769 Expected<std::unique_ptr<YAMLMemProfReader>>
create(const Twine & Path)770 YAMLMemProfReader::create(const Twine &Path) {
771 auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path, /*IsText=*/true);
772 if (std::error_code EC = BufferOr.getError())
773 return report(errorCodeToError(EC), Path.getSingleStringRef());
774
775 std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
776 return create(std::move(Buffer));
777 }
778
779 Expected<std::unique_ptr<YAMLMemProfReader>>
create(std::unique_ptr<MemoryBuffer> Buffer)780 YAMLMemProfReader::create(std::unique_ptr<MemoryBuffer> Buffer) {
781 auto Reader = std::make_unique<YAMLMemProfReader>();
782 Reader->parse(Buffer->getBuffer());
783 return std::move(Reader);
784 }
785
hasFormat(const StringRef Path)786 bool YAMLMemProfReader::hasFormat(const StringRef Path) {
787 auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path, /*IsText=*/true);
788 if (!BufferOr)
789 return false;
790
791 std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
792 return hasFormat(*Buffer);
793 }
794
hasFormat(const MemoryBuffer & Buffer)795 bool YAMLMemProfReader::hasFormat(const MemoryBuffer &Buffer) {
796 return Buffer.getBuffer().starts_with("---");
797 }
798
parse(StringRef YAMLData)799 void YAMLMemProfReader::parse(StringRef YAMLData) {
800 memprof::AllMemProfData Doc;
801 yaml::Input Yin(YAMLData);
802
803 Yin >> Doc;
804 if (Yin.error())
805 return;
806
807 // Add a call stack to MemProfData.CallStacks and return its CallStackId.
808 auto AddCallStack = [&](ArrayRef<Frame> CallStack) -> CallStackId {
809 SmallVector<FrameId> IndexedCallStack;
810 IndexedCallStack.reserve(CallStack.size());
811 for (const Frame &F : CallStack)
812 IndexedCallStack.push_back(MemProfData.addFrame(F));
813 return MemProfData.addCallStack(std::move(IndexedCallStack));
814 };
815
816 for (const auto &[GUID, Record] : Doc.HeapProfileRecords) {
817 IndexedMemProfRecord IndexedRecord;
818
819 // Convert AllocationInfo to IndexedAllocationInfo.
820 for (const AllocationInfo &AI : Record.AllocSites) {
821 CallStackId CSId = AddCallStack(AI.CallStack);
822 IndexedRecord.AllocSites.emplace_back(CSId, AI.Info);
823 }
824
825 // Populate CallSites with CalleeGuids.
826 for (const auto &CallSite : Record.CallSites) {
827 CallStackId CSId = AddCallStack(CallSite.Frames);
828 IndexedRecord.CallSites.emplace_back(CSId, CallSite.CalleeGuids);
829 }
830
831 MemProfData.Records.try_emplace(GUID, std::move(IndexedRecord));
832 }
833
834 if (Doc.YamlifiedDataAccessProfiles.isEmpty())
835 return;
836
837 auto ToSymHandleRef =
838 [](const memprof::SymbolHandle &Handle) -> memprof::SymbolHandleRef {
839 if (std::holds_alternative<std::string>(Handle))
840 return StringRef(std::get<std::string>(Handle));
841 return std::get<uint64_t>(Handle);
842 };
843
844 auto DataAccessProfileData = std::make_unique<memprof::DataAccessProfData>();
845 for (const auto &Record : Doc.YamlifiedDataAccessProfiles.Records)
846 if (Error E = DataAccessProfileData->setDataAccessProfile(
847 ToSymHandleRef(Record.SymHandle), Record.AccessCount,
848 Record.Locations))
849 reportFatalInternalError(std::move(E));
850
851 for (const uint64_t Hash : Doc.YamlifiedDataAccessProfiles.KnownColdStrHashes)
852 if (Error E = DataAccessProfileData->addKnownSymbolWithoutSamples(Hash))
853 reportFatalInternalError(std::move(E));
854
855 for (const std::string &Sym :
856 Doc.YamlifiedDataAccessProfiles.KnownColdSymbols)
857 if (Error E = DataAccessProfileData->addKnownSymbolWithoutSamples(Sym))
858 reportFatalInternalError(std::move(E));
859
860 setDataAccessProfileData(std::move(DataAccessProfileData));
861 }
862 } // namespace memprof
863 } // namespace llvm
864