Loading libmemunreachable/HeapWalker.cpp +40 −3 Original line number Diff line number Diff line Loading @@ -14,7 +14,10 @@ * limitations under the License. */ #include <errno.h> #include <inttypes.h> #include <sys/mman.h> #include <unistd.h> #include <map> #include <utility> Loading @@ -22,6 +25,7 @@ #include "Allocator.h" #include "HeapWalker.h" #include "LeakFolding.h" #include "ScopedSignalHandler.h" #include "log.h" bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) { Loading @@ -46,9 +50,15 @@ bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) { } } bool HeapWalker::IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info) { if (ptr >= valid_allocations_range_.begin && ptr < valid_allocations_range_.end) { AllocationMap::iterator it = allocations_.find(Range{ptr, ptr + 1}); bool HeapWalker::WordContainsAllocationPtr(uintptr_t word_ptr, Range* range, AllocationInfo** info) { walking_ptr_ = word_ptr; // This access may segfault if the process under test has done something strange, // for example mprotect(PROT_NONE) on a native heap page. If so, it will be // caught and handled by mmaping a zero page over the faulting page. uintptr_t value = *reinterpret_cast<uintptr_t*>(word_ptr); walking_ptr_ = 0; if (value >= valid_allocations_range_.begin && value < valid_allocations_range_.end) { AllocationMap::iterator it = allocations_.find(Range{value, value + 1}); if (it != allocations_.end()) { *range = it->first; *info = &it->second; Loading Loading @@ -135,3 +145,30 @@ bool HeapWalker::Leaked(allocator::vector<Range>& leaked, size_t limit, return true; } static bool MapOverPage(void* addr) { const size_t page_size = sysconf(_SC_PAGE_SIZE); void *page = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & ~(page_size-1)); void* ret = mmap(page, page_size, PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0); if (ret == MAP_FAILED) { ALOGE("failed to map page at %p: %s", page, strerror(errno)); return false; } return true; } void HeapWalker::HandleSegFault(ScopedSignalHandler& handler, int signal, siginfo_t* si, void* /*uctx*/) { uintptr_t addr = reinterpret_cast<uintptr_t>(si->si_addr); if (addr != walking_ptr_) { handler.reset(); return; } ALOGW("failed to read page at %p, signal %d", si->si_addr, signal); if (!MapOverPage(si->si_addr)) { handler.reset(); } } ScopedSignalHandler::SignalFn ScopedSignalHandler::handler_; libmemunreachable/HeapWalker.h +17 −3 Original line number Diff line number Diff line Loading @@ -17,9 +17,12 @@ #ifndef LIBMEMUNREACHABLE_HEAP_WALKER_H_ #define LIBMEMUNREACHABLE_HEAP_WALKER_H_ #include <signal.h> #include "android-base/macros.h" #include "Allocator.h" #include "ScopedSignalHandler.h" #include "Tarjan.h" // A range [begin, end) Loading @@ -41,10 +44,17 @@ class HeapWalker { public: HeapWalker(Allocator<HeapWalker> allocator) : allocator_(allocator), allocations_(allocator), allocation_bytes_(0), roots_(allocator), root_vals_(allocator) { roots_(allocator), root_vals_(allocator), segv_handler_(allocator), walking_ptr_(0) { valid_allocations_range_.end = 0; valid_allocations_range_.begin = ~valid_allocations_range_.end; segv_handler_.install(SIGSEGV, [=](ScopedSignalHandler& handler, int signal, siginfo_t* siginfo, void* uctx) { this->HandleSegFault(handler, signal, siginfo, uctx); }); } ~HeapWalker() {} bool Allocation(uintptr_t begin, uintptr_t end); void Root(uintptr_t begin, uintptr_t end); Loading @@ -70,7 +80,8 @@ class HeapWalker { private: void RecurseRoot(const Range& root); bool IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info); bool WordContainsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info); void HandleSegFault(ScopedSignalHandler&, int, siginfo_t*, void*); DISALLOW_COPY_AND_ASSIGN(HeapWalker); Allocator<HeapWalker> allocator_; Loading @@ -81,6 +92,9 @@ class HeapWalker { allocator::vector<Range> roots_; allocator::vector<uintptr_t> root_vals_; ScopedSignalHandler segv_handler_; uintptr_t walking_ptr_; }; template<class F> Loading @@ -92,7 +106,7 @@ inline void HeapWalker::ForEachPtrInRange(const Range& range, F&& f) { for (uintptr_t i = begin; i < range.end; i += sizeof(uintptr_t)) { Range ref_range; AllocationInfo* ref_info; if (IsAllocationPtr(*reinterpret_cast<uintptr_t*>(i), &ref_range, &ref_info)) { if (WordContainsAllocationPtr(i, &ref_range, &ref_info)) { f(ref_range, ref_info); } } Loading libmemunreachable/ScopedSignalHandler.h 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_ #define LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_ #include <errno.h> #include <signal.h> #include <functional> #include "android-base/macros.h" #include "log.h" class ScopedSignalHandler { public: using Fn = std::function<void(ScopedSignalHandler&, int, siginfo_t*, void*)>; ScopedSignalHandler(Allocator<Fn> allocator) : allocator_(allocator), signal_(-1) {} ~ScopedSignalHandler() { reset(); } template <class F> void install(int signal, F&& f) { LOG_ALWAYS_FATAL_IF(signal_ != -1, "ScopedSignalHandler already installed"); handler_ = SignalFn(std::allocator_arg, allocator_, [=](int signal, siginfo_t* si, void* uctx) { f(*this, signal, si, uctx); }); struct sigaction act{}; act.sa_sigaction = [](int signal, siginfo_t* si, void* uctx) { handler_(signal, si, uctx); }; act.sa_flags = SA_SIGINFO; int ret = sigaction(signal, &act, &old_act_); if (ret < 0) { LOG_ALWAYS_FATAL("failed to install segfault handler: %s", strerror(errno)); } signal_ = signal; } void reset() { if (signal_ != -1) { int ret = sigaction(signal_, &old_act_, NULL); if (ret < 0) { ALOGE("failed to uninstall segfault handler"); } handler_ = SignalFn{}; signal_ = -1; } } private: using SignalFn = std::function<void(int, siginfo_t*, void*)>; DISALLOW_COPY_AND_ASSIGN(ScopedSignalHandler); Allocator<Fn> allocator_; int signal_; struct sigaction old_act_; // TODO(ccross): to support multiple ScopedSignalHandlers handler_ would need // to be a static map of signals to handlers, but allocated with Allocator. static SignalFn handler_; }; #endif // LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_ libmemunreachable/tests/HeapWalker_test.cpp +27 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,9 @@ * limitations under the License. */ #include <sys/mman.h> #include <unistd.h> #include "HeapWalker.h" #include <gtest/gtest.h> Loading Loading @@ -172,3 +175,27 @@ TEST_F(HeapWalkerTest, cycle) { EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes); ASSERT_EQ(2U, leaked.size()); } TEST_F(HeapWalkerTest, segv) { const size_t page_size = sysconf(_SC_PAGE_SIZE); void* buffer1 = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); ASSERT_NE(buffer1, nullptr); void* buffer2; buffer2 = &buffer1; HeapWalker heap_walker(heap_); heap_walker.Allocation(buffer_begin(buffer1), buffer_begin(buffer1)+page_size); heap_walker.Root(buffer_begin(buffer2), buffer_end(buffer2)); ASSERT_EQ(true, heap_walker.DetectLeaks()); allocator::vector<Range> leaked(heap_); size_t num_leaks = 0; size_t leaked_bytes = 0; ASSERT_EQ(true, heap_walker.Leaked(leaked, 100, &num_leaks, &leaked_bytes)); EXPECT_EQ(0U, num_leaks); EXPECT_EQ(0U, leaked_bytes); ASSERT_EQ(0U, leaked.size()); } Loading
libmemunreachable/HeapWalker.cpp +40 −3 Original line number Diff line number Diff line Loading @@ -14,7 +14,10 @@ * limitations under the License. */ #include <errno.h> #include <inttypes.h> #include <sys/mman.h> #include <unistd.h> #include <map> #include <utility> Loading @@ -22,6 +25,7 @@ #include "Allocator.h" #include "HeapWalker.h" #include "LeakFolding.h" #include "ScopedSignalHandler.h" #include "log.h" bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) { Loading @@ -46,9 +50,15 @@ bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) { } } bool HeapWalker::IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info) { if (ptr >= valid_allocations_range_.begin && ptr < valid_allocations_range_.end) { AllocationMap::iterator it = allocations_.find(Range{ptr, ptr + 1}); bool HeapWalker::WordContainsAllocationPtr(uintptr_t word_ptr, Range* range, AllocationInfo** info) { walking_ptr_ = word_ptr; // This access may segfault if the process under test has done something strange, // for example mprotect(PROT_NONE) on a native heap page. If so, it will be // caught and handled by mmaping a zero page over the faulting page. uintptr_t value = *reinterpret_cast<uintptr_t*>(word_ptr); walking_ptr_ = 0; if (value >= valid_allocations_range_.begin && value < valid_allocations_range_.end) { AllocationMap::iterator it = allocations_.find(Range{value, value + 1}); if (it != allocations_.end()) { *range = it->first; *info = &it->second; Loading Loading @@ -135,3 +145,30 @@ bool HeapWalker::Leaked(allocator::vector<Range>& leaked, size_t limit, return true; } static bool MapOverPage(void* addr) { const size_t page_size = sysconf(_SC_PAGE_SIZE); void *page = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(addr) & ~(page_size-1)); void* ret = mmap(page, page_size, PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0); if (ret == MAP_FAILED) { ALOGE("failed to map page at %p: %s", page, strerror(errno)); return false; } return true; } void HeapWalker::HandleSegFault(ScopedSignalHandler& handler, int signal, siginfo_t* si, void* /*uctx*/) { uintptr_t addr = reinterpret_cast<uintptr_t>(si->si_addr); if (addr != walking_ptr_) { handler.reset(); return; } ALOGW("failed to read page at %p, signal %d", si->si_addr, signal); if (!MapOverPage(si->si_addr)) { handler.reset(); } } ScopedSignalHandler::SignalFn ScopedSignalHandler::handler_;
libmemunreachable/HeapWalker.h +17 −3 Original line number Diff line number Diff line Loading @@ -17,9 +17,12 @@ #ifndef LIBMEMUNREACHABLE_HEAP_WALKER_H_ #define LIBMEMUNREACHABLE_HEAP_WALKER_H_ #include <signal.h> #include "android-base/macros.h" #include "Allocator.h" #include "ScopedSignalHandler.h" #include "Tarjan.h" // A range [begin, end) Loading @@ -41,10 +44,17 @@ class HeapWalker { public: HeapWalker(Allocator<HeapWalker> allocator) : allocator_(allocator), allocations_(allocator), allocation_bytes_(0), roots_(allocator), root_vals_(allocator) { roots_(allocator), root_vals_(allocator), segv_handler_(allocator), walking_ptr_(0) { valid_allocations_range_.end = 0; valid_allocations_range_.begin = ~valid_allocations_range_.end; segv_handler_.install(SIGSEGV, [=](ScopedSignalHandler& handler, int signal, siginfo_t* siginfo, void* uctx) { this->HandleSegFault(handler, signal, siginfo, uctx); }); } ~HeapWalker() {} bool Allocation(uintptr_t begin, uintptr_t end); void Root(uintptr_t begin, uintptr_t end); Loading @@ -70,7 +80,8 @@ class HeapWalker { private: void RecurseRoot(const Range& root); bool IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info); bool WordContainsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info); void HandleSegFault(ScopedSignalHandler&, int, siginfo_t*, void*); DISALLOW_COPY_AND_ASSIGN(HeapWalker); Allocator<HeapWalker> allocator_; Loading @@ -81,6 +92,9 @@ class HeapWalker { allocator::vector<Range> roots_; allocator::vector<uintptr_t> root_vals_; ScopedSignalHandler segv_handler_; uintptr_t walking_ptr_; }; template<class F> Loading @@ -92,7 +106,7 @@ inline void HeapWalker::ForEachPtrInRange(const Range& range, F&& f) { for (uintptr_t i = begin; i < range.end; i += sizeof(uintptr_t)) { Range ref_range; AllocationInfo* ref_info; if (IsAllocationPtr(*reinterpret_cast<uintptr_t*>(i), &ref_range, &ref_info)) { if (WordContainsAllocationPtr(i, &ref_range, &ref_info)) { f(ref_range, ref_info); } } Loading
libmemunreachable/ScopedSignalHandler.h 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_ #define LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_ #include <errno.h> #include <signal.h> #include <functional> #include "android-base/macros.h" #include "log.h" class ScopedSignalHandler { public: using Fn = std::function<void(ScopedSignalHandler&, int, siginfo_t*, void*)>; ScopedSignalHandler(Allocator<Fn> allocator) : allocator_(allocator), signal_(-1) {} ~ScopedSignalHandler() { reset(); } template <class F> void install(int signal, F&& f) { LOG_ALWAYS_FATAL_IF(signal_ != -1, "ScopedSignalHandler already installed"); handler_ = SignalFn(std::allocator_arg, allocator_, [=](int signal, siginfo_t* si, void* uctx) { f(*this, signal, si, uctx); }); struct sigaction act{}; act.sa_sigaction = [](int signal, siginfo_t* si, void* uctx) { handler_(signal, si, uctx); }; act.sa_flags = SA_SIGINFO; int ret = sigaction(signal, &act, &old_act_); if (ret < 0) { LOG_ALWAYS_FATAL("failed to install segfault handler: %s", strerror(errno)); } signal_ = signal; } void reset() { if (signal_ != -1) { int ret = sigaction(signal_, &old_act_, NULL); if (ret < 0) { ALOGE("failed to uninstall segfault handler"); } handler_ = SignalFn{}; signal_ = -1; } } private: using SignalFn = std::function<void(int, siginfo_t*, void*)>; DISALLOW_COPY_AND_ASSIGN(ScopedSignalHandler); Allocator<Fn> allocator_; int signal_; struct sigaction old_act_; // TODO(ccross): to support multiple ScopedSignalHandlers handler_ would need // to be a static map of signals to handlers, but allocated with Allocator. static SignalFn handler_; }; #endif // LIBMEMUNREACHABLE_SCOPED_SIGNAL_HANDLER_H_
libmemunreachable/tests/HeapWalker_test.cpp +27 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,9 @@ * limitations under the License. */ #include <sys/mman.h> #include <unistd.h> #include "HeapWalker.h" #include <gtest/gtest.h> Loading Loading @@ -172,3 +175,27 @@ TEST_F(HeapWalkerTest, cycle) { EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes); ASSERT_EQ(2U, leaked.size()); } TEST_F(HeapWalkerTest, segv) { const size_t page_size = sysconf(_SC_PAGE_SIZE); void* buffer1 = mmap(NULL, page_size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); ASSERT_NE(buffer1, nullptr); void* buffer2; buffer2 = &buffer1; HeapWalker heap_walker(heap_); heap_walker.Allocation(buffer_begin(buffer1), buffer_begin(buffer1)+page_size); heap_walker.Root(buffer_begin(buffer2), buffer_end(buffer2)); ASSERT_EQ(true, heap_walker.DetectLeaks()); allocator::vector<Range> leaked(heap_); size_t num_leaks = 0; size_t leaked_bytes = 0; ASSERT_EQ(true, heap_walker.Leaked(leaked, 100, &num_leaks, &leaked_bytes)); EXPECT_EQ(0U, num_leaks); EXPECT_EQ(0U, leaked_bytes); ASSERT_EQ(0U, leaked.size()); }