123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- //===- RawMemProfReader.cpp - Instrumented memory profiling reader --------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- //
- // This file contains support for reading MemProf profiling data.
- //
- //===----------------------------------------------------------------------===//
- #include <algorithm>
- #include <cstdint>
- #include <memory>
- #include <type_traits>
- #include "llvm/ADT/ArrayRef.h"
- #include "llvm/ADT/DenseMap.h"
- #include "llvm/ADT/SetVector.h"
- #include "llvm/ADT/SmallVector.h"
- #include "llvm/ADT/StringExtras.h"
- #include "llvm/DebugInfo/DWARF/DWARFContext.h"
- #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
- #include "llvm/DebugInfo/Symbolize/SymbolizableObjectFile.h"
- #include "llvm/Object/Binary.h"
- #include "llvm/Object/ELFObjectFile.h"
- #include "llvm/Object/ObjectFile.h"
- #include "llvm/ProfileData/InstrProf.h"
- #include "llvm/ProfileData/MemProf.h"
- #include "llvm/ProfileData/MemProfData.inc"
- #include "llvm/ProfileData/RawMemProfReader.h"
- #include "llvm/Support/Endian.h"
- #include "llvm/Support/Path.h"
- #define DEBUG_TYPE "memprof"
- namespace llvm {
- namespace memprof {
- namespace {
- template <class T = uint64_t> inline T alignedRead(const char *Ptr) {
- static_assert(std::is_pod<T>::value, "Not a pod type.");
- assert(reinterpret_cast<size_t>(Ptr) % sizeof(T) == 0 && "Unaligned Read");
- return *reinterpret_cast<const T *>(Ptr);
- }
- Error checkBuffer(const MemoryBuffer &Buffer) {
- if (!RawMemProfReader::hasFormat(Buffer))
- return make_error<InstrProfError>(instrprof_error::bad_magic);
- if (Buffer.getBufferSize() == 0)
- return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
- if (Buffer.getBufferSize() < sizeof(Header)) {
- return make_error<InstrProfError>(instrprof_error::truncated);
- }
- // The size of the buffer can be > header total size since we allow repeated
- // serialization of memprof profiles to the same file.
- uint64_t TotalSize = 0;
- const char *Next = Buffer.getBufferStart();
- while (Next < Buffer.getBufferEnd()) {
- auto *H = reinterpret_cast<const Header *>(Next);
- if (H->Version != MEMPROF_RAW_VERSION) {
- return make_error<InstrProfError>(instrprof_error::unsupported_version);
- }
- TotalSize += H->TotalSize;
- Next += H->TotalSize;
- }
- if (Buffer.getBufferSize() != TotalSize) {
- return make_error<InstrProfError>(instrprof_error::malformed);
- }
- return Error::success();
- }
- llvm::SmallVector<SegmentEntry> readSegmentEntries(const char *Ptr) {
- using namespace support;
- const uint64_t NumItemsToRead =
- endian::readNext<uint64_t, little, unaligned>(Ptr);
- llvm::SmallVector<SegmentEntry> Items;
- for (uint64_t I = 0; I < NumItemsToRead; I++) {
- Items.push_back(*reinterpret_cast<const SegmentEntry *>(
- Ptr + I * sizeof(SegmentEntry)));
- }
- return Items;
- }
- llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>
- readMemInfoBlocks(const char *Ptr) {
- using namespace support;
- const uint64_t NumItemsToRead =
- endian::readNext<uint64_t, little, unaligned>(Ptr);
- llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;
- for (uint64_t I = 0; I < NumItemsToRead; I++) {
- const uint64_t Id = endian::readNext<uint64_t, little, unaligned>(Ptr);
- const MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);
- Items.push_back({Id, MIB});
- // Only increment by size of MIB since readNext implicitly increments.
- Ptr += sizeof(MemInfoBlock);
- }
- return Items;
- }
- CallStackMap readStackInfo(const char *Ptr) {
- using namespace support;
- const uint64_t NumItemsToRead =
- endian::readNext<uint64_t, little, unaligned>(Ptr);
- CallStackMap Items;
- for (uint64_t I = 0; I < NumItemsToRead; I++) {
- const uint64_t StackId = endian::readNext<uint64_t, little, unaligned>(Ptr);
- const uint64_t NumPCs = endian::readNext<uint64_t, little, unaligned>(Ptr);
- SmallVector<uint64_t> CallStack;
- for (uint64_t J = 0; J < NumPCs; J++) {
- CallStack.push_back(endian::readNext<uint64_t, little, unaligned>(Ptr));
- }
- Items[StackId] = CallStack;
- }
- return Items;
- }
- // Merges the contents of stack information in \p From to \p To. Returns true if
- // any stack ids observed previously map to a different set of program counter
- // addresses.
- bool mergeStackMap(const CallStackMap &From, CallStackMap &To) {
- for (const auto &IdStack : From) {
- auto I = To.find(IdStack.first);
- if (I == To.end()) {
- To[IdStack.first] = IdStack.second;
- } else {
- // Check that the PCs are the same (in order).
- if (IdStack.second != I->second)
- return true;
- }
- }
- return false;
- }
- Error report(Error E, const StringRef Context) {
- return joinErrors(createStringError(inconvertibleErrorCode(), Context),
- std::move(E));
- }
- bool isRuntimePath(const StringRef Path) {
- return StringRef(llvm::sys::path::convert_to_slash(Path))
- .contains("memprof/memprof_");
- }
- std::string getBuildIdString(const SegmentEntry &Entry) {
- constexpr size_t Size = sizeof(Entry.BuildId) / sizeof(uint8_t);
- constexpr uint8_t Zeros[Size] = {0};
- // If the build id is unset print a helpful string instead of all zeros.
- if (memcmp(Entry.BuildId, Zeros, Size) == 0)
- return "<None>";
- std::string Str;
- raw_string_ostream OS(Str);
- for (size_t I = 0; I < Size; I++) {
- OS << format_hex_no_prefix(Entry.BuildId[I], 2);
- }
- return OS.str();
- }
- } // namespace
- Expected<std::unique_ptr<RawMemProfReader>>
- RawMemProfReader::create(const Twine &Path, const StringRef ProfiledBinary,
- bool KeepName) {
- auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
- if (std::error_code EC = BufferOr.getError())
- return report(errorCodeToError(EC), Path.getSingleStringRef());
- std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
- if (Error E = checkBuffer(*Buffer))
- return report(std::move(E), Path.getSingleStringRef());
- if (ProfiledBinary.empty())
- return report(
- errorCodeToError(make_error_code(std::errc::invalid_argument)),
- "Path to profiled binary is empty!");
- auto BinaryOr = llvm::object::createBinary(ProfiledBinary);
- if (!BinaryOr) {
- return report(BinaryOr.takeError(), ProfiledBinary);
- }
- // Use new here since constructor is private.
- std::unique_ptr<RawMemProfReader> Reader(
- new RawMemProfReader(std::move(BinaryOr.get()), KeepName));
- if (Error E = Reader->initialize(std::move(Buffer))) {
- return std::move(E);
- }
- return std::move(Reader);
- }
- bool RawMemProfReader::hasFormat(const StringRef Path) {
- auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);
- if (!BufferOr)
- return false;
- std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());
- return hasFormat(*Buffer);
- }
- bool RawMemProfReader::hasFormat(const MemoryBuffer &Buffer) {
- if (Buffer.getBufferSize() < sizeof(uint64_t))
- return false;
- // Aligned read to sanity check that the buffer was allocated with at least 8b
- // alignment.
- const uint64_t Magic = alignedRead(Buffer.getBufferStart());
- return Magic == MEMPROF_RAW_MAGIC_64;
- }
- void RawMemProfReader::printYAML(raw_ostream &OS) {
- uint64_t NumAllocFunctions = 0, NumMibInfo = 0;
- for (const auto &KV : FunctionProfileData) {
- const size_t NumAllocSites = KV.second.AllocSites.size();
- if (NumAllocSites > 0) {
- NumAllocFunctions++;
- NumMibInfo += NumAllocSites;
- }
- }
- OS << "MemprofProfile:\n";
- OS << " Summary:\n";
- OS << " Version: " << MEMPROF_RAW_VERSION << "\n";
- OS << " NumSegments: " << SegmentInfo.size() << "\n";
- OS << " NumMibInfo: " << NumMibInfo << "\n";
- OS << " NumAllocFunctions: " << NumAllocFunctions << "\n";
- OS << " NumStackOffsets: " << StackMap.size() << "\n";
- // Print out the segment information.
- OS << " Segments:\n";
- for (const auto &Entry : SegmentInfo) {
- OS << " -\n";
- OS << " BuildId: " << getBuildIdString(Entry) << "\n";
- OS << " Start: 0x" << llvm::utohexstr(Entry.Start) << "\n";
- OS << " End: 0x" << llvm::utohexstr(Entry.End) << "\n";
- OS << " Offset: 0x" << llvm::utohexstr(Entry.Offset) << "\n";
- }
- // Print out the merged contents of the profiles.
- OS << " Records:\n";
- for (const auto &Entry : *this) {
- OS << " -\n";
- OS << " FunctionGUID: " << Entry.first << "\n";
- Entry.second.print(OS);
- }
- }
- Error RawMemProfReader::initialize(std::unique_ptr<MemoryBuffer> DataBuffer) {
- const StringRef FileName = Binary.getBinary()->getFileName();
- auto *ElfObject = dyn_cast<object::ELFObjectFileBase>(Binary.getBinary());
- if (!ElfObject) {
- return report(make_error<StringError>(Twine("Not an ELF file: "),
- inconvertibleErrorCode()),
- FileName);
- }
- // Check whether the profiled binary was built with position independent code
- // (PIC). For now we provide a error message until symbolization support
- // is added for pic.
- auto* Elf64LEObject = llvm::cast<llvm::object::ELF64LEObjectFile>(ElfObject);
- const llvm::object::ELF64LEFile& ElfFile = Elf64LEObject->getELFFile();
- auto PHdrsOr = ElfFile.program_headers();
- if(!PHdrsOr)
- return report(make_error<StringError>(Twine("Could not read program headers: "),
- inconvertibleErrorCode()),
- FileName);
- auto FirstLoadHeader = PHdrsOr->begin();
- while (FirstLoadHeader->p_type != llvm::ELF::PT_LOAD)
- ++FirstLoadHeader;
- if(FirstLoadHeader->p_vaddr == 0)
- return report(make_error<StringError>(Twine("Unsupported position independent code"),
- inconvertibleErrorCode()),
- FileName);
- auto Triple = ElfObject->makeTriple();
- if (!Triple.isX86())
- return report(make_error<StringError>(Twine("Unsupported target: ") +
- Triple.getArchName(),
- inconvertibleErrorCode()),
- FileName);
- auto *Object = cast<object::ObjectFile>(Binary.getBinary());
- std::unique_ptr<DIContext> Context = DWARFContext::create(
- *Object, DWARFContext::ProcessDebugRelocations::Process);
- auto SOFOr = symbolize::SymbolizableObjectFile::create(
- Object, std::move(Context), /*UntagAddresses=*/false);
- if (!SOFOr)
- return report(SOFOr.takeError(), FileName);
- Symbolizer = std::move(SOFOr.get());
- if (Error E = readRawProfile(std::move(DataBuffer)))
- return E;
- if (Error E = symbolizeAndFilterStackFrames())
- return E;
- return mapRawProfileToRecords();
- }
- Error RawMemProfReader::mapRawProfileToRecords() {
- // Hold a mapping from function to each callsite location we encounter within
- // it that is part of some dynamic allocation context. The location is stored
- // as a pointer to a symbolized list of inline frames.
- using LocationPtr = const llvm::SmallVector<FrameId> *;
- llvm::MapVector<GlobalValue::GUID, llvm::SetVector<LocationPtr>>
- PerFunctionCallSites;
- // Convert the raw profile callstack data into memprof records. While doing so
- // keep track of related contexts so that we can fill these in later.
- for (const auto &Entry : CallstackProfileData) {
- const uint64_t StackId = Entry.first;
- auto It = StackMap.find(StackId);
- if (It == StackMap.end())
- return make_error<InstrProfError>(
- instrprof_error::malformed,
- "memprof callstack record does not contain id: " + Twine(StackId));
- // Construct the symbolized callstack.
- llvm::SmallVector<FrameId> Callstack;
- Callstack.reserve(It->getSecond().size());
- llvm::ArrayRef<uint64_t> Addresses = It->getSecond();
- for (size_t I = 0; I < Addresses.size(); I++) {
- const uint64_t Address = Addresses[I];
- assert(SymbolizedFrame.count(Address) > 0 &&
- "Address not found in SymbolizedFrame map");
- const SmallVector<FrameId> &Frames = SymbolizedFrame[Address];
- assert(!idToFrame(Frames.back()).IsInlineFrame &&
- "The last frame should not be inlined");
- // Record the callsites for each function. Skip the first frame of the
- // first address since it is the allocation site itself that is recorded
- // as an alloc site.
- for (size_t J = 0; J < Frames.size(); J++) {
- if (I == 0 && J == 0)
- continue;
- // We attach the entire bottom-up frame here for the callsite even
- // though we only need the frames up to and including the frame for
- // Frames[J].Function. This will enable better deduplication for
- // compression in the future.
- const GlobalValue::GUID Guid = idToFrame(Frames[J]).Function;
- PerFunctionCallSites[Guid].insert(&Frames);
- }
- // Add all the frames to the current allocation callstack.
- Callstack.append(Frames.begin(), Frames.end());
- }
- // We attach the memprof record to each function bottom-up including the
- // first non-inline frame.
- for (size_t I = 0; /*Break out using the condition below*/; I++) {
- const Frame &F = idToFrame(Callstack[I]);
- auto Result =
- FunctionProfileData.insert({F.Function, IndexedMemProfRecord()});
- IndexedMemProfRecord &Record = Result.first->second;
- Record.AllocSites.emplace_back(Callstack, Entry.second);
- if (!F.IsInlineFrame)
- break;
- }
- }
- // Fill in the related callsites per function.
- for (const auto &[Id, Locs] : PerFunctionCallSites) {
- // Some functions may have only callsite data and no allocation data. Here
- // we insert a new entry for callsite data if we need to.
- auto Result = FunctionProfileData.insert({Id, IndexedMemProfRecord()});
- IndexedMemProfRecord &Record = Result.first->second;
- for (LocationPtr Loc : Locs) {
- Record.CallSites.push_back(*Loc);
- }
- }
- return Error::success();
- }
- Error RawMemProfReader::symbolizeAndFilterStackFrames() {
- // The specifier to use when symbolization is requested.
- const DILineInfoSpecifier Specifier(
- DILineInfoSpecifier::FileLineInfoKind::RawValue,
- DILineInfoSpecifier::FunctionNameKind::LinkageName);
- // For entries where all PCs in the callstack are discarded, we erase the
- // entry from the stack map.
- llvm::SmallVector<uint64_t> EntriesToErase;
- // We keep track of all prior discarded entries so that we can avoid invoking
- // the symbolizer for such entries.
- llvm::DenseSet<uint64_t> AllVAddrsToDiscard;
- for (auto &Entry : StackMap) {
- for (const uint64_t VAddr : Entry.getSecond()) {
- // Check if we have already symbolized and cached the result or if we
- // don't want to attempt symbolization since we know this address is bad.
- // In this case the address is also removed from the current callstack.
- if (SymbolizedFrame.count(VAddr) > 0 ||
- AllVAddrsToDiscard.contains(VAddr))
- continue;
- Expected<DIInliningInfo> DIOr = Symbolizer->symbolizeInlinedCode(
- getModuleOffset(VAddr), Specifier, /*UseSymbolTable=*/false);
- if (!DIOr)
- return DIOr.takeError();
- DIInliningInfo DI = DIOr.get();
- // Drop frames which we can't symbolize or if they belong to the runtime.
- if (DI.getFrame(0).FunctionName == DILineInfo::BadString ||
- isRuntimePath(DI.getFrame(0).FileName)) {
- AllVAddrsToDiscard.insert(VAddr);
- continue;
- }
- for (size_t I = 0, NumFrames = DI.getNumberOfFrames(); I < NumFrames;
- I++) {
- const auto &DIFrame = DI.getFrame(I);
- const uint64_t Guid =
- IndexedMemProfRecord::getGUID(DIFrame.FunctionName);
- const Frame F(Guid, DIFrame.Line - DIFrame.StartLine, DIFrame.Column,
- // Only the last entry is not an inlined location.
- I != NumFrames - 1);
- // Here we retain a mapping from the GUID to symbol name instead of
- // adding it to the frame object directly to reduce memory overhead.
- // This is because there can be many unique frames, particularly for
- // callsite frames.
- if (KeepSymbolName)
- GuidToSymbolName.insert({Guid, DIFrame.FunctionName});
- const FrameId Hash = F.hash();
- IdToFrame.insert({Hash, F});
- SymbolizedFrame[VAddr].push_back(Hash);
- }
- }
- auto &CallStack = Entry.getSecond();
- llvm::erase_if(CallStack, [&AllVAddrsToDiscard](const uint64_t A) {
- return AllVAddrsToDiscard.contains(A);
- });
- if (CallStack.empty())
- EntriesToErase.push_back(Entry.getFirst());
- }
- // Drop the entries where the callstack is empty.
- for (const uint64_t Id : EntriesToErase) {
- StackMap.erase(Id);
- CallstackProfileData.erase(Id);
- }
- if (StackMap.empty())
- return make_error<InstrProfError>(
- instrprof_error::malformed,
- "no entries in callstack map after symbolization");
- return Error::success();
- }
- Error RawMemProfReader::readRawProfile(
- std::unique_ptr<MemoryBuffer> DataBuffer) {
- const char *Next = DataBuffer->getBufferStart();
- while (Next < DataBuffer->getBufferEnd()) {
- auto *Header = reinterpret_cast<const memprof::Header *>(Next);
- // Read in the segment information, check whether its the same across all
- // profiles in this binary file.
- const llvm::SmallVector<SegmentEntry> Entries =
- readSegmentEntries(Next + Header->SegmentOffset);
- if (!SegmentInfo.empty() && SegmentInfo != Entries) {
- // We do not expect segment information to change when deserializing from
- // the same binary profile file. This can happen if dynamic libraries are
- // loaded/unloaded between profile dumping.
- return make_error<InstrProfError>(
- instrprof_error::malformed,
- "memprof raw profile has different segment information");
- }
- SegmentInfo.assign(Entries.begin(), Entries.end());
- // Read in the MemInfoBlocks. Merge them based on stack id - we assume that
- // raw profiles in the same binary file are from the same process so the
- // stackdepot ids are the same.
- for (const auto &Value : readMemInfoBlocks(Next + Header->MIBOffset)) {
- if (CallstackProfileData.count(Value.first)) {
- CallstackProfileData[Value.first].Merge(Value.second);
- } else {
- CallstackProfileData[Value.first] = Value.second;
- }
- }
- // Read in the callstack for each ids. For multiple raw profiles in the same
- // file, we expect that the callstack is the same for a unique id.
- const CallStackMap CSM = readStackInfo(Next + Header->StackOffset);
- if (StackMap.empty()) {
- StackMap = CSM;
- } else {
- if (mergeStackMap(CSM, StackMap))
- return make_error<InstrProfError>(
- instrprof_error::malformed,
- "memprof raw profile got different call stack for same id");
- }
- Next += Header->TotalSize;
- }
- return Error::success();
- }
- object::SectionedAddress
- RawMemProfReader::getModuleOffset(const uint64_t VirtualAddress) {
- LLVM_DEBUG({
- SegmentEntry *ContainingSegment = nullptr;
- for (auto &SE : SegmentInfo) {
- if (VirtualAddress > SE.Start && VirtualAddress <= SE.End) {
- ContainingSegment = &SE;
- }
- }
- // Ensure that the virtual address is valid.
- assert(ContainingSegment && "Could not find a segment entry");
- });
- // TODO: Compute the file offset based on the maps and program headers. For
- // now this only works for non PIE binaries.
- return object::SectionedAddress{VirtualAddress};
- }
- Error RawMemProfReader::readNextRecord(GuidMemProfRecordPair &GuidRecord) {
- if (FunctionProfileData.empty())
- return make_error<InstrProfError>(instrprof_error::empty_raw_profile);
- if (Iter == FunctionProfileData.end())
- return make_error<InstrProfError>(instrprof_error::eof);
- auto IdToFrameCallback = [this](const FrameId Id) {
- Frame F = this->idToFrame(Id);
- if (!this->KeepSymbolName)
- return F;
- auto Iter = this->GuidToSymbolName.find(F.Function);
- assert(Iter != this->GuidToSymbolName.end());
- F.SymbolName = Iter->getSecond();
- return F;
- };
- const IndexedMemProfRecord &IndexedRecord = Iter->second;
- GuidRecord = {Iter->first, MemProfRecord(IndexedRecord, IdToFrameCallback)};
- Iter++;
- return Error::success();
- }
- } // namespace memprof
- } // namespace llvm
|