platform_system_core/libmemunreachable/ThreadCapture.cpp
Colin Cross bcb4ed3eaa imprecise mark and sweep native memory leak detector
libmemunreachable uses an imprecise mark and sweep pass over all memory
allocated by jemalloc in order to find unreachable allocations.

Change-Id: Ia70bbf31f5b40ff71dab28cfd6cd06c5ef01a2d4
2016-02-08 17:08:49 -08:00

370 lines
9.6 KiB
C++

/*
* 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.
*/
#include "ThreadCapture.h"
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <map>
#include <memory>
#include <set>
#include <vector>
#include <android-base/unique_fd.h>
#include "Allocator.h"
#include "log.h"
// bionic interfaces used:
// atoi
// strlcat
// writev
// bionic interfaces reimplemented to avoid allocation:
// getdents64
// Convert a pid > 0 to a string. sprintf might allocate, so we can't use it.
// Returns a pointer somewhere in buf to a null terminated string, or NULL
// on error.
static char *pid_to_str(char *buf, size_t len, pid_t pid) {
if (pid <= 0) {
return nullptr;
}
char *ptr = buf + len - 1;
*ptr = 0;
while (pid > 0) {
ptr--;
if (ptr < buf) {
return nullptr;
}
*ptr = '0' + (pid % 10);
pid /= 10;
}
return ptr;
}
class ThreadCaptureImpl {
public:
ThreadCaptureImpl(pid_t pid, Allocator<ThreadCaptureImpl>& allocator);
~ThreadCaptureImpl() {}
bool ListThreads(TidList& tids);
bool CaptureThreads();
bool ReleaseThreads();
bool ReleaseThread(pid_t tid);
bool CapturedThreadInfo(ThreadInfoList& threads);
void InjectTestFunc(std::function<void(pid_t)>&& f) { inject_test_func_ = f; }
private:
int CaptureThread(pid_t tid);
bool ReleaseThread(pid_t tid, unsigned int signal);
int PtraceAttach(pid_t tid);
void PtraceDetach(pid_t tid, unsigned int signal);
bool PtraceThreadInfo(pid_t tid, ThreadInfo& thread_info);
allocator::map<unsigned int, pid_t> captured_threads_;
Allocator<ThreadCaptureImpl> allocator_;
pid_t pid_;
std::function<void(pid_t)> inject_test_func_;
};
ThreadCaptureImpl::ThreadCaptureImpl(pid_t pid, Allocator<ThreadCaptureImpl>& allocator) :
captured_threads_(allocator), allocator_(allocator), pid_(pid) {
}
bool ThreadCaptureImpl::ListThreads(TidList& tids) {
tids.clear();
char pid_buf[11];
char path[256] = "/proc/";
char* pid_str = pid_to_str(pid_buf, sizeof(pid_buf), pid_);
if (!pid_str) {
return false;
}
strlcat(path, pid_str, sizeof(path));
strlcat(path, "/task", sizeof(path));
int fd = open(path, O_CLOEXEC | O_DIRECTORY | O_RDONLY);
if (fd < 0) {
ALOGE("failed to open %s: %s", path, strerror(errno));
return false;
}
android::base::unique_fd fd_guard{fd};
struct linux_dirent64 {
uint64_t d_ino;
int64_t d_off;
uint16_t d_reclen;
char d_type;
char d_name[];
} __attribute((packed));
char dirent_buf[4096];
ssize_t nread;
do {
nread = syscall(SYS_getdents64, fd, dirent_buf, sizeof(dirent_buf));
if (nread < 0) {
ALOGE("failed to get directory entries from %s: %s", path, strerror(errno));
return false;
} else if (nread > 0) {
ssize_t off = 0;
while (off < nread) {
linux_dirent64* dirent = reinterpret_cast<linux_dirent64*>(dirent_buf + off);
off += dirent->d_reclen;
pid_t tid = atoi(dirent->d_name);
if (tid <= 0) {
continue;
}
tids.push_back(tid);
}
}
} while (nread != 0);
return true;
}
bool ThreadCaptureImpl::CaptureThreads() {
TidList tids{allocator_};
bool found_new_thread;
do {
if (!ListThreads(tids)) {
ReleaseThreads();
return false;
}
found_new_thread = false;
for (auto it = tids.begin(); it != tids.end(); it++) {
auto captured = captured_threads_.find(*it);
if (captured == captured_threads_.end()) {
if (CaptureThread(*it) < 0) {
ReleaseThreads();
return false;
}
found_new_thread = true;
}
}
} while (found_new_thread);
return true;
}
// Detatches from a thread, delivering signal if nonzero, logs on error
void ThreadCaptureImpl::PtraceDetach(pid_t tid, unsigned int signal) {
void* sig_ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(signal));
if (ptrace(PTRACE_DETACH, tid, NULL, sig_ptr) < 0 && errno != ESRCH) {
ALOGE("failed to detach from thread %d of process %d: %s", tid, pid_,
strerror(errno));
}
}
// Attaches to and pauses thread.
// Returns 1 on attach, 0 on tid not found, -1 and logs on error
int ThreadCaptureImpl::PtraceAttach(pid_t tid) {
int ret = ptrace(PTRACE_SEIZE, tid, NULL, NULL);
if (ret < 0) {
ALOGE("failed to attach to thread %d of process %d: %s", tid, pid_,
strerror(errno));
return -1;
}
if (inject_test_func_) {
inject_test_func_(tid);
}
if (ptrace(PTRACE_INTERRUPT, tid, 0, 0) < 0) {
if (errno == ESRCH) {
return 0;
} else {
ALOGE("failed to interrupt thread %d of process %d: %s", tid, pid_,
strerror(errno));
PtraceDetach(tid, 0);
return -1;
}
}
return 1;
}
bool ThreadCaptureImpl::PtraceThreadInfo(pid_t tid, ThreadInfo& thread_info) {
thread_info.tid = tid;
const unsigned int max_num_regs = 128; // larger than number of registers on any device
uintptr_t regs[max_num_regs];
struct iovec iovec;
iovec.iov_base = &regs;
iovec.iov_len = sizeof(regs);
if (ptrace(PTRACE_GETREGSET, tid, reinterpret_cast<void*>(NT_PRSTATUS), &iovec)) {
ALOGE("ptrace getregset for thread %d of process %d failed: %s",
tid, pid_, strerror(errno));
return false;
}
unsigned int num_regs = iovec.iov_len / sizeof(uintptr_t);
thread_info.regs.assign(&regs[0], &regs[num_regs]);
const int sp =
#if defined(__x86_64__)
offsetof(struct pt_regs, rsp) / sizeof(uintptr_t)
#elif defined(__i386__)
offsetof(struct pt_regs, esp) / sizeof(uintptr_t)
#elif defined(__arm__)
offsetof(struct pt_regs, ARM_sp) / sizeof(uintptr_t)
#elif defined(__aarch64__)
offsetof(struct user_pt_regs, sp) / sizeof(uintptr_t)
#elif defined(__mips__) || defined(__mips64__)
offsetof(struct pt_regs, regs[29]) / sizeof(uintptr_t)
#else
#error Unrecognized architecture
#endif
;
// TODO(ccross): use /proc/tid/status or /proc/pid/maps to get start_stack
thread_info.stack = std::pair<uintptr_t, uintptr_t>(regs[sp], 0);
return true;
}
int ThreadCaptureImpl::CaptureThread(pid_t tid) {
int ret = PtraceAttach(tid);
if (ret <= 0) {
return ret;
}
int status = 0;
if (TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL)) < 0) {
ALOGE("failed to wait for pause of thread %d of process %d: %s", tid, pid_,
strerror(errno));
PtraceDetach(tid, 0);
return -1;
}
if (!WIFSTOPPED(status)) {
ALOGE("thread %d of process %d was not paused after waitpid, killed?",
tid, pid_);
return 0;
}
unsigned int resume_signal = 0;
unsigned int signal = WSTOPSIG(status);
if ((status >> 16) == PTRACE_EVENT_STOP) {
switch (signal) {
case SIGSTOP:
case SIGTSTP:
case SIGTTIN:
case SIGTTOU:
// group-stop signals
break;
case SIGTRAP:
// normal ptrace interrupt stop
break;
default:
ALOGE("unexpected signal %d with PTRACE_EVENT_STOP for thread %d of process %d",
signal, tid, pid_);
return -1;
}
} else {
// signal-delivery-stop
resume_signal = signal;
}
captured_threads_[tid] = resume_signal;
return 1;
}
bool ThreadCaptureImpl::ReleaseThread(pid_t tid) {
auto it = captured_threads_.find(tid);
if (it == captured_threads_.end()) {
return false;
}
return ReleaseThread(it->first, it->second);
}
bool ThreadCaptureImpl::ReleaseThread(pid_t tid, unsigned int signal) {
PtraceDetach(tid, signal);
return true;
}
bool ThreadCaptureImpl::ReleaseThreads() {
bool ret = true;
for (auto it = captured_threads_.begin(); it != captured_threads_.end(); ) {
if (ReleaseThread(it->first, it->second)) {
it = captured_threads_.erase(it);
} else {
it++;
ret = false;
}
}
return ret;
}
bool ThreadCaptureImpl::CapturedThreadInfo(ThreadInfoList& threads) {
threads.clear();
for (auto it = captured_threads_.begin(); it != captured_threads_.end(); it++) {
ThreadInfo t{0, allocator::vector<uintptr_t>(allocator_), std::pair<uintptr_t, uintptr_t>(0, 0)};
if (!PtraceThreadInfo(it->first, t)) {
return false;
}
threads.push_back(t);
}
return true;
}
ThreadCapture::ThreadCapture(pid_t pid, Allocator<ThreadCapture> allocator) {
Allocator<ThreadCaptureImpl> impl_allocator = allocator;
impl_ = impl_allocator.make_unique(pid, impl_allocator);
}
ThreadCapture::~ThreadCapture() {}
bool ThreadCapture::ListThreads(TidList& tids) {
return impl_->ListThreads(tids);
}
bool ThreadCapture::CaptureThreads() {
return impl_->CaptureThreads();
}
bool ThreadCapture::ReleaseThreads() {
return impl_->ReleaseThreads();
}
bool ThreadCapture::ReleaseThread(pid_t tid) {
return impl_->ReleaseThread(tid);
}
bool ThreadCapture::CapturedThreadInfo(ThreadInfoList& threads) {
return impl_->CapturedThreadInfo(threads);
}
void ThreadCapture::InjectTestFunc(std::function<void(pid_t)>&& f) {
impl_->InjectTestFunc(std::forward<std::function<void(pid_t)>>(f));
}