diff options
Diffstat (limited to 'debuggerd/crash_dump.cpp')
-rw-r--r-- | debuggerd/crash_dump.cpp | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp new file mode 100644 index 000000000..b9dfedbb6 --- /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; +} |