Path: blob/main/contrib/llvm-project/llvm/lib/ProfileData/MemProfReader.cpp
35233 views
//===- RawMemProfReader.cpp - Instrumented memory profiling reader --------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file contains support for reading MemProf profiling data.9//10//===----------------------------------------------------------------------===//1112#include <algorithm>13#include <cstdint>14#include <memory>15#include <type_traits>1617#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/SampleProf.h"36#include "llvm/Support/Debug.h"37#include "llvm/Support/Endian.h"38#include "llvm/Support/Error.h"39#include "llvm/Support/MemoryBuffer.h"40#include "llvm/Support/Path.h"4142#define DEBUG_TYPE "memprof"43namespace llvm {44namespace memprof {45namespace {46template <class T = uint64_t> inline T alignedRead(const char *Ptr) {47static_assert(std::is_pod<T>::value, "Not a pod type.");48assert(reinterpret_cast<size_t>(Ptr) % sizeof(T) == 0 && "Unaligned Read");49return *reinterpret_cast<const T *>(Ptr);50}5152Error checkBuffer(const MemoryBuffer &Buffer) {53if (!RawMemProfReader::hasFormat(Buffer))54return make_error<InstrProfError>(instrprof_error::bad_magic);5556if (Buffer.getBufferSize() == 0)57return make_error<InstrProfError>(instrprof_error::empty_raw_profile);5859if (Buffer.getBufferSize() < sizeof(Header)) {60return make_error<InstrProfError>(instrprof_error::truncated);61}6263// The size of the buffer can be > header total size since we allow repeated64// serialization of memprof profiles to the same file.65uint64_t TotalSize = 0;66const char *Next = Buffer.getBufferStart();67while (Next < Buffer.getBufferEnd()) {68const auto *H = reinterpret_cast<const Header *>(Next);6970// Check if the version in header is among the supported versions.71bool IsSupported = false;72for (auto SupportedVersion : MEMPROF_RAW_SUPPORTED_VERSIONS) {73if (H->Version == SupportedVersion)74IsSupported = true;75}76if (!IsSupported) {77return make_error<InstrProfError>(instrprof_error::unsupported_version);78}7980TotalSize += H->TotalSize;81Next += H->TotalSize;82}8384if (Buffer.getBufferSize() != TotalSize) {85return make_error<InstrProfError>(instrprof_error::malformed);86}87return Error::success();88}8990llvm::SmallVector<SegmentEntry> readSegmentEntries(const char *Ptr) {91using namespace support;9293const uint64_t NumItemsToRead =94endian::readNext<uint64_t, llvm::endianness::little>(Ptr);95llvm::SmallVector<SegmentEntry> Items;96for (uint64_t I = 0; I < NumItemsToRead; I++) {97Items.push_back(*reinterpret_cast<const SegmentEntry *>(98Ptr + I * sizeof(SegmentEntry)));99}100return Items;101}102103llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>104readMemInfoBlocksV3(const char *Ptr) {105using namespace support;106107const uint64_t NumItemsToRead =108endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);109110llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;111for (uint64_t I = 0; I < NumItemsToRead; I++) {112const uint64_t Id =113endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);114115// We cheat a bit here and remove the const from cast to set the116// Histogram Pointer to newly allocated buffer. We also cheat, since V3 and117// V4 do not have the same fields. V3 is missing AccessHistogramSize and118// AccessHistogram. This means we read "dirty" data in here, but it should119// not segfault, since there will be callstack data placed after this in the120// binary format.121MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);122// Overwrite dirty data.123MIB.AccessHistogramSize = 0;124MIB.AccessHistogram = 0;125126Items.push_back({Id, MIB});127// Only increment by the size of MIB in V3.128Ptr += MEMPROF_V3_MIB_SIZE;129}130return Items;131}132133llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>134readMemInfoBlocksV4(const char *Ptr) {135using namespace support;136137const uint64_t NumItemsToRead =138endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);139140llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>> Items;141for (uint64_t I = 0; I < NumItemsToRead; I++) {142const uint64_t Id =143endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);144// We cheat a bit here and remove the const from cast to set the145// Histogram Pointer to newly allocated buffer.146MemInfoBlock MIB = *reinterpret_cast<const MemInfoBlock *>(Ptr);147148// Only increment by size of MIB since readNext implicitly increments.149Ptr += sizeof(MemInfoBlock);150151if (MIB.AccessHistogramSize > 0) {152MIB.AccessHistogram =153(uintptr_t)malloc(MIB.AccessHistogramSize * sizeof(uint64_t));154}155156for (uint64_t J = 0; J < MIB.AccessHistogramSize; J++) {157((uint64_t *)MIB.AccessHistogram)[J] =158endian::readNext<uint64_t, llvm::endianness::little, unaligned>(Ptr);159}160Items.push_back({Id, MIB});161}162return Items;163}164165CallStackMap readStackInfo(const char *Ptr) {166using namespace support;167168const uint64_t NumItemsToRead =169endian::readNext<uint64_t, llvm::endianness::little>(Ptr);170CallStackMap Items;171172for (uint64_t I = 0; I < NumItemsToRead; I++) {173const uint64_t StackId =174endian::readNext<uint64_t, llvm::endianness::little>(Ptr);175const uint64_t NumPCs =176endian::readNext<uint64_t, llvm::endianness::little>(Ptr);177178SmallVector<uint64_t> CallStack;179CallStack.reserve(NumPCs);180for (uint64_t J = 0; J < NumPCs; J++) {181CallStack.push_back(182endian::readNext<uint64_t, llvm::endianness::little>(Ptr));183}184185Items[StackId] = CallStack;186}187return Items;188}189190// Merges the contents of stack information in \p From to \p To. Returns true if191// any stack ids observed previously map to a different set of program counter192// addresses.193bool mergeStackMap(const CallStackMap &From, CallStackMap &To) {194for (const auto &[Id, Stack] : From) {195auto I = To.find(Id);196if (I == To.end()) {197To[Id] = Stack;198} else {199// Check that the PCs are the same (in order).200if (Stack != I->second)201return true;202}203}204return false;205}206207Error report(Error E, const StringRef Context) {208return joinErrors(createStringError(inconvertibleErrorCode(), Context),209std::move(E));210}211212bool isRuntimePath(const StringRef Path) {213const StringRef Filename = llvm::sys::path::filename(Path);214// This list should be updated in case new files with additional interceptors215// are added to the memprof runtime.216return Filename == "memprof_malloc_linux.cpp" ||217Filename == "memprof_interceptors.cpp" ||218Filename == "memprof_new_delete.cpp";219}220221std::string getBuildIdString(const SegmentEntry &Entry) {222// If the build id is unset print a helpful string instead of all zeros.223if (Entry.BuildIdSize == 0)224return "<None>";225226std::string Str;227raw_string_ostream OS(Str);228for (size_t I = 0; I < Entry.BuildIdSize; I++) {229OS << format_hex_no_prefix(Entry.BuildId[I], 2);230}231return OS.str();232}233} // namespace234235MemProfReader::MemProfReader(236llvm::DenseMap<FrameId, Frame> FrameIdMap,237llvm::MapVector<GlobalValue::GUID, IndexedMemProfRecord> ProfData)238: IdToFrame(std::move(FrameIdMap)),239FunctionProfileData(std::move(ProfData)) {240// Populate CSId in each IndexedAllocationInfo and IndexedMemProfRecord241// while storing CallStack in CSIdToCallStack.242for (auto &KV : FunctionProfileData) {243IndexedMemProfRecord &Record = KV.second;244for (auto &AS : Record.AllocSites) {245CallStackId CSId = hashCallStack(AS.CallStack);246AS.CSId = CSId;247CSIdToCallStack.insert({CSId, AS.CallStack});248}249for (auto &CS : Record.CallSites) {250CallStackId CSId = hashCallStack(CS);251Record.CallSiteIds.push_back(CSId);252CSIdToCallStack.insert({CSId, CS});253}254}255}256257Expected<std::unique_ptr<RawMemProfReader>>258RawMemProfReader::create(const Twine &Path, const StringRef ProfiledBinary,259bool KeepName) {260auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);261if (std::error_code EC = BufferOr.getError())262return report(errorCodeToError(EC), Path.getSingleStringRef());263264std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());265return create(std::move(Buffer), ProfiledBinary, KeepName);266}267268Expected<std::unique_ptr<RawMemProfReader>>269RawMemProfReader::create(std::unique_ptr<MemoryBuffer> Buffer,270const StringRef ProfiledBinary, bool KeepName) {271if (Error E = checkBuffer(*Buffer))272return report(std::move(E), Buffer->getBufferIdentifier());273274if (ProfiledBinary.empty()) {275// Peek the build ids to print a helpful error message.276const std::vector<std::string> BuildIds = peekBuildIds(Buffer.get());277std::string ErrorMessage(278R"(Path to profiled binary is empty, expected binary with one of the following build ids:279)");280for (const auto &Id : BuildIds) {281ErrorMessage += "\n BuildId: ";282ErrorMessage += Id;283}284return report(285make_error<StringError>(ErrorMessage, inconvertibleErrorCode()),286/*Context=*/"");287}288289auto BinaryOr = llvm::object::createBinary(ProfiledBinary);290if (!BinaryOr) {291return report(BinaryOr.takeError(), ProfiledBinary);292}293294// Use new here since constructor is private.295std::unique_ptr<RawMemProfReader> Reader(296new RawMemProfReader(std::move(BinaryOr.get()), KeepName));297if (Error E = Reader->initialize(std::move(Buffer))) {298return std::move(E);299}300return std::move(Reader);301}302303// We need to make sure that all leftover MIB histograms that have not been304// freed by merge are freed here.305RawMemProfReader::~RawMemProfReader() {306for (auto &[_, MIB] : CallstackProfileData) {307if (MemprofRawVersion >= 4ULL && MIB.AccessHistogramSize > 0) {308free((void *)MIB.AccessHistogram);309}310}311}312313bool RawMemProfReader::hasFormat(const StringRef Path) {314auto BufferOr = MemoryBuffer::getFileOrSTDIN(Path);315if (!BufferOr)316return false;317318std::unique_ptr<MemoryBuffer> Buffer(BufferOr.get().release());319return hasFormat(*Buffer);320}321322bool RawMemProfReader::hasFormat(const MemoryBuffer &Buffer) {323if (Buffer.getBufferSize() < sizeof(uint64_t))324return false;325// Aligned read to sanity check that the buffer was allocated with at least 8b326// alignment.327const uint64_t Magic = alignedRead(Buffer.getBufferStart());328return Magic == MEMPROF_RAW_MAGIC_64;329}330331void RawMemProfReader::printYAML(raw_ostream &OS) {332uint64_t NumAllocFunctions = 0, NumMibInfo = 0;333for (const auto &KV : FunctionProfileData) {334const size_t NumAllocSites = KV.second.AllocSites.size();335if (NumAllocSites > 0) {336NumAllocFunctions++;337NumMibInfo += NumAllocSites;338}339}340341OS << "MemprofProfile:\n";342OS << " Summary:\n";343OS << " Version: " << MemprofRawVersion << "\n";344OS << " NumSegments: " << SegmentInfo.size() << "\n";345OS << " NumMibInfo: " << NumMibInfo << "\n";346OS << " NumAllocFunctions: " << NumAllocFunctions << "\n";347OS << " NumStackOffsets: " << StackMap.size() << "\n";348// Print out the segment information.349OS << " Segments:\n";350for (const auto &Entry : SegmentInfo) {351OS << " -\n";352OS << " BuildId: " << getBuildIdString(Entry) << "\n";353OS << " Start: 0x" << llvm::utohexstr(Entry.Start) << "\n";354OS << " End: 0x" << llvm::utohexstr(Entry.End) << "\n";355OS << " Offset: 0x" << llvm::utohexstr(Entry.Offset) << "\n";356}357// Print out the merged contents of the profiles.358OS << " Records:\n";359for (const auto &[GUID, Record] : *this) {360OS << " -\n";361OS << " FunctionGUID: " << GUID << "\n";362Record.print(OS);363}364}365366Error RawMemProfReader::initialize(std::unique_ptr<MemoryBuffer> DataBuffer) {367const StringRef FileName = Binary.getBinary()->getFileName();368369auto *ElfObject = dyn_cast<object::ELFObjectFileBase>(Binary.getBinary());370if (!ElfObject) {371return report(make_error<StringError>(Twine("Not an ELF file: "),372inconvertibleErrorCode()),373FileName);374}375376// Check whether the profiled binary was built with position independent code377// (PIC). Perform sanity checks for assumptions we rely on to simplify378// symbolization.379auto *Elf64LEObject = llvm::cast<llvm::object::ELF64LEObjectFile>(ElfObject);380const llvm::object::ELF64LEFile &ElfFile = Elf64LEObject->getELFFile();381auto PHdrsOr = ElfFile.program_headers();382if (!PHdrsOr)383return report(384make_error<StringError>(Twine("Could not read program headers: "),385inconvertibleErrorCode()),386FileName);387388int NumExecutableSegments = 0;389for (const auto &Phdr : *PHdrsOr) {390if (Phdr.p_type == ELF::PT_LOAD) {391if (Phdr.p_flags & ELF::PF_X) {392// We assume only one text segment in the main binary for simplicity and393// reduce the overhead of checking multiple ranges during symbolization.394if (++NumExecutableSegments > 1) {395return report(396make_error<StringError>(397"Expect only one executable load segment in the binary",398inconvertibleErrorCode()),399FileName);400}401// Segment will always be loaded at a page boundary, expect it to be402// aligned already. Assume 4K pagesize for the machine from which the403// profile has been collected. This should be fine for now, in case we404// want to support other pagesizes it can be recorded in the raw profile405// during collection.406PreferredTextSegmentAddress = Phdr.p_vaddr;407assert(Phdr.p_vaddr == (Phdr.p_vaddr & ~(0x1000 - 1U)) &&408"Expect p_vaddr to always be page aligned");409assert(Phdr.p_offset == 0 && "Expect p_offset = 0 for symbolization.");410}411}412}413414auto Triple = ElfObject->makeTriple();415if (!Triple.isX86())416return report(make_error<StringError>(Twine("Unsupported target: ") +417Triple.getArchName(),418inconvertibleErrorCode()),419FileName);420421// Process the raw profile.422if (Error E = readRawProfile(std::move(DataBuffer)))423return E;424425if (Error E = setupForSymbolization())426return E;427428auto *Object = cast<object::ObjectFile>(Binary.getBinary());429std::unique_ptr<DIContext> Context = DWARFContext::create(430*Object, DWARFContext::ProcessDebugRelocations::Process);431432auto SOFOr = symbolize::SymbolizableObjectFile::create(433Object, std::move(Context), /*UntagAddresses=*/false);434if (!SOFOr)435return report(SOFOr.takeError(), FileName);436auto Symbolizer = std::move(SOFOr.get());437438// The symbolizer ownership is moved into symbolizeAndFilterStackFrames so439// that it is freed automatically at the end, when it is no longer used. This440// reduces peak memory since it won't be live while also mapping the raw441// profile into records afterwards.442if (Error E = symbolizeAndFilterStackFrames(std::move(Symbolizer)))443return E;444445return mapRawProfileToRecords();446}447448Error RawMemProfReader::setupForSymbolization() {449auto *Object = cast<object::ObjectFile>(Binary.getBinary());450object::BuildIDRef BinaryId = object::getBuildID(Object);451if (BinaryId.empty())452return make_error<StringError>(Twine("No build id found in binary ") +453Binary.getBinary()->getFileName(),454inconvertibleErrorCode());455456int NumMatched = 0;457for (const auto &Entry : SegmentInfo) {458llvm::ArrayRef<uint8_t> SegmentId(Entry.BuildId, Entry.BuildIdSize);459if (BinaryId == SegmentId) {460// We assume only one text segment in the main binary for simplicity and461// reduce the overhead of checking multiple ranges during symbolization.462if (++NumMatched > 1) {463return make_error<StringError>(464"We expect only one executable segment in the profiled binary",465inconvertibleErrorCode());466}467ProfiledTextSegmentStart = Entry.Start;468ProfiledTextSegmentEnd = Entry.End;469}470}471assert(NumMatched != 0 && "No matching executable segments in segment info.");472assert((PreferredTextSegmentAddress == 0 ||473(PreferredTextSegmentAddress == ProfiledTextSegmentStart)) &&474"Expect text segment address to be 0 or equal to profiled text "475"segment start.");476return Error::success();477}478479Error RawMemProfReader::mapRawProfileToRecords() {480// Hold a mapping from function to each callsite location we encounter within481// it that is part of some dynamic allocation context. The location is stored482// as a pointer to a symbolized list of inline frames.483using LocationPtr = const llvm::SmallVector<FrameId> *;484llvm::MapVector<GlobalValue::GUID, llvm::SetVector<LocationPtr>>485PerFunctionCallSites;486487// Convert the raw profile callstack data into memprof records. While doing so488// keep track of related contexts so that we can fill these in later.489for (const auto &[StackId, MIB] : CallstackProfileData) {490auto It = StackMap.find(StackId);491if (It == StackMap.end())492return make_error<InstrProfError>(493instrprof_error::malformed,494"memprof callstack record does not contain id: " + Twine(StackId));495496// Construct the symbolized callstack.497llvm::SmallVector<FrameId> Callstack;498Callstack.reserve(It->getSecond().size());499500llvm::ArrayRef<uint64_t> Addresses = It->getSecond();501for (size_t I = 0; I < Addresses.size(); I++) {502const uint64_t Address = Addresses[I];503assert(SymbolizedFrame.count(Address) > 0 &&504"Address not found in SymbolizedFrame map");505const SmallVector<FrameId> &Frames = SymbolizedFrame[Address];506507assert(!idToFrame(Frames.back()).IsInlineFrame &&508"The last frame should not be inlined");509510// Record the callsites for each function. Skip the first frame of the511// first address since it is the allocation site itself that is recorded512// as an alloc site.513for (size_t J = 0; J < Frames.size(); J++) {514if (I == 0 && J == 0)515continue;516// We attach the entire bottom-up frame here for the callsite even517// though we only need the frames up to and including the frame for518// Frames[J].Function. This will enable better deduplication for519// compression in the future.520const GlobalValue::GUID Guid = idToFrame(Frames[J]).Function;521PerFunctionCallSites[Guid].insert(&Frames);522}523524// Add all the frames to the current allocation callstack.525Callstack.append(Frames.begin(), Frames.end());526}527528CallStackId CSId = hashCallStack(Callstack);529CSIdToCallStack.insert({CSId, Callstack});530531// We attach the memprof record to each function bottom-up including the532// first non-inline frame.533for (size_t I = 0; /*Break out using the condition below*/; I++) {534const Frame &F = idToFrame(Callstack[I]);535auto Result =536FunctionProfileData.insert({F.Function, IndexedMemProfRecord()});537IndexedMemProfRecord &Record = Result.first->second;538Record.AllocSites.emplace_back(Callstack, CSId, MIB);539540if (!F.IsInlineFrame)541break;542}543}544545// Fill in the related callsites per function.546for (const auto &[Id, Locs] : PerFunctionCallSites) {547// Some functions may have only callsite data and no allocation data. Here548// we insert a new entry for callsite data if we need to.549auto Result = FunctionProfileData.insert({Id, IndexedMemProfRecord()});550IndexedMemProfRecord &Record = Result.first->second;551for (LocationPtr Loc : Locs) {552CallStackId CSId = hashCallStack(*Loc);553CSIdToCallStack.insert({CSId, *Loc});554Record.CallSites.push_back(*Loc);555Record.CallSiteIds.push_back(CSId);556}557}558559verifyFunctionProfileData(FunctionProfileData);560561return Error::success();562}563564Error RawMemProfReader::symbolizeAndFilterStackFrames(565std::unique_ptr<llvm::symbolize::SymbolizableModule> Symbolizer) {566// The specifier to use when symbolization is requested.567const DILineInfoSpecifier Specifier(568DILineInfoSpecifier::FileLineInfoKind::RawValue,569DILineInfoSpecifier::FunctionNameKind::LinkageName);570571// For entries where all PCs in the callstack are discarded, we erase the572// entry from the stack map.573llvm::SmallVector<uint64_t> EntriesToErase;574// We keep track of all prior discarded entries so that we can avoid invoking575// the symbolizer for such entries.576llvm::DenseSet<uint64_t> AllVAddrsToDiscard;577for (auto &Entry : StackMap) {578for (const uint64_t VAddr : Entry.getSecond()) {579// Check if we have already symbolized and cached the result or if we580// don't want to attempt symbolization since we know this address is bad.581// In this case the address is also removed from the current callstack.582if (SymbolizedFrame.count(VAddr) > 0 ||583AllVAddrsToDiscard.contains(VAddr))584continue;585586Expected<DIInliningInfo> DIOr = Symbolizer->symbolizeInlinedCode(587getModuleOffset(VAddr), Specifier, /*UseSymbolTable=*/false);588if (!DIOr)589return DIOr.takeError();590DIInliningInfo DI = DIOr.get();591592// Drop frames which we can't symbolize or if they belong to the runtime.593if (DI.getFrame(0).FunctionName == DILineInfo::BadString ||594isRuntimePath(DI.getFrame(0).FileName)) {595AllVAddrsToDiscard.insert(VAddr);596continue;597}598599for (size_t I = 0, NumFrames = DI.getNumberOfFrames(); I < NumFrames;600I++) {601const auto &DIFrame = DI.getFrame(I);602const uint64_t Guid =603IndexedMemProfRecord::getGUID(DIFrame.FunctionName);604const Frame F(Guid, DIFrame.Line - DIFrame.StartLine, DIFrame.Column,605// Only the last entry is not an inlined location.606I != NumFrames - 1);607// Here we retain a mapping from the GUID to canonical symbol name608// instead of adding it to the frame object directly to reduce memory609// overhead. This is because there can be many unique frames,610// particularly for callsite frames.611if (KeepSymbolName) {612StringRef CanonicalName =613sampleprof::FunctionSamples::getCanonicalFnName(614DIFrame.FunctionName);615GuidToSymbolName.insert({Guid, CanonicalName.str()});616}617618const FrameId Hash = F.hash();619IdToFrame.insert({Hash, F});620SymbolizedFrame[VAddr].push_back(Hash);621}622}623624auto &CallStack = Entry.getSecond();625llvm::erase_if(CallStack, [&AllVAddrsToDiscard](const uint64_t A) {626return AllVAddrsToDiscard.contains(A);627});628if (CallStack.empty())629EntriesToErase.push_back(Entry.getFirst());630}631632// Drop the entries where the callstack is empty.633for (const uint64_t Id : EntriesToErase) {634StackMap.erase(Id);635if(CallstackProfileData[Id].AccessHistogramSize > 0)636free((void*) CallstackProfileData[Id].AccessHistogram);637CallstackProfileData.erase(Id);638}639640if (StackMap.empty())641return make_error<InstrProfError>(642instrprof_error::malformed,643"no entries in callstack map after symbolization");644645return Error::success();646}647648std::vector<std::string>649RawMemProfReader::peekBuildIds(MemoryBuffer *DataBuffer) {650const char *Next = DataBuffer->getBufferStart();651// Use a SetVector since a profile file may contain multiple raw profile652// dumps, each with segment information. We want them unique and in order they653// were stored in the profile; the profiled binary should be the first entry.654// The runtime uses dl_iterate_phdr and the "... first object visited by655// callback is the main program."656// https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html657llvm::SetVector<std::string, std::vector<std::string>,658llvm::SmallSet<std::string, 10>>659BuildIds;660while (Next < DataBuffer->getBufferEnd()) {661const auto *Header = reinterpret_cast<const memprof::Header *>(Next);662663const llvm::SmallVector<SegmentEntry> Entries =664readSegmentEntries(Next + Header->SegmentOffset);665666for (const auto &Entry : Entries)667BuildIds.insert(getBuildIdString(Entry));668669Next += Header->TotalSize;670}671return BuildIds.takeVector();672}673674// FIXME: Add a schema for serializing similiar to IndexedMemprofReader. This675// will help being able to deserialize different versions raw memprof versions676// more easily.677llvm::SmallVector<std::pair<uint64_t, MemInfoBlock>>678RawMemProfReader::readMemInfoBlocks(const char *Ptr) {679if (MemprofRawVersion == 3ULL)680return readMemInfoBlocksV3(Ptr);681if (MemprofRawVersion == 4ULL)682return readMemInfoBlocksV4(Ptr);683llvm_unreachable(684"Panic: Unsupported version number when reading MemInfoBlocks");685}686687Error RawMemProfReader::readRawProfile(688std::unique_ptr<MemoryBuffer> DataBuffer) {689const char *Next = DataBuffer->getBufferStart();690691while (Next < DataBuffer->getBufferEnd()) {692const auto *Header = reinterpret_cast<const memprof::Header *>(Next);693694// Set Reader version to memprof raw version of profile. Checking if version695// is supported is checked before creating the reader.696MemprofRawVersion = Header->Version;697698// Read in the segment information, check whether its the same across all699// profiles in this binary file.700const llvm::SmallVector<SegmentEntry> Entries =701readSegmentEntries(Next + Header->SegmentOffset);702if (!SegmentInfo.empty() && SegmentInfo != Entries) {703// We do not expect segment information to change when deserializing from704// the same binary profile file. This can happen if dynamic libraries are705// loaded/unloaded between profile dumping.706return make_error<InstrProfError>(707instrprof_error::malformed,708"memprof raw profile has different segment information");709}710SegmentInfo.assign(Entries.begin(), Entries.end());711712// Read in the MemInfoBlocks. Merge them based on stack id - we assume that713// raw profiles in the same binary file are from the same process so the714// stackdepot ids are the same.715for (const auto &[Id, MIB] : readMemInfoBlocks(Next + Header->MIBOffset)) {716if (CallstackProfileData.count(Id)) {717718if (MemprofRawVersion >= 4ULL &&719(CallstackProfileData[Id].AccessHistogramSize > 0 ||720MIB.AccessHistogramSize > 0)) {721uintptr_t ShorterHistogram;722if (CallstackProfileData[Id].AccessHistogramSize >723MIB.AccessHistogramSize)724ShorterHistogram = MIB.AccessHistogram;725else726ShorterHistogram = CallstackProfileData[Id].AccessHistogram;727CallstackProfileData[Id].Merge(MIB);728free((void *)ShorterHistogram);729} else {730CallstackProfileData[Id].Merge(MIB);731}732} else {733CallstackProfileData[Id] = MIB;734}735}736737// Read in the callstack for each ids. For multiple raw profiles in the same738// file, we expect that the callstack is the same for a unique id.739const CallStackMap CSM = readStackInfo(Next + Header->StackOffset);740if (StackMap.empty()) {741StackMap = CSM;742} else {743if (mergeStackMap(CSM, StackMap))744return make_error<InstrProfError>(745instrprof_error::malformed,746"memprof raw profile got different call stack for same id");747}748749Next += Header->TotalSize;750}751752return Error::success();753}754755object::SectionedAddress756RawMemProfReader::getModuleOffset(const uint64_t VirtualAddress) {757if (VirtualAddress > ProfiledTextSegmentStart &&758VirtualAddress <= ProfiledTextSegmentEnd) {759// For PIE binaries, the preferred address is zero and we adjust the virtual760// address by start of the profiled segment assuming that the offset of the761// segment in the binary is zero. For non-PIE binaries the preferred and762// profiled segment addresses should be equal and this is a no-op.763const uint64_t AdjustedAddress =764VirtualAddress + PreferredTextSegmentAddress - ProfiledTextSegmentStart;765return object::SectionedAddress{AdjustedAddress};766}767// Addresses which do not originate from the profiled text segment in the768// binary are not adjusted. These will fail symbolization and be filtered out769// during processing.770return object::SectionedAddress{VirtualAddress};771}772773Error RawMemProfReader::readNextRecord(774GuidMemProfRecordPair &GuidRecord,775std::function<const Frame(const FrameId)> Callback) {776// Create a new callback for the RawMemProfRecord iterator so that we can777// provide the symbol name if the reader was initialized with KeepSymbolName =778// true. This is useful for debugging and testing.779auto IdToFrameCallback = [this](const FrameId Id) {780Frame F = this->idToFrame(Id);781if (!this->KeepSymbolName)782return F;783auto Iter = this->GuidToSymbolName.find(F.Function);784assert(Iter != this->GuidToSymbolName.end());785F.SymbolName = std::make_unique<std::string>(Iter->getSecond());786return F;787};788return MemProfReader::readNextRecord(GuidRecord, IdToFrameCallback);789}790} // namespace memprof791} // namespace llvm792793794