Merge "debuggerd: advance our amazing bet."

This commit is contained in:
Josh Gao 2017-01-18 23:04:08 +00:00 committed by Gerrit Code Review
commit 230201dcbf
68 changed files with 2429 additions and 2004 deletions

View File

@ -343,6 +343,6 @@ LOCAL_STATIC_LIBRARIES := \
libcrypto_utils \
libcrypto \
libminijail \
libdebuggerd_client \
libdebuggerd_handler \
include $(BUILD_EXECUTABLE)

View File

@ -34,9 +34,9 @@
#include <libminijail.h>
#include <scoped_minijail.h>
#include "debuggerd/client.h"
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
#include "debuggerd/handler.h"
#include "selinux/android.h"
#include "adb.h"

View File

@ -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"]
}

View File

@ -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)

View File

@ -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 <debuggerd/client.h>
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <sched.h>
#include <fcntl.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "private/libc_logging.h"
#include <chrono>
#if defined(TARGET_IS_64_BIT) && !defined(__LP64__)
#define SOCKET_NAME "android:debuggerd32"
#else
#define SOCKET_NAME "android:debuggerd"
#endif
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#include <debuggerd/handler.h>
#include <debuggerd/protocol.h>
#include <debuggerd/util.h>
// 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<sockaddr*>(&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<unsigned long>(thread_name), 0, 0, 0) != 0) {
strcpy(thread_name, "<name unknown>");
} 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/<pid> is owned by the effective UID of the process.
// Ownership of most of the other files in /proc/<pid> 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<uintptr_t>(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<std::chrono::microseconds>(end - now);
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(time_left);
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(time_left - seconds);
struct timeval timeout = {
.tv_sec = static_cast<long>(seconds.count()),
.tv_usec = static_cast<long>(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<debugger_thread_info*>(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<char*>(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;
}

388
debuggerd/crash_dump.cpp Normal file
View File

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

View File

@ -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)

View File

@ -33,7 +33,7 @@
#include <log/log.h>
#if defined(STATIC_CRASHER)
#include "debuggerd/client.h"
#include "debuggerd/handler.h"
#endif
#define noinline __attribute__((__noinline__))

View File

@ -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 <arpa/inet.h>
#include <dirent.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <err.h>
#include <stdio.h>
#include <sys/poll.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include <set>
#include <string>
#include <selinux/android.h>
#include <limits>
#include <thread>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/unique_fd.h>
#include <cutils/debugger.h>
#include <cutils/properties.h>
#include <cutils/sockets.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
#include <debuggerd/client.h>
#include <debuggerd/util.h>
#include <selinux/selinux.h>
#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<pid_t>::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<debugger_request_t*>(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<void*>(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<debugger_action_t>(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<struct sockaddr*>(&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<pid_t>& 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<DIR, int (*)(DIR*)> 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<pid_t>& 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/<PID>/{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<pid_t> siblings;
if (!attach_gdb) {
ptrace_siblings(request.pid, request.tid, request.ignore_tid, siblings);
}
// Generate the backtrace map before dropping privileges.
std::unique_ptr<BacktraceMap> 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<std::string> 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] [<tid>]\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();
}

View File

@ -1,3 +0,0 @@
service debuggerd /system/bin/debuggerd
group root readproc
writepid /dev/cpuset/system-background/tasks

View File

@ -1,3 +0,0 @@
service debuggerd64 /system/bin/debuggerd64
group root readproc
writepid /dev/cpuset/system-background/tasks

View File

@ -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 <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <chrono>
#include <regex>
#include <thread>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#include <debuggerd/handler.h>
#include <debuggerd/protocol.h>
#include <debuggerd/util.h>
#include <gtest/gtest.h>
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)");
}

View File

@ -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 <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/limits.h>
#include <sys/poll.h>
#include <unistd.h>
#include <memory>
#include <android/log.h>
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<pollfd*>(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<char**>(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<struct inotify_event*>(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, decltype(&closedir)> 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<pollfd*>(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<int>(sizeof(event))) {
fprintf(stderr, "could not get event\n");
return -1;
}
return 0;
}
}
}
}
return 0;
}

View File

@ -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

View File

@ -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 <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#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<unsigned long>(thread_name), 0, 0, 0) != 0) {
strcpy(thread_name, "<name unknown>");
} 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<debugger_thread_info*>(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<char*>(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);
}

View File

@ -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 <stdint.h>
#include <stdbool.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#include <unistd.h>
// 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 <android-base/unique_fd.h>
// 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);

View File

@ -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 <signal.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>
__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

View File

@ -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 <stdint.h>
// 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
};

View File

@ -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 <sys/cdefs.h>
#include <sys/types.h>
#include <android-base/unique_fd.h>
// *** 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);

View File

@ -23,6 +23,8 @@
#include <set>
#include <string>
#include "open_files_list.h"
class BacktraceMap;
/* Create and open a tombstone file for writing.

View File

@ -18,6 +18,7 @@
#ifndef _DEBUGGERD_UTILITY_H
#define _DEBUGGERD_UTILITY_H
#include <signal.h>
#include <stdbool.h>
#include <sys/types.h>
@ -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, ...);

View File

@ -32,6 +32,7 @@
#include <memory>
#include <string>
#include <android/log.h>
#include <android-base/stringprintf.h>
#include <backtrace/Backtrace.h>
#include <backtrace/BacktraceMap.h>
@ -39,9 +40,8 @@
#include <log/log.h>
#include <log/logprint.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>
#include <selinux/android.h>
#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 "<debuggerd signal>";
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<pid_t>& 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");

View File

@ -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;
}
}
}

View File

@ -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 <sys/types.h>
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

View File

@ -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);

View File

@ -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;
}

View File

@ -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 <inttypes.h>
#include <sys/types.h>
#include <unordered_map>
#include <event2/event.h>
#include <event2/listener.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#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<Intercept*>(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<Intercept*>(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<pid_t>::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);
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<InterceptManager*>(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;
}

View File

@ -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 <sys/types.h>
#include <unordered_map>
#include <event2/event.h>
#include <event2/listener.h>
#include <android-base/unique_fd.h>
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<pid_t, std::unique_ptr<Intercept>> 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);
};

View File

@ -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 <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <array>
#include <deque>
#include <unordered_map>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/thread.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#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<Crash*> 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<time_t>::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<Crash*>(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<Crash*>(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);
}

View File

@ -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

96
debuggerd/util.cpp Normal file
View File

@ -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 <sys/socket.h>
#include <utility>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
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<void*>(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<int*>(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<void*>(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<int*>(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;
}

View File

@ -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 <sys/cdefs.h>
#include <sys/types.h>
#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 */

View File

@ -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 */

View File

@ -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: {

View File

@ -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 <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cutils/debugger.h>
#include <cutils/sockets.h>
#include <log/log.h>
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;
}

View File

@ -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/*" },