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
diff --git a/libmemunreachable/ThreadCapture.cpp b/libmemunreachable/ThreadCapture.cpp
new file mode 100644
index 0000000..6357840
--- /dev/null
+++ b/libmemunreachable/ThreadCapture.cpp
@@ -0,0 +1,370 @@
+/*
+ * 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));
+}