debuggerd: advance our amazing bet.
Remove debuggerd in favor of a helper process that gets execed by
crashing processes.
Bug: http://b/30705528
Test: debuggerd_test
Change-Id: I9906c69473989cbf7fe5ea6cccf9a9c563d75906
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
new file mode 100644
index 0000000..b9dfedb
--- /dev/null
+++ b/debuggerd/crash_dump.cpp
@@ -0,0 +1,388 @@
+/*
+ * Copyright 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 <arpa/inet.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <syscall.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <limits>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <log/logger.h>
+#include <procinfo/process.h>
+#include <selinux/selinux.h>
+
+#include "backtrace.h"
+#include "tombstone.h"
+#include "utility.h"
+
+#include "debuggerd/handler.h"
+#include "debuggerd/protocol.h"
+#include "debuggerd/util.h"
+
+using android::base::unique_fd;
+using android::base::StringPrintf;
+
+static bool pid_contains_tid(pid_t pid, pid_t tid) {
+ std::string task_path = StringPrintf("/proc/%d/task/%d", pid, tid);
+ return access(task_path.c_str(), F_OK) == 0;
+}
+
+// Attach to a thread, and verify that it's still a member of the given process
+static bool ptrace_attach_thread(pid_t pid, pid_t tid) {
+ if (ptrace(PTRACE_ATTACH, tid, 0, 0) != 0) {
+ return false;
+ }
+
+ // Make sure that the task we attached to is actually part of the pid we're dumping.
+ if (!pid_contains_tid(pid, tid)) {
+ if (ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
+ PLOG(FATAL) << "failed to detach from thread " << tid;
+ }
+ errno = ECHILD;
+ return false;
+ }
+ return true;
+}
+
+static bool activity_manager_notify(int pid, int signal, const std::string& amfd_data) {
+ android::base::unique_fd amfd(socket_local_client("/data/system/ndebugsocket", ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM));
+ if (amfd.get() == -1) {
+ PLOG(ERROR) << "unable to connect to activity manager";
+ return false;
+ }
+
+ struct timeval tv = {
+ .tv_sec = 1,
+ .tv_usec = 0,
+ };
+ if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
+ PLOG(ERROR) << "failed to set send timeout on activity manager socket";
+ return false;
+ }
+ tv.tv_sec = 3; // 3 seconds on handshake read
+ if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
+ PLOG(ERROR) << "failed to set receive timeout on activity manager socket";
+ return false;
+ }
+
+ // Activity Manager protocol: binary 32-bit network-byte-order ints for the
+ // pid and signal number, followed by the raw text of the dump, culminating
+ // in a zero byte that marks end-of-data.
+ uint32_t datum = htonl(pid);
+ if (!android::base::WriteFully(amfd, &datum, 4)) {
+ PLOG(ERROR) << "AM pid write failed";
+ return false;
+ }
+ datum = htonl(signal);
+ if (!android::base::WriteFully(amfd, &datum, 4)) {
+ PLOG(ERROR) << "AM signal write failed";
+ return false;
+ }
+ if (!android::base::WriteFully(amfd, amfd_data.c_str(), amfd_data.size() + 1)) {
+ PLOG(ERROR) << "AM data write failed";
+ return false;
+ }
+
+ // 3 sec timeout reading the ack; we're fine if the read fails.
+ char ack;
+ android::base::ReadFully(amfd, &ack, 1);
+ return true;
+}
+
+static bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* output_fd) {
+ unique_fd sockfd(socket_local_client(kTombstonedCrashSocketName,
+ ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET));
+ if (sockfd == -1) {
+ PLOG(ERROR) << "failed to connect to tombstoned";
+ return false;
+ }
+
+ TombstonedCrashPacket packet = {};
+ packet.packet_type = CrashPacketType::kDumpRequest;
+ packet.packet.dump_request.pid = pid;
+ if (TEMP_FAILURE_RETRY(write(sockfd, &packet, sizeof(packet))) != sizeof(packet)) {
+ PLOG(ERROR) << "failed to write DumpRequest packet";
+ return false;
+ }
+
+ unique_fd tmp_output_fd;
+ ssize_t rc = recv_fd(sockfd, &packet, sizeof(packet), &tmp_output_fd);
+ if (rc == -1) {
+ PLOG(ERROR) << "failed to read response to DumpRequest packet";
+ return false;
+ } else if (rc != sizeof(packet)) {
+ LOG(ERROR) << "read DumpRequest response packet of incorrect length (expected "
+ << sizeof(packet) << ", got " << rc << ")";
+ return false;
+ }
+
+ *tombstoned_socket = std::move(sockfd);
+ *output_fd = std::move(tmp_output_fd);
+ return true;
+}
+
+static bool tombstoned_notify_completion(int tombstoned_socket) {
+ TombstonedCrashPacket packet = {};
+ packet.packet_type = CrashPacketType::kCompletedDump;
+ if (TEMP_FAILURE_RETRY(write(tombstoned_socket, &packet, sizeof(packet))) != sizeof(packet)) {
+ return false;
+ }
+ return true;
+}
+
+static void abort_handler(pid_t target, const bool& tombstoned_connected,
+ unique_fd& tombstoned_socket, unique_fd& output_fd,
+ const char* abort_msg) {
+ LOG(ERROR) << abort_msg;
+
+ // If we abort before we get an output fd, contact tombstoned to let any
+ // potential listeners know that we failed.
+ if (!tombstoned_connected) {
+ if (!tombstoned_connect(target, &tombstoned_socket, &output_fd)) {
+ // We failed to connect, not much we can do.
+ LOG(ERROR) << "failed to connected to tombstoned to report failure";
+ _exit(1);
+ }
+ }
+
+ dprintf(output_fd.get(), "crash_dump failed to dump process %d: %s\n", target, abort_msg);
+
+ // Don't dump ourselves.
+ _exit(1);
+}
+
+static void check_process(int proc_fd, pid_t expected_pid) {
+ android::procinfo::ProcessInfo proc_info;
+ if (!android::procinfo::GetProcessInfoFromProcPidFd(proc_fd, &proc_info)) {
+ LOG(FATAL) << "failed to fetch process info";
+ }
+
+ if (proc_info.pid != expected_pid) {
+ LOG(FATAL) << "pid mismatch: expected " << expected_pid << ", actual " << proc_info.ppid;
+ }
+}
+
+int main(int argc, char** argv) {
+ pid_t target = getppid();
+ bool tombstoned_connected = false;
+ unique_fd tombstoned_socket;
+ unique_fd output_fd;
+
+ android::base::InitLogging(argv);
+ android::base::SetAborter([&](const char* abort_msg) {
+ abort_handler(target, tombstoned_connected, tombstoned_socket, output_fd, abort_msg);
+ });
+
+ if (argc != 2) {
+ return 1;
+ }
+
+ pid_t main_tid;
+
+ if (target == 1) {
+ LOG(FATAL) << "target died before we could attach";
+ }
+
+ if (!android::base::ParseInt(argv[1], &main_tid, 1, std::numeric_limits<pid_t>::max())) {
+ LOG(FATAL) << "invalid main tid: " << argv[1];
+ }
+
+ android::procinfo::ProcessInfo target_info;
+ if (!android::procinfo::GetProcessInfo(main_tid, &target_info)) {
+ LOG(FATAL) << "failed to fetch process info for target " << main_tid;
+ }
+
+ if (main_tid != target_info.tid || target != target_info.pid) {
+ LOG(FATAL) << "target info mismatch, expected pid " << target << ", tid " << main_tid
+ << ", received pid " << target_info.pid << ", tid " << target_info.tid;
+ }
+
+ // Open /proc/`getppid()` in the original process, and pass it down to the forked child.
+ std::string target_proc_path = "/proc/" + std::to_string(target);
+ int target_proc_fd = open(target_proc_path.c_str(), O_DIRECTORY | O_RDONLY);
+ if (target_proc_fd == -1) {
+ PLOG(FATAL) << "failed to open " << target_proc_path;
+ }
+
+ // Reparent ourselves to init, so that the signal handler can waitpid on the
+ // original process to avoid leaving a zombie for non-fatal dumps.
+ pid_t forkpid = fork();
+ if (forkpid == -1) {
+ PLOG(FATAL) << "fork failed";
+ } else if (forkpid != 0) {
+ exit(0);
+ }
+
+ check_process(target_proc_fd, target);
+
+ int attach_error = 0;
+ if (!ptrace_attach_thread(target, main_tid)) {
+ PLOG(FATAL) << "failed to attach to thread " << main_tid << " in process " << target;
+ }
+
+ check_process(target_proc_fd, target);
+
+ LOG(INFO) << "obtaining output fd from tombstoned";
+ tombstoned_connected = tombstoned_connect(target, &tombstoned_socket, &output_fd);
+
+ // Write a '\1' to stdout to tell the crashing process to resume.
+ if (TEMP_FAILURE_RETRY(write(STDOUT_FILENO, "\1", 1)) == -1) {
+ PLOG(ERROR) << "failed to communicate to target process";
+ }
+
+ if (tombstoned_connected) {
+ if (TEMP_FAILURE_RETRY(dup2(output_fd.get(), STDOUT_FILENO)) == -1) {
+ PLOG(ERROR) << "failed to dup2 output fd (" << output_fd.get() << ") to STDOUT_FILENO";
+ }
+ } else {
+ unique_fd devnull(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)));
+ TEMP_FAILURE_RETRY(dup2(devnull.get(), STDOUT_FILENO));
+ }
+
+ if (attach_error != 0) {
+ PLOG(FATAL) << "failed to attach to thread " << main_tid << " in process " << target;
+ }
+
+ LOG(INFO) << "performing dump of process " << target << " (target tid = " << main_tid << ")";
+
+ // At this point, the thread that made the request has been PTRACE_ATTACHed
+ // and has the signal that triggered things queued. Send PTRACE_CONT, and
+ // then wait for the signal.
+ if (ptrace(PTRACE_CONT, main_tid, 0, 0) != 0) {
+ PLOG(ERROR) << "PTRACE_CONT(" << main_tid << ") failed";
+ exit(1);
+ }
+
+ siginfo_t siginfo = {};
+ if (!wait_for_signal(main_tid, &siginfo)) {
+ printf("failed to wait for signal in tid %d: %s\n", main_tid, strerror(errno));
+ exit(1);
+ }
+
+ int signo = siginfo.si_signo;
+ bool backtrace = false;
+ uintptr_t abort_address = 0;
+
+ // si_value can represent three things:
+ // 0: dump tombstone
+ // 1: dump backtrace
+ // everything else: abort message address (implies dump tombstone)
+ if (siginfo.si_value.sival_int == 1) {
+ backtrace = true;
+ } else if (siginfo.si_value.sival_ptr != nullptr) {
+ abort_address = reinterpret_cast<uintptr_t>(siginfo.si_value.sival_ptr);
+ }
+
+ // Now that we have the signal that kicked things off, attach all of the
+ // sibling threads, and then proceed.
+ bool fatal_signal = signo != DEBUGGER_SIGNAL;
+ int resume_signal = fatal_signal ? signo : 0;
+ std::set<pid_t> siblings;
+ if (resume_signal == 0) {
+ if (!android::procinfo::GetProcessTids(target, &siblings)) {
+ PLOG(FATAL) << "failed to get process siblings";
+ }
+ siblings.erase(main_tid);
+
+ for (pid_t sibling_tid : siblings) {
+ if (!ptrace_attach_thread(target, sibling_tid)) {
+ PLOG(FATAL) << "failed to attach to thread " << main_tid << " in process " << target;
+ }
+ }
+ }
+
+ check_process(target_proc_fd, target);
+
+ // TODO: Use seccomp to lock ourselves down.
+
+ std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(main_tid));
+ std::string amfd_data;
+
+ if (backtrace) {
+ dump_backtrace(output_fd.get(), backtrace_map.get(), target, main_tid, siblings, 0);
+ } else {
+ // Collect the list of open files.
+ OpenFilesList open_files;
+ populate_open_files_list(target, &open_files);
+
+ engrave_tombstone(output_fd.get(), backtrace_map.get(), open_files, target, main_tid, siblings,
+ abort_address, fatal_signal ? &amfd_data : nullptr);
+ }
+
+ bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
+ if (wait_for_gdb) {
+ // Don't wait_for_gdb when the process didn't actually crash.
+ if (!fatal_signal) {
+ wait_for_gdb = false;
+ } else {
+ // Use ALOGI to line up with output from engrave_tombstone.
+ ALOGI(
+ "***********************************************************\n"
+ "* Process %d has been suspended while crashing.\n"
+ "* To attach gdbserver and start gdb, run this on the host:\n"
+ "*\n"
+ "* gdbclient.py -p %d\n"
+ "*\n"
+ "***********************************************************",
+ target, main_tid);
+ }
+ }
+
+ for (pid_t tid : siblings) {
+ // Don't send the signal to sibling threads.
+ if (ptrace(PTRACE_DETACH, tid, 0, wait_for_gdb ? SIGSTOP : 0) != 0) {
+ PLOG(ERROR) << "ptrace detach from " << tid << " failed";
+ }
+ }
+
+ if (ptrace(PTRACE_DETACH, main_tid, 0, wait_for_gdb ? SIGSTOP : resume_signal)) {
+ PLOG(ERROR) << "ptrace detach from main thread " << main_tid << " failed";
+ }
+
+ if (wait_for_gdb) {
+ if (tgkill(target, main_tid, resume_signal) != 0) {
+ PLOG(ERROR) << "failed to resend signal to process " << target;
+ }
+ }
+
+ if (fatal_signal) {
+ activity_manager_notify(target, signo, amfd_data);
+ }
+
+ // Close stdout before we notify tombstoned of completion.
+ close(STDOUT_FILENO);
+ if (!tombstoned_notify_completion(tombstoned_socket.get())) {
+ LOG(ERROR) << "failed to notify tombstoned of completion";
+ }
+
+ return 0;
+}