Index: lib/asan/asan_allocator.cc =================================================================== --- lib/asan/asan_allocator.cc +++ lib/asan/asan_allocator.cc @@ -1049,6 +1049,11 @@ __asan::get_allocator().ForEachChunk(callback, arg); } +void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, void *arg) { + __asan::AsanAllocator::ForEachChunkOutOfProcess(prc, callback, arg); +} + IgnoreObjectResult IgnoreObjectLocked(const void *p) { uptr addr = reinterpret_cast(p); __asan::AsanChunk *m = __asan::instance.GetAsanChunkByAddr(addr); Index: lib/lsan/lsan_common.h =================================================================== --- lib/lsan/lsan_common.h +++ lib/lsan/lsan_common.h @@ -19,6 +19,7 @@ #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_internal_defs.h" #include "sanitizer_common/sanitizer_platform.h" +#include "sanitizer_common/sanitizer_process_vm_reader.h" #include "sanitizer_common/sanitizer_stoptheworld.h" #include "sanitizer_common/sanitizer_symbolizer.h" @@ -195,6 +196,13 @@ // The following must be implemented in the parent tool. void ForEachChunk(ForEachChunkCallback callback, void *arg); + +// Enumerate the chunks of the global allocator (returned by +// `__lsan::GetAllocatorGlobalRange()`) of a remote process. `prc` should have +// already performed a read of the allocator of the remote process. +void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, void *arg); + // Returns the address range occupied by the global allocator object. void GetAllocatorGlobalRange(uptr *begin, uptr *end); // Wrappers for allocator's ForceLock()/ForceUnlock(). Index: lib/sanitizer_common/CMakeLists.txt =================================================================== --- lib/sanitizer_common/CMakeLists.txt +++ lib/sanitizer_common/CMakeLists.txt @@ -25,6 +25,9 @@ sanitizer_platform_limits_solaris.cc sanitizer_posix.cc sanitizer_printf.cc + sanitizer_process_vm_reader_common.cc + sanitizer_process_vm_reader_mac.cc + sanitizer_process_vm_reader_stubs.cc sanitizer_procmaps_common.cc sanitizer_procmaps_bsd.cc sanitizer_procmaps_linux.cc Index: lib/sanitizer_common/sanitizer_allocator.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator.h +++ lib/sanitizer_common/sanitizer_allocator.h @@ -14,12 +14,13 @@ #ifndef SANITIZER_ALLOCATOR_H #define SANITIZER_ALLOCATOR_H -#include "sanitizer_internal_defs.h" #include "sanitizer_common.h" +#include "sanitizer_internal_defs.h" +#include "sanitizer_lfstack.h" #include "sanitizer_libc.h" #include "sanitizer_list.h" #include "sanitizer_mutex.h" -#include "sanitizer_lfstack.h" +#include "sanitizer_process_vm_reader.h" #include "sanitizer_procmaps.h" namespace __sanitizer { Index: lib/sanitizer_common/sanitizer_allocator_combined.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_combined.h +++ lib/sanitizer_common/sanitizer_allocator_combined.h @@ -24,6 +24,9 @@ class SecondaryAllocator> // NOLINT class CombinedAllocator { public: + typedef CombinedAllocator + ThisT; void InitLinkerInitialized(s32 release_to_os_interval_ms) { primary_.Init(release_to_os_interval_ms); secondary_.InitLinkerInitialized(); @@ -189,6 +192,31 @@ secondary_.ForEachChunk(callback, arg); } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + CHECK_NE(callback, nullptr); + // We don't use `__builtin_offset(ThisT, ...)` here because + // `AllocatorGlobalStats` is not a POD type. + u64 offset_of_primary = + reinterpret_cast( + &(reinterpret_cast(prc->GetLocalAddress())->primary_)) - + reinterpret_cast(prc->GetLocalAddress()); + __sanitizer::ProcessVMReaderContext primaryReader = + prc->MakeSlice(offset_of_primary, sizeof(PrimaryAllocator)); + PrimaryAllocator::ForEachChunkOutOfProcess(&primaryReader, callback, arg); + u64 offset_of_secondary = + reinterpret_cast( + &(reinterpret_cast(prc->GetLocalAddress())->secondary_)) - + reinterpret_cast(prc->GetLocalAddress()); + __sanitizer::ProcessVMReaderContext secondaryReader = + prc->MakeSlice(offset_of_secondary, sizeof(SecondaryAllocator)); + SecondaryAllocator::ForEachChunkOutOfProcess(&secondaryReader, callback, + arg); + } + private: PrimaryAllocator primary_; SecondaryAllocator secondary_; Index: lib/sanitizer_common/sanitizer_allocator_primary32.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_primary32.h +++ lib/sanitizer_common/sanitizer_allocator_primary32.h @@ -250,6 +250,14 @@ } } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg) { + // TODO(dliew) + CHECK(prc->IsUsingExternalStorage()); + UNIMPLEMENTED(); + } + void PrintStats() {} static uptr AdditionalSize() { return 0; } Index: lib/sanitizer_common/sanitizer_allocator_primary64.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_primary64.h +++ lib/sanitizer_common/sanitizer_allocator_primary64.h @@ -303,6 +303,63 @@ } } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + CHECK_NE(callback, nullptr); + + // NOTE: Do not dereference any pointers that are not members of `ThisT` + // because we are working with a copy of `ThisT` from another process. + // This means we have to be very careful when calling methods on this + // type. + ThisT *oop_allocator_cpy = + reinterpret_cast(prc->GetLocalAddress()); + + // Sanity check that the out of process allocator and the version of the + // allocator in the current process agree on memory layout. + // FIXME(dliew): Calling `SpaceBeg()` doesn't really do what we want. It + // uses global constants and so that's not part of what gets copied from + // the remote process. So we are using the analysis process's version of + // these constants and not the remote's versions. Right now everything + // "works" because the allocator is configured in the same way in the remote + // and analysis process. + CHECK_EQ(oop_allocator_cpy->address_range.base(), + oop_allocator_cpy->SpaceBeg()); + uptr TotalSpaceSize = kSpaceSize + oop_allocator_cpy->AdditionalSize(); + CHECK_EQ(oop_allocator_cpy->address_range.size(), TotalSpaceSize); + // TODO(dliew): Check that kNumClasses correspond between other process and + // this one. We need a way of finding the address of the global constant + // in the remote process. + + ProcessVMReaderContext::ReadErrorTy success; + // Skip class_id 0 because it is used for zero sized allocations. + for (uptr class_id = 1; class_id < kNumClasses; ++class_id) { + RegionInfo region_info; + // Get the address of the region in the target + uptr region_oop = + reinterpret_cast(oop_allocator_cpy->GetRegionInfo(class_id)); + // Copy into this process + success = prc->Read(region_oop, sizeof(RegionInfo)); + CHECK_EQ(success, ProcessVMReaderContext::SUCCESS); + prc->SetExternalStorage(®ion_info); + + uptr chunk_size = oop_allocator_cpy->ClassIdToSize(class_id); + uptr region_beg = oop_allocator_cpy->SpaceBeg() + class_id * kRegionSize; + + // Iterate over the chunks in the region + for (uptr chunk = region_beg; + chunk < region_beg + region_info.allocated_user; + chunk += chunk_size) { + // We are passing the address to the chunk in the remote process. + // It is the responsibility of the callback to load it into the current + // process. + callback(chunk, arg); + } + } + } + static uptr ClassIdToSize(uptr class_id) { return SizeClassMap::Size(class_id); } Index: lib/sanitizer_common/sanitizer_allocator_secondary.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_secondary.h +++ lib/sanitizer_common/sanitizer_allocator_secondary.h @@ -71,6 +71,8 @@ class PtrArrayT = DefaultLargeMmapAllocatorPtrArray> class LargeMmapAllocator { public: + typedef LargeMmapAllocator ThisT; + void InitLinkerInitialized() { page_size_ = GetPageSizeCached(); chunks_ = reinterpret_cast(ptr_array_.Init()); @@ -281,6 +283,42 @@ } } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + CHECK_NE(callback, nullptr); + + // NOTE: Do not dereference any pointers that are not members of `ThisT` + // because we are working with a copy of `ThisT` from another process. + // This means we have to be very careful when calling methods on this + // type. + ThisT *oop_allocator_cpy = + reinterpret_cast(prc->GetLocalAddress()); + // Sanity check + CHECK_EQ(oop_allocator_cpy->page_size_, GetPageSize()); + + // NOTE: Unlike the in-process `ForEachChunk()` we can't modify the memory + // of the external process so we can't ensure that the chunks are sorted. + ProcessVMReaderContext::ReadErrorTy success; + for (uptr i = 0; i < oop_allocator_cpy->n_chunks_; ++i) { + uptr header_oop_ptr_oop = + reinterpret_cast(oop_allocator_cpy->chunks_) + + (sizeof(Header *) * i); + success = prc->Read(header_oop_ptr_oop, sizeof(Header *)); + CHECK_EQ(success, ProcessVMReaderContext::SUCCESS); + uptr header_oop_ptr = *(reinterpret_cast(prc->GetLocalAddress())); + + uptr chunk = reinterpret_cast(oop_allocator_cpy->GetUser( + reinterpret_cast
(header_oop_ptr))); + // We are passing the address to the chunk in the remote process. + // It is the responsibility of the callback to load it into the current + // process. + callback(chunk, arg); + } + } + private: struct Header { uptr map_beg; Index: lib/sanitizer_common/sanitizer_process_vm_reader.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_process_vm_reader.h @@ -0,0 +1,112 @@ +//===-- sanitizer_process_vm_reader.h ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// ProcessVMReaderContext is an abstraction that allows clients to read the +// memory of a target process. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_PROCESS_VM_READER_H +#define SANITIZER_PROCESS_VM_READER_H +#include "sanitizer_internal_defs.h" +#include "sanitizer_platform.h" + +#if SANITIZER_MAC +#include +#endif + +namespace __sanitizer { + +struct ProcessVMReaderContext { +#if SANITIZER_MAC + typedef task_t ProcessHandle; +#else + // Stub type so code compiles. + typedef int ProcessHandle; +#endif + enum ReadErrorTy { + SUCCESS, + FAILURE, + }; + + private: + uptr remote_address; // Address in remote process. + uptr local_address; // Address in current process. + u64 size; + void* reader; // Platform specific + ProcessHandle remote; + bool using_external_storage; + bool finalized; + + public: + // Returns a pointer to the local copy of memory read from the remote process. + // Returns nullptr if no reads have been made or `Reset()` was called. + void* GetLocalAddress() const { return (void*)local_address; } + + // Returns the address in the remote process of the last call to`Read()`. + // Returns nullptr if no reads have been made or `Reset()` was called. + uptr GetRemoteAddress() const { return remote_address; } + + // Returns the size of previous read. Returns `0` if no reads have been made + // or `Reset()` was called. + u64 GetSize() const { return size; } + + // Return the native handle to the remote process. + ProcessHandle GetRemote() const { return remote; } + bool IsUsingExternalStorage() const { return using_external_storage; } + void Reset(); + // Disallow further modifications being made on this instance. + void Finalize() { finalized = true; } + bool IsFinalized() const { return finalized; } + + // Out-of-process version, i.e. the target and current process are different + ProcessVMReaderContext(void* reader, ProcessHandle remote); + + // In-process version, i.e. the target and current process are the same. + ProcessVMReaderContext(); + + // Try to prevent clients from creating multiple copies of an instance. + // Instead clients should use MakeForNewRead(). + private: + ProcessVMReaderContext(const ProcessVMReaderContext&) = default; + + public: + ProcessVMReaderContext& operator=(const ProcessVMReaderContext&) = delete; + ProcessVMReaderContext(ProcessVMReaderContext&&) = default; + + // Read memory from a remote process. The memory will accessible at the + // address pointed to by `GetLocalAddress()`. + // + // The lifetime of the memory allocated by the `Read()` is valid until the + // next call. To extend the lifetime use `setExternalStorage()` after calling + // `Read()`. + // + // Returns `ReadErrorTy::SUCCESS` on a sucessful read and + // `ReadErrorTy::FAILURE` otherwise. + ReadErrorTy Read(uptr remote_address, u64 size); + + // Copy previously read memory to storage at `dst`. Note `Read()` must have + // been called previously and succeeded. It is the client's responsibility to + // manage the memory pointed to by `dst`. + void SetExternalStorage(void* dst); + + // Get a new ProcessVMReaderContext that refers to an offset + // at previously read memory. + ProcessVMReaderContext MakeSlice(u64 offset, u64 new_size); + + // Create a new ProcessVMReaderContext that will read from the same target + // process but has not performed any reads itself. The instance this is called + // on must have never previously performed a read or must be using external + // storage. + ProcessVMReaderContext MakeForNewRead(); +}; + +} // namespace __sanitizer + +#endif Index: lib/sanitizer_common/sanitizer_process_vm_reader_common.cc =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_process_vm_reader_common.cc @@ -0,0 +1,63 @@ +//===-- sanitizer_process_vm_reader_common.cc -------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implementation of `ProcessVMReaderContext` methods common to all platforms. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common.h" +#include "sanitizer_platform.h" +#include "sanitizer_process_vm_reader.h" + +namespace __sanitizer { + +ProcessVMReaderContext ProcessVMReaderContext::MakeForNewRead() { + CHECK(IsUsingExternalStorage() || local_address == 0); + return ProcessVMReaderContext(reader, remote); +} + +ProcessVMReaderContext ProcessVMReaderContext::MakeSlice(u64 offset, + u64 new_size) { + CHECK_NE(local_address, 0); + CHECK_GT(size, 0); + CHECK_LT(offset, size); + CHECK_LT(new_size, (size - offset)); + // Make a copy + ProcessVMReaderContext that(*this); + that.remote_address += offset; + that.local_address += offset; + that.size = new_size; + that.finalized = false; + return that; +} + +void ProcessVMReaderContext::Reset() { + if (IsFinalized()) { + Report("Cannot reset on finalized instance.\n"); + Die(); + } + remote_address = 0; + local_address = 0; + size = 0; + using_external_storage = false; +} + +void ProcessVMReaderContext::SetExternalStorage(void* dst) { + CHECK_NE(dst, nullptr); + CHECK_NE(local_address, 0); + CHECK_GT(size, 0); + if (IsFinalized()) { + Report("Cannot set external storage on finalized instance.\n"); + Die(); + } + internal_memcpy(dst, (void*)local_address, size); + local_address = (uptr)dst; + using_external_storage = true; +} + +} // namespace __sanitizer Index: lib/sanitizer_common/sanitizer_process_vm_reader_mac.cc =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_process_vm_reader_mac.cc @@ -0,0 +1,78 @@ +//===-- sanitizer_process_vm_reader_mac.cc ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implementation of `ProcessVMReaderContext` for Apple platforms. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_platform.h" +#if SANITIZER_MAC +#include +#include "sanitizer_common.h" +#include "sanitizer_libc.h" +#include "sanitizer_process_vm_reader.h" + +// Taken from +typedef kern_return_t memory_reader_t(task_t remote_task, + vm_address_t remote_address, + vm_size_t size, void** local_memory); + +namespace __sanitizer { + +ProcessVMReaderContext::ProcessVMReaderContext(void* reader, + ProcessHandle remote) + : remote_address(0), + local_address(0), + size(0), + reader(reader), + remote(remote), + using_external_storage(false), + finalized(false) {} + +ProcessVMReaderContext::ProcessVMReaderContext() + : ProcessVMReaderContext(nullptr, mach_task_self()) {} + +ProcessVMReaderContext::ReadErrorTy ProcessVMReaderContext::Read( + uptr remote_address, u64 size) { + if (IsFinalized()) { + Report("Cannot read on finalized instance.\n"); + Die(); + } + if (reader == nullptr || mach_task_self() == remote) { + // The remote is the current process, copy isn't necessary. + local_address = remote_address; + this->size = size; + using_external_storage = false; + return SUCCESS; + } + CHECK_NE(reader, nullptr); + CHECK_GT(size, 0); + + if (remote_address == this->remote_address && size == this->size) { + // Read already performed in previous call + return SUCCESS; + } + memory_reader_t* native_reader = (memory_reader_t*)reader; + kern_return_t result = + native_reader(remote, remote_address, size, (void**)(&(local_address))); + using_external_storage = false; + if (result == KERN_SUCCESS) { + this->remote_address = remote_address; + this->size = size; + return SUCCESS; + } + Report("Failed to read %p of size %d from remote process. Error code: %d\n", + remote_address, size, result); + // Reset the fields. + Reset(); + return FAILURE; +} + +} // namespace __sanitizer + +#endif Index: lib/sanitizer_common/sanitizer_process_vm_reader_stubs.cc =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_process_vm_reader_stubs.cc @@ -0,0 +1,60 @@ +//===-- sanitizer_process_vm_reader_stubs.cc --------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Stub implementation of `ProcessVMReaderContext` for non-Apple platforms. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_platform.h" +#if !SANITIZER_MAC +#include "sanitizer_common.h" +#include "sanitizer_process_vm_reader.h" + +namespace __sanitizer { + +ProcessVMReaderContext::ProcessVMReaderContext(void* reader, + ProcessHandle remote) + : remote_address(0), + local_address(0), + size(0), + reader(0), + remote(0), + using_external_storage(false), + finalized(false) { + // Only in-process reading is allowed in + // stub implementation. + // TODO(dliew): Implement this properly for other platforms. + // * For recent Linux we can use `process_vm_readv(...)` + // * For older Linux we can attach with `PTRACE_DETACH` and + // then `pread()` `/proc//mem`. + CHECK_EQ(reader, nullptr); + CHECK_EQ(remote, 0); +} + +ProcessVMReaderContext::ProcessVMReaderContext() + : ProcessVMReaderContext(nullptr, 0) {} + +ProcessVMReaderContext::ReadErrorTy ProcessVMReaderContext::Read( + uptr remote_address, u64 size) { + if (IsFinalized()) { + Report("Cannot read on finalized instance.\n"); + Die(); + } + // Stub implementation should only ever + // do in-process read. + CHECK_EQ(reader, nullptr); + CHECK_EQ(remote, 0); + this->local_address = remote_address; + this->remote_address = remote_address; + this->size = size; + return ReadErrorTy::SUCCESS; +} + +} // namespace __sanitizer + +#endif Index: lib/sanitizer_common/tests/sanitizer_allocator_test.cc =================================================================== --- lib/sanitizer_common/tests/sanitizer_allocator_test.cc +++ lib/sanitizer_common/tests/sanitizer_allocator_test.cc @@ -14,6 +14,7 @@ #include "sanitizer_common/sanitizer_allocator.h" #include "sanitizer_common/sanitizer_allocator_internal.h" #include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_process_vm_reader.h" #include "sanitizer_test_utils.h" #include "sanitizer_pthread_wrappers.h" @@ -828,7 +829,7 @@ reinterpret_cast *>(arg)->insert(chunk); } -template +template void TestSizeClassAllocatorIteration() { Allocator *a = new Allocator; a->Init(kReleaseToOSIntervalNever); @@ -855,15 +856,38 @@ } } + // Use in-process version of ProcessVMReaderContext because there + // is no external process to read. + auto prc = ProcessVMReaderContext(); + u8 storage[sizeof(Allocator)]; + if (TestOutOfProcess) { + auto success = prc.Read(reinterpret_cast(a), sizeof(Allocator)); + ASSERT_EQ(success, ProcessVMReaderContext::ReadErrorTy::SUCCESS); + // The allocators expect external storage to be used. + prc.SetExternalStorage(storage); + } + std::set reported_chunks; + std::set reported_chunks_oop; a->ForceLock(); a->ForEachChunk(IterationTestCallback, &reported_chunks); + if (TestOutOfProcess) { + a->ForEachChunkOutOfProcess(&prc, IterationTestCallback, + &reported_chunks_oop); + // NOTE reported_chunks_oop.size() >= allocated.size() + // due to the SizeClassAllocatorLocalCache. + ASSERT_EQ(reported_chunks_oop.size(), reported_chunks.size()); + } a->ForceUnlock(); for (uptr i = 0; i < allocated.size(); i++) { // Don't use EXPECT_NE. Reporting the first mismatch is enough. ASSERT_NE(reported_chunks.find(reinterpret_cast(allocated[i])), reported_chunks.end()); + if (TestOutOfProcess) { + ASSERT_NE(reported_chunks_oop.find(reinterpret_cast(allocated[i])), + reported_chunks_oop.end()); + } } a->TestOnlyUnmap(); @@ -884,7 +908,8 @@ #endif TEST(SanitizerCommon, SizeClassAllocator32Iteration) { - TestSizeClassAllocatorIteration(); + // TODO(dliew): Support 32-bit allocator out-of-process enumeration + TestSizeClassAllocatorIteration(); } TEST(SanitizerCommon, LargeMmapAllocatorIteration) { @@ -901,14 +926,30 @@ allocated[i] = (char *)a.Allocate(&stats, size, 1); std::set reported_chunks; + std::set reported_chunks_oop; + + // Use in-process version of ProcessVMReaderContext because there + // is no external process to read. + auto prc = ProcessVMReaderContext(); + u8 storage[sizeof(a)]; + auto success = prc.Read(reinterpret_cast(&a), sizeof(a)); + ASSERT_EQ(success, ProcessVMReaderContext::ReadErrorTy::SUCCESS); + // The allocators expect external storage to be used. + prc.SetExternalStorage(storage); + a.ForceLock(); a.ForEachChunk(IterationTestCallback, &reported_chunks); + a.ForEachChunkOutOfProcess(&prc, IterationTestCallback, &reported_chunks_oop); a.ForceUnlock(); + ASSERT_EQ(reported_chunks_oop.size(), reported_chunks.size()); + ASSERT_EQ(reported_chunks.size(), kNumAllocs); for (uptr i = 0; i < kNumAllocs; i++) { // Don't use EXPECT_NE. Reporting the first mismatch is enough. ASSERT_NE(reported_chunks.find(reinterpret_cast(allocated[i])), reported_chunks.end()); + ASSERT_NE(reported_chunks_oop.find(reinterpret_cast(allocated[i])), + reported_chunks_oop.end()); } for (uptr i = 0; i < kNumAllocs; i++) a.Deallocate(&stats, allocated[i]);