250 changes: 250 additions & 0 deletions compiler-rt/lib/memprof/memprof_rawprofile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
#include "memprof_rawprofile.h"
#include "memprof_meminfoblock.h"
#include "sanitizer_common/sanitizer_allocator_internal.h"
#include "sanitizer_common/sanitizer_linux.h"
#include "sanitizer_common/sanitizer_procmaps.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_stackdepotbase.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "sanitizer_common/sanitizer_vector.h"

#include <stdlib.h>
#include <string.h>

namespace __memprof {
using ::__sanitizer::Vector;

namespace {
typedef struct __attribute__((__packed__)) {
u64 start;
u64 end;
u64 offset;
u8 buildId[32];
} SegmentEntry;

typedef struct __attribute__((__packed__)) {
u64 magic;
u64 version;
u64 total_size;
u64 segment_offset;
u64 mib_offset;
u64 stack_offset;
} Header;

template <class T> char *WriteBytes(T Pod, char *&Buffer) {
*(T *)Buffer = Pod;
return Buffer + sizeof(T);
}

void RecordStackId(const uptr Key, UNUSED LockedMemInfoBlock *const &MIB,
void *Arg) {
// No need to touch the MIB value here since we are only recording the key.
auto *StackIds = reinterpret_cast<Vector<u64> *>(Arg);
StackIds->PushBack(Key);
}
} // namespace

u64 SegmentSizeBytes(MemoryMappingLayoutBase &Layout) {
u64 NumSegmentsToRecord = 0;
MemoryMappedSegment segment;
for (Layout.Reset(); Layout.Next(&segment);)
if (segment.IsReadable() && segment.IsExecutable())
NumSegmentsToRecord++;

return sizeof(u64) // A header which stores the number of records.
+ sizeof(SegmentEntry) * NumSegmentsToRecord;
}

// The segment section uses the following format:
// ---------- Segment Info
// Num Entries
// ---------- Segment Entry
// Start
// End
// Offset
// BuildID 32B
// ----------
// ...
void SerializeSegmentsToBuffer(MemoryMappingLayoutBase &Layout,
const u64 ExpectedNumBytes, char *&Buffer) {
char *Ptr = Buffer;
// Reserve space for the final count.
Ptr += sizeof(u64);

u64 NumSegmentsRecorded = 0;
MemoryMappedSegment segment;

for (Layout.Reset(); Layout.Next(&segment);) {
if (segment.IsReadable() && segment.IsExecutable()) {
SegmentEntry entry{};
entry.start = segment.start;
entry.end = segment.end;
entry.offset = segment.offset;
memcpy(entry.buildId, segment.uuid, sizeof(segment.uuid));
memcpy(Ptr, &entry, sizeof(SegmentEntry));
Ptr += sizeof(SegmentEntry);
NumSegmentsRecorded++;
}
}

// Store the number of segments we recorded in the space we reserved.
*((u64 *)Buffer) = NumSegmentsRecorded;
CHECK(ExpectedNumBytes == static_cast<u64>(Ptr - Buffer) &&
"Expected num bytes != actual bytes written");
}

u64 StackSizeBytes(const Vector<u64> &StackIds) {
u64 NumBytesToWrite = sizeof(u64);

const u64 NumIds = StackIds.Size();
for (unsigned k = 0; k < NumIds; ++k) {
const u64 Id = StackIds[k];
// One entry for the id and then one more for the number of stack pcs.
NumBytesToWrite += 2 * sizeof(u64);
const StackTrace St = StackDepotGet(Id);

CHECK(St.trace != nullptr && St.size > 0 && "Empty stack trace");
for (uptr i = 0; i < St.size && St.trace[i] != 0; i++) {
NumBytesToWrite += sizeof(u64);
}
}
return NumBytesToWrite;
}

// The stack info section uses the following format:
//
// ---------- Stack Info
// Num Entries
// ---------- Stack Entry
// Num Stacks
// PC1
// PC2
// ...
// ----------
void SerializeStackToBuffer(const Vector<u64> &StackIds,
const u64 ExpectedNumBytes, char *&Buffer) {
const u64 NumIds = StackIds.Size();
char *Ptr = Buffer;
Ptr = WriteBytes(static_cast<u64>(NumIds), Ptr);

for (unsigned k = 0; k < NumIds; ++k) {
const u64 Id = StackIds[k];
Ptr = WriteBytes(Id, Ptr);
Ptr += sizeof(u64); // Bump it by u64, we will fill this in later.
u64 Count = 0;
const StackTrace St = StackDepotGet(Id);
for (uptr i = 0; i < St.size && St.trace[i] != 0; i++) {
// PCs in stack traces are actually the return addresses, that is,
// addresses of the next instructions after the call.
uptr pc = StackTrace::GetPreviousInstructionPc(St.trace[i]);
Ptr = WriteBytes(static_cast<u64>(pc), Ptr);
++Count;
}
// Store the count in the space we reserved earlier.
*(u64 *)(Ptr - (Count + 1) * sizeof(u64)) = Count;
}

CHECK(ExpectedNumBytes == static_cast<u64>(Ptr - Buffer) &&
"Expected num bytes != actual bytes written");
}

// The MIB section has the following format:
// ---------- MIB Info
// Num Entries
// ---------- MIB Entry 0
// Alloc Count
// ...
// ---------- MIB Entry 1
// Alloc Count
// ...
// ----------
void SerializeMIBInfoToBuffer(MIBMapTy &MIBMap, const Vector<u64> &StackIds,
const u64 ExpectedNumBytes, char *&Buffer) {
char *Ptr = Buffer;
const u64 NumEntries = StackIds.Size();
Ptr = WriteBytes(NumEntries, Ptr);

for (u64 i = 0; i < NumEntries; i++) {
const u64 Key = StackIds[i];
MIBMapTy::Handle h(&MIBMap, Key, /*remove=*/true, /*create=*/false);
CHECK(h.exists());
Ptr = WriteBytes(Key, Ptr);
Ptr = WriteBytes((*h)->mib, Ptr);
}

CHECK(ExpectedNumBytes == static_cast<u64>(Ptr - Buffer) &&
"Expected num bytes != actual bytes written");
}

// Format
// ---------- Header
// Magic
// Version
// Total Size
// Segment Offset
// MIB Info Offset
// Stack Offset
// ---------- Segment Info
// Num Entries
// ---------- Segment Entry
// Start
// End
// Offset
// BuildID 32B
// ----------
// ...
// ---------- MIB Info
// Num Entries
// ---------- MIB Entry
// Alloc Count
// ...
// ---------- Stack Info
// Num Entries
// ---------- Stack Entry
// Num Stacks
// PC1
// PC2
// ...
// ----------
// ...
u64 SerializeToRawProfile(MIBMapTy &MIBMap, MemoryMappingLayoutBase &Layout,
char *&Buffer) {
const u64 NumSegmentBytes = SegmentSizeBytes(Layout);

Vector<u64> StackIds;
MIBMap.ForEach(RecordStackId, reinterpret_cast<void *>(&StackIds));
// The first 8b are for the total number of MIB records. Each MIB record is
// preceded by a 8b stack id which is associated with stack frames in the next
// section.
const u64 NumMIBInfoBytes =
sizeof(u64) + StackIds.Size() * (sizeof(u64) + sizeof(MemInfoBlock));

const u64 NumStackBytes = StackSizeBytes(StackIds);

const u64 TotalSizeBytes =
sizeof(Header) + NumSegmentBytes + NumStackBytes + NumMIBInfoBytes;

// Allocate the memory for the entire buffer incl. info blocks.
Buffer = (char *)InternalAlloc(TotalSizeBytes);
char *Ptr = Buffer;

Header header{MEMPROF_RAW_MAGIC_64,
MEMPROF_RAW_VERSION,
static_cast<u64>(TotalSizeBytes),
sizeof(Header),
sizeof(Header) + NumSegmentBytes,
sizeof(Header) + NumSegmentBytes + NumMIBInfoBytes};
Ptr = WriteBytes(header, Ptr);

SerializeSegmentsToBuffer(Layout, NumSegmentBytes, Ptr);
Ptr += NumSegmentBytes;

SerializeMIBInfoToBuffer(MIBMap, StackIds, NumMIBInfoBytes, Ptr);
Ptr += NumMIBInfoBytes;

SerializeStackToBuffer(StackIds, NumStackBytes, Ptr);

return TotalSizeBytes;
}

} // namespace __memprof
21 changes: 21 additions & 0 deletions compiler-rt/lib/memprof/memprof_rawprofile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef MEMPROF_RAWPROFILE_H_
#define MEMPROF_RAWPROFILE_H_

#include "memprof_mibmap.h"
#include "sanitizer_common/sanitizer_procmaps.h"

namespace __memprof {

// TODO: pull these in from MemProfData.inc
#define MEMPROF_RAW_MAGIC_64 \
(u64)255 << 56 | (u64)'m' << 48 | (u64)'p' << 40 | (u64)'r' << 32 | \
(u64)'o' << 24 | (u64)'f' << 16 | (u64)'r' << 8 | (u64)129

#define MEMPROF_RAW_VERSION 1ULL

u64 SerializeToRawProfile(MIBMapTy &BlockCache, MemoryMappingLayoutBase &Layout,
char *&Buffer);

} // namespace __memprof

#endif // MEMPROF_RAWPROFILE_H_
52 changes: 52 additions & 0 deletions compiler-rt/lib/memprof/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
include(CheckCXXCompilerFlag)
include(CompilerRTCompile)
include(CompilerRTLink)

set(MEMPROF_UNITTEST_CFLAGS
${COMPILER_RT_UNITTEST_CFLAGS}
${COMPILER_RT_GTEST_CFLAGS}
${COMPILER_RT_GMOCK_CFLAGS}
-I${COMPILER_RT_SOURCE_DIR}/lib/
-O2
-g
-fno-rtti
-Wno-gnu-zero-variadic-macro-arguments
-fno-omit-frame-pointer)

file(GLOB MEMPROF_HEADERS ../*.h)

set(MEMPROF_SOURCES
../memprof_mibmap.cpp
../memprof_rawprofile.cpp)

set(MEMPROF_UNITTESTS
rawprofile.cpp
driver.cpp)

set(MEMPROF_UNIT_TEST_HEADERS
${MEMPROF_HEADERS})

if(NOT WIN32)
list(APPEND MEMPROF_UNITTEST_LINK_FLAGS -pthread)
endif()

if(COMPILER_RT_DEFAULT_TARGET_ARCH IN_LIST MEMPROF_SUPPORTED_ARCH)
# MemProf unit tests are only run on the host machine.
set(arch ${COMPILER_RT_DEFAULT_TARGET_ARCH})

add_executable(MemProfUnitTests
${MEMPROF_UNITTESTS}
${COMPILER_RT_GTEST_SOURCE}
${COMPILER_RT_GMOCK_SOURCE}
${MEMPROF_SOURCES}
$<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
$<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
$<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
$<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>)
set_target_compile_flags(MemProfUnitTests ${MEMPROF_UNITTEST_CFLAGS})
set_target_link_flags(MemProfUnitTests ${MEMPROF_UNITTEST_LINK_FLAGS})
target_link_libraries(MemProfUnitTests dl)

set_target_properties(MemProfUnitTests PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
14 changes: 14 additions & 0 deletions compiler-rt/lib/memprof/tests/driver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//===-- driver.cpp ----------------------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "gtest/gtest.h"

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
188 changes: 188 additions & 0 deletions compiler-rt/lib/memprof/tests/rawprofile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include "memprof/memprof_rawprofile.h"

#include "memprof/memprof_meminfoblock.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_procmaps.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include <memory>

namespace {

using ::__memprof::MemInfoBlock;
using ::__memprof::MIBMapTy;
using ::__memprof::SerializeToRawProfile;
using ::__sanitizer::MemoryMappedSegment;
using ::__sanitizer::MemoryMappingLayoutBase;
using ::__sanitizer::StackDepotPut;
using ::__sanitizer::StackTrace;
using ::testing::_;
using ::testing::Action;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;

class MockMemoryMappingLayout final : public MemoryMappingLayoutBase {
public:
MOCK_METHOD(bool, Next, (MemoryMappedSegment *), (override));
MOCK_METHOD(void, Reset, (), (override));
};

u64 PopulateFakeMap(const MemInfoBlock &FakeMIB, uptr StackPCBegin,
MIBMapTy &FakeMap) {
constexpr int kSize = 5;
uptr array[kSize];
for (int i = 0; i < kSize; i++) {
array[i] = StackPCBegin + i;
}
StackTrace St(array, kSize);
u32 Id = StackDepotPut(St);

InsertOrMerge(Id, FakeMIB, FakeMap);
return Id;
}

template <class T = u64> T Read(char *&Buffer) {
static_assert(std::is_pod<T>::value, "Must be a POD type.");
T t = *reinterpret_cast<T *>(Buffer);
Buffer += sizeof(T);
return t;
}

TEST(MemProf, Basic) {
MockMemoryMappingLayout Layout;
MemoryMappedSegment FakeSegment;
memset(&FakeSegment, 0, sizeof(FakeSegment));
FakeSegment.start = 0x10;
FakeSegment.end = 0x20;
FakeSegment.offset = 0x10;
uint8_t uuid[__sanitizer::kModuleUUIDSize] = {0xC, 0x0, 0xF, 0xF, 0xE, 0xE};
memcpy(FakeSegment.uuid, uuid, __sanitizer::kModuleUUIDSize);
FakeSegment.protection =
__sanitizer::kProtectionExecute | __sanitizer::kProtectionRead;

const Action<bool(MemoryMappedSegment *)> SetSegment =
DoAll(SetArgPointee<0>(FakeSegment), Return(true));
EXPECT_CALL(Layout, Next(_))
.WillOnce(SetSegment)
.WillOnce(Return(false))
.WillOnce(SetSegment)
.WillRepeatedly(Return(false));

EXPECT_CALL(Layout, Reset).Times(2);

MIBMapTy FakeMap;
MemInfoBlock FakeMIB;
// Since we want to override the constructor set vals to make it easier to
// test.
memset(&FakeMIB, 0, sizeof(MemInfoBlock));
FakeMIB.alloc_count = 0x1;
FakeMIB.total_access_count = 0x2;

u64 FakeIds[2];
FakeIds[0] = PopulateFakeMap(FakeMIB, /*StackPCBegin=*/2, FakeMap);
FakeIds[1] = PopulateFakeMap(FakeMIB, /*StackPCBegin=*/3, FakeMap);

char *Ptr = nullptr;
u64 NumBytes = SerializeToRawProfile(FakeMap, Layout, Ptr);
const char *Buffer = Ptr;

ASSERT_GT(NumBytes, 0ULL);
ASSERT_TRUE(Ptr);

// Check the header.
EXPECT_THAT(Read(Ptr), MEMPROF_RAW_MAGIC_64);
EXPECT_THAT(Read(Ptr), MEMPROF_RAW_VERSION);
const u64 TotalSize = Read(Ptr);
const u64 SegmentOffset = Read(Ptr);
const u64 MIBOffset = Read(Ptr);
const u64 StackOffset = Read(Ptr);

// ============= Check sizes.
EXPECT_EQ(TotalSize, NumBytes);

// Should be equal to the size of the raw profile header.
EXPECT_EQ(SegmentOffset, 48ULL);

// We expect only 1 segment entry, 8b for the count and 56b for SegmentEntry
// in memprof_rawprofile.cpp.
EXPECT_EQ(MIBOffset - SegmentOffset, 64ULL);

EXPECT_EQ(MIBOffset, 112ULL);
// We expect 2 mib entry, 8b for the count and sizeof(u64) +
// sizeof(MemInfoBlock) contains stack id + MeminfoBlock.
EXPECT_EQ(StackOffset - MIBOffset, 8 + 2 * (8 + sizeof(MemInfoBlock)));

EXPECT_EQ(StackOffset, 336ULL);
// We expect 2 stack entries, with 5 frames - 8b for total count,
// 2 * (8b for id, 8b for frame count and 5*8b for fake frames)
EXPECT_EQ(TotalSize - StackOffset, 8ULL + 2 * (8 + 8 + 5 * 8));

// ============= Check contents.
unsigned char ExpectedSegmentBytes[64] = {
0x01, 0, 0, 0, 0, 0, 0, 0, // Number of entries
0x10, 0, 0, 0, 0, 0, 0, 0, // Start
0x20, 0, 0, 0, 0, 0, 0, 0, // End
0x10, 0, 0, 0, 0, 0, 0, 0, // Offset
0x0C, 0x0, 0xF, 0xF, 0xE, 0xE, // Uuid
};
EXPECT_EQ(memcmp(Buffer + SegmentOffset, ExpectedSegmentBytes, 64), 0);

// Check that the number of entries is 2.
EXPECT_EQ(*reinterpret_cast<const u64 *>(Buffer + MIBOffset), 2ULL);
// Check that stack id is set.
EXPECT_EQ(*reinterpret_cast<const u64 *>(Buffer + MIBOffset + 8), FakeIds[0]);

// Only check a few fields of the first MemInfoBlock.
unsigned char ExpectedMIBBytes[sizeof(MemInfoBlock)] = {
0x01, 0, 0, 0, // Alloc count
0x02, 0, 0, 0, // Total access count
};
// Compare contents of 1st MIB after skipping count and stack id.
EXPECT_EQ(
memcmp(Buffer + MIBOffset + 16, ExpectedMIBBytes, sizeof(MemInfoBlock)),
0);
// Compare contents of 2nd MIB after skipping count and stack id for the first
// and only the id for the second.
EXPECT_EQ(memcmp(Buffer + MIBOffset + 16 + sizeof(MemInfoBlock) + 8,
ExpectedMIBBytes, sizeof(MemInfoBlock)),
0);

// Check that the number of entries is 2.
EXPECT_EQ(*reinterpret_cast<const u64 *>(Buffer + StackOffset), 2ULL);
// Check that the 1st stack id is set.
EXPECT_EQ(*reinterpret_cast<const u64 *>(Buffer + StackOffset + 8),
FakeIds[0]);
// Contents are num pcs, value of each pc - 1.
unsigned char ExpectedStackBytes[2][6 * 8] = {
{
0x5, 0, 0, 0, 0, 0, 0, 0, // Number of PCs
0x1, 0, 0, 0, 0, 0, 0, 0, // PC ...
0x2, 0, 0, 0, 0, 0, 0, 0, 0x3, 0, 0, 0, 0, 0, 0, 0,
0x4, 0, 0, 0, 0, 0, 0, 0, 0x5, 0, 0, 0, 0, 0, 0, 0,
},
{
0x5, 0, 0, 0, 0, 0, 0, 0, // Number of PCs
0x2, 0, 0, 0, 0, 0, 0, 0, // PC ...
0x3, 0, 0, 0, 0, 0, 0, 0, 0x4, 0, 0, 0, 0, 0, 0, 0,
0x5, 0, 0, 0, 0, 0, 0, 0, 0x6, 0, 0, 0, 0, 0, 0, 0,
},
};
EXPECT_EQ(memcmp(Buffer + StackOffset + 16, ExpectedStackBytes[0],
sizeof(ExpectedStackBytes[0])),
0);

// Check that the 2nd stack id is set.
EXPECT_EQ(
*reinterpret_cast<const u64 *>(Buffer + StackOffset + 8 + 6 * 8 + 8),
FakeIds[1]);

EXPECT_EQ(memcmp(Buffer + StackOffset + 16 + 6 * 8 + 8, ExpectedStackBytes[1],
sizeof(ExpectedStackBytes[1])),
0);
}

} // namespace
18 changes: 14 additions & 4 deletions compiler-rt/lib/sanitizer_common/sanitizer_procmaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,23 @@ class MemoryMappedSegment {
MemoryMappedSegmentData *data_;
};

class MemoryMappingLayout {
class MemoryMappingLayoutBase {
public:
virtual bool Next(MemoryMappedSegment *segment) { UNIMPLEMENTED(); }
virtual bool Error() const { UNIMPLEMENTED(); };
virtual void Reset() { UNIMPLEMENTED(); }

protected:
~MemoryMappingLayoutBase() {}
};

class MemoryMappingLayout final : public MemoryMappingLayoutBase {
public:
explicit MemoryMappingLayout(bool cache_enabled);
~MemoryMappingLayout();
bool Next(MemoryMappedSegment *segment);
bool Error() const;
void Reset();
virtual bool Next(MemoryMappedSegment *segment) override;
virtual bool Error() const override;
virtual void Reset() override;
// In some cases, e.g. when running under a sandbox on Linux, ASan is unable
// to obtain the memory mappings. It should fall back to pre-cached data
// instead of aborting.
Expand Down
16 changes: 11 additions & 5 deletions compiler-rt/test/memprof/TestCases/memprof_profile_dump.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// RUN: %clangxx_memprof %s -o %t

// RUN: %env_memprof_opts=log_path=stdout %run %t | FileCheck %s
// RUN: %env_memprof_opts=log_path=stdout %run %t | FileCheck --check-prefix=CHECK-TEXT %s
// RUN: %env_memprof_opts=log_path=stdout,print_text=false %run %t > %t.memprofraw
// RUN: od -c -N 8 %t.memprofraw | FileCheck --check-prefix=CHECK-RAW %s

#include <sanitizer/memprof_interface.h>
#include <stdlib.h>
Expand All @@ -17,7 +19,11 @@ int main(int argc, char **argv) {
}
// We should get 2 rounds of profile info, one from the explicit dump request,
// and one at exit.
// CHECK: Memory allocation stack id
// CHECK: Stack for id
// CHECK: Memory allocation stack id
// CHECK: Stack for id
// CHECK-TEXT: Memory allocation stack id
// CHECK-TEXT: Stack for id
// CHECK-TEXT: Memory allocation stack id
// CHECK-TEXT: Stack for id
//
// For the raw profile just check the header magic. The following check assumes that memprof
// runs on little endian architectures.
// CHECK-RAW: 0000000 201 r f o r p m 377