Handle segfaults while walking native heap
Vendor blobs on ryu mprotect heap pages, causing segfaults when dumping unreachable memory. Handle segfaults within HeapWalker by mapping a zero page over any unreadable pages. HeapWalker runs in the forked process, so the mapping will not affect the original process. Bug: 28269332 Change-Id: I16245af722123f2ad467cbc6f245a70666c55544
This commit is contained in:
parent
721c9ce4a5
commit
ba5d9ff6d9
4 changed files with 168 additions and 6 deletions
|
|
@ -14,7 +14,10 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
@ -22,6 +25,7 @@
|
||||||
#include "Allocator.h"
|
#include "Allocator.h"
|
||||||
#include "HeapWalker.h"
|
#include "HeapWalker.h"
|
||||||
#include "LeakFolding.h"
|
#include "LeakFolding.h"
|
||||||
|
#include "ScopedSignalHandler.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) {
|
bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) {
|
||||||
|
|
@ -46,9 +50,15 @@ bool HeapWalker::Allocation(uintptr_t begin, uintptr_t end) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HeapWalker::IsAllocationPtr(uintptr_t ptr, Range* range, AllocationInfo** info) {
|
bool HeapWalker::WordContainsAllocationPtr(uintptr_t word_ptr, Range* range, AllocationInfo** info) {
|
||||||
if (ptr >= valid_allocations_range_.begin && ptr < valid_allocations_range_.end) {
|
walking_ptr_ = word_ptr;
|
||||||
AllocationMap::iterator it = allocations_.find(Range{ptr, ptr + 1});
|
// 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()) {
|
if (it != allocations_.end()) {
|
||||||
*range = it->first;
|
*range = it->first;
|
||||||
*info = &it->second;
|
*info = &it->second;
|
||||||
|
|
@ -135,3 +145,30 @@ bool HeapWalker::Leaked(allocator::vector<Range>& leaked, size_t limit,
|
||||||
|
|
||||||
return true;
|
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_;
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,12 @@
|
||||||
#ifndef LIBMEMUNREACHABLE_HEAP_WALKER_H_
|
#ifndef LIBMEMUNREACHABLE_HEAP_WALKER_H_
|
||||||
#define LIBMEMUNREACHABLE_HEAP_WALKER_H_
|
#define LIBMEMUNREACHABLE_HEAP_WALKER_H_
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#include "android-base/macros.h"
|
#include "android-base/macros.h"
|
||||||
|
|
||||||
#include "Allocator.h"
|
#include "Allocator.h"
|
||||||
|
#include "ScopedSignalHandler.h"
|
||||||
#include "Tarjan.h"
|
#include "Tarjan.h"
|
||||||
|
|
||||||
// A range [begin, end)
|
// A range [begin, end)
|
||||||
|
|
@ -41,10 +44,17 @@ class HeapWalker {
|
||||||
public:
|
public:
|
||||||
HeapWalker(Allocator<HeapWalker> allocator) : allocator_(allocator),
|
HeapWalker(Allocator<HeapWalker> allocator) : allocator_(allocator),
|
||||||
allocations_(allocator), allocation_bytes_(0),
|
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_.end = 0;
|
||||||
valid_allocations_range_.begin = ~valid_allocations_range_.end;
|
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() {}
|
~HeapWalker() {}
|
||||||
bool Allocation(uintptr_t begin, uintptr_t end);
|
bool Allocation(uintptr_t begin, uintptr_t end);
|
||||||
void Root(uintptr_t begin, uintptr_t end);
|
void Root(uintptr_t begin, uintptr_t end);
|
||||||
|
|
@ -70,7 +80,8 @@ class HeapWalker {
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void RecurseRoot(const Range& root);
|
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);
|
DISALLOW_COPY_AND_ASSIGN(HeapWalker);
|
||||||
Allocator<HeapWalker> allocator_;
|
Allocator<HeapWalker> allocator_;
|
||||||
|
|
@ -81,6 +92,9 @@ class HeapWalker {
|
||||||
|
|
||||||
allocator::vector<Range> roots_;
|
allocator::vector<Range> roots_;
|
||||||
allocator::vector<uintptr_t> root_vals_;
|
allocator::vector<uintptr_t> root_vals_;
|
||||||
|
|
||||||
|
ScopedSignalHandler segv_handler_;
|
||||||
|
uintptr_t walking_ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class F>
|
template<class F>
|
||||||
|
|
@ -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)) {
|
for (uintptr_t i = begin; i < range.end; i += sizeof(uintptr_t)) {
|
||||||
Range ref_range;
|
Range ref_range;
|
||||||
AllocationInfo* ref_info;
|
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);
|
f(ref_range, ref_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
84
libmemunreachable/ScopedSignalHandler.h
Normal file
84
libmemunreachable/ScopedSignalHandler.h
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* 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_
|
||||||
|
|
@ -14,6 +14,9 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "HeapWalker.h"
|
#include "HeapWalker.h"
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
@ -172,3 +175,27 @@ TEST_F(HeapWalkerTest, cycle) {
|
||||||
EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes);
|
EXPECT_EQ(2*sizeof(uintptr_t), leaked_bytes);
|
||||||
ASSERT_EQ(2U, leaked.size());
|
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…
Add table
Reference in a new issue