Merge "debuggerd: advance our amazing bet."
am: 230201dcbf
Change-Id: Id3594e862e0861756daa11124bd3ee90ddca0cbc
This commit is contained in:
commit
11b938e881
|
@ -343,6 +343,6 @@ LOCAL_STATIC_LIBRARIES := \
|
|||
libcrypto_utils \
|
||||
libcrypto \
|
||||
libminijail \
|
||||
libdebuggerd_client \
|
||||
libdebuggerd_handler \
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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__))
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
service debuggerd /system/bin/debuggerd
|
||||
group root readproc
|
||||
writepid /dev/cpuset/system-background/tasks
|
|
@ -1,3 +0,0 @@
|
|||
service debuggerd64 /system/bin/debuggerd64
|
||||
group root readproc
|
||||
writepid /dev/cpuset/system-background/tasks
|
|
@ -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)");
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
};
|
|
@ -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);
|
|
@ -23,6 +23,8 @@
|
|||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "open_files_list.h"
|
||||
|
||||
class BacktraceMap;
|
||||
|
||||
/* Create and open a tombstone file for writing.
|
|
@ -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, ...);
|
||||
|
|
@ -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");
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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/*" },
|
||||
|
|
Loading…
Reference in New Issue