diff --git a/compiler-rt/lib/memprof/CMakeLists.txt b/compiler-rt/lib/memprof/CMakeLists.txt --- a/compiler-rt/lib/memprof/CMakeLists.txt +++ b/compiler-rt/lib/memprof/CMakeLists.txt @@ -10,6 +10,7 @@ memprof_malloc_linux.cpp memprof_mibmap.cpp memprof_posix.cpp + memprof_rawprofile.cpp memprof_rtl.cpp memprof_shadow_setup.cpp memprof_stack.cpp @@ -38,6 +39,7 @@ memprof_mapping.h memprof_meminfoblock.h memprof_mibmap.h + memprof_rawprofile.h memprof_stack.h memprof_stats.h memprof_thread.h @@ -195,3 +197,8 @@ add_dependencies(memprof clang_rt.memprof-${arch}-symbols) endif() endforeach() + + +if(COMPILER_RT_INCLUDE_TESTS) + add_subdirectory(tests) +endif() diff --git a/compiler-rt/lib/memprof/memprof_allocator.cpp b/compiler-rt/lib/memprof/memprof_allocator.cpp --- a/compiler-rt/lib/memprof/memprof_allocator.cpp +++ b/compiler-rt/lib/memprof/memprof_allocator.cpp @@ -17,6 +17,7 @@ #include "memprof_mapping.h" #include "memprof_meminfoblock.h" #include "memprof_mibmap.h" +#include "memprof_rawprofile.h" #include "memprof_stack.h" #include "memprof_thread.h" #include "sanitizer_common/sanitizer_allocator_checks.h" @@ -27,7 +28,9 @@ #include "sanitizer_common/sanitizer_flags.h" #include "sanitizer_common/sanitizer_internal_defs.h" #include "sanitizer_common/sanitizer_list.h" +#include "sanitizer_common/sanitizer_procmaps.h" #include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_vector.h" #include #include @@ -220,13 +223,20 @@ // Holds the mapping of stack ids to MemInfoBlocks. MIBMapTy MIBMap; - bool destructing; - bool constructed = false; + atomic_uint8_t destructing; + atomic_uint8_t constructed; + bool print_text; // ------------------- Initialization ------------------------ - explicit Allocator(LinkerInitialized) - : destructing(false), constructed(true) {} - ~Allocator() { FinishAndPrint(); } + explicit Allocator(LinkerInitialized) : print_text(flags()->print_text) { + atomic_store_relaxed(&destructing, 0); + atomic_store_relaxed(&constructed, 1); + } + + ~Allocator() { + atomic_store_relaxed(&destructing, 1); + FinishAndWrite(); + } static void PrintCallback(const uptr Key, LockedMemInfoBlock *const &Value, void *Arg) { @@ -234,12 +244,32 @@ Value->mib.Print(Key, bool(Arg)); } - void FinishAndPrint() { + void FinishAndWrite() { if (common_flags()->print_module_map) DumpProcessMap(); - if (!flags()->print_terse) + + InsertLiveBlocks(); + + if (print_text) { + WriteTextProfile(); + return; + } + + // Serialize the contents to a raw profile. Format documented in + // memprof_rawprofile.h. + char *Buffer = nullptr; + + MemoryMappingLayout Layout(/*cache_enabled=*/true); + int BytesSerialized = SerializeToRawProfile(MIBMap, Layout, Buffer); + CHECK(Buffer && BytesSerialized && "could not serialize to buffer"); + report_file.Write(Buffer, BytesSerialized); + } + + // Inserts any blocks which have been allocated but not yet deallocated. + void InsertLiveBlocks() { + if (print_text && !flags()->print_terse) Printf("Live on exit:\n"); - allocator.ForceLock(); + allocator.ForEachChunk( [](uptr chunk, void *alloc) { u64 user_requested_size; @@ -256,12 +286,12 @@ InsertOrMerge(m->alloc_context_id, newMIB, A->MIBMap); }, this); + } - destructing = true; + void WriteTextProfile() { MIBMap.ForEach(PrintCallback, reinterpret_cast(flags()->print_terse)); StackDepotPrintAll(); - allocator.ForceUnlock(); } void InitLinkerInitialized() { @@ -393,7 +423,9 @@ u64 user_requested_size = atomic_exchange(&m->user_requested_size, 0, memory_order_acquire); - if (memprof_inited && memprof_init_done && constructed && !destructing) { + if (memprof_inited && memprof_init_done && + atomic_load_relaxed(&constructed) && + !atomic_load_relaxed(&destructing)) { u64 c = GetShadowCount(p, user_requested_size); long curtime = GetTimestamp(); @@ -503,16 +535,6 @@ void Purge(BufferedStackTrace *stack) { allocator.ForceReleaseToOS(); } void PrintStats() { allocator.PrintStats(); } - - void ForceLock() NO_THREAD_SAFETY_ANALYSIS { - allocator.ForceLock(); - fallback_mutex.Lock(); - } - - void ForceUnlock() NO_THREAD_SAFETY_ANALYSIS { - fallback_mutex.Unlock(); - allocator.ForceUnlock(); - } }; static Allocator instance(LINKER_INITIALIZED); @@ -666,7 +688,7 @@ } int __memprof_profile_dump() { - instance.FinishAndPrint(); + instance.FinishAndWrite(); // In the future we may want to return non-zero if there are any errors // detected during the dumping process. return 0; diff --git a/compiler-rt/lib/memprof/memprof_flags.inc b/compiler-rt/lib/memprof/memprof_flags.inc --- a/compiler-rt/lib/memprof/memprof_flags.inc +++ b/compiler-rt/lib/memprof/memprof_flags.inc @@ -35,5 +35,7 @@ "realloc(p, 0) is equivalent to free(p) by default (Same as the " "POSIX standard). If set to false, realloc(p, 0) will return a " "pointer to an allocated space which can not be used.") +MEMPROF_FLAG(bool, print_text, true, + "If set, prints the heap profile in text format. Else use the raw binary serialization format.") MEMPROF_FLAG(bool, print_terse, false, - "If set, prints memory profile in a terse format.") + "If set, prints memory profile in a terse format. Only applicable if print_text = true.") diff --git a/compiler-rt/lib/memprof/memprof_rawprofile.h b/compiler-rt/lib/memprof/memprof_rawprofile.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/memprof/memprof_rawprofile.h @@ -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 + +int SerializeToRawProfile(MIBMapTy &BlockCache, MemoryMappingLayoutBase &Layout, + char *&Buffer); + +} // namespace __memprof + +#endif // MEMPROF_RAWPROFILE_H_ diff --git a/compiler-rt/lib/memprof/memprof_rawprofile.cpp b/compiler-rt/lib/memprof/memprof_rawprofile.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/memprof/memprof_rawprofile.cpp @@ -0,0 +1,224 @@ +#include "memprof_rawprofile.h" +#include "memprof_meminfoblock.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 +#include + +namespace __memprof { +using ::__sanitizer::Vector; + +namespace { +typedef struct __attribute__((__packed__)) { + u64 start; + u64 end; + u64 offset; + u8 buildId[32]; +} SegmentEntry; + +template char *WriteBytes(T Pod, char *&Buffer) { + *(T *)Buffer = Pod; + return Buffer + sizeof(T); +} + +} // namespace + +int SerializeSegments(MemoryMappingLayoutBase &Layout, char *&Buffer) { + u32 NumSegmentsToRecord = 0; + MemoryMappedSegment segment; + + for (Layout.Reset(); Layout.Next(&segment);) + if (segment.IsReadable() && segment.IsExecutable()) + NumSegmentsToRecord++; + + CHECK(Buffer == nullptr); + // Allocate a buffer + int NumBytesToWrite = + sizeof(u64) // A header which stores the number of records. + + sizeof(SegmentEntry) * NumSegmentsToRecord; + Buffer = (char *)malloc(NumBytesToWrite); + + char *Ptr = Buffer; + *((u64 *)Ptr) = NumSegmentsToRecord; + Ptr += sizeof(u64); + + 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); + } + } + + return NumBytesToWrite; +} + +// The stack info section uses the following format: +// +// ---------- Stack Info +// Num Entries +// ---------- Stack Entry +// Num Stacks +// PC1 +// PC2 +// ... +// ---------- +int SerializeStack(const Vector &StackIds, char *&Buffer) { + int 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); + } + } + + Buffer = (char *)malloc(NumBytesToWrite); + char *Ptr = Buffer; + Ptr = WriteBytes(static_cast(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(pc), Ptr); + ++Count; + } + // Store the count in the space we reserved earlier. + *(u64 *)(Ptr - (Count + 1) * sizeof(u64)) = Count; + } + + return NumBytesToWrite; +} + +static void RecordStackId(const uptr Key, UNUSED LockedMemInfoBlock *const &MIB, + void *Arg) { + // No need to touch the value here since we are only recording the key. + auto *StackIds = reinterpret_cast *>(Arg); + StackIds->PushBack(Key); +} + +int SerializeMIBInfo(MIBMapTy &MIBMap, Vector &StackIds, char *&Buffer) { + CHECK(StackIds.Size() == 0); + + // The first value we write is the number of MIBs we serialize. + int NumBytesToWrite = sizeof(u64); + + MIBMap.ForEach(RecordStackId, reinterpret_cast(&StackIds)); + const u64 NumEntries = StackIds.Size(); + // Each MIB is preceded by a u64 stack id. + NumBytesToWrite += NumEntries * (sizeof(u64) + sizeof(MemInfoBlock)); + + Buffer = (char *)malloc(NumBytesToWrite); + char *Ptr = Buffer; + Ptr = WriteBytes(NumEntries, Ptr); + + for (uptr i = 0; i < StackIds.Size(); 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); + } + + return NumBytesToWrite; +} + +// 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 +// ... +// ---------- +// ... +int SerializeToRawProfile(MIBMapTy &MIBMap, MemoryMappingLayoutBase &Layout, + char *&Buffer) { + char *SegmentBuffer = nullptr; + int NumSegmentBytes = SerializeSegments(Layout, SegmentBuffer); + + char *MIBInfoBuffer = nullptr; + Vector StackIds; + int NumMIBInfoBytes = SerializeMIBInfo(MIBMap, StackIds, MIBInfoBuffer); + + char *StackBuffer = nullptr; + int NumStackBytes = SerializeStack(StackIds, StackBuffer); + + // Compute how many bytes we need for the header. + int NumBytesHeader = sizeof(MEMPROF_RAW_MAGIC_64) + + sizeof(MEMPROF_RAW_VERSION) + + 4 * sizeof(u64); // Offset into Segment and Stack + // sections, MIB and total size. + + int TotalSizeBytes = + NumBytesHeader + NumSegmentBytes + NumStackBytes + NumMIBInfoBytes; + + // Allocate the memory for the entire buffer incl. info blocks. + Buffer = (char *)malloc(TotalSizeBytes); + char *Ptr = Buffer; + + // Write Header + Ptr = WriteBytes(MEMPROF_RAW_MAGIC_64, Ptr); + Ptr = WriteBytes(MEMPROF_RAW_VERSION, Ptr); + Ptr = WriteBytes(static_cast(TotalSizeBytes), Ptr); + Ptr = WriteBytes(static_cast(NumBytesHeader), Ptr); + Ptr = WriteBytes(static_cast(NumBytesHeader + NumSegmentBytes), Ptr); + Ptr = WriteBytes( + static_cast(NumBytesHeader + NumSegmentBytes + NumMIBInfoBytes), + Ptr); + + memcpy(Ptr, SegmentBuffer, NumSegmentBytes); + Ptr += NumSegmentBytes; + memcpy(Ptr, MIBInfoBuffer, NumMIBInfoBytes); + Ptr += NumMIBInfoBytes; + memcpy(Ptr, StackBuffer, NumStackBytes); + // We don't need Ptr anymore.. just return the total size. + + return TotalSizeBytes; +} + +} // namespace __memprof diff --git a/compiler-rt/lib/memprof/tests/CMakeLists.txt b/compiler-rt/lib/memprof/tests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/memprof/tests/CMakeLists.txt @@ -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} + $ + $ + $ + $) + 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() diff --git a/compiler-rt/lib/memprof/tests/driver.cpp b/compiler-rt/lib/memprof/tests/driver.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/memprof/tests/driver.cpp @@ -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(); +} diff --git a/compiler-rt/lib/memprof/tests/rawprofile.cpp b/compiler-rt/lib/memprof/tests/rawprofile.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/memprof/tests/rawprofile.cpp @@ -0,0 +1,157 @@ +#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 + +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)); +}; + +std::unique_ptr MakeFakeMap() { + MemInfoBlock FakeMIB; + memset(&FakeMIB, 0, sizeof(MemInfoBlock)); + FakeMIB.alloc_count = 0x1; + FakeMIB.total_access_count = 0x2; + + // Create a fake stack trace, start from 2 since we deduct 1 to get prior pc + // address. + uptr array[] = {2, 3, 4, 5, 6}; + StackTrace St(array, ARRAY_SIZE(array)); + u32 Id = StackDepotPut(St); + + auto FakeMap = std::make_unique(); + InsertOrMerge(Id, FakeMIB, *FakeMap); + + return FakeMap; +} + +template T Read(char *&Buffer) { + static_assert(std::is_pod::value, "Must be a POD type."); + T t = *reinterpret_cast(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 = 0x20; + 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 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); + + auto FakeMap = MakeFakeMap(); + char *Ptr = nullptr; + int NumBytes = SerializeToRawProfile(*FakeMap, Layout, Ptr); + const char *Buffer = Ptr; + + ASSERT_GT(NumBytes, 0); + 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_GT(TotalSize, 0ULL); + + EXPECT_GT(SegmentOffset, 0ULL); + // We expect only 1 segment entry, 8b for the count and 56b for SegmentEntry + // in memprof_rawprofile.cpp. + EXPECT_EQ(MIBOffset - SegmentOffset, 64ULL); + + EXPECT_GT(MIBOffset, 0ULL); + // We expect 1 mib entry, 8b for the count and sizeof(u64) + + // sizeof(MemInfoBlock) contains stack id + MeminfoBlock. + EXPECT_EQ(StackOffset - MIBOffset, 8 + 8 + sizeof(MemInfoBlock)); + + EXPECT_GT(StackOffset, 0ULL); + // We expect 1 stack entries, with 5 frames - 8b for total count, 8b for + // id, 8b for frame count and 5*8b for fake frame. + EXPECT_EQ(TotalSize - StackOffset, 8ULL + (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 + 0x20, 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 1. + EXPECT_EQ(*reinterpret_cast(Buffer + MIBOffset), 1ULL); + // Check that stack id is set. + EXPECT_NE(*reinterpret_cast(Buffer + MIBOffset + 8), 0ULL); + + // 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 after skipping count and stack id. + EXPECT_EQ( + memcmp(Buffer + MIBOffset + 16, ExpectedMIBBytes, sizeof(MemInfoBlock)), + 0); + + // Check that the number of entries is 1. + EXPECT_EQ(*reinterpret_cast(Buffer + StackOffset), 1ULL); + // Check that the stack id is not null. + EXPECT_NE(*reinterpret_cast(Buffer + StackOffset + 8), 0ULL); + // Contents are num pcs, value of each pc - 1. + unsigned char ExpectedStackBytes[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, + }; + EXPECT_EQ(memcmp(Buffer + StackOffset + 16, ExpectedStackBytes, + sizeof(ExpectedStackBytes)), + 0); +} + +} // namespace diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps.h b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_procmaps.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_procmaps.h @@ -65,13 +65,23 @@ 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. diff --git a/compiler-rt/test/memprof/TestCases/memprof_profile_dump.cpp b/compiler-rt/test/memprof/TestCases/memprof_profile_dump.cpp --- a/compiler-rt/test/memprof/TestCases/memprof_profile_dump.cpp +++ b/compiler-rt/test/memprof/TestCases/memprof_profile_dump.cpp @@ -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 #include @@ -17,7 +19,10 @@ } // 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. +// CHECK-RAW: 0000000 201 r f o r p m 377