Skip to content

Commit

Permalink
[memprof] Support symbolization of PIE binaries.
Browse files Browse the repository at this point in the history
Support symolization of PIE binaries in memprof. We assume that the
profiled binary has one executable text segment for simplicity. Update
the memprof-pic test to now expect the same output as the memprof-basic test.

Reviewed By: tejohnson

Differential Revision: https://reviews.llvm.org/D146181
  • Loading branch information
snehasish committed Mar 21, 2023
1 parent e240e6b commit cef71d0
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 28 deletions.
16 changes: 14 additions & 2 deletions llvm/include/llvm/ProfileData/RawMemProfReader.h
Expand Up @@ -106,6 +106,8 @@ class RawMemProfReader {
Error initialize(std::unique_ptr<MemoryBuffer> DataBuffer);
// Read and parse the contents of the `DataBuffer` as a binary format profile.
Error readRawProfile(std::unique_ptr<MemoryBuffer> DataBuffer);
// Initialize the segment mapping information for symbolization.
Error setupForSymbolization();
// Symbolize and cache all the virtual addresses we encounter in the
// callstacks from the raw profile. Also prune callstack frames which we can't
// symbolize or those that belong to the runtime. For profile entries where
Expand All @@ -125,11 +127,21 @@ class RawMemProfReader {

object::SectionedAddress getModuleOffset(uint64_t VirtualAddress);

// The profiled binary.
object::OwningBinary<object::Binary> Binary;
// A symbolizer to translate virtual addresses to code locations.
std::unique_ptr<llvm::symbolize::SymbolizableModule> Symbolizer;
// The preferred load address of the executable segment.
uint64_t PreferredTextSegmentAddress = 0;
// The base address of the text segment in the process during profiling.
uint64_t ProfiledTextSegmentStart = 0;
// The limit address of the text segment in the process during profiling.
uint64_t ProfiledTextSegmentEnd = 0;

// The memory mapped segment information for all executable segments in the
// profiled binary (filtered from the raw profile using the build id).
llvm::SmallVector<SegmentEntry, 2> SegmentInfo;

// The contents of the raw profile.
llvm::SmallVector<SegmentEntry, 16> SegmentInfo;
// A map from callstack id (same as key in CallStackMap below) to the heap
// information recorded for that allocation context.
llvm::MapVector<uint64_t, MemInfoBlock> CallstackProfileData;
Expand Down
106 changes: 82 additions & 24 deletions llvm/lib/ProfileData/RawMemProfReader.cpp
Expand Up @@ -24,13 +24,16 @@
#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
#include "llvm/DebugInfo/Symbolize/SymbolizableObjectFile.h"
#include "llvm/Object/Binary.h"
#include "llvm/Object/BuildID.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/Debug.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Path.h"

#define DEBUG_TYPE "memprof"
Expand Down Expand Up @@ -270,17 +273,37 @@ Error RawMemProfReader::initialize(std::unique_ptr<MemoryBuffer> DataBuffer) {
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);
if (!PHdrsOr)
return report(
make_error<StringError>(Twine("Could not read program headers: "),
inconvertibleErrorCode()),
FileName);

int NumExecutableSegments = 0;
for (const auto &Phdr : *PHdrsOr) {
if (Phdr.p_type == ELF::PT_LOAD) {
if (Phdr.p_flags & ELF::PF_X) {
// We assume only one text segment in the main binary for simplicity and
// reduce the overhead of checking multiple ranges during symbolization.
if (++NumExecutableSegments > 1) {
return report(
make_error<StringError>(
"Expect only one executable load segment in the binary",
inconvertibleErrorCode()),
FileName);
}
// Segment will always be loaded at a page boundary, expect it to be
// aligned already. Assume 4K pagesize for the machine from which the
// profile has been collected. This should be fine for now, in case we
// want to support other pagesizes it can be recorded in the raw profile
// during collection.
PreferredTextSegmentAddress = Phdr.p_vaddr;
assert(Phdr.p_vaddr == (Phdr.p_vaddr & ~(0x1000 - 1U)) &&
"Expect p_vaddr to always be page aligned");
assert(Phdr.p_offset == 0 && "Expect p_offset = 0 for symbolization.");
}
}
}

auto Triple = ElfObject->makeTriple();
if (!Triple.isX86())
Expand All @@ -299,15 +322,51 @@ Error RawMemProfReader::initialize(std::unique_ptr<MemoryBuffer> DataBuffer) {
return report(SOFOr.takeError(), FileName);
Symbolizer = std::move(SOFOr.get());

// Process the raw profile.
if (Error E = readRawProfile(std::move(DataBuffer)))
return E;

if (Error E = setupForSymbolization())
return E;

if (Error E = symbolizeAndFilterStackFrames())
return E;

return mapRawProfileToRecords();
}

Error RawMemProfReader::setupForSymbolization() {
auto *Object = cast<object::ObjectFile>(Binary.getBinary());
auto BuildIdOr = object::getBuildID(Object);
if (!BuildIdOr.has_value())
return make_error<StringError>(Twine("No build id found in binary ") +
Binary.getBinary()->getFileName(),
inconvertibleErrorCode());
llvm::ArrayRef<uint8_t> BinaryId = BuildIdOr.value();

int NumMatched = 0;
for (const auto &Entry : SegmentInfo) {
llvm::ArrayRef<uint8_t> SegmentId(Entry.BuildId, Entry.BuildIdSize);
if (BinaryId == SegmentId) {
// We assume only one text segment in the main binary for simplicity and
// reduce the overhead of checking multiple ranges during symbolization.
if (++NumMatched > 1) {
return make_error<StringError>(
"We expect only one executable segment in the profiled binary",
inconvertibleErrorCode());
}
ProfiledTextSegmentStart = Entry.Start;
ProfiledTextSegmentEnd = Entry.End;
}
}
assert(NumMatched != 0 && "No matching executable segments in segment info.");
assert(PreferredTextSegmentAddress == 0 ||
(PreferredTextSegmentAddress == ProfiledTextSegmentStart) &&
"Expect text segment address to be 0 or equal to profiled text "
"segment start.");
return Error::success();
}

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
Expand Down Expand Up @@ -516,20 +575,19 @@ Error RawMemProfReader::readRawProfile(

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;
}
if (VirtualAddress > ProfiledTextSegmentStart &&
VirtualAddress <= ProfiledTextSegmentEnd) {
// For PIE binaries, the preferred address is zero and we adjust the virtual
// address by start of the profiled segment assuming that the offset of the
// segment in the binary is zero. For non-PIE binaries the preferred and
// profiled segment addresses should be equal and this is a no-op.
const uint64_t AdjustedAddress =
VirtualAddress + PreferredTextSegmentAddress - ProfiledTextSegmentStart;
return object::SectionedAddress{AdjustedAddress};
}

// 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.
// Addresses which do not originate from the profiled text segment in the
// binary are not adjusted. These will fail symbolization and be filtered out
// during processing.
return object::SectionedAddress{VirtualAddress};
}

Expand Down
90 changes: 88 additions & 2 deletions llvm/test/tools/llvm-profdata/memprof-pic.test
Expand Up @@ -7,6 +7,92 @@ libraries linked in which could change the number of segments
recorded.

To update the inputs used below run Inputs/update_memprof_inputs.sh /path/to/updated/clang
RUN: not llvm-profdata show --memory %p/Inputs/pic.memprofraw --profiled-binary %p/Inputs/pic.memprofexe -o - 2>&1 | FileCheck %s
RUN: llvm-profdata show --memory %p/Inputs/pic.memprofraw --profiled-binary %p/Inputs/pic.memprofexe -o - 2>&1 | FileCheck %s

CHECK: Unsupported position independent code
CHECK: MemprofProfile:
CHECK-NEXT: Summary:
CHECK-NEXT: Version: 3
CHECK-NEXT: NumSegments: {{[0-9]+}}
CHECK-NEXT: NumMibInfo: 2
CHECK-NEXT: NumAllocFunctions: 1
CHECK-NEXT: NumStackOffsets: 2
CHECK-NEXT: Segments:
CHECK-NEXT: -
CHECK-NEXT: BuildId: {{[[:xdigit:]]+}}
CHECK-NEXT: Start: 0x{{[[:xdigit:]]+}}
CHECK-NEXT: End: 0x{{[[:xdigit:]]+}}
CHECK-NEXT: Offset: 0x{{[[:xdigit:]]+}}
CHECK-NEXT: -

CHECK: Records:
CHECK-NEXT: -
CHECK-NEXT: FunctionGUID: {{[0-9]+}}
CHECK-NEXT: AllocSites:
CHECK-NEXT: -
CHECK-NEXT: Callstack:
CHECK-NEXT: -
CHECK-NEXT: Function: {{[0-9]+}}
CHECK-NEXT: SymbolName: main
CHECK-NEXT: LineOffset: 1
CHECK-NEXT: Column: 21
CHECK-NEXT: Inline: 0
CHECK-NEXT: MemInfoBlock:
CHECK-NEXT: AllocCount: 1
CHECK-NEXT: TotalAccessCount: 2
CHECK-NEXT: MinAccessCount: 2
CHECK-NEXT: MaxAccessCount: 2
CHECK-NEXT: TotalSize: 10
CHECK-NEXT: MinSize: 10
CHECK-NEXT: MaxSize: 10
CHECK-NEXT: AllocTimestamp: {{[0-9]+}}
CHECK-NEXT: DeallocTimestamp: {{[0-9]+}}
CHECK-NEXT: TotalLifetime: 0
CHECK-NEXT: MinLifetime: 0
CHECK-NEXT: MaxLifetime: 0
CHECK-NEXT: AllocCpuId: {{[0-9]+}}
CHECK-NEXT: DeallocCpuId: {{[0-9]+}}
CHECK-NEXT: NumMigratedCpu: 0
CHECK-NEXT: NumLifetimeOverlaps: 0
CHECK-NEXT: NumSameAllocCpu: 0
CHECK-NEXT: NumSameDeallocCpu: 0
CHECK-NEXT: DataTypeId: {{[0-9]+}}
CHECK-NEXT: TotalAccessDensity: 20
CHECK-NEXT: MinAccessDensity: 20
CHECK-NEXT: MaxAccessDensity: 20
CHECK-NEXT: TotalLifetimeAccessDensity: 20000
CHECK-NEXT: MinLifetimeAccessDensity: 20000
CHECK-NEXT: MaxLifetimeAccessDensity: 20000
CHECK-NEXT: -
CHECK-NEXT: Callstack:
CHECK-NEXT: -
CHECK-NEXT: Function: {{[0-9]+}}
CHECK-NEXT: SymbolName: main
CHECK-NEXT: LineOffset: 4
CHECK-NEXT: Column: 15
CHECK-NEXT: Inline: 0
CHECK-NEXT: MemInfoBlock:
CHECK-NEXT: AllocCount: 1
CHECK-NEXT: TotalAccessCount: 2
CHECK-NEXT: MinAccessCount: 2
CHECK-NEXT: MaxAccessCount: 2
CHECK-NEXT: TotalSize: 10
CHECK-NEXT: MinSize: 10
CHECK-NEXT: MaxSize: 10
CHECK-NEXT: AllocTimestamp: {{[0-9]+}}
CHECK-NEXT: DeallocTimestamp: {{[0-9]+}}
CHECK-NEXT: TotalLifetime: 0
CHECK-NEXT: MinLifetime: 0
CHECK-NEXT: MaxLifetime: 0
CHECK-NEXT: AllocCpuId: {{[0-9]+}}
CHECK-NEXT: DeallocCpuId: {{[0-9]+}}
CHECK-NEXT: NumMigratedCpu: 0
CHECK-NEXT: NumLifetimeOverlaps: 0
CHECK-NEXT: NumSameAllocCpu: 0
CHECK-NEXT: NumSameDeallocCpu: 0
CHECK-NEXT: DataTypeId: {{[0-9]+}}
CHECK-NEXT: TotalAccessDensity: 20
CHECK-NEXT: MinAccessDensity: 20
CHECK-NEXT: MaxAccessDensity: 20
CHECK-NEXT: TotalLifetimeAccessDensity: 20000
CHECK-NEXT: MinLifetimeAccessDensity: 20000
CHECK-NEXT: MaxLifetimeAccessDensity: 20000

0 comments on commit cef71d0

Please sign in to comment.