diff --git a/adb/Android.mk b/adb/Android.mk index 49e492c30..2f5a2ee14 100644 --- a/adb/Android.mk +++ b/adb/Android.mk @@ -343,6 +343,6 @@ LOCAL_STATIC_LIBRARIES := \ libcrypto_utils \ libcrypto \ libminijail \ - libdebuggerd_client \ + libdebuggerd_handler \ include $(BUILD_EXECUTABLE) diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp index 1a39f6afc..6382b6789 100644 --- a/adb/daemon/main.cpp +++ b/adb/daemon/main.cpp @@ -34,9 +34,9 @@ #include #include -#include "debuggerd/client.h" #include #include +#include "debuggerd/handler.h" #include "selinux/android.h" #include "adb.h" diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index f0131b81a..6dc6675ac 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -1,25 +1,197 @@ -cc_library_static { - name: "libdebuggerd_client", - srcs: ["client/debuggerd_client.cpp"], +cc_defaults { + name: "debuggerd_defaults", + cflags: [ + "-Wall", + "-Wextra", + "-Wno-error", + "-Wno-nullability-completeness", + "-Os", + ], - cflags: [ - "-Wall", - "-Wextra", - "-Werror", - "-Os", - ], - - target: { - android64: { - cflags: ["-DTARGET_IS_64_BIT"], - }, - }, - - local_include_dirs: ["include"], - export_include_dirs: ["include"], - - // libdebuggerd_client gets async signal safe logging via libc_logging, - // which defines its interface in bionic private headers. - include_dirs: ["bionic/libc"], - static_libs: ["libc_logging"], + local_include_dirs: ["include"], +} + +cc_library_static { + name: "libdebuggerd_handler", + defaults: ["debuggerd_defaults"], + srcs: ["handler/debuggerd_handler.cpp"], + + // libdebuggerd_handler gets async signal safe logging via libc_logging, + // which defines its interface in bionic private headers. + include_dirs: ["bionic/libc"], + static_libs: ["libc_logging"], + + export_include_dirs: ["include"], +} + +cc_library { + name: "libdebuggerd_client", + defaults: ["debuggerd_defaults"], + srcs: [ + "client/debuggerd_client.cpp", + "util.cpp", + ], + + shared_libs: [ + "libbase", + "libcutils", + ], + export_include_dirs: ["include"], +} + +cc_library { + name: "libdebuggerd", + defaults: ["debuggerd_defaults"], + + srcs: [ + "libdebuggerd/backtrace.cpp", + "libdebuggerd/elf_utils.cpp", + "libdebuggerd/open_files_list.cpp", + "libdebuggerd/tombstone.cpp", + "libdebuggerd/utility.cpp", + ], + + target: { + android_arm: { + srcs: ["libdebuggerd/arm/machine.cpp"], + }, + android_arm64: { + srcs: ["libdebuggerd/arm64/machine.cpp"], + }, + android_mips: { + srcs: ["libdebuggerd/mips/machine.cpp"], + }, + android_mips64: { + srcs: ["libdebuggerd/mips64/machine.cpp"], + }, + android_x86: { + srcs: ["libdebuggerd/x86/machine.cpp"], + }, + android_x86_64: { + srcs: ["libdebuggerd/x86_64/machine.cpp"], + }, + }, + + local_include_dirs: ["libdebuggerd/include"], + export_include_dirs: ["libdebuggerd/include"], + + shared_libs: [ + "libbacktrace", + "libbase", + "libcutils", + "liblog", + ], +} + +cc_test { + name: "debuggerd_test", + defaults: ["debuggerd_defaults"], + + cflags: ["-Wno-missing-field-initializers"], + srcs: [ + "libdebuggerd/test/dump_memory_test.cpp", + "libdebuggerd/test/elf_fake.cpp", + "libdebuggerd/test/log_fake.cpp", + "libdebuggerd/test/open_files_list_test.cpp", + "libdebuggerd/test/property_fake.cpp", + "libdebuggerd/test/ptrace_fake.cpp", + "libdebuggerd/test/tombstone_test.cpp", + ], + + target: { + android: { + srcs: [ + "debuggerd_test.cpp", + "util.cpp" + ], + }, + }, + + shared_libs: [ + "libbacktrace", + "libbase", + "libcutils", + ], + + static_libs: [ + "libdebuggerd" + ], + + local_include_dirs: [ + "libdebuggerd", + ], + + compile_multilib: "both", + multilib: { + lib32: { + stem: "debuggerd_test32", + }, + lib64: { + stem: "debuggerd_test64", + }, + }, +} + +cc_binary { + name: "crash_dump", + srcs: [ + "crash_dump.cpp", + "util.cpp", + ], + defaults: ["debuggerd_defaults"], + + compile_multilib: "both", + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + + shared_libs: [ + "libbacktrace", + "libbase", + "libdebuggerd", + "liblog", + "libprocinfo", + "libselinux", + ], +} + +cc_binary { + name: "debuggerd", + srcs: [ + "debuggerd.cpp", + ], + defaults: ["debuggerd_defaults"], + + shared_libs: [ + "libbase", + "libdebuggerd_client", + "liblog", + "libselinux", + ], + + local_include_dirs: ["include"], +} + +cc_binary { + name: "tombstoned", + srcs: [ + "util.cpp", + "tombstoned/intercept_manager.cpp", + "tombstoned/tombstoned.cpp", + ], + defaults: ["debuggerd_defaults"], + + static_libs: [ + "libbase", + "libcutils", + "libevent", + "liblog", + ], + + init_rc: ["tombstoned/tombstoned.rc"] } diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk deleted file mode 100644 index e3bdd43c3..000000000 --- a/debuggerd/Android.mk +++ /dev/null @@ -1,167 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -common_cppflags := \ - -W \ - -Wall \ - -Wextra \ - -Wunused \ - -Werror \ - -ifeq ($(TARGET_IS_64_BIT),true) -common_cppflags += -DTARGET_IS_64_BIT -endif - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - backtrace.cpp \ - debuggerd.cpp \ - elf_utils.cpp \ - getevent.cpp \ - open_files_list.cpp \ - signal_sender.cpp \ - tombstone.cpp \ - utility.cpp \ - -LOCAL_SRC_FILES_arm := arm/machine.cpp -LOCAL_SRC_FILES_arm64 := arm64/machine.cpp -LOCAL_SRC_FILES_mips := mips/machine.cpp -LOCAL_SRC_FILES_mips64 := mips64/machine.cpp -LOCAL_SRC_FILES_x86 := x86/machine.cpp -LOCAL_SRC_FILES_x86_64 := x86_64/machine.cpp - -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -LOCAL_CPPFLAGS := $(common_cppflags) - -LOCAL_INIT_RC_32 := debuggerd.rc -LOCAL_INIT_RC_64 := debuggerd64.rc - -LOCAL_SHARED_LIBRARIES := \ - libbacktrace \ - libbase \ - libcutils \ - liblog \ - libselinux \ - -LOCAL_CLANG := true - -LOCAL_MODULE := debuggerd -LOCAL_MODULE_STEM_32 := debuggerd -LOCAL_MODULE_STEM_64 := debuggerd64 -LOCAL_MULTILIB := both - -include $(BUILD_EXECUTABLE) - -crasher_cppflags := $(common_cppflags) -O0 -fstack-protector-all -Wno-free-nonheap-object - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := crasher.cpp -LOCAL_SRC_FILES_arm := arm/crashglue.S -LOCAL_SRC_FILES_arm64 := arm64/crashglue.S -LOCAL_SRC_FILES_mips := mips/crashglue.S -LOCAL_SRC_FILES_mips64 := mips64/crashglue.S -LOCAL_SRC_FILES_x86 := x86/crashglue.S -LOCAL_SRC_FILES_x86_64 := x86_64/crashglue.S -LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) -LOCAL_MODULE_TAGS := optional -LOCAL_CPPFLAGS := $(crasher_cppflags) -LOCAL_SHARED_LIBRARIES := libbase liblog - -# The arm emulator has VFP but not VFPv3-D32. -ifeq ($(ARCH_ARM_HAVE_VFP_D32),true) -LOCAL_ASFLAGS_arm += -DHAS_VFP_D32 -endif - -LOCAL_MODULE := crasher -LOCAL_MODULE_STEM_32 := crasher -LOCAL_MODULE_STEM_64 := crasher64 -LOCAL_MULTILIB := both - -include $(BUILD_EXECUTABLE) - -include $(CLEAR_VARS) -LOCAL_SRC_FILES := crasher.cpp -LOCAL_SRC_FILES_arm := arm/crashglue.S -LOCAL_SRC_FILES_arm64 := arm64/crashglue.S -LOCAL_SRC_FILES_mips := mips/crashglue.S -LOCAL_SRC_FILES_mips64 := mips64/crashglue.S -LOCAL_SRC_FILES_x86 := x86/crashglue.S -LOCAL_SRC_FILES_x86_64 := x86_64/crashglue.S -LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) -LOCAL_MODULE_TAGS := optional -LOCAL_CPPFLAGS := $(crasher_cppflags) -DSTATIC_CRASHER -LOCAL_FORCE_STATIC_EXECUTABLE := true - -# The arm emulator has VFP but not VFPv3-D32. -ifeq ($(ARCH_ARM_HAVE_VFP_D32),true) -LOCAL_ASFLAGS_arm += -DHAS_VFP_D32 -endif - -LOCAL_MODULE := static_crasher -LOCAL_MODULE_STEM_32 := static_crasher -LOCAL_MODULE_STEM_64 := static_crasher64 -LOCAL_MULTILIB := both - -LOCAL_STATIC_LIBRARIES := libdebuggerd_client libbase liblog - -include $(BUILD_EXECUTABLE) - -debuggerd_test_src_files := \ - utility.cpp \ - open_files_list.cpp \ - test/dump_memory_test.cpp \ - test/elf_fake.cpp \ - test/log_fake.cpp \ - test/open_files_list_test.cpp \ - test/property_fake.cpp \ - test/ptrace_fake.cpp \ - test/tombstone_test.cpp \ - test/selinux_fake.cpp \ - -debuggerd_shared_libraries := \ - libbacktrace \ - libbase \ - libcutils \ - liblog - -debuggerd_c_includes := \ - $(LOCAL_PATH)/test \ - -debuggerd_cpp_flags := \ - $(common_cppflags) \ - -Wno-missing-field-initializers \ - -fno-rtti \ - -# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 -debuggerd_cpp_flags += -Wno-varargs - -# Only build the host tests on linux. -ifeq ($(HOST_OS),linux) - -include $(CLEAR_VARS) - -LOCAL_MODULE := debuggerd_test -LOCAL_SRC_FILES := $(debuggerd_test_src_files) -LOCAL_SHARED_LIBRARIES := $(debuggerd_shared_libraries) -LOCAL_C_INCLUDES := $(debuggerd_c_includes) -LOCAL_CPPFLAGS := $(debuggerd_cpp_flags) - -LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32 -LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64 -LOCAL_MULTILIB := both -include $(BUILD_HOST_NATIVE_TEST) - -endif - -include $(CLEAR_VARS) - -LOCAL_MODULE := debuggerd_test -LOCAL_SRC_FILES := $(debuggerd_test_src_files) -LOCAL_SHARED_LIBRARIES := $(debuggerd_shared_libraries) -LOCAL_C_INCLUDES := $(debuggerd_c_includes) -LOCAL_CPPFLAGS := $(debuggerd_cpp_flags) - -LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32 -LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64 -LOCAL_MULTILIB := both -include $(BUILD_NATIVE_TEST) diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp index c67d747fc..81d70df1c 100644 --- a/debuggerd/client/debuggerd_client.cpp +++ b/debuggerd/client/debuggerd_client.cpp @@ -1,379 +1,189 @@ /* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. + * Copyright 2016, The Android Open Source Project * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. + * 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 * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * 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 "debuggerd/client.h" +#include -#include -#include -#include -#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include "private/libc_logging.h" +#include -#if defined(TARGET_IS_64_BIT) && !defined(__LP64__) -#define SOCKET_NAME "android:debuggerd32" -#else -#define SOCKET_NAME "android:debuggerd" -#endif +#include +#include +#include +#include +#include +#include +#include -// see man(2) prctl, specifically the section about PR_GET_NAME -#define MAX_TASK_NAME_LEN (16) +using android::base::unique_fd; -static debuggerd_callbacks_t g_callbacks; - -// Don't use __libc_fatal because it exits via abort, which might put us back into a signal handler. -#define fatal(...) \ - do { \ - __libc_format_log(ANDROID_LOG_FATAL, "libc", __VA_ARGS__); \ - _exit(1); \ - } while (0) - -static int socket_abstract_client(const char* name, int type) { - sockaddr_un addr; - - // Test with length +1 for the *initial* '\0'. - size_t namelen = strlen(name); - if ((namelen + 1) > sizeof(addr.sun_path)) { - errno = EINVAL; - return -1; - } - - // This is used for abstract socket namespace, we need - // an initial '\0' at the start of the Unix socket path. - // - // Note: The path in this case is *not* supposed to be - // '\0'-terminated. ("man 7 unix" for the gory details.) - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_LOCAL; - addr.sun_path[0] = 0; - memcpy(addr.sun_path + 1, name, namelen); - - socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + 1; - - int s = socket(AF_LOCAL, type, 0); - if (s == -1) { - return -1; - } - - int rc = TEMP_FAILURE_RETRY(connect(s, reinterpret_cast(&addr), alen)); - if (rc == -1) { - close(s); - return -1; - } - - return s; -} - -/* - * Writes a summary of the signal to the log file. We do this so that, if - * for some reason we're not able to contact debuggerd, there is still some - * indication of the failure in the log. - * - * We could be here as a result of native heap corruption, or while a - * mutex is being held, so we don't want to use any libc functions that - * could allocate memory or hold a lock. - */ -static void log_signal_summary(int signum, const siginfo_t* info) { - const char* signal_name = "???"; - bool has_address = false; - switch (signum) { - case SIGABRT: - signal_name = "SIGABRT"; - break; - case SIGBUS: - signal_name = "SIGBUS"; - has_address = true; - break; - case SIGFPE: - signal_name = "SIGFPE"; - has_address = true; - break; - case SIGILL: - signal_name = "SIGILL"; - has_address = true; - break; - case SIGSEGV: - signal_name = "SIGSEGV"; - has_address = true; - break; -#if defined(SIGSTKFLT) - case SIGSTKFLT: - signal_name = "SIGSTKFLT"; - break; -#endif - case SIGSYS: - signal_name = "SIGSYS"; - break; - case SIGTRAP: - signal_name = "SIGTRAP"; - break; - } - - char thread_name[MAX_TASK_NAME_LEN + 1]; // one more for termination - if (prctl(PR_GET_NAME, reinterpret_cast(thread_name), 0, 0, 0) != 0) { - strcpy(thread_name, ""); - } else { - // short names are null terminated by prctl, but the man page - // implies that 16 byte names are not. - thread_name[MAX_TASK_NAME_LEN] = 0; - } - - // "info" will be null if the siginfo_t information was not available. - // Many signals don't have an address or a code. - char code_desc[32]; // ", code -6" - char addr_desc[32]; // ", fault addr 0x1234" - addr_desc[0] = code_desc[0] = 0; - if (info != nullptr) { - // For a rethrown signal, this si_code will be right and the one debuggerd shows will - // always be SI_TKILL. - __libc_format_buffer(code_desc, sizeof(code_desc), ", code %d", info->si_code); - if (has_address) { - __libc_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr); - } - } - __libc_format_log(ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s)%s%s in tid %d (%s)", signum, - signal_name, code_desc, addr_desc, gettid(), thread_name); -} - -/* - * Returns true if the handler for signal "signum" has SA_SIGINFO set. - */ -static bool have_siginfo(int signum) { - struct sigaction old_action, new_action; - - memset(&new_action, 0, sizeof(new_action)); - new_action.sa_handler = SIG_DFL; - new_action.sa_flags = SA_RESTART; - sigemptyset(&new_action.sa_mask); - - if (sigaction(signum, &new_action, &old_action) < 0) { - __libc_format_log(ANDROID_LOG_WARN, "libc", "Failed testing for SA_SIGINFO: %s", - strerror(errno)); +static bool send_signal(pid_t pid, bool backtrace) { + sigval val; + val.sival_int = backtrace; + if (sigqueue(pid, DEBUGGER_SIGNAL, val) != 0) { + PLOG(ERROR) << "libdebuggerd_client: failed to send signal to pid " << pid; return false; } - bool result = (old_action.sa_flags & SA_SIGINFO) != 0; - - if (sigaction(signum, &old_action, nullptr) == -1) { - __libc_format_log(ANDROID_LOG_WARN, "libc", "Restore failed in test for SA_SIGINFO: %s", - strerror(errno)); - } - return result; + return true; } -static void send_debuggerd_packet(pid_t crashing_tid, pid_t pseudothread_tid) { - // Mutex to prevent multiple crashing threads from trying to talk - // to debuggerd at the same time. - static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER; - int ret = pthread_mutex_trylock(&crash_mutex); - if (ret != 0) { - if (ret == EBUSY) { - __libc_format_log(ANDROID_LOG_INFO, "libc", - "Another thread contacted debuggerd first; not contacting debuggerd."); - // This will never complete since the lock is never released. - pthread_mutex_lock(&crash_mutex); - } else { - __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_trylock failed: %s", strerror(ret)); +static bool check_dumpable(pid_t pid) { + // /proc/ is owned by the effective UID of the process. + // Ownership of most of the other files in /proc/ varies based on PR_SET_DUMPABLE. + // If PR_GET_DUMPABLE would return 0, they're owned by root, instead. + std::string proc_pid_path = android::base::StringPrintf("/proc/%d/", pid); + std::string proc_pid_status_path = proc_pid_path + "/status"; + + unique_fd proc_pid_fd(open(proc_pid_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)); + if (proc_pid_fd == -1) { + return false; + } + unique_fd proc_pid_status_fd(openat(proc_pid_fd, "status", O_RDONLY | O_CLOEXEC)); + if (proc_pid_status_fd == -1) { + return false; + } + + struct stat proc_pid_st; + struct stat proc_pid_status_st; + if (fstat(proc_pid_fd.get(), &proc_pid_st) != 0 || + fstat(proc_pid_status_fd.get(), &proc_pid_status_st) != 0) { + return false; + } + + // We can't figure out if a process is dumpable if its effective UID is root, but that's fine + // because being root bypasses the PR_SET_DUMPABLE check for ptrace. + if (proc_pid_st.st_uid == 0) { + return true; + } + + if (proc_pid_status_st.st_uid == 0) { + return false; + } + + return true; +} + +bool debuggerd_trigger_dump(pid_t pid, unique_fd output_fd, DebuggerdDumpType dump_type, + int timeout_ms) { + LOG(INFO) << "libdebuggerd_client: started dumping process " << pid; + unique_fd sockfd; + const auto end = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); + auto set_timeout = [timeout_ms, &sockfd, &end]() { + if (timeout_ms <= 0) { + return true; } - return; - } - int s = socket_abstract_client(SOCKET_NAME, SOCK_STREAM | SOCK_CLOEXEC); - if (s == -1) { - __libc_format_log(ANDROID_LOG_FATAL, "libc", "Unable to open connection to debuggerd: %s", - strerror(errno)); - return; - } - - // debuggerd knows our pid from the credentials on the - // local socket but we need to tell it the tid of the crashing thread. - // debuggerd will be paranoid and verify that we sent a tid - // that's actually in our process. - debugger_msg_t msg; - msg.action = DEBUGGER_ACTION_CRASH; - msg.tid = crashing_tid; - msg.ignore_tid = pseudothread_tid; - msg.abort_msg_address = 0; - - if (g_callbacks.get_abort_message) { - msg.abort_msg_address = reinterpret_cast(g_callbacks.get_abort_message()); - } - - ret = TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))); - if (ret == sizeof(msg)) { - char debuggerd_ack; - ret = TEMP_FAILURE_RETRY(read(s, &debuggerd_ack, 1)); - if (g_callbacks.post_dump) { - g_callbacks.post_dump(); + auto now = std::chrono::steady_clock::now(); + if (now > end) { + return false; } - } else { - // read or write failed -- broken connection? - __libc_format_log(ANDROID_LOG_FATAL, "libc", "Failed while talking to debuggerd: %s", - strerror(errno)); - } - close(s); -} + auto time_left = std::chrono::duration_cast(end - now); + auto seconds = std::chrono::duration_cast(time_left); + auto microseconds = std::chrono::duration_cast(time_left - seconds); + struct timeval timeout = { + .tv_sec = static_cast(seconds.count()), + .tv_usec = static_cast(microseconds.count()), + }; -struct debugger_thread_info { - pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - pid_t crashing_tid; - pid_t pseudothread_tid; - int signal_number; - siginfo_t* info; -}; + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) != 0) { + return false; + } + if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) != 0) { + return false; + } -// Logging and contacting debuggerd requires free file descriptors, which we might not have. -// Work around this by spawning a "thread" that shares its parent's address space, but not its file -// descriptor table, so that we can close random file descriptors without affecting the original -// process. Note that this doesn't go through pthread_create, so TLS is shared with the spawning -// process. -static void* pseudothread_stack; -static int debuggerd_dispatch_pseudothread(void* arg) { - debugger_thread_info* thread_info = static_cast(arg); - - for (int i = 3; i < 1024; ++i) { - close(i); - } - - log_signal_summary(thread_info->signal_number, thread_info->info); - send_debuggerd_packet(thread_info->crashing_tid, thread_info->pseudothread_tid); - pthread_mutex_unlock(&thread_info->mutex); - return 0; -} - -/* - * Catches fatal signals so we can ask debuggerd to ptrace us before - * we crash. - */ -static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void*) { - // It's possible somebody cleared the SA_SIGINFO flag, which would mean - // our "info" arg holds an undefined value. - if (!have_siginfo(signal_number)) { - info = nullptr; - } - - debugger_thread_info thread_info = { - .crashing_tid = gettid(), - .signal_number = signal_number, - .info = info + return true; }; - pthread_mutex_lock(&thread_info.mutex); - pid_t child_pid = clone(debuggerd_dispatch_pseudothread, pseudothread_stack, - CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID, - &thread_info, nullptr, nullptr, &thread_info.pseudothread_tid); - - if (child_pid == -1) { - fatal("failed to spawn debuggerd dispatch thread: %s", strerror(errno)); + if (!check_dumpable(pid)) { + dprintf(output_fd.get(), "target pid %d is not dumpable\n", pid); + return true; } - // Wait for the child to finish and unlock the mutex. - // This relies on bionic behavior that isn't guaranteed by the standard. - pthread_mutex_lock(&thread_info.mutex); - - - // We need to return from the signal handler so that debuggerd can dump the - // thread that crashed, but returning here does not guarantee that the signal - // will be thrown again, even for SIGSEGV and friends, since the signal could - // have been sent manually. Resend the signal with rt_tgsigqueueinfo(2) to - // preserve the SA_SIGINFO contents. - signal(signal_number, SIG_DFL); - - struct siginfo si; - if (!info) { - memset(&si, 0, sizeof(si)); - si.si_code = SI_USER; - si.si_pid = getpid(); - si.si_uid = getuid(); - info = &si; - } else if (info->si_code >= 0 || info->si_code == SI_TKILL) { - // rt_tgsigqueueinfo(2)'s documentation appears to be incorrect on kernels - // that contain commit 66dd34a (3.9+). The manpage claims to only allow - // negative si_code values that are not SI_TKILL, but 66dd34a changed the - // check to allow all si_code values in calls coming from inside the house. + sockfd.reset(socket(AF_LOCAL, SOCK_SEQPACKET, 0)); + if (sockfd == -1) { + PLOG(ERROR) << "libdebugger_client: failed to create socket"; + return false; } - int rc = syscall(SYS_rt_tgsigqueueinfo, getpid(), gettid(), signal_number, info); - if (rc != 0) { - fatal("failed to resend signal during crash: %s", strerror(errno)); + if (!set_timeout()) { + PLOG(ERROR) << "libdebugger_client: failed to set timeout"; + return false; } + + if (socket_local_client_connect(sockfd.get(), kTombstonedInterceptSocketName, + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET) == -1) { + PLOG(ERROR) << "libdebuggerd_client: failed to connect to tombstoned"; + return false; + } + + InterceptRequest req = {.pid = pid }; + if (!set_timeout()) { + PLOG(ERROR) << "libdebugger_client: failed to set timeout"; + } + + if (send_fd(sockfd.get(), &req, sizeof(req), std::move(output_fd)) != sizeof(req)) { + PLOG(ERROR) << "libdebuggerd_client: failed to send output fd to tombstoned"; + return false; + } + + bool backtrace = dump_type == kDebuggerdBacktrace; + send_signal(pid, backtrace); + + if (!set_timeout()) { + PLOG(ERROR) << "libdebugger_client: failed to set timeout"; + } + + InterceptResponse response; + ssize_t rc = TEMP_FAILURE_RETRY(recv(sockfd.get(), &response, sizeof(response), MSG_TRUNC)); + if (rc == 0) { + LOG(ERROR) << "libdebuggerd_client: failed to read response from tombstoned: timeout reached?"; + return false; + } else if (rc != sizeof(response)) { + LOG(ERROR) + << "libdebuggerd_client: received packet of unexpected length from tombstoned: expected " + << sizeof(response) << ", received " << rc; + return false; + } + + if (response.success != 1) { + response.error_message[sizeof(response.error_message) - 1] = '\0'; + LOG(ERROR) << "libdebuggerd_client: tombstoned reported failure: " << response.error_message; + } + + LOG(INFO) << "libdebuggerd_client: done dumping process " << pid; + + return true; } -void debuggerd_init(debuggerd_callbacks_t* callbacks) { - if (callbacks) { - g_callbacks = *callbacks; - } - - void* thread_stack_allocation = - mmap(nullptr, PAGE_SIZE * 3, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (thread_stack_allocation == MAP_FAILED) { - fatal("failed to allocate debuggerd thread stack"); - } - - char* stack = static_cast(thread_stack_allocation) + PAGE_SIZE; - if (mprotect(stack, PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) { - fatal("failed to mprotect debuggerd thread stack"); - } - - // Stack grows negatively, set it to the last byte in the page... - stack = (stack + PAGE_SIZE - 1); - // and align it. - stack -= 15; - pseudothread_stack = stack; - - struct sigaction action; - memset(&action, 0, sizeof(action)); - sigemptyset(&action.sa_mask); - action.sa_sigaction = debuggerd_signal_handler; - action.sa_flags = SA_RESTART | SA_SIGINFO; - - // Use the alternate signal stack if available so we can catch stack overflows. - action.sa_flags |= SA_ONSTACK; - - sigaction(SIGABRT, &action, nullptr); - sigaction(SIGBUS, &action, nullptr); - sigaction(SIGFPE, &action, nullptr); - sigaction(SIGILL, &action, nullptr); - sigaction(SIGSEGV, &action, nullptr); -#if defined(SIGSTKFLT) - sigaction(SIGSTKFLT, &action, nullptr); -#endif - sigaction(SIGTRAP, &action, nullptr); +int dump_backtrace_to_file(pid_t tid, int fd) { + return dump_backtrace_to_file_timeout(tid, fd, 0); +} + +int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) { + android::base::unique_fd copy(dup(fd)); + if (copy == -1) { + return -1; + } + int timeout_ms = timeout_secs > 0 ? timeout_secs * 1000 : 0; + return debuggerd_trigger_dump(tid, std::move(copy), kDebuggerdBacktrace, timeout_ms) ? 0 : -1; } 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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::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(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 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 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; +} diff --git a/debuggerd/crasher/Android.mk b/debuggerd/crasher/Android.mk new file mode 100644 index 000000000..b8b786be0 --- /dev/null +++ b/debuggerd/crasher/Android.mk @@ -0,0 +1,66 @@ +LOCAL_PATH := $(call my-dir) + +crasher_cppflags := \ + -std=gnu++14 \ + -W \ + -Wall \ + -Wextra \ + -Wunused \ + -Werror \ + -O0 \ + -fstack-protector-all \ + -Wno-free-nonheap-object \ + -Wno-date-time + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := crasher.cpp +LOCAL_SRC_FILES_arm := arm/crashglue.S +LOCAL_SRC_FILES_arm64 := arm64/crashglue.S +LOCAL_SRC_FILES_mips := mips/crashglue.S +LOCAL_SRC_FILES_mips64 := mips64/crashglue.S +LOCAL_SRC_FILES_x86 := x86/crashglue.S +LOCAL_SRC_FILES_x86_64 := x86_64/crashglue.S +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := optional +LOCAL_CPPFLAGS := $(crasher_cppflags) +LOCAL_SHARED_LIBRARIES := libbase liblog + +# The arm emulator has VFP but not VFPv3-D32. +ifeq ($(ARCH_ARM_HAVE_VFP_D32),true) +LOCAL_ASFLAGS_arm += -DHAS_VFP_D32 +endif + +LOCAL_MODULE := crasher +LOCAL_MODULE_STEM_32 := crasher +LOCAL_MODULE_STEM_64 := crasher64 +LOCAL_MULTILIB := both + +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := crasher.cpp +LOCAL_SRC_FILES_arm := arm/crashglue.S +LOCAL_SRC_FILES_arm64 := arm64/crashglue.S +LOCAL_SRC_FILES_mips := mips/crashglue.S +LOCAL_SRC_FILES_mips64 := mips64/crashglue.S +LOCAL_SRC_FILES_x86 := x86/crashglue.S +LOCAL_SRC_FILES_x86_64 := x86_64/crashglue.S +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := optional +LOCAL_CPPFLAGS := $(crasher_cppflags) -DSTATIC_CRASHER +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_SHARED_LIBRARIES := libbase liblog + +# The arm emulator has VFP but not VFPv3-D32. +ifeq ($(ARCH_ARM_HAVE_VFP_D32),true) +LOCAL_ASFLAGS_arm += -DHAS_VFP_D32 +endif + +LOCAL_MODULE := static_crasher +LOCAL_MODULE_STEM_32 := static_crasher +LOCAL_MODULE_STEM_64 := static_crasher64 +LOCAL_MULTILIB := both + +LOCAL_STATIC_LIBRARIES := libdebuggerd_handler libbase liblog + +include $(BUILD_EXECUTABLE) diff --git a/debuggerd/arm/crashglue.S b/debuggerd/crasher/arm/crashglue.S similarity index 100% rename from debuggerd/arm/crashglue.S rename to debuggerd/crasher/arm/crashglue.S diff --git a/debuggerd/arm64/crashglue.S b/debuggerd/crasher/arm64/crashglue.S similarity index 100% rename from debuggerd/arm64/crashglue.S rename to debuggerd/crasher/arm64/crashglue.S diff --git a/debuggerd/crasher.cpp b/debuggerd/crasher/crasher.cpp similarity index 99% rename from debuggerd/crasher.cpp rename to debuggerd/crasher/crasher.cpp index 689f4d4c4..288f1165e 100644 --- a/debuggerd/crasher.cpp +++ b/debuggerd/crasher/crasher.cpp @@ -33,7 +33,7 @@ #include #if defined(STATIC_CRASHER) -#include "debuggerd/client.h" +#include "debuggerd/handler.h" #endif #define noinline __attribute__((__noinline__)) diff --git a/debuggerd/mips/crashglue.S b/debuggerd/crasher/mips/crashglue.S similarity index 100% rename from debuggerd/mips/crashglue.S rename to debuggerd/crasher/mips/crashglue.S diff --git a/debuggerd/mips64/crashglue.S b/debuggerd/crasher/mips64/crashglue.S similarity index 100% rename from debuggerd/mips64/crashglue.S rename to debuggerd/crasher/mips64/crashglue.S diff --git a/debuggerd/x86/crashglue.S b/debuggerd/crasher/x86/crashglue.S similarity index 100% rename from debuggerd/x86/crashglue.S rename to debuggerd/crasher/x86/crashglue.S diff --git a/debuggerd/x86_64/crashglue.S b/debuggerd/crasher/x86_64/crashglue.S similarity index 100% rename from debuggerd/x86_64/crashglue.S rename to debuggerd/crasher/x86_64/crashglue.S diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp index 893adc860..320823082 100644 --- a/debuggerd/debuggerd.cpp +++ b/debuggerd/debuggerd.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2006, The Android Open Source Project + * 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. @@ -14,937 +14,68 @@ * limitations under the License. */ -#define LOG_TAG "debuggerd" - -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include -#include -#include -#include - -#include +#include +#include #include +#include +#include #include -#include -#include -#include -#include - -#include - #include +#include +#include -#include "backtrace.h" -#include "getevent.h" -#include "open_files_list.h" -#include "signal_sender.h" -#include "tombstone.h" -#include "utility.h" +using android::base::unique_fd; -// If the 32 bit executable is compiled on a 64 bit system, -// use the 32 bit socket name. -#if defined(TARGET_IS_64_BIT) && !defined(__LP64__) -#define SOCKET_NAME DEBUGGER32_SOCKET_NAME -#else -#define SOCKET_NAME DEBUGGER_SOCKET_NAME -#endif +static void usage(int exit_code) { + fprintf(stderr, "usage: debuggerd [-b] PID\n"); + _exit(exit_code); +} -struct debugger_request_t { - debugger_action_t action; - pid_t pid, tid; - uid_t uid, gid; - pid_t ignore_tid; - uintptr_t abort_msg_address; -}; +static std::thread spawn_redirect_thread(unique_fd fd) { + return std::thread([fd{ std::move(fd) }]() { + while (true) { + char buf[BUFSIZ]; + ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), buf, sizeof(buf))); + if (rc <= 0) { + return; + } -static void wait_for_user_action(const debugger_request_t& request) { - // Explain how to attach the debugger. - 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" - "* Wait for gdb to start, then press the VOLUME DOWN key\n" - "* to let the process continue crashing.\n" - "***********************************************************", - request.pid, request.tid); - - // Wait for VOLUME DOWN. - while (true) { - input_event e; - if (get_event(&e, -1) == 0) { - if (e.type == EV_KEY && e.code == KEY_VOLUMEDOWN && e.value == 0) { - break; + if (!android::base::WriteFully(STDOUT_FILENO, buf, rc)) { + return; } } - } - - ALOGI("debuggerd resuming process %d", request.pid); + }); } -static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* out_gid) { - char path[64]; - snprintf(path, sizeof(path), "/proc/%d/status", tid); +int main(int argc, char* argv[]) { + if (argc <= 1) usage(0); + if (argc > 3) usage(1); + if (argc == 3 && strcmp(argv[1], "-b") != 0) usage(1); - FILE* fp = fopen(path, "r"); - if (!fp) { - return -1; + pid_t pid; + if (!android::base::ParseInt(argv[argc - 1], &pid, 1, std::numeric_limits::max())) { + usage(1); } - int fields = 0; - char line[1024]; - while (fgets(line, sizeof(line), fp)) { - size_t len = strlen(line); - if (len > 6 && !memcmp(line, "Tgid:\t", 6)) { - *out_pid = atoi(line + 6); - fields |= 1; - } else if (len > 5 && !memcmp(line, "Uid:\t", 5)) { - *out_uid = atoi(line + 5); - fields |= 2; - } else if (len > 5 && !memcmp(line, "Gid:\t", 5)) { - *out_gid = atoi(line + 5); - fields |= 4; - } - } - fclose(fp); - return fields == 7 ? 0 : -1; -} - -/* - * Corresponds with debugger_action_t enum type in - * include/cutils/debugger.h. - */ -static const char *debuggerd_perms[] = { - NULL, /* crash is only used on self, no check applied */ - "dump_tombstone", - "dump_backtrace" -}; - -static int audit_callback(void* data, security_class_t /* cls */, char* buf, size_t len) -{ - struct debugger_request_t* req = reinterpret_cast(data); - - if (!req) { - ALOGE("No debuggerd request audit data"); - return 0; - } - - snprintf(buf, len, "pid=%d uid=%d gid=%d", req->pid, req->uid, req->gid); - return 0; -} - -static bool selinux_action_allowed(int s, debugger_request_t* request) -{ - char *scon = NULL, *tcon = NULL; - const char *tclass = "debuggerd"; - const char *perm; - bool allowed = false; - - if (request->action <= 0 || request->action >= (sizeof(debuggerd_perms)/sizeof(debuggerd_perms[0]))) { - ALOGE("SELinux: No permission defined for debugger action %d", request->action); - return false; + unique_fd piperead, pipewrite; + if (!Pipe(&piperead, &pipewrite)) { + err(1, "failed to create pipe"); } - perm = debuggerd_perms[request->action]; - - if (getpeercon(s, &scon) < 0) { - ALOGE("Cannot get peer context from socket\n"); - goto out; + std::thread redirect_thread = spawn_redirect_thread(std::move(piperead)); + bool backtrace = argc == 3; + if (!debuggerd_trigger_dump(pid, std::move(pipewrite), + backtrace ? kDebuggerdBacktrace : kDebuggerdBacktrace, 0)) { + redirect_thread.join(); + errx(1, "failed to dump process %d", pid); } - if (getpidcon(request->tid, &tcon) < 0) { - ALOGE("Cannot get context for tid %d\n", request->tid); - goto out; - } - - allowed = (selinux_check_access(scon, tcon, tclass, perm, reinterpret_cast(request)) == 0); - -out: - freecon(scon); - freecon(tcon); - return allowed; -} - -static bool pid_contains_tid(pid_t pid, pid_t tid) { - char task_path[PATH_MAX]; - if (snprintf(task_path, PATH_MAX, "/proc/%d/task/%d", pid, tid) >= PATH_MAX) { - ALOGE("debuggerd: task path overflow (pid = %d, tid = %d)\n", pid, tid); - exit(1); - } - - return access(task_path, F_OK) == 0; -} - -static int read_request(int fd, debugger_request_t* out_request) { - ucred cr; - socklen_t len = sizeof(cr); - int status = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); - if (status != 0) { - ALOGE("cannot get credentials"); - return -1; - } - - ALOGV("reading tid"); - pollfd pollfds[1]; - pollfds[0].fd = fd; - pollfds[0].events = POLLIN; - pollfds[0].revents = 0; - status = TEMP_FAILURE_RETRY(poll(pollfds, 1, 3000)); - if (status != 1) { - ALOGE("timed out reading tid (from pid=%d uid=%d)\n", cr.pid, cr.uid); - return -1; - } - - debugger_msg_t msg; - memset(&msg, 0, sizeof(msg)); - status = TEMP_FAILURE_RETRY(read(fd, &msg, sizeof(msg))); - if (status < 0) { - ALOGE("read failure? %s (pid=%d uid=%d)\n", strerror(errno), cr.pid, cr.uid); - return -1; - } - if (status != sizeof(debugger_msg_t)) { - ALOGE("invalid crash request of size %d (from pid=%d uid=%d)\n", status, cr.pid, cr.uid); - return -1; - } - - out_request->action = static_cast(msg.action); - out_request->tid = msg.tid; - out_request->ignore_tid = msg.ignore_tid; - out_request->pid = cr.pid; - out_request->uid = cr.uid; - out_request->gid = cr.gid; - out_request->abort_msg_address = msg.abort_msg_address; - - if (msg.action == DEBUGGER_ACTION_CRASH) { - // Ensure that the tid reported by the crashing process is valid. - // This check needs to happen again after ptracing the requested thread to prevent a race. - if (!pid_contains_tid(out_request->pid, out_request->tid)) { - ALOGE("tid %d does not exist in pid %d. ignoring debug request\n", out_request->tid, - out_request->pid); - return -1; - } - } else if (cr.uid == 0 || (cr.uid == AID_SYSTEM && msg.action == DEBUGGER_ACTION_DUMP_BACKTRACE)) { - // Only root or system can ask us to attach to any process and dump it explicitly. - // However, system is only allowed to collect backtraces but cannot dump tombstones. - status = get_process_info(out_request->tid, &out_request->pid, - &out_request->uid, &out_request->gid); - if (status < 0) { - ALOGE("tid %d does not exist. ignoring explicit dump request\n", out_request->tid); - return -1; - } - - if (!selinux_action_allowed(fd, out_request)) - return -1; - } else { - // No one else is allowed to dump arbitrary processes. - return -1; - } + redirect_thread.join(); return 0; } - -static int activity_manager_connect() { - android::base::unique_fd amfd(socket(PF_UNIX, SOCK_STREAM, 0)); - if (amfd.get() < -1) { - ALOGE("debuggerd: Unable to connect to activity manager (socket failed: %s)", strerror(errno)); - return -1; - } - - struct sockaddr_un address; - memset(&address, 0, sizeof(address)); - address.sun_family = AF_UNIX; - // The path used here must match the value defined in NativeCrashListener.java. - strncpy(address.sun_path, "/data/system/ndebugsocket", sizeof(address.sun_path)); - if (TEMP_FAILURE_RETRY(connect(amfd.get(), reinterpret_cast(&address), - sizeof(address))) == -1) { - ALOGE("debuggerd: Unable to connect to activity manager (connect failed: %s)", strerror(errno)); - return -1; - } - - struct timeval tv; - memset(&tv, 0, sizeof(tv)); - tv.tv_sec = 1; // tight leash - if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { - ALOGE("debuggerd: Unable to connect to activity manager (setsockopt SO_SNDTIMEO failed: %s)", - strerror(errno)); - return -1; - } - - tv.tv_sec = 3; // 3 seconds on handshake read - if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { - ALOGE("debuggerd: Unable to connect to activity manager (setsockopt SO_RCVTIMEO failed: %s)", - strerror(errno)); - return -1; - } - - return amfd.release(); -} - -static void activity_manager_write(int pid, int signal, int amfd, const std::string& amfd_data) { - if (amfd == -1) { - return; - } - - // 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)) { - ALOGE("AM pid write failed: %s\n", strerror(errno)); - return; - } - datum = htonl(signal); - if (!android::base::WriteFully(amfd, &datum, 4)) { - ALOGE("AM signal write failed: %s\n", strerror(errno)); - return; - } - - if (!android::base::WriteFully(amfd, amfd_data.c_str(), amfd_data.size())) { - ALOGE("AM data write failed: %s\n", strerror(errno)); - return; - } - - // Send EOD to the Activity Manager, then wait for its ack to avoid racing - // ahead and killing the target out from under it. - uint8_t eodMarker = 0; - if (!android::base::WriteFully(amfd, &eodMarker, 1)) { - ALOGE("AM eod write failed: %s\n", strerror(errno)); - return; - } - // 3 sec timeout reading the ack; we're fine if the read fails. - android::base::ReadFully(amfd, &eodMarker, 1); -} - -static bool should_attach_gdb(const debugger_request_t& request) { - if (request.action == DEBUGGER_ACTION_CRASH) { - return property_get_bool("debug.debuggerd.wait_for_gdb", false); - } - return false; -} - -#if defined(__LP64__) -static bool is32bit(pid_t tid) { - char* exeline; - if (asprintf(&exeline, "/proc/%d/exe", tid) == -1) { - return false; - } - int fd = TEMP_FAILURE_RETRY(open(exeline, O_RDONLY | O_CLOEXEC)); - int saved_errno = errno; - free(exeline); - if (fd == -1) { - ALOGW("Failed to open /proc/%d/exe %s", tid, strerror(saved_errno)); - return false; - } - - char ehdr[EI_NIDENT]; - ssize_t bytes = TEMP_FAILURE_RETRY(read(fd, &ehdr, sizeof(ehdr))); - close(fd); - if (bytes != (ssize_t) sizeof(ehdr) || memcmp(ELFMAG, ehdr, SELFMAG) != 0) { - return false; - } - if (ehdr[EI_CLASS] == ELFCLASS32) { - return true; - } - return false; -} - -static void redirect_to_32(int fd, debugger_request_t* request) { - debugger_msg_t msg; - memset(&msg, 0, sizeof(msg)); - msg.tid = request->tid; - msg.action = request->action; - - int sock_fd = socket_local_client(DEBUGGER32_SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, - SOCK_STREAM | SOCK_CLOEXEC); - if (sock_fd < 0) { - ALOGE("Failed to connect to debuggerd32: %s", strerror(errno)); - return; - } - - if (TEMP_FAILURE_RETRY(write(sock_fd, &msg, sizeof(msg))) != (ssize_t) sizeof(msg)) { - ALOGE("Failed to write request to debuggerd32 socket: %s", strerror(errno)); - close(sock_fd); - return; - } - - char ack; - if (TEMP_FAILURE_RETRY(read(sock_fd, &ack, 1)) == -1) { - ALOGE("Failed to read ack from debuggerd32 socket: %s", strerror(errno)); - close(sock_fd); - return; - } - - char buffer[1024]; - ssize_t bytes_read; - while ((bytes_read = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) { - ssize_t bytes_to_send = bytes_read; - ssize_t bytes_written; - do { - bytes_written = TEMP_FAILURE_RETRY(write(fd, buffer + bytes_read - bytes_to_send, - bytes_to_send)); - if (bytes_written == -1) { - if (errno == EAGAIN) { - // Retry the write. - continue; - } - ALOGE("Error while writing data to fd: %s", strerror(errno)); - break; - } - bytes_to_send -= bytes_written; - } while (bytes_written != 0 && bytes_to_send > 0); - if (bytes_to_send != 0) { - ALOGE("Failed to write all data to fd: read %zd, sent %zd", bytes_read, bytes_to_send); - break; - } - } - close(sock_fd); -} -#endif - -// 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) { - ALOGE("debuggerd: failed to detach from thread '%d'", tid); - exit(1); - } - return false; - } - - return true; -} - -static void ptrace_siblings(pid_t pid, pid_t main_tid, pid_t ignore_tid, std::set& tids) { - char task_path[PATH_MAX]; - - if (snprintf(task_path, PATH_MAX, "/proc/%d/task", pid) >= PATH_MAX) { - ALOGE("debuggerd: task path overflow (pid = %d)\n", pid); - abort(); - } - - std::unique_ptr d(opendir(task_path), closedir); - - // Bail early if the task directory cannot be opened. - if (!d) { - ALOGE("debuggerd: failed to open /proc/%d/task: %s", pid, strerror(errno)); - return; - } - - struct dirent* de; - while ((de = readdir(d.get())) != NULL) { - // Ignore "." and "..". - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) { - continue; - } - - char* end; - pid_t tid = strtoul(de->d_name, &end, 10); - if (*end) { - continue; - } - - if (tid == main_tid || tid == ignore_tid) { - continue; - } - - if (!ptrace_attach_thread(pid, tid)) { - ALOGE("debuggerd: ptrace attach to %d failed: %s", tid, strerror(errno)); - continue; - } - - tids.insert(tid); - } -} - -static bool perform_dump(const debugger_request_t& request, int fd, int tombstone_fd, - BacktraceMap* backtrace_map, const OpenFilesList& open_files, - const std::set& siblings, - int* crash_signal, std::string* amfd_data) { - if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) { - ALOGE("debuggerd: failed to respond to client: %s\n", strerror(errno)); - return false; - } - - while (true) { - // wait_for_signal waits for forever, but the watchdog process will kill us - // if it takes too long. - int signal = wait_for_signal(request.tid); - switch (signal) { - case -1: - ALOGE("debuggerd: timed out waiting for signal"); - return false; - - case SIGSTOP: - if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - ALOGV("debuggerd: stopped -- dumping to tombstone"); - engrave_tombstone(tombstone_fd, backtrace_map, open_files, - request.pid, request.tid, siblings, - request.abort_msg_address, amfd_data); - } else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) { - ALOGV("debuggerd: stopped -- dumping to fd"); - dump_backtrace(fd, backtrace_map, request.pid, request.tid, siblings, nullptr); - } else { - ALOGV("debuggerd: stopped -- continuing"); - if (ptrace(PTRACE_CONT, request.tid, 0, 0) != 0) { - ALOGE("debuggerd: ptrace continue failed: %s", strerror(errno)); - return false; - } - continue; // loop again - } - break; - - case SIGABRT: - case SIGBUS: - case SIGFPE: - case SIGILL: - case SIGSEGV: -#ifdef SIGSTKFLT - case SIGSTKFLT: -#endif - case SIGSYS: - case SIGTRAP: - ALOGV("stopped -- fatal signal\n"); - *crash_signal = signal; - engrave_tombstone(tombstone_fd, backtrace_map, open_files, - request.pid, request.tid, siblings, - request.abort_msg_address, amfd_data); - break; - - default: - ALOGE("debuggerd: process stopped due to unexpected signal %d\n", signal); - break; - } - break; - } - - return true; -} - -static bool drop_privileges() { - // AID_LOG: for reading the logs data associated with the crashing process. - // AID_READPROC: for reading /proc//{comm,cmdline}. - gid_t groups[] = { AID_DEBUGGERD, AID_LOG, AID_READPROC }; - if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) { - ALOGE("debuggerd: failed to setgroups: %s", strerror(errno)); - return false; - } - - if (setresgid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { - ALOGE("debuggerd: failed to setresgid: %s", strerror(errno)); - return false; - } - - if (setresuid(AID_DEBUGGERD, AID_DEBUGGERD, AID_DEBUGGERD) != 0) { - ALOGE("debuggerd: failed to setresuid: %s", strerror(errno)); - return false; - } - - return true; -} - -static void worker_process(int fd, debugger_request_t& request) { - // Open the tombstone file if we need it. - std::string tombstone_path; - int tombstone_fd = -1; - switch (request.action) { - case DEBUGGER_ACTION_DUMP_TOMBSTONE: - case DEBUGGER_ACTION_CRASH: - tombstone_fd = open_tombstone(&tombstone_path); - if (tombstone_fd == -1) { - ALOGE("debuggerd: failed to open tombstone file: %s\n", strerror(errno)); - exit(1); - } - break; - - case DEBUGGER_ACTION_DUMP_BACKTRACE: - break; - - default: - ALOGE("debuggerd: unexpected request action: %d", request.action); - exit(1); - } - - // At this point, the thread that made the request is blocked in - // a read() call. If the thread has crashed, then this gives us - // time to PTRACE_ATTACH to it before it has a chance to really fault. - // - // The PTRACE_ATTACH sends a SIGSTOP to the target process, but it - // won't necessarily have stopped by the time ptrace() returns. (We - // currently assume it does.) We write to the file descriptor to - // ensure that it can run as soon as we call PTRACE_CONT below. - // See details in client/debuggerd_client.cpp, in function - // debugger_signal_handler(). - - // Attach to the target process. - if (!ptrace_attach_thread(request.pid, request.tid)) { - ALOGE("debuggerd: ptrace attach failed: %s", strerror(errno)); - exit(1); - } - - // DEBUGGER_ACTION_CRASH requests can come from arbitrary processes and the tid field in the - // request is sent from the other side. If an attacker can cause a process to be spawned with the - // pid of their process, they could trick debuggerd into dumping that process by exiting after - // sending the request. Validate the trusted request.uid/gid to defend against this. - if (request.action == DEBUGGER_ACTION_CRASH) { - pid_t pid; - uid_t uid; - gid_t gid; - if (get_process_info(request.tid, &pid, &uid, &gid) != 0) { - ALOGE("debuggerd: failed to get process info for tid '%d'", request.tid); - exit(1); - } - - if (pid != request.pid || uid != request.uid || gid != request.gid) { - ALOGE( - "debuggerd: attached task %d does not match request: " - "expected pid=%d,uid=%d,gid=%d, actual pid=%d,uid=%d,gid=%d", - request.tid, request.pid, request.uid, request.gid, pid, uid, gid); - exit(1); - } - } - - // Don't attach to the sibling threads if we want to attach gdb. - // Supposedly, it makes the process less reliable. - bool attach_gdb = should_attach_gdb(request); - if (attach_gdb) { - // Open all of the input devices we need to listen for VOLUMEDOWN before dropping privileges. - if (init_getevent() != 0) { - ALOGE("debuggerd: failed to initialize input device, not waiting for gdb"); - attach_gdb = false; - } - - } - - std::set siblings; - if (!attach_gdb) { - ptrace_siblings(request.pid, request.tid, request.ignore_tid, siblings); - } - - // Generate the backtrace map before dropping privileges. - std::unique_ptr backtrace_map(BacktraceMap::Create(request.pid)); - - // Collect the list of open files before dropping privileges. - OpenFilesList open_files; - populate_open_files_list(request.pid, &open_files); - - int amfd = -1; - std::unique_ptr amfd_data; - if (request.action == DEBUGGER_ACTION_CRASH) { - // Connect to the activity manager before dropping privileges. - amfd = activity_manager_connect(); - amfd_data.reset(new std::string); - } - - bool succeeded = false; - - // Now that we've done everything that requires privileges, we can drop them. - if (!drop_privileges()) { - ALOGE("debuggerd: failed to drop privileges, exiting"); - _exit(1); - } - - int crash_signal = SIGKILL; - succeeded = perform_dump(request, fd, tombstone_fd, backtrace_map.get(), open_files, - siblings, &crash_signal, amfd_data.get()); - if (succeeded) { - if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - if (!tombstone_path.empty()) { - android::base::WriteFully(fd, tombstone_path.c_str(), tombstone_path.length()); - } - } - } - - if (attach_gdb) { - // Tell the signal process to send SIGSTOP to the target. - if (!send_signal(request.pid, 0, SIGSTOP)) { - ALOGE("debuggerd: failed to stop process for gdb attach: %s", strerror(errno)); - attach_gdb = false; - } - } - - if (!attach_gdb) { - // Tell the Activity Manager about the crashing process. If we are - // waiting for gdb to attach, do not send this or Activity Manager - // might kill the process before anyone can attach. - activity_manager_write(request.pid, crash_signal, amfd, *amfd_data.get()); - } - - if (ptrace(PTRACE_DETACH, request.tid, 0, 0) != 0) { - ALOGE("debuggerd: ptrace detach from %d failed: %s", request.tid, strerror(errno)); - } - - for (pid_t sibling : siblings) { - ptrace(PTRACE_DETACH, sibling, 0, 0); - } - - // Send the signal back to the process if it crashed and we're not waiting for gdb. - if (!attach_gdb && request.action == DEBUGGER_ACTION_CRASH) { - if (!send_signal(request.pid, request.tid, crash_signal)) { - ALOGE("debuggerd: failed to kill process %d: %s", request.pid, strerror(errno)); - } - } - - // Wait for gdb, if requested. - if (attach_gdb) { - wait_for_user_action(request); - - // Now tell the activity manager about this process. - activity_manager_write(request.pid, crash_signal, amfd, *amfd_data.get()); - - // Tell the signal process to send SIGCONT to the target. - if (!send_signal(request.pid, 0, SIGCONT)) { - ALOGE("debuggerd: failed to resume process %d: %s", request.pid, strerror(errno)); - } - - uninit_getevent(); - } - - close(amfd); - - exit(!succeeded); -} - -static void monitor_worker_process(int child_pid, const debugger_request_t& request) { - struct timespec timeout = {.tv_sec = 10, .tv_nsec = 0 }; - if (should_attach_gdb(request)) { - // If wait_for_gdb is enabled, set the timeout to something large. - timeout.tv_sec = INT_MAX; - } - - sigset_t signal_set; - sigemptyset(&signal_set); - sigaddset(&signal_set, SIGCHLD); - - bool kill_worker = false; - bool kill_target = false; - bool kill_self = false; - - int status; - siginfo_t siginfo; - int signal = TEMP_FAILURE_RETRY(sigtimedwait(&signal_set, &siginfo, &timeout)); - if (signal == SIGCHLD) { - pid_t rc = waitpid(-1, &status, WNOHANG | WUNTRACED); - if (rc != child_pid) { - ALOGE("debuggerd: waitpid returned unexpected pid (%d), committing murder-suicide", rc); - - if (WIFEXITED(status)) { - ALOGW("debuggerd: pid %d exited with status %d", rc, WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - ALOGW("debuggerd: pid %d received signal %d", rc, WTERMSIG(status)); - } else if (WIFSTOPPED(status)) { - ALOGW("debuggerd: pid %d stopped by signal %d", rc, WSTOPSIG(status)); - } else if (WIFCONTINUED(status)) { - ALOGW("debuggerd: pid %d continued", rc); - } - - kill_worker = true; - kill_target = true; - kill_self = true; - } else if (WIFSIGNALED(status)) { - ALOGE("debuggerd: worker process %d terminated due to signal %d", child_pid, WTERMSIG(status)); - kill_worker = false; - kill_target = true; - } else if (WIFSTOPPED(status)) { - ALOGE("debuggerd: worker process %d stopped due to signal %d", child_pid, WSTOPSIG(status)); - kill_worker = true; - kill_target = true; - } - } else { - ALOGE("debuggerd: worker process %d timed out", child_pid); - kill_worker = true; - kill_target = true; - } - - if (kill_worker) { - // Something bad happened, kill the worker. - if (kill(child_pid, SIGKILL) != 0) { - ALOGE("debuggerd: failed to kill worker process %d: %s", child_pid, strerror(errno)); - } else { - waitpid(child_pid, &status, 0); - } - } - - int exit_signal = SIGCONT; - if (kill_target && request.action == DEBUGGER_ACTION_CRASH) { - ALOGE("debuggerd: killing target %d", request.pid); - exit_signal = SIGKILL; - } else { - ALOGW("debuggerd: resuming target %d", request.pid); - } - - if (kill(request.pid, exit_signal) != 0) { - ALOGE("debuggerd: failed to send signal %d to target: %s", exit_signal, strerror(errno)); - } - - if (kill_self) { - stop_signal_sender(); - _exit(1); - } -} - -static void handle_request(int fd) { - ALOGV("handle_request(%d)\n", fd); - - android::base::unique_fd closer(fd); - debugger_request_t request; - memset(&request, 0, sizeof(request)); - int status = read_request(fd, &request); - if (status != 0) { - return; - } - - ALOGW("debuggerd: handling request: pid=%d uid=%d gid=%d tid=%d\n", request.pid, request.uid, - request.gid, request.tid); - -#if defined(__LP64__) - // On 64 bit systems, requests to dump 32 bit and 64 bit tids come - // to the 64 bit debuggerd. If the process is a 32 bit executable, - // redirect the request to the 32 bit debuggerd. - if (is32bit(request.tid)) { - // Only dump backtrace and dump tombstone requests can be redirected. - if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE || - request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) { - redirect_to_32(fd, &request); - } else { - ALOGE("debuggerd: Not allowed to redirect action %d to 32 bit debuggerd\n", request.action); - } - return; - } -#endif - - // Fork a child to handle the rest of the request. - pid_t fork_pid = fork(); - if (fork_pid == -1) { - ALOGE("debuggerd: failed to fork: %s\n", strerror(errno)); - } else if (fork_pid == 0) { - worker_process(fd, request); - } else { - monitor_worker_process(fork_pid, request); - } -} - -static int do_server() { - // debuggerd crashes can't be reported to debuggerd. - // Reset all of the crash handlers. - signal(SIGABRT, SIG_DFL); - signal(SIGBUS, SIG_DFL); - signal(SIGFPE, SIG_DFL); - signal(SIGILL, SIG_DFL); - signal(SIGSEGV, SIG_DFL); -#ifdef SIGSTKFLT - signal(SIGSTKFLT, SIG_DFL); -#endif - signal(SIGTRAP, SIG_DFL); - - // Ignore failed writes to closed sockets - signal(SIGPIPE, SIG_IGN); - - // Block SIGCHLD so we can sigtimedwait for it. - sigset_t sigchld; - sigemptyset(&sigchld); - sigaddset(&sigchld, SIGCHLD); - sigprocmask(SIG_SETMASK, &sigchld, nullptr); - - int s = socket_local_server(SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, - SOCK_STREAM | SOCK_CLOEXEC); - if (s == -1) return 1; - - // Fork a process that stays root, and listens on a pipe to pause and resume the target. - if (!start_signal_sender()) { - ALOGE("debuggerd: failed to fork signal sender"); - return 1; - } - - ALOGI("debuggerd: starting\n"); - - for (;;) { - ALOGV("waiting for connection\n"); - int fd = accept4(s, nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK); - if (fd == -1) { - ALOGE("accept failed: %s\n", strerror(errno)); - continue; - } - - handle_request(fd); - } - return 0; -} - -static int do_explicit_dump(pid_t tid, bool dump_backtrace) { - fprintf(stdout, "Sending request to dump task %d...\n", tid); - fflush(stdout); - - // TODO: we could have better error reporting if debuggerd sent an error string back. - if (dump_backtrace) { - if (dump_backtrace_to_file(tid, fileno(stdout)) < 0) { - fputs("Error dumping backtrace (check logcat).\n", stderr); - return 1; - } - } else { - char tombstone_path[PATH_MAX]; - if (dump_tombstone(tid, tombstone_path, sizeof(tombstone_path)) < 0) { - fputs("Error dumping tombstone (check logcat).\n", stderr); - return 1; - } - fprintf(stderr, "Tombstone written to: %s\n", tombstone_path); - } - return 0; -} - -static int usage() { - fputs("usage: debuggerd [-b] []\n" - "\n" - "Given a thread id, sends a request to debuggerd to dump that thread.\n" - "Otherwise, starts the debuggerd server.\n" - "\n" - "-b\tdump backtrace to console, otherwise generate tombstone\n", stderr); - return EXIT_FAILURE; -} - -int main(int argc, char** argv) { - union selinux_callback cb; - if (argc == 1) { - cb.func_audit = audit_callback; - selinux_set_callback(SELINUX_CB_AUDIT, cb); - cb.func_log = selinux_log_callback; - selinux_set_callback(SELINUX_CB_LOG, cb); - return do_server(); - } - - bool dump_backtrace = false; - pid_t tid = 0; - for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-b")) { - dump_backtrace = true; - } else if (tid != 0 || (tid = atoi(argv[i])) == 0) { - // Only one tid is allowed. (And 0 isn't a valid tid.) - // atoi(3) returns 0 on failure to parse, so this catches anything else too. - return usage(); - } - } - return (tid != 0) ? do_explicit_dump(tid, dump_backtrace) : usage(); -} diff --git a/debuggerd/debuggerd.rc b/debuggerd/debuggerd.rc deleted file mode 100644 index 1c6b9ff94..000000000 --- a/debuggerd/debuggerd.rc +++ /dev/null @@ -1,3 +0,0 @@ -service debuggerd /system/bin/debuggerd - group root readproc - writepid /dev/cpuset/system-background/tasks diff --git a/debuggerd/debuggerd64.rc b/debuggerd/debuggerd64.rc deleted file mode 100644 index 3e8847a5a..000000000 --- a/debuggerd/debuggerd64.rc +++ /dev/null @@ -1,3 +0,0 @@ -service debuggerd64 /system/bin/debuggerd64 - group root readproc - writepid /dev/cpuset/system-background/tasks diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp new file mode 100644 index 000000000..f51b5f253 --- /dev/null +++ b/debuggerd/debuggerd_test.cpp @@ -0,0 +1,390 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; +using android::base::unique_fd; + +#if defined(__LP64__) +#define CRASHER_PATH "/system/xbin/crasher64" +#define ARCH_SUFFIX "64" +#else +#define CRASHER_PATH "/system/xbin/crasher" +#define ARCH_SUFFIX "" +#endif + +constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb"; + +#define TIMEOUT(seconds, expr) \ + [&]() { \ + struct sigaction old_sigaction; \ + struct sigaction new_sigaction = {}; \ + new_sigaction.sa_handler = [](int) {}; \ + if (sigaction(SIGALRM, &new_sigaction, &new_sigaction) != 0) { \ + err(1, "sigaction failed"); \ + } \ + alarm(seconds); \ + auto value = expr; \ + int saved_errno = errno; \ + if (sigaction(SIGALRM, &old_sigaction, nullptr) != 0) { \ + err(1, "sigaction failed"); \ + } \ + alarm(0); \ + errno = saved_errno; \ + return value; \ + }() + +#define ASSERT_MATCH(str, pattern) \ + do { \ + std::regex r((pattern)); \ + if (!std::regex_search((str), r)) { \ + FAIL() << "regex mismatch: expected " << (pattern) << " in: \n" << (str); \ + } \ + } while (0) + +class CrasherTest : public ::testing::Test { + public: + pid_t crasher_pid = -1; + bool previous_wait_for_gdb; + unique_fd crasher_pipe; + unique_fd intercept_fd; + + CrasherTest(); + ~CrasherTest(); + + void StartIntercept(unique_fd* output_fd); + + // Returns -1 if we fail to read a response from tombstoned, otherwise the received return code. + void FinishIntercept(int* result); + + void StartCrasher(const std::string& crash_type); + void FinishCrasher(); + void AssertDeath(int signo); +}; + +CrasherTest::CrasherTest() { + previous_wait_for_gdb = android::base::GetBoolProperty(kWaitForGdbKey, false); + android::base::SetProperty(kWaitForGdbKey, "0"); +} + +CrasherTest::~CrasherTest() { + if (crasher_pid != -1) { + kill(crasher_pid, SIGKILL); + int status; + waitpid(crasher_pid, &status, WUNTRACED); + } + + android::base::SetProperty(kWaitForGdbKey, previous_wait_for_gdb ? "1" : "0"); +} + +void CrasherTest::StartIntercept(unique_fd* output_fd) { + if (crasher_pid == -1) { + FAIL() << "crasher hasn't been started"; + } + + intercept_fd.reset(socket_local_client(kTombstonedInterceptSocketName, + ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_SEQPACKET)); + if (intercept_fd == -1) { + FAIL() << "failed to contact tombstoned: " << strerror(errno); + } + + InterceptRequest req = {.pid = crasher_pid }; + + unique_fd output_pipe_write; + if (!Pipe(output_fd, &output_pipe_write)) { + FAIL() << "failed to create output pipe: " << strerror(errno); + } + + std::string pipe_size_str; + int pipe_buffer_size; + if (!android::base::ReadFileToString("/proc/sys/fs/pipe-max-size", &pipe_size_str)) { + FAIL() << "failed to read /proc/sys/fs/pipe-max-size: " << strerror(errno); + } + + pipe_size_str = android::base::Trim(pipe_size_str); + + if (!android::base::ParseInt(pipe_size_str.c_str(), &pipe_buffer_size, 0)) { + FAIL() << "failed to parse pipe max size"; + } + + if (fcntl(output_fd->get(), F_SETPIPE_SZ, pipe_buffer_size) != pipe_buffer_size) { + FAIL() << "failed to set pipe size: " << strerror(errno); + } + + if (send_fd(intercept_fd.get(), &req, sizeof(req), std::move(output_pipe_write)) != sizeof(req)) { + FAIL() << "failed to send output fd to tombstoned: " << strerror(errno); + } +} + +void CrasherTest::FinishIntercept(int* result) { + InterceptResponse response; + + // Timeout for tombstoned intercept is 10 seconds. + ssize_t rc = TIMEOUT(20, read(intercept_fd.get(), &response, sizeof(response))); + if (rc == -1) { + FAIL() << "failed to read response from tombstoned: " << strerror(errno); + } else if (rc == 0) { + *result = -1; + } else if (rc != sizeof(response)) { + FAIL() << "received packet of unexpected length from tombstoned: expected " << sizeof(response) + << ", received " << rc; + } else { + *result = response.success; + } +} + +void CrasherTest::StartCrasher(const std::string& crash_type) { + std::string type = "wait-" + crash_type; + + unique_fd crasher_read_pipe; + if (!Pipe(&crasher_read_pipe, &crasher_pipe)) { + FAIL() << "failed to create pipe: " << strerror(errno); + } + + crasher_pid = fork(); + if (crasher_pid == -1) { + FAIL() << "fork failed: " << strerror(errno); + } else if (crasher_pid == 0) { + unique_fd devnull(open("/dev/null", O_WRONLY)); + dup2(crasher_read_pipe.get(), STDIN_FILENO); + dup2(devnull.get(), STDOUT_FILENO); + dup2(devnull.get(), STDERR_FILENO); + execl(CRASHER_PATH, CRASHER_PATH, type.c_str(), nullptr); + err(1, "exec failed"); + } +} + +void CrasherTest::FinishCrasher() { + if (crasher_pipe == -1) { + FAIL() << "crasher pipe uninitialized"; + } + + ssize_t rc = write(crasher_pipe.get(), "\n", 1); + if (rc == -1) { + FAIL() << "failed to write to crasher pipe: " << strerror(errno); + } else if (rc == 0) { + FAIL() << "crasher pipe was closed"; + } +} + +void CrasherTest::AssertDeath(int signo) { + int status; + pid_t pid = TIMEOUT(5, waitpid(crasher_pid, &status, 0)); + if (pid != crasher_pid) { + FAIL() << "failed to wait for crasher: " << strerror(errno); + } + + if (!WIFSIGNALED(status)) { + FAIL() << "crasher didn't terminate via a signal"; + } + ASSERT_EQ(signo, WTERMSIG(status)); + crasher_pid = -1; +} + +static void ConsumeFd(unique_fd fd, std::string* output) { + constexpr size_t read_length = PAGE_SIZE; + std::string result; + + while (true) { + size_t offset = result.size(); + result.resize(result.size() + PAGE_SIZE); + ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &result[offset], read_length)); + if (rc == -1) { + FAIL() << "read failed: " << strerror(errno); + } else if (rc == 0) { + result.resize(result.size() - PAGE_SIZE); + break; + } + + result.resize(result.size() - PAGE_SIZE + rc); + } + + *output = std::move(result); +} + +TEST_F(CrasherTest, smoke) { + int intercept_result; + unique_fd output_fd; + StartCrasher("SIGSEGV"); + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGSEGV); + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0xdead)"); +} + +TEST_F(CrasherTest, abort) { + int intercept_result; + unique_fd output_fd; + StartCrasher("abort"); + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_MATCH(result, R"(#00 pc [0-9a-f]+\s+ /system/lib)" ARCH_SUFFIX R"(/libc.so \(tgkill)"); +} + +TEST_F(CrasherTest, signal) { + int intercept_result; + unique_fd output_fd; + StartCrasher("abort"); + StartIntercept(&output_fd); + + // Wait for a bit, or we might end up killing the process before the signal + // handler even gets a chance to be registered. + std::this_thread::sleep_for(100ms); + ASSERT_EQ(0, kill(crasher_pid, SIGSEGV)); + + AssertDeath(SIGSEGV); + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 0 \(SI_USER\), fault addr --------)"); + ASSERT_MATCH(result, R"(backtrace:)"); +} + +TEST_F(CrasherTest, abort_message) { + int intercept_result; + unique_fd output_fd; + StartCrasher("smash-stack"); + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_MATCH(result, R"(Abort message: 'stack corruption detected \(-fstack-protector\)')"); +} + +TEST_F(CrasherTest, intercept_timeout) { + int intercept_result; + unique_fd output_fd; + StartCrasher("abort"); + StartIntercept(&output_fd); + + // Don't let crasher finish until we timeout. + FinishIntercept(&intercept_result); + + ASSERT_NE(1, intercept_result) << "tombstoned reported success? (intercept_result = " + << intercept_result << ")"; + + FinishCrasher(); + AssertDeath(SIGABRT); +} + +TEST_F(CrasherTest, wait_for_gdb) { + if (!android::base::SetProperty(kWaitForGdbKey, "1")) { + FAIL() << "failed to enable wait_for_gdb"; + } + sleep(1); + + StartCrasher("abort"); + FinishCrasher(); + + int status; + ASSERT_EQ(crasher_pid, waitpid(crasher_pid, &status, WUNTRACED)); + ASSERT_TRUE(WIFSTOPPED(status)); + ASSERT_EQ(SIGSTOP, WSTOPSIG(status)); + + ASSERT_EQ(0, kill(crasher_pid, SIGCONT)); + + AssertDeath(SIGABRT); +} + +TEST_F(CrasherTest, wait_for_gdb_signal) { + if (!android::base::SetProperty(kWaitForGdbKey, "1")) { + FAIL() << "failed to enable wait_for_gdb"; + } + + StartCrasher("abort"); + ASSERT_EQ(0, kill(crasher_pid, SIGABRT)) << strerror(errno); + + std::this_thread::sleep_for(500ms); + + int status; + ASSERT_EQ(crasher_pid, (TIMEOUT(1, waitpid(crasher_pid, &status, WUNTRACED)))); + ASSERT_TRUE(WIFSTOPPED(status)); + ASSERT_EQ(SIGSTOP, WSTOPSIG(status)); + + ASSERT_EQ(0, kill(crasher_pid, SIGCONT)) << strerror(errno); + + AssertDeath(SIGABRT); +} + +TEST_F(CrasherTest, backtrace) { + std::string result; + int intercept_result; + unique_fd output_fd; + StartCrasher("abort"); + StartIntercept(&output_fd); + + std::this_thread::sleep_for(500ms); + + sigval val; + val.sival_int = 1; + ASSERT_EQ(0, sigqueue(crasher_pid, DEBUGGER_SIGNAL, val)) << strerror(errno); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + ConsumeFd(std::move(output_fd), &result); + ASSERT_MATCH(result, R"(#00 pc [0-9a-f]+ /system/lib)" ARCH_SUFFIX R"(/libc.so \(read\+)"); + + int status; + ASSERT_EQ(0, waitpid(crasher_pid, &status, WNOHANG | WUNTRACED)); + + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + ConsumeFd(std::move(output_fd), &result); + ASSERT_MATCH(result, R"(#00 pc [0-9a-f]+\s+ /system/lib)" ARCH_SUFFIX R"(/libc.so \(tgkill)"); +} diff --git a/debuggerd/getevent.cpp b/debuggerd/getevent.cpp deleted file mode 100644 index dbb878acb..000000000 --- a/debuggerd/getevent.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2014 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -static struct pollfd* ufds; -static char** device_names; -static int nfds; - -static int open_device(const char* device) { - int version; - int fd; - struct pollfd* new_ufds; - char** new_device_names; - char name[80]; - char location[80]; - char idstr[80]; - struct input_id id; - - fd = open(device, O_RDWR); - if (fd < 0) { - return -1; - } - - if (ioctl(fd, EVIOCGVERSION, &version)) { - return -1; - } - if (ioctl(fd, EVIOCGID, &id)) { - return -1; - } - name[sizeof(name) - 1] = '\0'; - location[sizeof(location) - 1] = '\0'; - idstr[sizeof(idstr) - 1] = '\0'; - if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) { - name[0] = '\0'; - } - if (ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) { - location[0] = '\0'; - } - if (ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) { - idstr[0] = '\0'; - } - - new_ufds = reinterpret_cast(realloc(ufds, sizeof(ufds[0]) * (nfds + 1))); - if (new_ufds == NULL) { - fprintf(stderr, "out of memory\n"); - return -1; - } - ufds = new_ufds; - new_device_names = reinterpret_cast(realloc( - device_names, sizeof(device_names[0]) * (nfds + 1))); - if (new_device_names == NULL) { - fprintf(stderr, "out of memory\n"); - return -1; - } - device_names = new_device_names; - ufds[nfds].fd = fd; - ufds[nfds].events = POLLIN; - device_names[nfds] = strdup(device); - nfds++; - - return 0; -} - -int close_device(const char* device) { - int i; - for (i = 1; i < nfds; i++) { - if (strcmp(device_names[i], device) == 0) { - int count = nfds - i - 1; - free(device_names[i]); - memmove(device_names + i, device_names + i + 1, sizeof(device_names[0]) * count); - memmove(ufds + i, ufds + i + 1, sizeof(ufds[0]) * count); - nfds--; - return 0; - } - } - return -1; -} - -static int read_notify(const char* dirname, int nfd) { - int res; - char devname[PATH_MAX]; - char* filename; - char event_buf[512]; - int event_size; - int event_pos = 0; - struct inotify_event *event; - - res = read(nfd, event_buf, sizeof(event_buf)); - if (res < (int)sizeof(*event)) { - if (errno == EINTR) - return 0; - fprintf(stderr, "could not get event, %s\n", strerror(errno)); - return 1; - } - - strcpy(devname, dirname); - filename = devname + strlen(devname); - *filename++ = '/'; - - while (res >= (int)sizeof(*event)) { - event = reinterpret_cast(event_buf + event_pos); - if (event->len) { - strcpy(filename, event->name); - if (event->mask & IN_CREATE) { - open_device(devname); - } else { - close_device(devname); - } - } - event_size = sizeof(*event) + event->len; - res -= event_size; - event_pos += event_size; - } - return 0; -} - -static int scan_dir(const char* dirname) { - char devname[PATH_MAX]; - char* filename; - struct dirent* de; - std::unique_ptr dir(opendir(dirname), closedir); - if (dir == NULL) - return -1; - strcpy(devname, dirname); - filename = devname + strlen(devname); - *filename++ = '/'; - while ((de = readdir(dir.get()))) { - if ((de->d_name[0] == '.' && de->d_name[1] == '\0') || - (de->d_name[1] == '.' && de->d_name[2] == '\0')) - continue; - strcpy(filename, de->d_name); - open_device(devname); - } - return 0; -} - -int init_getevent() { - int res; - const char* device_path = "/dev/input"; - - nfds = 1; - ufds = reinterpret_cast(calloc(1, sizeof(ufds[0]))); - ufds[0].fd = inotify_init(); - ufds[0].events = POLLIN; - - res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE); - if (res < 0) { - return 1; - } - res = scan_dir(device_path); - if (res < 0) { - return 1; - } - return 0; -} - -void uninit_getevent() { - int i; - for (i = 0; i < nfds; i++) { - close(ufds[i].fd); - } - free(ufds); - ufds = 0; - nfds = 0; -} - -int get_event(struct input_event* event, int timeout) { - int res; - int i; - int pollres; - const char* device_path = "/dev/input"; - while (1) { - pollres = poll(ufds, nfds, timeout); - if (pollres == 0) { - return 1; - } - if (ufds[0].revents & POLLIN) { - read_notify(device_path, ufds[0].fd); - } - for (i = 1; i < nfds; i++) { - if (ufds[i].revents) { - if (ufds[i].revents & POLLIN) { - res = read(ufds[i].fd, event, sizeof(*event)); - if (res < static_cast(sizeof(event))) { - fprintf(stderr, "could not get event\n"); - return -1; - } - return 0; - } - } - } - } - return 0; -} diff --git a/debuggerd/getevent.h b/debuggerd/getevent.h deleted file mode 100644 index 426139d33..000000000 --- a/debuggerd/getevent.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2011 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 _DEBUGGERD_GETEVENT_H -#define _DEBUGGERD_GETEVENT_H - -int init_getevent(); -void uninit_getevent(); -int get_event(struct input_event* event, int timeout); - -#endif // _DEBUGGERD_GETEVENT_H diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp new file mode 100644 index 000000000..6033a6b53 --- /dev/null +++ b/debuggerd/handler/debuggerd_handler.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "debuggerd/handler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "private/libc_logging.h" + +// see man(2) prctl, specifically the section about PR_GET_NAME +#define MAX_TASK_NAME_LEN (16) + +#if defined(__LP64__) +#define CRASH_DUMP_NAME "crash_dump64" +#else +#define CRASH_DUMP_NAME "crash_dump32" +#endif + +#define CRASH_DUMP_PATH "/system/bin/" CRASH_DUMP_NAME + +static debuggerd_callbacks_t g_callbacks; + +// Don't use __libc_fatal because it exits via abort, which might put us back into a signal handler. +#define fatal(...) \ + do { \ + __libc_format_log(ANDROID_LOG_FATAL, "libc", __VA_ARGS__); \ + _exit(1); \ + } while (0) + +/* + * Writes a summary of the signal to the log file. We do this so that, if + * for some reason we're not able to contact debuggerd, there is still some + * indication of the failure in the log. + * + * We could be here as a result of native heap corruption, or while a + * mutex is being held, so we don't want to use any libc functions that + * could allocate memory or hold a lock. + */ +static void log_signal_summary(int signum, const siginfo_t* info) { + const char* signal_name = "???"; + bool has_address = false; + switch (signum) { + case SIGABRT: + signal_name = "SIGABRT"; + break; + case SIGBUS: + signal_name = "SIGBUS"; + has_address = true; + break; + case SIGFPE: + signal_name = "SIGFPE"; + has_address = true; + break; + case SIGILL: + signal_name = "SIGILL"; + has_address = true; + break; + case SIGSEGV: + signal_name = "SIGSEGV"; + has_address = true; + break; +#if defined(SIGSTKFLT) + case SIGSTKFLT: + signal_name = "SIGSTKFLT"; + break; +#endif + case SIGSYS: + signal_name = "SIGSYS"; + break; + case SIGTRAP: + signal_name = "SIGTRAP"; + break; + } + + char thread_name[MAX_TASK_NAME_LEN + 1]; // one more for termination + if (prctl(PR_GET_NAME, reinterpret_cast(thread_name), 0, 0, 0) != 0) { + strcpy(thread_name, ""); + } else { + // short names are null terminated by prctl, but the man page + // implies that 16 byte names are not. + thread_name[MAX_TASK_NAME_LEN] = 0; + } + + // "info" will be null if the siginfo_t information was not available. + // Many signals don't have an address or a code. + char code_desc[32]; // ", code -6" + char addr_desc[32]; // ", fault addr 0x1234" + addr_desc[0] = code_desc[0] = 0; + if (info != nullptr) { + __libc_format_buffer(code_desc, sizeof(code_desc), ", code %d", info->si_code); + if (has_address) { + __libc_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr); + } + } + __libc_format_log(ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s)%s%s in tid %d (%s)", signum, + signal_name, code_desc, addr_desc, gettid(), thread_name); +} + +/* + * Returns true if the handler for signal "signum" has SA_SIGINFO set. + */ +static bool have_siginfo(int signum) { + struct sigaction old_action; + if (sigaction(signum, nullptr, &old_action) < 0) { + __libc_format_log(ANDROID_LOG_WARN, "libc", "Failed testing for SA_SIGINFO: %s", + strerror(errno)); + return false; + } + return (old_action.sa_flags & SA_SIGINFO) != 0; +} + +struct debugger_thread_info { + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + bool crash_dump_started = false; + pid_t crashing_tid; + pid_t pseudothread_tid; + int signal_number; + siginfo_t* info; +}; + +// Logging and contacting debuggerd requires free file descriptors, which we might not have. +// Work around this by spawning a "thread" that shares its parent's address space, but not its file +// descriptor table, so that we can close random file descriptors without affecting the original +// process. Note that this doesn't go through pthread_create, so TLS is shared with the spawning +// process. +static void* pseudothread_stack; + +static int debuggerd_dispatch_pseudothread(void* arg) { + debugger_thread_info* thread_info = static_cast(arg); + + for (int i = 0; i < 1024; ++i) { + close(i); + } + + int devnull = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)); + + // devnull will be 0. + TEMP_FAILURE_RETRY(dup2(devnull, STDOUT_FILENO)); + TEMP_FAILURE_RETRY(dup2(devnull, STDERR_FILENO)); + + int pipefds[2]; + if (pipe(pipefds) != 0) { + fatal("failed to create pipe"); + } + + // Don't use fork(2) to avoid calling pthread_atfork handlers. + int forkpid = clone(nullptr, nullptr, SIGCHLD, nullptr); + if (forkpid == -1) { + __libc_format_log(ANDROID_LOG_FATAL, "libc", "failed to fork in debuggerd signal handler: %s", + strerror(errno)); + } else if (forkpid == 0) { + TEMP_FAILURE_RETRY(dup2(pipefds[1], STDOUT_FILENO)); + close(pipefds[0]); + close(pipefds[1]); + + char buf[10]; + snprintf(buf, sizeof(buf), "%d", thread_info->crashing_tid); + execl(CRASH_DUMP_PATH, CRASH_DUMP_NAME, buf, nullptr); + + fatal("exec failed: %s", strerror(errno)); + } else { + close(pipefds[1]); + char buf[4]; + ssize_t rc = TEMP_FAILURE_RETRY(read(pipefds[0], &buf, sizeof(buf))); + if (rc == -1) { + __libc_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s", strerror(errno)); + } else if (rc == 0) { + __libc_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper failed to exec"); + } else if (rc != 1) { + __libc_format_log(ANDROID_LOG_FATAL, "libc", + "read of IPC pipe returned unexpected value: %zd", rc); + } else { + if (buf[0] != '\1') { + __libc_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper reported failure"); + } else { + thread_info->crash_dump_started = true; + } + } + close(pipefds[0]); + + // Don't leave a zombie child. + siginfo_t child_siginfo; + if (TEMP_FAILURE_RETRY(waitid(P_PID, forkpid, &child_siginfo, WEXITED)) != 0) { + __libc_format_log(ANDROID_LOG_FATAL, "libc", "failed to wait for crash_dump helper: %s", + strerror(errno)); + thread_info->crash_dump_started = false; + } + } + + pthread_mutex_unlock(&thread_info->mutex); + return 0; +} + +// Handler that does crash dumping by forking and doing the processing in the child. +// Do this by ptracing the relevant thread, and then execing debuggerd to do the actual dump. +static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void*) { + // Mutex to prevent multiple crashing threads from trying to talk + // to debuggerd at the same time. + static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER; + int ret = pthread_mutex_lock(&crash_mutex); + if (ret != 0) { + __libc_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret)); + return; + } + + // It's possible somebody cleared the SA_SIGINFO flag, which would mean + // our "info" arg holds an undefined value. + if (!have_siginfo(signal_number)) { + info = nullptr; + } + + log_signal_summary(signal_number, info); + if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) { + // process has disabled core dumps and PTRACE_ATTACH, and does not want to be dumped. + // Honor that intention by not connecting to debuggerd and asking it to dump our internal state. + __libc_format_log(ANDROID_LOG_INFO, "libc", + "Suppressing debuggerd output because prctl(PR_GET_DUMPABLE)==0"); + + pthread_mutex_unlock(&crash_mutex); + return; + } + + void* abort_message = nullptr; + if (g_callbacks.get_abort_message) { + abort_message = g_callbacks.get_abort_message(); + } + + debugger_thread_info thread_info = { + .crashing_tid = gettid(), + .signal_number = signal_number, + .info = info + }; + pthread_mutex_lock(&thread_info.mutex); + + // Essentially pthread_create without CLONE_FILES (see debuggerd_dispatch_pseudothread). + pid_t child_pid = clone(debuggerd_dispatch_pseudothread, pseudothread_stack, + CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID, + &thread_info, nullptr, nullptr, &thread_info.pseudothread_tid); + if (child_pid == -1) { + fatal("failed to spawn debuggerd dispatch thread: %s", strerror(errno)); + } + + // Wait for the child to finish and unlock the mutex. + // This relies on bionic behavior that isn't guaranteed by the standard. + pthread_mutex_lock(&thread_info.mutex); + + // Signals can either be fatal or nonfatal. + // For fatal signals, crash_dump will PTRACE_CONT us with the signal we + // crashed with, so that processes using waitpid on us will see that we + // exited with the correct exit status (e.g. so that sh will report + // "Segmentation fault" instead of "Killed"). For this to work, we need + // to deregister our signal handler for that signal before continuing. + if (signal_number != DEBUGGER_SIGNAL) { + signal(signal_number, SIG_DFL); + } + + // We need to return from the signal handler so that debuggerd can dump the + // thread that crashed, but returning here does not guarantee that the signal + // will be thrown again, even for SIGSEGV and friends, since the signal could + // have been sent manually. Resend the signal with rt_tgsigqueueinfo(2) to + // preserve the SA_SIGINFO contents. + struct siginfo si; + if (!info) { + memset(&si, 0, sizeof(si)); + si.si_code = SI_USER; + si.si_pid = getpid(); + si.si_uid = getuid(); + info = &si; + } else if (info->si_code >= 0 || info->si_code == SI_TKILL) { + // rt_tgsigqueueinfo(2)'s documentation appears to be incorrect on kernels + // that contain commit 66dd34a (3.9+). The manpage claims to only allow + // negative si_code values that are not SI_TKILL, but 66dd34a changed the + // check to allow all si_code values in calls coming from inside the house. + } + + // Populate si_value with the abort message address, if found. + if (abort_message) { + info->si_value.sival_ptr = abort_message; + } + + // Only resend the signal if we know that either crash_dump has ptraced us or + // the signal was fatal. + if (thread_info.crash_dump_started || signal_number != DEBUGGER_SIGNAL) { + int rc = syscall(SYS_rt_tgsigqueueinfo, getpid(), gettid(), signal_number, info); + if (rc != 0) { + fatal("failed to resend signal during crash: %s", strerror(errno)); + } + } + + if (signal_number == DEBUGGER_SIGNAL) { + pthread_mutex_unlock(&crash_mutex); + } +} + +void debuggerd_init(debuggerd_callbacks_t* callbacks) { + if (callbacks) { + g_callbacks = *callbacks; + } + + void* thread_stack_allocation = + mmap(nullptr, PAGE_SIZE * 3, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (thread_stack_allocation == MAP_FAILED) { + fatal("failed to allocate debuggerd thread stack"); + } + + char* stack = static_cast(thread_stack_allocation) + PAGE_SIZE; + if (mprotect(stack, PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) { + fatal("failed to mprotect debuggerd thread stack"); + } + + // Stack grows negatively, set it to the last byte in the page... + stack = (stack + PAGE_SIZE - 1); + // and align it. + stack -= 15; + pseudothread_stack = stack; + + struct sigaction action; + memset(&action, 0, sizeof(action)); + sigfillset(&action.sa_mask); + action.sa_sigaction = debuggerd_signal_handler; + action.sa_flags = SA_RESTART | SA_SIGINFO; + + // Use the alternate signal stack if available so we can catch stack overflows. + action.sa_flags |= SA_ONSTACK; + + sigaction(SIGABRT, &action, nullptr); + sigaction(SIGBUS, &action, nullptr); + sigaction(SIGFPE, &action, nullptr); + sigaction(SIGILL, &action, nullptr); + sigaction(SIGSEGV, &action, nullptr); +#if defined(SIGSTKFLT) + sigaction(SIGSTKFLT, &action, nullptr); +#endif + sigaction(SIGTRAP, &action, nullptr); + sigaction(DEBUGGER_SIGNAL, &action, nullptr); +} diff --git a/debuggerd/include/debuggerd/client.h b/debuggerd/include/debuggerd/client.h index aeb723b42..91f143ba5 100644 --- a/debuggerd/include/debuggerd/client.h +++ b/debuggerd/include/debuggerd/client.h @@ -1,5 +1,5 @@ /* - * Copyright 2016 The Android Open Source Project + * 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. @@ -16,44 +16,21 @@ #pragma once -#include +#include #include -#include +#include -// On 32-bit devices, DEBUGGER_SOCKET_NAME is a 32-bit debuggerd. -// On 64-bit devices, DEBUGGER_SOCKET_NAME is a 64-bit debuggerd. -#define DEBUGGER_SOCKET_NAME "android:debuggerd" +#include -// Used only on 64-bit devices for debuggerd32. -#define DEBUGGER32_SOCKET_NAME "android:debuggerd32" +enum DebuggerdDumpType { + kDebuggerdBacktrace, + kDebuggerdTombstone, +}; -__BEGIN_DECLS +// Trigger a dump of specified process to output_fd. +// output_fd is *not* consumed, timeouts <= 0 will wait forever. +bool debuggerd_trigger_dump(pid_t pid, android::base::unique_fd output_fd, + enum DebuggerdDumpType dump_type, int timeout_ms); -typedef enum { - // dump a crash - DEBUGGER_ACTION_CRASH, - // dump a tombstone file - DEBUGGER_ACTION_DUMP_TOMBSTONE, - // dump a backtrace only back to the socket - DEBUGGER_ACTION_DUMP_BACKTRACE, -} debugger_action_t; - -// Make sure that all values have a fixed size so that this structure -// is the same for 32 bit and 64 bit processes. -typedef struct __attribute__((packed)) { - int32_t action; - pid_t tid; - pid_t ignore_tid; - uint64_t abort_msg_address; -} debugger_msg_t; - -// These callbacks are called in a signal handler, and thus must be async signal safe. -// If null, the callbacks will not be called. -typedef struct { - struct abort_msg_t* (*get_abort_message)(); - void (*post_dump)(); -} debuggerd_callbacks_t; - -void debuggerd_init(debuggerd_callbacks_t* callbacks); - -__END_DECLS +int dump_backtrace_to_file(pid_t tid, int fd); +int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs); diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h new file mode 100644 index 000000000..302f4c20e --- /dev/null +++ b/debuggerd/include/debuggerd/handler.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +__BEGIN_DECLS + +// These callbacks are called in a signal handler, and thus must be async signal safe. +// If null, the callbacks will not be called. +typedef struct { + struct abort_msg_t* (*get_abort_message)(); + void (*post_dump)(); +} debuggerd_callbacks_t; + +void debuggerd_init(debuggerd_callbacks_t* callbacks); + +// DEBUGGER_ACTION_DUMP_TOMBSTONE and DEBUGGER_ACTION_DUMP_BACKTRACE are both +// triggered via DEBUGGER_SIGNAL. The debugger_action_t is sent via si_value +// using sigqueue(2) or equivalent. If no si_value is specified (e.g. if the +// signal is sent by kill(2)), the default behavior is to print the backtrace +// to the log. +#define DEBUGGER_SIGNAL (__SIGRTMIN + 3) + +__END_DECLS diff --git a/debuggerd/include/debuggerd/protocol.h b/debuggerd/include/debuggerd/protocol.h new file mode 100644 index 000000000..bb2ab0dc9 --- /dev/null +++ b/debuggerd/include/debuggerd/protocol.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#pragma once + +#include + +// Sockets in the ANDROID_SOCKET_NAMESPACE_RESERVED namespace. +// Both sockets are SOCK_SEQPACKET sockets, so no explicit length field is needed. +constexpr char kTombstonedCrashSocketName[] = "tombstoned_crash"; +constexpr char kTombstonedInterceptSocketName[] = "tombstoned_intercept"; + +enum class CrashPacketType : uint8_t { + // Initial request from crash_dump. + kDumpRequest = 0, + + // Notification of a completed crash dump. + // Sent after a dump is completed and the process has been untraced, but + // before it has been resumed with SIGCONT. + kCompletedDump, + + // Responses to kRequest. + // kPerformDump sends along an output fd via cmsg(3). + kPerformDump = 128, + kAbortDump, +}; + +struct DumpRequest { + int32_t pid; +}; + +// The full packet must always be written, regardless of whether the union is used. +struct TombstonedCrashPacket { + CrashPacketType packet_type; + union { + DumpRequest dump_request; + } packet; +}; + +// Comes with a file descriptor via SCM_RIGHTS. +// This packet should be sent before an actual dump happens. +struct InterceptRequest { + int32_t pid; +}; + +// Sent either immediately upon failure, or when the intercept has been used. +struct InterceptResponse { + uint8_t success; // 0 or 1 + char error_message[127]; // always null-terminated +}; diff --git a/debuggerd/include/debuggerd/util.h b/debuggerd/include/debuggerd/util.h new file mode 100644 index 000000000..60517146b --- /dev/null +++ b/debuggerd/include/debuggerd/util.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +// *** WARNING *** +// tombstoned's sockets are SOCK_SEQPACKET sockets. +// Short reads are treated as errors and short writes are assumed to not happen. + +// Sends a packet with an attached fd. +ssize_t send_fd(int sockfd, const void* _Nonnull data, size_t len, android::base::unique_fd fd); + +// Receives a packet and optionally, its attached fd. +// If out_fd is non-null, packets can optionally have an attached fd. +// If out_fd is null, received packets must not have an attached fd. +// +// Errors: +// EOVERFLOW: sockfd is SOCK_DGRAM or SOCK_SEQPACKET and buffer is too small. +// The first len bytes of the packet are stored in data, but the +// rest of the packet is dropped. +// ERANGE: too many file descriptors were attached to the packet. +// ENOMSG: not enough file descriptors were attached to the packet. +// +// plus any errors returned by the underlying recvmsg. +ssize_t recv_fd(int sockfd, void* _Nonnull data, size_t len, + android::base::unique_fd* _Nullable out_fd); + +bool Pipe(android::base::unique_fd* read, android::base::unique_fd* write); diff --git a/debuggerd/arm/machine.cpp b/debuggerd/libdebuggerd/arm/machine.cpp similarity index 100% rename from debuggerd/arm/machine.cpp rename to debuggerd/libdebuggerd/arm/machine.cpp diff --git a/debuggerd/arm64/machine.cpp b/debuggerd/libdebuggerd/arm64/machine.cpp similarity index 100% rename from debuggerd/arm64/machine.cpp rename to debuggerd/libdebuggerd/arm64/machine.cpp diff --git a/debuggerd/backtrace.cpp b/debuggerd/libdebuggerd/backtrace.cpp similarity index 100% rename from debuggerd/backtrace.cpp rename to debuggerd/libdebuggerd/backtrace.cpp diff --git a/debuggerd/elf_utils.cpp b/debuggerd/libdebuggerd/elf_utils.cpp similarity index 100% rename from debuggerd/elf_utils.cpp rename to debuggerd/libdebuggerd/elf_utils.cpp diff --git a/debuggerd/backtrace.h b/debuggerd/libdebuggerd/include/backtrace.h similarity index 100% rename from debuggerd/backtrace.h rename to debuggerd/libdebuggerd/include/backtrace.h diff --git a/debuggerd/elf_utils.h b/debuggerd/libdebuggerd/include/elf_utils.h similarity index 100% rename from debuggerd/elf_utils.h rename to debuggerd/libdebuggerd/include/elf_utils.h diff --git a/debuggerd/machine.h b/debuggerd/libdebuggerd/include/machine.h similarity index 100% rename from debuggerd/machine.h rename to debuggerd/libdebuggerd/include/machine.h diff --git a/debuggerd/open_files_list.h b/debuggerd/libdebuggerd/include/open_files_list.h similarity index 100% rename from debuggerd/open_files_list.h rename to debuggerd/libdebuggerd/include/open_files_list.h diff --git a/debuggerd/tombstone.h b/debuggerd/libdebuggerd/include/tombstone.h similarity index 97% rename from debuggerd/tombstone.h rename to debuggerd/libdebuggerd/include/tombstone.h index 126f80401..4ff24af26 100644 --- a/debuggerd/tombstone.h +++ b/debuggerd/libdebuggerd/include/tombstone.h @@ -23,6 +23,8 @@ #include #include +#include "open_files_list.h" + class BacktraceMap; /* Create and open a tombstone file for writing. diff --git a/debuggerd/utility.h b/debuggerd/libdebuggerd/include/utility.h similarity index 96% rename from debuggerd/utility.h rename to debuggerd/libdebuggerd/include/utility.h index f7a3f731b..bbc45468b 100644 --- a/debuggerd/utility.h +++ b/debuggerd/libdebuggerd/include/utility.h @@ -18,6 +18,7 @@ #ifndef _DEBUGGERD_UTILITY_H #define _DEBUGGERD_UTILITY_H +#include #include #include @@ -78,7 +79,7 @@ enum logtype { void _LOG(log_t* log, logtype ltype, const char *fmt, ...) __attribute__ ((format(printf, 3, 4))); -int wait_for_signal(pid_t tid); +bool wait_for_signal(pid_t tid, siginfo_t* siginfo); void dump_memory(log_t* log, Backtrace* backtrace, uintptr_t addr, const char* fmt, ...); diff --git a/debuggerd/mips/machine.cpp b/debuggerd/libdebuggerd/mips/machine.cpp similarity index 100% rename from debuggerd/mips/machine.cpp rename to debuggerd/libdebuggerd/mips/machine.cpp diff --git a/debuggerd/mips64/machine.cpp b/debuggerd/libdebuggerd/mips64/machine.cpp similarity index 100% rename from debuggerd/mips64/machine.cpp rename to debuggerd/libdebuggerd/mips64/machine.cpp diff --git a/debuggerd/open_files_list.cpp b/debuggerd/libdebuggerd/open_files_list.cpp similarity index 100% rename from debuggerd/open_files_list.cpp rename to debuggerd/libdebuggerd/open_files_list.cpp diff --git a/debuggerd/test/BacktraceMock.h b/debuggerd/libdebuggerd/test/BacktraceMock.h similarity index 100% rename from debuggerd/test/BacktraceMock.h rename to debuggerd/libdebuggerd/test/BacktraceMock.h diff --git a/debuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp similarity index 100% rename from debuggerd/test/dump_memory_test.cpp rename to debuggerd/libdebuggerd/test/dump_memory_test.cpp diff --git a/debuggerd/test/elf_fake.cpp b/debuggerd/libdebuggerd/test/elf_fake.cpp similarity index 100% rename from debuggerd/test/elf_fake.cpp rename to debuggerd/libdebuggerd/test/elf_fake.cpp diff --git a/debuggerd/test/elf_fake.h b/debuggerd/libdebuggerd/test/elf_fake.h similarity index 100% rename from debuggerd/test/elf_fake.h rename to debuggerd/libdebuggerd/test/elf_fake.h diff --git a/debuggerd/test/host_signal_fixup.h b/debuggerd/libdebuggerd/test/host_signal_fixup.h similarity index 100% rename from debuggerd/test/host_signal_fixup.h rename to debuggerd/libdebuggerd/test/host_signal_fixup.h diff --git a/debuggerd/test/log_fake.cpp b/debuggerd/libdebuggerd/test/log_fake.cpp similarity index 100% rename from debuggerd/test/log_fake.cpp rename to debuggerd/libdebuggerd/test/log_fake.cpp diff --git a/debuggerd/test/log_fake.h b/debuggerd/libdebuggerd/test/log_fake.h similarity index 100% rename from debuggerd/test/log_fake.h rename to debuggerd/libdebuggerd/test/log_fake.h diff --git a/debuggerd/test/open_files_list_test.cpp b/debuggerd/libdebuggerd/test/open_files_list_test.cpp similarity index 100% rename from debuggerd/test/open_files_list_test.cpp rename to debuggerd/libdebuggerd/test/open_files_list_test.cpp diff --git a/debuggerd/test/property_fake.cpp b/debuggerd/libdebuggerd/test/property_fake.cpp similarity index 100% rename from debuggerd/test/property_fake.cpp rename to debuggerd/libdebuggerd/test/property_fake.cpp diff --git a/debuggerd/test/ptrace_fake.cpp b/debuggerd/libdebuggerd/test/ptrace_fake.cpp similarity index 100% rename from debuggerd/test/ptrace_fake.cpp rename to debuggerd/libdebuggerd/test/ptrace_fake.cpp diff --git a/debuggerd/test/ptrace_fake.h b/debuggerd/libdebuggerd/test/ptrace_fake.h similarity index 100% rename from debuggerd/test/ptrace_fake.h rename to debuggerd/libdebuggerd/test/ptrace_fake.h diff --git a/debuggerd/test/sys/system_properties.h b/debuggerd/libdebuggerd/test/sys/system_properties.h similarity index 100% rename from debuggerd/test/sys/system_properties.h rename to debuggerd/libdebuggerd/test/sys/system_properties.h diff --git a/debuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp similarity index 100% rename from debuggerd/test/tombstone_test.cpp rename to debuggerd/libdebuggerd/test/tombstone_test.cpp diff --git a/debuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp similarity index 98% rename from debuggerd/tombstone.cpp rename to debuggerd/libdebuggerd/tombstone.cpp index e76edb9de..01e9cf69b 100644 --- a/debuggerd/tombstone.cpp +++ b/debuggerd/libdebuggerd/tombstone.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -39,9 +40,8 @@ #include #include #include -#include -#include +#include "debuggerd/handler.h" #include "backtrace.h" #include "elf_utils.h" @@ -86,6 +86,7 @@ static const char* get_signame(int sig) { case SIGSTOP: return "SIGSTOP"; case SIGSYS: return "SIGSYS"; case SIGTRAP: return "SIGTRAP"; + case DEBUGGER_SIGNAL: return ""; default: return "?"; } } @@ -625,7 +626,9 @@ static void dump_crash(log_t* log, BacktraceMap* map, const OpenFilesList& open_files, pid_t pid, pid_t tid, const std::set& siblings, uintptr_t abort_msg_address) { // don't copy log messages to tombstone unless this is a dev device - bool want_logs = __android_log_is_debuggable(); + char value[PROPERTY_VALUE_MAX]; + property_get("ro.debuggable", value, "0"); + bool want_logs = (value[0] == '1'); _LOG(log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n"); diff --git a/debuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp similarity index 95% rename from debuggerd/utility.cpp rename to debuggerd/libdebuggerd/utility.cpp index 419d36c3b..57209aaef 100644 --- a/debuggerd/utility.cpp +++ b/debuggerd/libdebuggerd/utility.cpp @@ -71,21 +71,25 @@ void _LOG(log_t* log, enum logtype ltype, const char* fmt, ...) { } } -int wait_for_signal(pid_t tid) { +bool wait_for_signal(pid_t tid, siginfo_t* siginfo) { while (true) { int status; pid_t n = TEMP_FAILURE_RETRY(waitpid(tid, &status, __WALL)); if (n == -1) { ALOGE("waitpid failed: tid %d, %s", tid, strerror(errno)); - return -1; + return false; } else if (n == tid) { if (WIFSTOPPED(status)) { - return WSTOPSIG(status); + if (ptrace(PTRACE_GETSIGINFO, tid, nullptr, siginfo) != 0) { + ALOGE("PTRACE_GETSIGINFO failed: %s", strerror(errno)); + return false; + } + return true; } else { ALOGE("unexpected waitpid response: n=%d, status=%08x\n", n, status); // This is the only circumstance under which we can allow a detach // to fail with ESRCH, which indicates the tid has exited. - return -1; + return false; } } } diff --git a/debuggerd/x86/machine.cpp b/debuggerd/libdebuggerd/x86/machine.cpp similarity index 100% rename from debuggerd/x86/machine.cpp rename to debuggerd/libdebuggerd/x86/machine.cpp diff --git a/debuggerd/x86_64/machine.cpp b/debuggerd/libdebuggerd/x86_64/machine.cpp similarity index 100% rename from debuggerd/x86_64/machine.cpp rename to debuggerd/libdebuggerd/x86_64/machine.cpp diff --git a/debuggerd/signal_sender.h b/debuggerd/signal_sender.h deleted file mode 100644 index 0443272e6..000000000 --- a/debuggerd/signal_sender.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 _DEBUGGERD_SIGNAL_SENDER_H -#define _DEBUGGERD_SIGNAL_SENDER_H - -#include - -bool start_signal_sender(); -bool stop_signal_sender(); - -// Sends a signal to a target process or thread. -// If tid is greater than zero, this performs tgkill(pid, tid, signal). -// Otherwise, it performs kill(pid, signal). -bool send_signal(pid_t pid, pid_t tid, int signal); - -#endif diff --git a/debuggerd/test/selinux/android.h b/debuggerd/test/selinux/android.h deleted file mode 100644 index abed0870c..000000000 --- a/debuggerd/test/selinux/android.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - -extern "C" int selinux_android_restorecon(const char*, unsigned int); diff --git a/debuggerd/test/selinux_fake.cpp b/debuggerd/test/selinux_fake.cpp deleted file mode 100644 index acdd0a97c..000000000 --- a/debuggerd/test/selinux_fake.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - -extern "C" int selinux_android_restorecon(const char*, unsigned int) { - return 0; -} diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp new file mode 100644 index 000000000..789260d07 --- /dev/null +++ b/debuggerd/tombstoned/intercept_manager.cpp @@ -0,0 +1,182 @@ +/* + * 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 "intercept_manager.h" + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "debuggerd/protocol.h" +#include "debuggerd/util.h" + +using android::base::unique_fd; + +static void intercept_close_cb(evutil_socket_t sockfd, short event, void* arg) { + auto intercept = reinterpret_cast(arg); + InterceptManager* intercept_manager = intercept->intercept_manager; + + CHECK_EQ(sockfd, intercept->sockfd.get()); + + // If we can read, either we received unexpected data from the other side, or the other side + // closed their end of the socket. Either way, kill the intercept. + + // Ownership of intercept differs based on whether we've registered it with InterceptManager. + if (!intercept->registered) { + delete intercept; + } else { + auto it = intercept_manager->intercepts.find(intercept->intercept_pid); + if (it == intercept_manager->intercepts.end()) { + LOG(FATAL) << "intercept close callback called after intercept was already removed?"; + } + if (it->second.get() != intercept) { + LOG(FATAL) << "intercept close callback has different Intercept from InterceptManager?"; + } + + const char* reason; + if ((event & EV_TIMEOUT) != 0) { + reason = "due to timeout"; + } else { + reason = "due to input"; + } + + LOG(INFO) << "intercept for pid " << intercept->intercept_pid << " terminated " << reason; + intercept_manager->intercepts.erase(it); + } +} + +static void intercept_request_cb(evutil_socket_t sockfd, short ev, void* arg) { + auto intercept = reinterpret_cast(arg); + InterceptManager* intercept_manager = intercept->intercept_manager; + + CHECK_EQ(sockfd, intercept->sockfd.get()); + + if ((ev & EV_TIMEOUT) != 0) { + LOG(WARNING) << "tombstoned didn't receive InterceptRequest before timeout"; + goto fail; + } else if ((ev & EV_READ) == 0) { + LOG(WARNING) << "tombstoned received unexpected event on intercept socket"; + goto fail; + } + + { + unique_fd rcv_fd; + InterceptRequest intercept_request; + ssize_t result = recv_fd(sockfd, &intercept_request, sizeof(intercept_request), &rcv_fd); + + if (result == -1) { + PLOG(WARNING) << "failed to read from intercept socket"; + goto fail; + } else if (result != sizeof(intercept_request)) { + LOG(WARNING) << "intercept socket received short read of length " << result << " (expected " + << sizeof(intercept_request) << ")"; + goto fail; + } + + // Move the received FD to the upper half, in order to more easily notice FD leaks. + int moved_fd = fcntl(rcv_fd.get(), F_DUPFD, 512); + if (moved_fd == -1) { + LOG(WARNING) << "failed to move received fd (" << rcv_fd.get() << ")"; + goto fail; + } + rcv_fd.reset(moved_fd); + + // We trust the other side, so only do minimal validity checking. + if (intercept_request.pid <= 0 || intercept_request.pid > std::numeric_limits::max()) { + InterceptResponse response = {}; + snprintf(response.error_message, sizeof(response.error_message), "invalid pid %" PRId32, + intercept_request.pid); + TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response))); + goto fail; + } + + intercept->intercept_pid = intercept_request.pid; + + // Register the intercept with the InterceptManager. + if (intercept_manager->intercepts.count(intercept_request.pid) > 0) { + InterceptResponse response = {}; + snprintf(response.error_message, sizeof(response.error_message), + "pid %" PRId32 " already intercepted", intercept_request.pid); + TEMP_FAILURE_RETRY(write(sockfd, &response, sizeof(response))); + LOG(WARNING) << response.error_message; + goto fail; + } + + intercept->output_fd = std::move(rcv_fd); + intercept_manager->intercepts[intercept_request.pid] = std::unique_ptr(intercept); + intercept->registered = true; + + LOG(INFO) << "tombstoned registered intercept for pid " << intercept_request.pid; + + // Register a different read event on the socket so that we can remove intercepts if the socket + // closes (e.g. if a user CTRL-C's the process that requested the intercept). + event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT, + intercept_close_cb, arg); + + struct timeval timeout = { .tv_sec = 10, .tv_usec = 0 }; + event_add(intercept->intercept_event, &timeout); + } + + return; + +fail: + delete intercept; +} + +static void intercept_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, + void* arg) { + Intercept* intercept = new Intercept(); + intercept->intercept_manager = static_cast(arg); + intercept->sockfd.reset(sockfd); + + struct timeval timeout = { 1, 0 }; + event_base* base = evconnlistener_get_base(listener); + event* intercept_event = + event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept); + intercept->intercept_event = intercept_event; + event_add(intercept_event, &timeout); +} + +InterceptManager::InterceptManager(event_base* base, int intercept_socket) : base(base) { + this->listener = evconnlistener_new(base, intercept_accept_cb, this, -1, LEV_OPT_CLOSE_ON_FREE, + intercept_socket); +} + +bool InterceptManager::GetIntercept(pid_t pid, android::base::unique_fd* out_fd) { + auto it = this->intercepts.find(pid); + if (it == this->intercepts.end()) { + return false; + } + + auto intercept = std::move(it->second); + this->intercepts.erase(it); + + LOG(INFO) << "found intercept fd " << intercept->output_fd.get() << " for pid " << pid; + InterceptResponse response = {}; + response.success = 1; + TEMP_FAILURE_RETRY(write(intercept->sockfd, &response, sizeof(response))); + *out_fd = std::move(intercept->output_fd); + + return true; +} diff --git a/debuggerd/tombstoned/intercept_manager.h b/debuggerd/tombstoned/intercept_manager.h new file mode 100644 index 000000000..cb5db6262 --- /dev/null +++ b/debuggerd/tombstoned/intercept_manager.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +#include +#include + +#include + +struct InterceptManager; + +struct Intercept { + ~Intercept() { + event_free(intercept_event); + } + + InterceptManager* intercept_manager = nullptr; + event* intercept_event = nullptr; + android::base::unique_fd sockfd; + + pid_t intercept_pid = -1; + android::base::unique_fd output_fd; + bool registered = false; +}; + +struct InterceptManager { + event_base* base; + std::unordered_map> intercepts; + evconnlistener* listener = nullptr; + + InterceptManager(event_base* _Nonnull base, int intercept_socket); + InterceptManager(InterceptManager& copy) = delete; + InterceptManager(InterceptManager&& move) = delete; + + bool GetIntercept(pid_t pid, android::base::unique_fd* out_fd); +}; diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp new file mode 100644 index 000000000..3c1dcaf3c --- /dev/null +++ b/debuggerd/tombstoned/tombstoned.cpp @@ -0,0 +1,283 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "debuggerd/protocol.h" +#include "debuggerd/util.h" + +#include "intercept_manager.h" + +using android::base::StringPrintf; +using android::base::unique_fd; + +static InterceptManager* intercept_manager; + +enum CrashStatus { + kCrashStatusRunning, + kCrashStatusQueued, +}; + +// Ownership of Crash is a bit messy. +// It's either owned by an active event that must have a timeout, or owned by +// queued_requests, in the case that multiple crashes come in at the same time. +struct Crash { + ~Crash() { + event_free(crash_event); + } + + unique_fd crash_fd; + pid_t crash_pid; + event* crash_event = nullptr; +}; + +static constexpr char kTombstoneDirectory[] = "/data/tombstones/"; +static constexpr size_t kTombstoneCount = 10; +static int tombstone_directory_fd = -1; +static int next_tombstone = 0; + +static constexpr size_t kMaxConcurrentDumps = 1; +static size_t num_concurrent_dumps = 0; + +static std::deque queued_requests; + +// Forward declare the callbacks so they can be placed in a sensible order. +static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, void*); +static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg); +static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg); + +static void find_oldest_tombstone() { + size_t oldest_tombstone = 0; + time_t oldest_time = std::numeric_limits::max(); + + for (size_t i = 0; i < kTombstoneCount; ++i) { + std::string path = android::base::StringPrintf("%stombstone_%02zu", kTombstoneDirectory, i); + struct stat st; + if (stat(path.c_str(), &st) != 0) { + PLOG(ERROR) << "failed to stat " << path; + } + + if (st.st_mtime < oldest_time) { + oldest_tombstone = i; + oldest_time = st.st_mtime; + } + } + + next_tombstone = oldest_tombstone; +} + +static unique_fd get_tombstone_fd() { + // If kMaxConcurrentDumps is greater than 1, then theoretically the same + // filename could be handed out to multiple processes. Unlink and create the + // file, instead of using O_TRUNC, to avoid two processes interleaving their + // output. + unique_fd result; + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "tombstone_%02d", next_tombstone); + if (unlinkat(tombstone_directory_fd, buf, 0) != 0 && errno != ENOENT) { + PLOG(FATAL) << "failed to unlink tombstone at " << kTombstoneDirectory << buf; + } + + result.reset( + openat(tombstone_directory_fd, buf, O_CREAT | O_EXCL | O_WRONLY | O_APPEND, O_CLOEXEC, 0700)); + if (result == -1) { + PLOG(FATAL) << "failed to create tombstone at " << kTombstoneDirectory << buf; + } + + next_tombstone = (next_tombstone + 1) % kTombstoneCount; + return result; +} + +static void dequeue_request(Crash* crash) { + ++num_concurrent_dumps; + + unique_fd output_fd; + if (!intercept_manager->GetIntercept(crash->crash_pid, &output_fd)) { + output_fd = get_tombstone_fd(); + } + + TombstonedCrashPacket response = { + .packet_type = CrashPacketType::kPerformDump + }; + ssize_t rc = send_fd(crash->crash_fd, &response, sizeof(response), std::move(output_fd)); + if (rc == -1) { + PLOG(WARNING) << "failed to send response to CrashRequest"; + goto fail; + } else if (rc != sizeof(response)) { + PLOG(WARNING) << "crash socket write returned short"; + goto fail; + } else { + // TODO: Make this configurable by the interceptor? + struct timeval timeout = { 10, 0 }; + + event_base* base = event_get_base(crash->crash_event); + event_assign(crash->crash_event, base, crash->crash_fd, EV_TIMEOUT | EV_READ, + crash_completed_cb, crash); + event_add(crash->crash_event, &timeout); + } + return; + +fail: + delete crash; +} + +static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, + void*) { + event_base* base = evconnlistener_get_base(listener); + Crash* crash = new Crash(); + + struct timeval timeout = { 1, 0 }; + event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash); + crash->crash_fd.reset(sockfd); + crash->crash_event = crash_event; + event_add(crash_event, &timeout); +} + +static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) { + ssize_t rc; + Crash* crash = static_cast(arg); + TombstonedCrashPacket request = {}; + + if ((ev & EV_TIMEOUT) != 0) { + LOG(WARNING) << "crash request timed out"; + goto fail; + } else if ((ev & EV_READ) == 0) { + LOG(WARNING) << "tombstoned received unexpected event from crash socket"; + goto fail; + } + + rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request))); + if (rc == -1) { + PLOG(WARNING) << "failed to read from crash socket"; + goto fail; + } else if (rc != sizeof(request)) { + LOG(WARNING) << "crash socket received short read of length " << rc << " (expected " + << sizeof(request) << ")"; + goto fail; + } + + if (request.packet_type != CrashPacketType::kDumpRequest) { + LOG(WARNING) << "unexpected crash packet type, expected kDumpRequest, received " + << StringPrintf("%#2hhX", request.packet_type); + goto fail; + } + + crash->crash_pid = request.packet.dump_request.pid; + LOG(INFO) << "received crash request for pid " << crash->crash_pid; + + if (num_concurrent_dumps == kMaxConcurrentDumps) { + LOG(INFO) << "enqueueing crash request for pid " << crash->crash_pid; + queued_requests.push_back(crash); + } else { + dequeue_request(crash); + } + + return; + +fail: + delete crash; +} + +static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg) { + ssize_t rc; + Crash* crash = static_cast(arg); + TombstonedCrashPacket request = {}; + + --num_concurrent_dumps; + + if ((ev & EV_READ) == 0) { + goto fail; + } + + rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request))); + if (rc == -1) { + PLOG(WARNING) << "failed to read from crash socket"; + goto fail; + } else if (rc != sizeof(request)) { + LOG(WARNING) << "crash socket received short read of length " << rc << " (expected " + << sizeof(request) << ")"; + goto fail; + } + + if (request.packet_type != CrashPacketType::kCompletedDump) { + LOG(WARNING) << "unexpected crash packet type, expected kCompletedDump, received " + << uint32_t(request.packet_type); + goto fail; + } + +fail: + delete crash; + + // If there's something queued up, let them proceed. + if (!queued_requests.empty()) { + Crash* next_crash = queued_requests.front(); + queued_requests.pop_front(); + dequeue_request(next_crash); + } +} + +int main(int, char* []) { + tombstone_directory_fd = open(kTombstoneDirectory, O_DIRECTORY | O_RDONLY | O_CLOEXEC); + if (tombstone_directory_fd == -1) { + PLOG(FATAL) << "failed to open tombstone directory"; + } + + find_oldest_tombstone(); + + int intercept_socket = android_get_control_socket(kTombstonedInterceptSocketName); + int crash_socket = android_get_control_socket(kTombstonedCrashSocketName); + + if (intercept_socket == -1 || crash_socket == -1) { + PLOG(FATAL) << "failed to get socket from init"; + } + + evutil_make_socket_nonblocking(intercept_socket); + evutil_make_socket_nonblocking(crash_socket); + + event_base* base = event_base_new(); + if (!base) { + LOG(FATAL) << "failed to create event_base"; + } + + intercept_manager = new InterceptManager(base, intercept_socket); + + evconnlistener* listener = + evconnlistener_new(base, crash_accept_cb, nullptr, -1, LEV_OPT_CLOSE_ON_FREE, crash_socket); + if (!listener) { + LOG(FATAL) << "failed to create evconnlistener"; + } + + LOG(INFO) << "tombstoned successfully initialized"; + event_base_dispatch(base); +} diff --git a/debuggerd/tombstoned/tombstoned.rc b/debuggerd/tombstoned/tombstoned.rc new file mode 100644 index 000000000..3aacf332f --- /dev/null +++ b/debuggerd/tombstoned/tombstoned.rc @@ -0,0 +1,9 @@ +service tombstoned /system/bin/tombstoned + class core + + user tombstoned + group system + + socket tombstoned_crash seqpacket 0666 system system + socket tombstoned_intercept seqpacket 0666 system system + writepid /dev/cpuset/system-background/tasks diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp new file mode 100644 index 000000000..738abdf7b --- /dev/null +++ b/debuggerd/util.cpp @@ -0,0 +1,96 @@ +/* + * 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 "debuggerd/util.h" + +#include + +#include + +#include +#include + +ssize_t send_fd(int sockfd, const void* data, size_t len, android::base::unique_fd fd) { + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + + iovec iov = { .iov_base = const_cast(data), .iov_len = len }; + msghdr msg = { + .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsg_buf, .msg_controllen = sizeof(cmsg_buf), + }; + auto cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *reinterpret_cast(CMSG_DATA(cmsg)) = fd.get(); + + return TEMP_FAILURE_RETRY(sendmsg(sockfd, &msg, 0)); +} + +ssize_t recv_fd(int sockfd, void* _Nonnull data, size_t len, + android::base::unique_fd* _Nullable out_fd) { + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + + iovec iov = { .iov_base = const_cast(data), .iov_len = len }; + msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsg_buf, + .msg_controllen = sizeof(cmsg_buf), + .msg_flags = 0, + }; + auto cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + ssize_t result = TEMP_FAILURE_RETRY(recvmsg(sockfd, &msg, 0)); + if (result == -1) { + return -1; + } + + android::base::unique_fd fd; + bool received_fd = msg.msg_controllen == sizeof(cmsg_buf); + if (received_fd) { + fd.reset(*reinterpret_cast(CMSG_DATA(cmsg))); + } + + if ((msg.msg_flags & MSG_TRUNC) != 0) { + errno = EFBIG; + return -1; + } else if ((msg.msg_flags & MSG_CTRUNC) != 0) { + errno = ERANGE; + return -1; + } + + if (out_fd) { + *out_fd = std::move(fd); + } else if (received_fd) { + errno = ERANGE; + return -1; + } + + return result; +} + +bool Pipe(android::base::unique_fd* read, android::base::unique_fd* write) { + int pipefds[2]; + if (pipe(pipefds) != 0) { + return false; + } + read->reset(pipefds[0]); + write->reset(pipefds[1]); + return true; +} diff --git a/include/cutils/debugger.h b/include/cutils/debugger.h deleted file mode 100644 index 20e8796b7..000000000 --- a/include/cutils/debugger.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2012 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 __CUTILS_DEBUGGER_H -#define __CUTILS_DEBUGGER_H - -#include -#include - -#include "debuggerd/client.h" - -__BEGIN_DECLS - -/* Dumps a process backtrace, registers, and stack to a tombstone file (requires root). - * Stores the tombstone path in the provided buffer. - * Returns 0 on success, -1 on error. - */ -int dump_tombstone(pid_t tid, char* pathbuf, size_t pathlen); - -/* Dumps a process backtrace, registers, and stack to a tombstone file (requires root). - * Stores the tombstone path in the provided buffer. - * If reading debugger data from debuggerd ever takes longer than timeout_secs - * seconds, then stop and return an error. - * Returns 0 on success, -1 on error. - */ -int dump_tombstone_timeout(pid_t tid, char* pathbuf, size_t pathlen, int timeout_secs); - -/* Dumps a process backtrace only to the specified file (requires root). - * Returns 0 on success, -1 on error. - */ -int dump_backtrace_to_file(pid_t tid, int fd); - -/* Dumps a process backtrace only to the specified file (requires root). - * If reading debugger data from debuggerd ever takes longer than timeout_secs - * seconds, then stop and return an error. - * Returns 0 on success, -1 on error. - */ -int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs); - -__END_DECLS - -#endif /* __CUTILS_DEBUGGER_H */ diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h index eb71fb82d..7db28d8f4 100644 --- a/include/private/android_filesystem_config.h +++ b/include/private/android_filesystem_config.h @@ -126,6 +126,7 @@ #define AID_MEDIA_AUDIO 1055 /* GID for audio files on internal media storage */ #define AID_MEDIA_VIDEO 1056 /* GID for video files on internal media storage */ #define AID_MEDIA_IMAGE 1057 /* GID for image files on internal media storage */ +#define AID_TOMBSTONED 1058 /* tombstoned user */ /* Changes to this file must be made in AOSP, *not* in internal branches. */ #define AID_SHELL 2000 /* adb and debug shell user */ diff --git a/libcutils/Android.bp b/libcutils/Android.bp index 39f8aba8e..b96e3ae24 100644 --- a/libcutils/Android.bp +++ b/libcutils/Android.bp @@ -78,7 +78,6 @@ cc_library { srcs: libcutils_nonwindows_sources + [ "android_reboot.c", "ashmem-dev.c", - "debugger.c", "klog.cpp", "partition_utils.c", "properties.c", @@ -86,9 +85,6 @@ cc_library { "trace-dev.c", "uevent.c", ], - - static_libs: ["libdebuggerd_client"], - export_static_lib_headers: ["libdebuggerd_client"], }, android_arm: { diff --git a/libcutils/debugger.c b/libcutils/debugger.c deleted file mode 100644 index 32fac98b7..000000000 --- a/libcutils/debugger.c +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2012 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. - */ - -#define LOG_TAG "DEBUG" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -static int send_request(int sock_fd, void* msg_ptr, size_t msg_len) { - int result = 0; - if (TEMP_FAILURE_RETRY(write(sock_fd, msg_ptr, msg_len)) != (ssize_t) msg_len) { - result = -1; - } else { - char ack; - if (TEMP_FAILURE_RETRY(read(sock_fd, &ack, 1)) != 1) { - result = -1; - } - } - return result; -} - -static int make_dump_request(debugger_action_t action, pid_t tid, int timeout_secs) { - debugger_msg_t msg; - memset(&msg, 0, sizeof(msg)); - msg.tid = tid; - msg.action = action; - - int sock_fd = socket_local_client(DEBUGGER_SOCKET_NAME, ANDROID_SOCKET_NAMESPACE_ABSTRACT, - SOCK_STREAM | SOCK_CLOEXEC); - if (sock_fd < 0) { - return -1; - } - - if (timeout_secs > 0) { - struct timeval tm; - tm.tv_sec = timeout_secs; - tm.tv_usec = 0; - if (setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm)) == -1) { - ALOGE("WARNING: Cannot set receive timeout value on socket: %s", strerror(errno)); - } - - if (setsockopt(sock_fd, SOL_SOCKET, SO_SNDTIMEO, &tm, sizeof(tm)) == -1) { - ALOGE("WARNING: Cannot set send timeout value on socket: %s", strerror(errno)); - } - } - - if (send_request(sock_fd, &msg, sizeof(msg)) < 0) { - close(sock_fd); - return -1; - } - - return sock_fd; -} - -int dump_backtrace_to_file(pid_t tid, int fd) { - return dump_backtrace_to_file_timeout(tid, fd, 0); -} - -int dump_backtrace_to_file_timeout(pid_t tid, int fd, int timeout_secs) { - int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_BACKTRACE, tid, timeout_secs); - if (sock_fd < 0) { - return -1; - } - - /* Write the data read from the socket to the fd. */ - int result = 0; - char buffer[1024]; - ssize_t n; - while ((n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer)))) > 0) { - if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) { - result = -1; - break; - } - } - close(sock_fd); - return result; -} - -int dump_tombstone(pid_t tid, char* pathbuf, size_t pathlen) { - return dump_tombstone_timeout(tid, pathbuf, pathlen, 0); -} - -int dump_tombstone_timeout(pid_t tid, char* pathbuf, size_t pathlen, int timeout_secs) { - int sock_fd = make_dump_request(DEBUGGER_ACTION_DUMP_TOMBSTONE, tid, timeout_secs); - if (sock_fd < 0) { - return -1; - } - - /* Read the tombstone file name. */ - char buffer[100]; /* This is larger than the largest tombstone path. */ - int result = 0; - ssize_t n = TEMP_FAILURE_RETRY(read(sock_fd, buffer, sizeof(buffer) - 1)); - if (n <= 0) { - result = -1; - } else { - if (pathbuf && pathlen) { - if (n >= (ssize_t) pathlen) { - n = pathlen - 1; - } - buffer[n] = '\0'; - memcpy(pathbuf, buffer, n + 1); - } - } - close(sock_fd); - return result; -} diff --git a/libcutils/fs_config.c b/libcutils/fs_config.c index f43f1e6e7..594b23def 100644 --- a/libcutils/fs_config.c +++ b/libcutils/fs_config.c @@ -177,6 +177,9 @@ static const struct fs_path_config android_files[] = { CAP_MASK_LONG(CAP_SETPCAP), "system/bin/webview_zygote64" }, + { 00755, AID_ROOT, AID_SHELL, 0, "system/bin/crash_dump32" }, + { 00755, AID_ROOT, AID_SHELL, 0, "system/bin/crash_dump64" }, + { 00755, AID_ROOT, AID_SHELL, 0, "system/bin/debuggerd" }, { 00750, AID_ROOT, AID_ROOT, 0, "system/bin/uncrypt" }, { 00750, AID_ROOT, AID_ROOT, 0, "system/bin/install-recovery.sh" }, { 00755, AID_ROOT, AID_SHELL, 0, "system/bin/*" },