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 <algorithm>
14 #include <cstdint>
15 #include <type_traits>
16 
17 #include "llvm/ADT/ArrayRef.h"
18 #include "llvm/ADT/DenseMap.h"
19 #include "llvm/ADT/SmallVector.h"
20 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
21 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
22 #include "llvm/DebugInfo/Symbolize/SymbolizableObjectFile.h"
23 #include "llvm/Object/Binary.h"
24 #include "llvm/Object/ELFObjectFile.h"
25 #include "llvm/Object/ObjectFile.h"
26 #include "llvm/ProfileData/InstrProf.h"
27 #include "llvm/ProfileData/MemProf.h"
28 #include "llvm/ProfileData/MemProfData.inc"
29 #include "llvm/ProfileData/RawMemProfReader.h"
30 #include "llvm/Support/Endian.h"
31 #include "llvm/Support/Path.h"
32 
33 #define DEBUG_TYPE "memprof"
34 
35 namespace llvm {
36 namespace memprof {
37 namespace {
38 
39 struct Summary {
40   uint64_t Version;
41   uint64_t TotalSizeBytes;
42   uint64_t NumSegments;
43   uint64_t NumMIBInfo;
44   uint64_t NumStackOffsets;
45 };
46 
47 template <class T = uint64_t> inline T alignedRead(const char *Ptr) {
48   static_assert(std::is_pod<T>::value, "Not a pod type.");
49   assert(reinterpret_cast<size_t>(Ptr) % sizeof(T) == 0 && "Unaligned Read");
50   return *reinterpret_cast<const T *>(Ptr);
51 }
52 
53 Summary computeSummary(const char *Start) {
54   auto *H = reinterpret_cast<const Header *>(Start);
55 
56   // Check alignment while reading the number of items in each section.
57   return Summary{
58       H->Version,
59       H->TotalSize,
60       alignedRead(Start + H->SegmentOffset),
61       alignedRead(Start + H->MIBOffset),
62       alignedRead(Start + H->StackOffset),
63   };
64 }
65 
66 Error checkBuffer(const MemoryBuffer &Buffer) {
67   if (!RawMemProfReader::hasFormat(Buffer))
68     return make_error<InstrProfError>(instrprof_error::bad_magic);
69 
70   if (Buffer.getBufferSize() == 0)
71     return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
72 
73   if (Buffer.getBufferSize() < sizeof(Header)) {
74     return make_error<InstrProfError>(instrprof_error::truncated);
75   }
76 
77   // The size of the buffer can be > header total size since we allow repeated
78   // serialization of memprof profiles to the same file.
79   uint64_t TotalSize = 0;
80   const char *Next = Buffer.getBufferStart();
81   while (Next < Buffer.getBufferEnd()) {
82     auto *H = reinterpret_cast<const Header *>(Next);
83     if (H->Version != MEMPROF_RAW_VERSION) {
84       return make_error<InstrProfError>(instrprof_error::unsupported_version);
85     }
86 
87     TotalSize += H->TotalSize;
88     Next += H->TotalSize;
89   }
90 
91   if (Buffer.getBufferSize() != TotalSize) {
92     return make_error<InstrProfError>(instrprof_error::malformed);
93   }
94   return Error::success();
95 }
96 
97 llvm::SmallVector<SegmentEntry> readSegmentEntries(const char *Ptr) {
98   using namespace support;
99 
100   const uint64_t NumItemsToRead =
101       endian::readNext<uint64_t, little, unaligned>(Ptr);
102   llvm::SmallVector<SegmentEntry> Items;
103   for (uint64_t I = 0; I < NumItemsToRead; I++) {
104     Items.push_back(*reinterpret_cast<const SegmentEntry *>(
105         Ptr + I * sizeof(SegmentEntry)));
106   }
107   return Items;
108 }
109 
110 llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>
111 readMemInfoBlocks(const char *Ptr) {
112   using namespace support;
113 
114   const uint64_t NumItemsToRead =
115       endian::readNext<uint64_t, little, unaligned>(Ptr);
116   llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;
117   for (uint64_t I = 0; I < NumItemsToRead; I++) {
118     const uint64_t Id = endian::readNext<uint64_t, little, unaligned>(Ptr);
119     const MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);
120     Items.push_back({Id, MIB});
121     // Only increment by size of MIB since readNext implicitly increments.
122     Ptr += sizeof(MemInfoBlock);
123   }
124   return Items;
125 }
126 
127 CallStackMap readStackInfo(const char *Ptr) {
128   using namespace support;
129 
130   const uint64_t NumItemsToRead =
131       endian::readNext<uint64_t, little, unaligned>(Ptr);
132   CallStackMap Items;
133 
134   for (uint64_t I = 0; I < NumItemsToRead; I++) {
135     const uint64_t StackId = endian::readNext<uint64_t, little, unaligned>(Ptr);
136     const uint64_t NumPCs = endian::readNext<uint64_t, little, unaligned>(Ptr);
137 
138     SmallVector<uint64_t> CallStack;
139     for (uint64_t J = 0; J < NumPCs; J++) {
140       CallStack.push_back(endian::readNext<uint64_t, little, unaligned>(Ptr));
141     }
142 
143     Items[StackId] = CallStack;
144   }
145   return Items;
146 }
147 
148 // Merges the contents of stack information in \p From to \p To. Returns true if
149 // any stack ids observed previously map to a different set of program counter
150 // addresses.
151 bool mergeStackMap(const CallStackMap &From, CallStackMap &To) {
152   for (const auto &IdStack : From) {
153     auto I = To.find(IdStack.first);
154     if (I == To.end()) {
155       To[IdStack.first] = IdStack.second;
156     } else {
157       // Check that the PCs are the same (in order).
158       if (IdStack.second != I->second)
159         return true;
160     }
161   }
162   return false;
163 }
164 
165 Error report(Error E, const StringRef Context) {
166   return joinErrors(createStringError(inconvertibleErrorCode(), Context),
167                     std::move(E));
168 }
169 
170 bool isRuntimePath(const StringRef Path) {
171   return StringRef(llvm::sys::path::convert_to_slash(Path))
172       .contains("memprof/memprof_");
173 }
174 } // namespace
175 
176 Expected<std::unique_ptr<RawMemProfReader>>
177 RawMemProfReader::create(const Twine &Path, const StringRef ProfiledBinary) {
178   auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
179   if (std::error_code EC = BufferOr.getError())
180     return report(errorCodeToError(EC), Path.getSingleStringRef());
181 
182   std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
183   if (Error E = checkBuffer(*Buffer))
184     return report(std::move(E), Path.getSingleStringRef());
185 
186   if (ProfiledBinary.empty())
187     return report(
188         errorCodeToError(make_error_code(std::errc::invalid_argument)),
189         "Path to profiled binary is empty!");
190 
191   auto BinaryOr = llvm::object::createBinary(ProfiledBinary);
192   if (!BinaryOr) {
193     return report(BinaryOr.takeError(), ProfiledBinary);
194   }
195 
196   std::unique_ptr<RawMemProfReader> Reader(
197       new RawMemProfReader(std::move(Buffer), std::move(BinaryOr.get())));
198   if (Error E = Reader->initialize()) {
199     return std::move(E);
200   }
201   return std::move(Reader);
202 }
203 
204 bool RawMemProfReader::hasFormat(const StringRef Path) {
205   auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
206   if (!BufferOr)
207     return false;
208 
209   std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
210   return hasFormat(*Buffer);
211 }
212 
213 bool RawMemProfReader::hasFormat(const MemoryBuffer &Buffer) {
214   if (Buffer.getBufferSize() < sizeof(uint64_t))
215     return false;
216   // Aligned read to sanity check that the buffer was allocated with at least 8b
217   // alignment.
218   const uint64_t Magic = alignedRead(Buffer.getBufferStart());
219   return Magic == MEMPROF_RAW_MAGIC_64;
220 }
221 
222 void RawMemProfReader::printYAML(raw_ostream &OS) {
223   OS << "MemprofProfile:\n";
224   // TODO: Update printSummaries to print out the data after the profile has
225   // been symbolized and pruned. We can parse some raw profile characteristics
226   // from the data buffer for additional information.
227   printSummaries(OS);
228   // Print out the merged contents of the profiles.
229   OS << "  Records:\n";
230   for (const auto &Entry : *this) {
231     OS << "  -\n";
232     OS << "    FunctionGUID: " << Entry.first << "\n";
233     Entry.second.print(OS);
234   }
235 }
236 
237 void RawMemProfReader::printSummaries(raw_ostream &OS) const {
238   const char *Next = DataBuffer->getBufferStart();
239   while (Next < DataBuffer->getBufferEnd()) {
240     auto Summary = computeSummary(Next);
241     OS << "  -\n";
242     OS << "  Header:\n";
243     OS << "    Version: " << Summary.Version << "\n";
244     OS << "    TotalSizeBytes: " << Summary.TotalSizeBytes << "\n";
245     OS << "    NumSegments: " << Summary.NumSegments << "\n";
246     OS << "    NumMibInfo: " << Summary.NumMIBInfo << "\n";
247     OS << "    NumStackOffsets: " << Summary.NumStackOffsets << "\n";
248     // TODO: Print the build ids once we can record them using the
249     // sanitizer_procmaps library for linux.
250 
251     auto *H = reinterpret_cast<const Header *>(Next);
252     Next += H->TotalSize;
253   }
254 }
255 
256 Error RawMemProfReader::initialize() {
257   const StringRef FileName = Binary.getBinary()->getFileName();
258 
259   auto *ElfObject = dyn_cast<object::ELFObjectFileBase>(Binary.getBinary());
260   if (!ElfObject) {
261     return report(make_error<StringError>(Twine("Not an ELF file: "),
262                                           inconvertibleErrorCode()),
263                   FileName);
264   }
265 
266   auto Triple = ElfObject->makeTriple();
267   if (!Triple.isX86())
268     return report(make_error<StringError>(Twine("Unsupported target: ") +
269                                               Triple.getArchName(),
270                                           inconvertibleErrorCode()),
271                   FileName);
272 
273   auto *Object = cast<object::ObjectFile>(Binary.getBinary());
274   std::unique_ptr<DIContext> Context = DWARFContext::create(
275       *Object, DWARFContext::ProcessDebugRelocations::Process);
276 
277   auto SOFOr = symbolize::SymbolizableObjectFile::create(
278       Object, std::move(Context), /*UntagAddresses=*/false);
279   if (!SOFOr)
280     return report(SOFOr.takeError(), FileName);
281   Symbolizer = std::move(SOFOr.get());
282 
283   if (Error E = readRawProfile())
284     return E;
285 
286   if (Error E = symbolizeAndFilterStackFrames())
287     return E;
288 
289   return mapRawProfileToRecords();
290 }
291 
292 Error RawMemProfReader::mapRawProfileToRecords() {
293   // Hold a mapping from function to each callsite location we encounter within
294   // it that is part of some dynamic allocation context. The location is stored
295   // as a pointer to a symbolized list of inline frames.
296   using LocationPtr = const llvm::SmallVector<MemProfRecord::Frame> *;
297   llvm::DenseMap<GlobalValue::GUID, llvm::SetVector<LocationPtr>>
298       PerFunctionCallSites;
299 
300   // Convert the raw profile callstack data into memprof records. While doing so
301   // keep track of related contexts so that we can fill these in later.
302   for (const auto &Entry : CallstackProfileData) {
303     const uint64_t StackId = Entry.first;
304 
305     auto It = StackMap.find(StackId);
306     if (It == StackMap.end())
307       return make_error<InstrProfError>(
308           instrprof_error::malformed,
309           "memprof callstack record does not contain id: " + Twine(StackId));
310 
311     // Construct the symbolized callstack.
312     llvm::SmallVector<MemProfRecord::Frame> Callstack;
313     Callstack.reserve(It->getSecond().size());
314 
315     llvm::ArrayRef<uint64_t> Addresses = It->getSecond();
316     for (size_t I = 0; I < Addresses.size(); I++) {
317       const uint64_t Address = Addresses[I];
318       assert(SymbolizedFrame.count(Address) > 0 &&
319              "Address not found in SymbolizedFrame map");
320       const SmallVector<MemProfRecord::Frame> &Frames =
321           SymbolizedFrame[Address];
322 
323       assert(!Frames.back().IsInlineFrame &&
324              "The last frame should not be inlined");
325 
326       // Record the callsites for each function. Skip the first frame of the
327       // first address since it is the allocation site itself that is recorded
328       // as an alloc site.
329       for (size_t J = 0; J < Frames.size(); J++) {
330         if (I == 0 && J == 0)
331           continue;
332         // We attach the entire bottom-up frame here for the callsite even
333         // though we only need the frames up to and including the frame for
334         // Frames[J].Function. This will enable better deduplication for
335         // compression in the future.
336         PerFunctionCallSites[Frames[J].Function].insert(&Frames);
337       }
338 
339       // Add all the frames to the current allocation callstack.
340       Callstack.append(Frames.begin(), Frames.end());
341     }
342 
343     // We attach the memprof record to each function bottom-up including the
344     // first non-inline frame.
345     for (size_t I = 0; /*Break out using the condition below*/; I++) {
346       auto Result =
347           FunctionProfileData.insert({Callstack[I].Function, MemProfRecord()});
348       MemProfRecord &Record = Result.first->second;
349       Record.AllocSites.emplace_back(Callstack, Entry.second);
350 
351       if (!Callstack[I].IsInlineFrame)
352         break;
353     }
354   }
355 
356   // Fill in the related callsites per function.
357   for (auto I = PerFunctionCallSites.begin(), E = PerFunctionCallSites.end();
358        I != E; I++) {
359     const GlobalValue::GUID Id = I->first;
360     // Some functions may have only callsite data and no allocation data. Here
361     // we insert a new entry for callsite data if we need to.
362     auto Result = FunctionProfileData.insert({Id, MemProfRecord()});
363     MemProfRecord &Record = Result.first->second;
364     for (LocationPtr Loc : I->getSecond()) {
365       Record.CallSites.push_back(*Loc);
366     }
367   }
368 
369   return Error::success();
370 }
371 
372 Error RawMemProfReader::symbolizeAndFilterStackFrames() {
373   // The specifier to use when symbolization is requested.
374   const DILineInfoSpecifier Specifier(
375       DILineInfoSpecifier::FileLineInfoKind::RawValue,
376       DILineInfoSpecifier::FunctionNameKind::LinkageName);
377 
378   // For entries where all PCs in the callstack are discarded, we erase the
379   // entry from the stack map.
380   llvm::SmallVector<uint64_t> EntriesToErase;
381   // We keep track of all prior discarded entries so that we can avoid invoking
382   // the symbolizer for such entries.
383   llvm::DenseSet<uint64_t> AllVAddrsToDiscard;
384   for (auto &Entry : StackMap) {
385     for (const uint64_t VAddr : Entry.getSecond()) {
386       // Check if we have already symbolized and cached the result or if we
387       // don't want to attempt symbolization since we know this address is bad.
388       // In this case the address is also removed from the current callstack.
389       if (SymbolizedFrame.count(VAddr) > 0 ||
390           AllVAddrsToDiscard.contains(VAddr))
391         continue;
392 
393       Expected<DIInliningInfo> DIOr = Symbolizer->symbolizeInlinedCode(
394           getModuleOffset(VAddr), Specifier, /*UseSymbolTable=*/false);
395       if (!DIOr)
396         return DIOr.takeError();
397       DIInliningInfo DI = DIOr.get();
398 
399       // Drop frames which we can't symbolize or if they belong to the runtime.
400       if (DI.getFrame(0).FunctionName == DILineInfo::BadString ||
401           isRuntimePath(DI.getFrame(0).FileName)) {
402         AllVAddrsToDiscard.insert(VAddr);
403         continue;
404       }
405 
406       for (size_t I = 0, NumFrames = DI.getNumberOfFrames(); I < NumFrames;
407            I++) {
408         const auto &Frame = DI.getFrame(I);
409         LLVM_DEBUG(
410             // Print out the name to guid mapping for debugging.
411             llvm::dbgs() << "FunctionName: " << Frame.FunctionName << " GUID: "
412                          << MemProfRecord::getGUID(Frame.FunctionName)
413                          << "\n";);
414         SymbolizedFrame[VAddr].emplace_back(
415             MemProfRecord::getGUID(Frame.FunctionName),
416             Frame.Line - Frame.StartLine, Frame.Column,
417             // Only the last entry is not an inlined location.
418             I != NumFrames - 1);
419       }
420     }
421 
422     auto &CallStack = Entry.getSecond();
423     CallStack.erase(std::remove_if(CallStack.begin(), CallStack.end(),
424                                    [&AllVAddrsToDiscard](const uint64_t A) {
425                                      return AllVAddrsToDiscard.contains(A);
426                                    }),
427                     CallStack.end());
428     if (CallStack.empty())
429       EntriesToErase.push_back(Entry.getFirst());
430   }
431 
432   // Drop the entries where the callstack is empty.
433   for (const uint64_t Id : EntriesToErase) {
434     StackMap.erase(Id);
435     CallstackProfileData.erase(Id);
436   }
437 
438   if (StackMap.empty())
439     return make_error<InstrProfError>(
440         instrprof_error::malformed,
441         "no entries in callstack map after symbolization");
442 
443   return Error::success();
444 }
445 
446 Error RawMemProfReader::readRawProfile() {
447   const char *Next = DataBuffer->getBufferStart();
448 
449   while (Next < DataBuffer->getBufferEnd()) {
450     auto *Header = reinterpret_cast<const memprof::Header *>(Next);
451 
452     // Read in the segment information, check whether its the same across all
453     // profiles in this binary file.
454     const llvm::SmallVector<SegmentEntry> Entries =
455         readSegmentEntries(Next + Header->SegmentOffset);
456     if (!SegmentInfo.empty() && SegmentInfo != Entries) {
457       // We do not expect segment information to change when deserializing from
458       // the same binary profile file. This can happen if dynamic libraries are
459       // loaded/unloaded between profile dumping.
460       return make_error<InstrProfError>(
461           instrprof_error::malformed,
462           "memprof raw profile has different segment information");
463     }
464     SegmentInfo.assign(Entries.begin(), Entries.end());
465 
466     // Read in the MemInfoBlocks. Merge them based on stack id - we assume that
467     // raw profiles in the same binary file are from the same process so the
468     // stackdepot ids are the same.
469     for (const auto &Value : readMemInfoBlocks(Next + Header->MIBOffset)) {
470       if (CallstackProfileData.count(Value.first)) {
471         CallstackProfileData[Value.first].Merge(Value.second);
472       } else {
473         CallstackProfileData[Value.first] = Value.second;
474       }
475     }
476 
477     // Read in the callstack for each ids. For multiple raw profiles in the same
478     // file, we expect that the callstack is the same for a unique id.
479     const CallStackMap CSM = readStackInfo(Next + Header->StackOffset);
480     if (StackMap.empty()) {
481       StackMap = CSM;
482     } else {
483       if (mergeStackMap(CSM, StackMap))
484         return make_error<InstrProfError>(
485             instrprof_error::malformed,
486             "memprof raw profile got different call stack for same id");
487     }
488 
489     Next += Header->TotalSize;
490   }
491 
492   return Error::success();
493 }
494 
495 object::SectionedAddress
496 RawMemProfReader::getModuleOffset(const uint64_t VirtualAddress) {
497   LLVM_DEBUG({
498   SegmentEntry *ContainingSegment = nullptr;
499   for (auto &SE : SegmentInfo) {
500     if (VirtualAddress > SE.Start && VirtualAddress <= SE.End) {
501       ContainingSegment = &SE;
502     }
503   }
504 
505   // Ensure that the virtual address is valid.
506   assert(ContainingSegment && "Could not find a segment entry");
507   });
508 
509   // TODO: Compute the file offset based on the maps and program headers. For
510   // now this only works for non PIE binaries.
511   return object::SectionedAddress{VirtualAddress};
512 }
513 
514 Error RawMemProfReader::readNextRecord(GuidMemProfRecordPair &GuidRecord) {
515   if (FunctionProfileData.empty())
516     return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
517 
518   if (Iter == FunctionProfileData.end())
519     return make_error<InstrProfError>(instrprof_error::eof);
520 
521   GuidRecord = {Iter->first, Iter->second};
522   Iter++;
523   return Error::success();
524 }
525 } // namespace memprof
526 } // namespace llvm
527