| 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 |
| 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_ |
| 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() |
| 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(); | ||
| } |
| 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 |