Index: lib/sanitizer_common/CMakeLists.txt =================================================================== --- lib/sanitizer_common/CMakeLists.txt +++ lib/sanitizer_common/CMakeLists.txt @@ -24,6 +24,9 @@ sanitizer_platform_limits_openbsd.cc sanitizer_platform_limits_posix.cc sanitizer_platform_limits_solaris.cc + sanitizer_process_vm_reader_common.cc + sanitizer_process_vm_reader_mac.cc + sanitizer_process_vm_reader_stubs.cc sanitizer_posix.cc sanitizer_printf.cc sanitizer_procmaps_common.cc @@ -96,15 +99,21 @@ sanitizer_addrhashmap.h sanitizer_allocator.h sanitizer_allocator_bytemap.h + sanitizer_allocator_bytemap_oop.h sanitizer_allocator_checks.h sanitizer_allocator_combined.h + sanitizer_allocator_combined_oop.h sanitizer_allocator_interface.h sanitizer_allocator_internal.h sanitizer_allocator_local_cache.h + sanitizer_allocator_oop.h sanitizer_allocator_primary32.h + sanitizer_allocator_primary32_oop.h sanitizer_allocator_primary64.h + sanitizer_allocator_primary64_oop.h sanitizer_allocator_report.h sanitizer_allocator_secondary.h + sanitizer_allocator_secondary_oop.h sanitizer_allocator_size_class_map.h sanitizer_allocator_stats.h sanitizer_asm.h 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_bytemap.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_bytemap.h +++ lib/sanitizer_common/sanitizer_allocator_bytemap.h @@ -18,6 +18,7 @@ template class FlatByteMap { public: + typedef FlatByteMap ThisT; void Init() { internal_memset(map_, 0, sizeof(map_)); } @@ -32,6 +33,10 @@ // FIXME: CHECK may be too expensive here. return map_[idx]; } + + static u8 ReadOutOfProcess(uptr idx, + __sanitizer::ProcessVMReaderContext *prc); + private: u8 map_[kSize]; }; @@ -44,6 +49,7 @@ template class TwoLevelByteMap { public: + typedef TwoLevelByteMap ThisT; void Init() { internal_memset(map1_, 0, sizeof(map1_)); mu_.Init(); @@ -76,6 +82,9 @@ return map2[idx % kSize2]; } + static u8 ReadOutOfProcess(uptr idx, + __sanitizer::ProcessVMReaderContext *prc); + private: u8 *Get(uptr idx) const { CHECK_LT(idx, kSize1); Index: lib/sanitizer_common/sanitizer_allocator_bytemap_oop.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_allocator_bytemap_oop.h @@ -0,0 +1,54 @@ +//===-- sanitizer_allocator_bytemap_oop.h ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Out-of-process part of ByteMap. It is provided as a separate header file +// so that only clients that need this functionality need to include it. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_BYTEMAP_OOP_H +#define SANITIZER_ALLOCATOR_BYTEMAP_OOP_H + +#ifndef SANITIZER_ALLOCATOR_H +#error This file cannot be included before sanitizer_allocator.h +#endif + +namespace __sanitizer { + +template +u8 FlatByteMap::ReadOutOfProcess( + uptr idx, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); // Sanity check + // FIXME(dliew): Can we check the template parameters match? + ThisT *fbm = reinterpret_cast(prc->GetLocalAddress()); + return fbm->map_[idx]; +} + +template +u8 TwoLevelByteMap::ReadOutOfProcess( + uptr idx, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); // Sanity check + // FIXME(dliew): Can we check the template parameters match? + ThisT *tlbm = reinterpret_cast(prc->GetLocalAddress()); + uptr map2 = reinterpret_cast(tlbm->Get(idx / kSize2)); + if (!map2) + return 0; + uptr map2_byte_oop = map2 + (idx % kSize2); + __sanitizer::ProcessVMReaderContext new_prc = prc->MakeForNewRead(); + bool success = new_prc.Read(map2_byte_oop, 1); + if (!success) { + Report("Failed to read from %p in remote process\n", map2_byte_oop); + Die(); + } + return *(reinterpret_cast(new_prc.GetLocalAddress())); +} +} // namespace __sanitizer +#endif 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(); @@ -117,12 +120,21 @@ return primary_.PointerIsMine(p); } + static bool FromPrimaryOutOfProcess(uptr p, + __sanitizer::ProcessVMReaderContext *prc); + void *GetMetaData(const void *p) { if (primary_.PointerIsMine(p)) return primary_.GetMetaData(p); return secondary_.GetMetaData(p); } + // Get a pointer in the target process for the meta data + // for a pointer in the target process. The client + // is responsible for copying over the metadata. + static uptr GetMetaDataPtrOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc); + void *GetBlockBegin(const void *p) { if (primary_.PointerIsMine(p)) return primary_.GetBlockBegin(p); @@ -189,6 +201,10 @@ secondary_.ForEachChunk(callback, arg); } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg); + private: PrimaryAllocator primary_; SecondaryAllocator secondary_; Index: lib/sanitizer_common/sanitizer_allocator_combined_oop.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_allocator_combined_oop.h @@ -0,0 +1,93 @@ +//===-- sanitizer_allocator_combined_oop.h ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Out-of-process part of the Sanitizer Allocator. It is provided as a separate +// header file so that only clients that need this functionality need to +// include it. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_COMBINED_OOP_H +#define SANITIZER_ALLOCATOR_COMBINED_OOP_H + +#ifndef SANITIZER_ALLOCATOR_H +#error This file cannot be included before sanitizer_allocator.h +#endif + +#define SANITIZER_RUNTIME_COMPUTE_OFFSET(TYPE, FIELD_NAME, BASE_INSTANCE_PTR) \ + (reinterpret_cast( \ + &(reinterpret_cast(BASE_INSTANCE_PTR)->FIELD_NAME)) - \ + (reinterpret_cast(BASE_INSTANCE_PTR))) /* NOLINT */ + +namespace __sanitizer { + +template +bool CombinedAllocator:: + FromPrimaryOutOfProcess(uptr p, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + // We don't use `__builtin_offset(ThisT, ...)` here because + // `AllocatorGlobalStats` is not a POD type which makes `ThisT` + // a non POD type. + u64 offset_of_primary = + SANITIZER_RUNTIME_COMPUTE_OFFSET(ThisT, primary_, prc->GetLocalAddress()); + __sanitizer::ProcessVMReaderContext primaryReader = + prc->MakeSlice(offset_of_primary, sizeof(PrimaryAllocator)); + return PrimaryAllocator::PointerIsMineOutOfProcess(p, &primaryReader); +} +template +uptr CombinedAllocator:: + GetMetaDataPtrOutOfProcess(uptr p, + __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + if (FromPrimaryOutOfProcess(p, prc)) { + // TODO(dliew): Only implement this if we actually need it. + UNIMPLEMENTED(); + Die(); + } + // We don't use `__builtin_offset(ThisT, ...)` here because + // `AllocatorGlobalStats` is not a POD type which makes `ThisT` + // a non POD type. + u64 offset_of_secondary = SANITIZER_RUNTIME_COMPUTE_OFFSET( + ThisT, secondary_, prc->GetLocalAddress()); + __sanitizer::ProcessVMReaderContext secondary_reader = + prc->MakeSlice(offset_of_secondary, sizeof(SecondaryAllocator)); + return SecondaryAllocator::GetMetaDataOutOfProcess(p, &secondary_reader); +} + +template +void CombinedAllocator:: + 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 which makes `ThisT` + // a non POD type. + u64 offset_of_primary = + SANITIZER_RUNTIME_COMPUTE_OFFSET(ThisT, primary_, prc->GetLocalAddress()); + __sanitizer::ProcessVMReaderContext primaryReader = + prc->MakeSlice(offset_of_primary, sizeof(PrimaryAllocator)); + PrimaryAllocator::ForEachChunkOutOfProcess(&primaryReader, callback, arg); + u64 offset_of_secondary = SANITIZER_RUNTIME_COMPUTE_OFFSET( + ThisT, secondary_, prc->GetLocalAddress()); + __sanitizer::ProcessVMReaderContext secondaryReader = + prc->MakeSlice(offset_of_secondary, sizeof(SecondaryAllocator)); + SecondaryAllocator::ForEachChunkOutOfProcess(&secondaryReader, callback, arg); +} + +#undef SANITIZER_RUNTIME_COMPUTE_OFFSET + +} // namespace __sanitizer +#endif Index: lib/sanitizer_common/sanitizer_allocator_oop.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_allocator_oop.h @@ -0,0 +1,28 @@ +//===-- sanitizer_allocator_oop.h -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Part of the Sanitizer Allocator. +// This is a convenience header that provides definitions for all +// out-of-process allocator functions. +// +//===----------------------------------------------------------------------===// +#ifndef SANITIZER_ALLOCATOR_H +#error This file cannot be included before sanitizer_allocator.h +#endif + +#ifndef SANITIZER_ALLOCATOR_OOP_H +#define SANITIZER_ALLOCATOR_OOP_H + +#include "sanitizer_allocator_bytemap_oop.h" +#include "sanitizer_allocator_combined_oop.h" +#include "sanitizer_allocator_primary32_oop.h" +#include "sanitizer_allocator_primary64_oop.h" +#include "sanitizer_allocator_secondary_oop.h" + +#endif Index: lib/sanitizer_common/sanitizer_allocator_primary32.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_primary32.h +++ lib/sanitizer_common/sanitizer_allocator_primary32.h @@ -182,10 +182,16 @@ return GetSizeClass(p) != 0; } + static bool PointerIsMineOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc); + uptr GetSizeClass(const void *p) { return possible_regions[ComputeRegionId(reinterpret_cast(p))]; } + static uptr GetSizeClassOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc); + void *GetBlockBegin(const void *p) { CHECK(PointerIsMine(p)); uptr mem = reinterpret_cast(p); @@ -250,6 +256,10 @@ } } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg); + void PrintStats() {} static uptr AdditionalSize() { return 0; } Index: lib/sanitizer_common/sanitizer_allocator_primary32_oop.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_allocator_primary32_oop.h @@ -0,0 +1,78 @@ +//===-- sanitizer_allocator_primary32_oop.h ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Out-of-process part of the Sanitizer Allocator. It is provided as a separate +// header file so that only clients that need this functionality need to +// include it. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_PRIMARY_32_OOP_H +#define SANITIZER_ALLOCATOR_PRIMARY_32_OOP_H + +#ifndef SANITIZER_ALLOCATOR_H +#error This file cannot be included before sanitizer_allocator.h +#endif + +namespace __sanitizer { + +template +bool SizeClassAllocator32::PointerIsMineOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + if (p < kSpaceBeg || p >= kSpaceBeg + kSpaceSize) + return false; + return GetSizeClassOutOfProcess(p, prc) != 0; +} + +template +uptr SizeClassAllocator32::GetSizeClassOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + uptr offset_possible_regions = __builtin_offsetof(ThisT, possible_regions); + __sanitizer::ProcessVMReaderContext possible_regions_slice = + prc->MakeSlice(offset_possible_regions, sizeof(possible_regions)); + ThisT *allocator_cpy = reinterpret_cast(prc->GetLocalAddress()); + // It's currently safe to call `ComputeRegionId` because it doesn't + // dereference any pointers. + auto region_id = allocator_cpy->ComputeRegionId(p); + return ByteMap::ReadOutOfProcess(region_id, &possible_regions_slice); +} + +template +void SizeClassAllocator32::ForEachChunkOutOfProcess( + __sanitizer::ProcessVMReaderContext *prc, ForEachChunkCallback callback, + void *arg) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + uptr offset_possible_regions = __builtin_offsetof(ThisT, possible_regions); + __sanitizer::ProcessVMReaderContext possible_regions_slice = + prc->MakeSlice(offset_possible_regions, sizeof(possible_regions)); + for (uptr region = 0; region <= kNumPossibleRegions; region++) { + uptr region_beg = region * kRegionSize; + auto class_id = ByteMap::ReadOutOfProcess(region, &possible_regions_slice); + if (!class_id) { + continue; + } + + uptr chunk_size = ClassIdToSize(class_id); + uptr max_chunks_in_region = kRegionSize / (chunk_size + kMetadataSize); + + for (uptr chunk = region_beg; + chunk < region_beg + max_chunks_in_region * chunk_size; + chunk += chunk_size) { + callback(chunk, arg); + } + } +} + +} // namespace __sanitizer +#endif Index: lib/sanitizer_common/sanitizer_allocator_primary64.h =================================================================== --- lib/sanitizer_common/sanitizer_allocator_primary64.h +++ lib/sanitizer_common/sanitizer_allocator_primary64.h @@ -160,6 +160,9 @@ return P >= SpaceBeg() && P < SpaceEnd(); } + static bool PointerIsMineOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc); + uptr GetRegionBegin(const void *p) { if (kUsingConstantSpaceBeg) return reinterpret_cast(p) & ~(kRegionSize - 1); @@ -303,6 +306,10 @@ } } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg); + static uptr ClassIdToSize(uptr class_id) { return SizeClassMap::Size(class_id); } Index: lib/sanitizer_common/sanitizer_allocator_primary64_oop.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_allocator_primary64_oop.h @@ -0,0 +1,93 @@ +//===-- sanitizer_allocator_primary64_oop.h ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Out-of-process part of the Sanitizer Allocator. It is provided as a separate +// header file so that only clients that need this functionality need to +// include it. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_PRIMARY_64_OOP_H +#define SANITIZER_ALLOCATOR_PRIMARY_64_OOP_H + +#ifndef SANITIZER_ALLOCATOR_H +#error This file cannot be included before sanitizer_allocator.h +#endif + +namespace __sanitizer { + +template +bool SizeClassAllocator64::PointerIsMineOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + // Right now `PointerIsMine` is safe to call because it doesn't do + // any pointer dereferences. + ThisT *allocator_cpy = reinterpret_cast(prc->GetLocalAddress()); + return allocator_cpy->PointerIsMine(reinterpret_cast(p)); +} + +template +void SizeClassAllocator64::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 target + // 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. + + bool 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(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); + } + } +} + +} // namespace __sanitizer +#endif 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()); @@ -178,6 +180,9 @@ return GetHeader(p) + 1; } + static uptr GetMetaDataOutOfProcess(uptr p, + __sanitizer::ProcessVMReaderContext *prc); + void *GetBlockBegin(const void *ptr) { uptr p = reinterpret_cast(ptr); SpinMutexLock l(&mutex_); @@ -281,6 +286,10 @@ } } + static void ForEachChunkOutOfProcess(__sanitizer::ProcessVMReaderContext *prc, + ForEachChunkCallback callback, + void *arg); + private: struct Header { uptr map_beg; Index: lib/sanitizer_common/sanitizer_allocator_secondary_oop.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_allocator_secondary_oop.h @@ -0,0 +1,72 @@ +//===-- sanitizer_allocator_seconary_oop.h ----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Out-of-process part of the Sanitizer Allocator. It is provided as a separate +// header file so that only clients that need this functionality need to +// include it. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_SECONDARY_OOP_H +#define SANITIZER_ALLOCATOR_SECONDARY_OOP_H + +#ifndef SANITIZER_ALLOCATOR_H +#error This file cannot be included before sanitizer_allocator.h +#endif + +namespace __sanitizer { + +template +uptr LargeMmapAllocator::GetMetaDataOutOfProcess( + uptr p, __sanitizer::ProcessVMReaderContext *prc) { + CHECK(prc->IsUsingExternalStorage()); + CHECK_EQ(prc->GetSize(), sizeof(ThisT)); + ThisT *allocator_cpy = reinterpret_cast(prc->GetLocalAddress()); + // It's currently safe to call GetMetaData directly. + return reinterpret_cast( + allocator_cpy->GetMetaData(reinterpret_cast(p))); +} + +template +void LargeMmapAllocator::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. + bool 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(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); + } +} +} // namespace __sanitizer +#endif Index: lib/sanitizer_common/sanitizer_internal_defs.h =================================================================== --- lib/sanitizer_common/sanitizer_internal_defs.h +++ lib/sanitizer_common/sanitizer_internal_defs.h @@ -170,6 +170,16 @@ typedef int pid_t; #endif +// process_vm_read_handle_t is a platform specific type used by a platform to +// read memory from another process. +#if SANITIZER_MAC +typedef unsigned int task_t; +typedef task_t process_vm_read_handle_t; +#else +// Stub value for other platforms. +typedef int process_read_handle_t; +#endif + #if SANITIZER_FREEBSD || SANITIZER_NETBSD || \ SANITIZER_OPENBSD || SANITIZER_MAC || \ (SANITIZER_LINUX && defined(__x86_64__)) Index: lib/sanitizer_common/sanitizer_process_vm_reader.h =================================================================== --- /dev/null +++ lib/sanitizer_common/sanitizer_process_vm_reader.h @@ -0,0 +1,98 @@ +//===-- 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" + +namespace __sanitizer { + +struct ProcessVMReaderContext { + typedef __sanitizer::process_vm_read_handle_t ProcessHandle; + + 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 `true` on a sucessful read and `false` otherwise. + bool 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,77 @@ +//===-- 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()) {} + +bool 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 true; + } + CHECK_NE(reader, nullptr); + CHECK_GT(size, 0); + + if (remote_address == this->remote_address && size == this->size) { + // Read already performed in previous call + return true; + } + 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 true; + } + Report("Failed to read %p of size %d from remote process. Error code: %d\n", + remote_address, size, result); + // Reset the fields. + Reset(); + return false; +} + +} // 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,51 @@ +//===-- 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. + CHECK_EQ(reader, nullptr); + CHECK_EQ(remote, 0); +} + +ProcessVMReaderContext::ProcessVMReaderContext() + : ProcessVMReaderContext(nullptr, 0) {} + +bool ProcessVMReaderContext::Read(uptr remote_address, u64 size) { + // 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 true; +} + +} // 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 @@ -13,7 +13,9 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_allocator.h" #include "sanitizer_common/sanitizer_allocator_internal.h" +#include "sanitizer_common/sanitizer_allocator_oop.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" @@ -29,8 +31,6 @@ using namespace __sanitizer; -// Too slow for debug build -#if !SANITIZER_DEBUG #if SANITIZER_CAN_USE_ALLOCATOR64 #if SANITIZER_WINDOWS @@ -846,7 +846,7 @@ reinterpret_cast *>(arg)->insert(chunk); } -template +template void TestSizeClassAllocatorIteration() { Allocator *a = new Allocator; a->Init(kReleaseToOSIntervalNever); @@ -873,19 +873,50 @@ } } + // Use in-process version of ProcessVMReaderContext because there + // is no external process to read. + auto prc = ProcessVMReaderContext(); + u8 *storage = nullptr; + if (TestOutOfProcess) { + // We have to allocate storage for the allocator on the heap because the + // Allocator might be too large as a stack allocation. E.g. + // `Allocator32Compact` is much larger that typical default stack limits. + storage = (u8 *)malloc(sizeof(Allocator)); + ASSERT_NE(storage, nullptr); + auto success = prc.Read(reinterpret_cast(a), sizeof(Allocator)); + ASSERT_TRUE(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(); delete a; + if (storage) { + free(storage); + } } #if SANITIZER_CAN_USE_ALLOCATOR64 @@ -902,7 +933,7 @@ #endif TEST(SanitizerCommon, SizeClassAllocator32Iteration) { - TestSizeClassAllocatorIteration(); + TestSizeClassAllocatorIteration(); } TEST(SanitizerCommon, LargeMmapAllocatorIteration) { @@ -919,14 +950,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_TRUE(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]); @@ -1364,4 +1411,3 @@ EXPECT_EQ((uptr)TestMapUnmapCallback::unmap_count, m.size1()); } -#endif // #if !SANITIZER_DEBUG