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