crash_dump: fork a copy of the target's address space.
Reduce the amount of time that a process remains paused by pausing its threads, fetching their registers, and then performing unwinding on a copy of its address space. This also works around a kernel change that's in 4.9 that prevents ptrace from reading memory of processes that we don't have immediate permissions to ptrace (even if we previously ptraced them). Bug: http://b/62112103 Bug: http://b/63989615 Test: treehugger Change-Id: I7b9cc5dd8f54a354bc61f1bda0d2b7a8a55733c4
This commit is contained in:
parent
385ea22741
commit
2b2ae0c88e
31 changed files with 1187 additions and 1785 deletions
|
|
@ -4,9 +4,12 @@ cc_defaults {
|
|||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Wno-unused-argument",
|
||||
"-Wno-unused-function",
|
||||
"-Wno-nullability-completeness",
|
||||
"-Os",
|
||||
],
|
||||
cpp_std: "gnu++17",
|
||||
|
||||
local_include_dirs: ["include"],
|
||||
}
|
||||
|
|
@ -73,6 +76,7 @@ cc_library_static {
|
|||
|
||||
whole_static_libs: [
|
||||
"libasync_safe",
|
||||
"libcutils",
|
||||
"libdebuggerd",
|
||||
],
|
||||
|
||||
|
|
@ -148,27 +152,6 @@ cc_library_static {
|
|||
"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"],
|
||||
|
||||
|
|
@ -193,7 +176,6 @@ cc_test {
|
|||
"libdebuggerd/test/elf_fake.cpp",
|
||||
"libdebuggerd/test/log_fake.cpp",
|
||||
"libdebuggerd/test/open_files_list_test.cpp",
|
||||
"libdebuggerd/test/ptrace_fake.cpp",
|
||||
"libdebuggerd/test/tombstone_test.cpp",
|
||||
],
|
||||
|
||||
|
|
@ -218,6 +200,7 @@ cc_test {
|
|||
|
||||
static_libs: [
|
||||
"libdebuggerd",
|
||||
"libunwindstack",
|
||||
],
|
||||
|
||||
local_include_dirs: [
|
||||
|
|
@ -264,6 +247,7 @@ cc_binary {
|
|||
"libbase",
|
||||
"liblog",
|
||||
"libprocinfo",
|
||||
"libunwindstack",
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@
|
|||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
|
@ -47,6 +47,8 @@
|
|||
#define ATRACE_TAG ATRACE_TAG_BIONIC
|
||||
#include <utils/Trace.h>
|
||||
|
||||
#include <unwindstack/Regs.h>
|
||||
|
||||
#include "libdebuggerd/backtrace.h"
|
||||
#include "libdebuggerd/tombstone.h"
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
|
@ -58,21 +60,9 @@
|
|||
#include "util.h"
|
||||
|
||||
using android::base::unique_fd;
|
||||
using android::base::ReadFileToString;
|
||||
using android::base::StringPrintf;
|
||||
using android::base::Trim;
|
||||
|
||||
static std::string get_process_name(pid_t pid) {
|
||||
std::string result = "<unknown>";
|
||||
ReadFileToString(StringPrintf("/proc/%d/cmdline", pid), &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string get_thread_name(pid_t tid) {
|
||||
std::string result = "<unknown>";
|
||||
ReadFileToString(StringPrintf("/proc/%d/comm", tid), &result);
|
||||
return Trim(result);
|
||||
}
|
||||
using unwindstack::Regs;
|
||||
|
||||
static bool pid_contains_tid(int pid_proc_fd, pid_t tid) {
|
||||
struct stat st;
|
||||
|
|
@ -90,8 +80,8 @@ static pid_t get_tracer(pid_t tracee) {
|
|||
}
|
||||
|
||||
// Attach to a thread, and verify that it's still a member of the given process
|
||||
static bool ptrace_seize_thread(int pid_proc_fd, pid_t tid, std::string* error) {
|
||||
if (ptrace(PTRACE_SEIZE, tid, 0, 0) != 0) {
|
||||
static bool ptrace_seize_thread(int pid_proc_fd, pid_t tid, std::string* error, int flags = 0) {
|
||||
if (ptrace(PTRACE_SEIZE, tid, 0, flags) != 0) {
|
||||
if (errno == EPERM) {
|
||||
pid_t tracer = get_tracer(tid);
|
||||
if (tracer != -1) {
|
||||
|
|
@ -108,18 +98,43 @@ static bool ptrace_seize_thread(int pid_proc_fd, pid_t tid, std::string* error)
|
|||
// Make sure that the task we attached to is actually part of the pid we're dumping.
|
||||
if (!pid_contains_tid(pid_proc_fd, tid)) {
|
||||
if (ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to detach from thread " << tid;
|
||||
PLOG(WARNING) << "failed to detach from thread " << tid;
|
||||
}
|
||||
*error = StringPrintf("thread %d is not in process", tid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put the task into ptrace-stop state.
|
||||
if (ptrace(PTRACE_INTERRUPT, tid, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to interrupt thread " << tid;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool wait_for_stop(pid_t tid, int* received_signal) {
|
||||
while (true) {
|
||||
int status;
|
||||
pid_t result = waitpid(tid, &status, __WALL);
|
||||
if (result != tid) {
|
||||
PLOG(ERROR) << "waitpid failed on " << tid << " while detaching";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (WIFSTOPPED(status)) {
|
||||
if (status >> 16 == PTRACE_EVENT_STOP) {
|
||||
*received_signal = 0;
|
||||
} else {
|
||||
*received_signal = WSTOPSIG(status);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interrupt a process and wait for it to be interrupted.
|
||||
static bool ptrace_interrupt(pid_t tid, int* received_signal) {
|
||||
if (ptrace(PTRACE_INTERRUPT, tid, 0, 0) == 0) {
|
||||
return wait_for_stop(tid, received_signal);
|
||||
}
|
||||
|
||||
return true;
|
||||
PLOG(ERROR) << "failed to interrupt " << tid << " to detach";
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data) {
|
||||
|
|
@ -169,70 +184,39 @@ static bool activity_manager_notify(pid_t pid, int signal, const std::string& am
|
|||
return true;
|
||||
}
|
||||
|
||||
static void signal_handler(int) {
|
||||
// We can't log easily, because the heap might be corrupt.
|
||||
// Just die and let the surrounding log context explain things.
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static void abort_handler(pid_t target, const bool tombstoned_connected,
|
||||
unique_fd& tombstoned_socket, unique_fd& output_fd,
|
||||
const char* 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, kDebuggerdAnyIntercept)) {
|
||||
// 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");
|
||||
if (target != 1) {
|
||||
dprintf(output_fd.get(), " %d: %s\n", target, abort_msg);
|
||||
} else {
|
||||
dprintf(output_fd.get(), ": %s\n", abort_msg);
|
||||
}
|
||||
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static void drop_capabilities() {
|
||||
ATRACE_CALL();
|
||||
__user_cap_header_struct capheader;
|
||||
memset(&capheader, 0, sizeof(capheader));
|
||||
capheader.version = _LINUX_CAPABILITY_VERSION_3;
|
||||
capheader.pid = 0;
|
||||
|
||||
__user_cap_data_struct capdata[2];
|
||||
memset(&capdata, 0, sizeof(capdata));
|
||||
|
||||
if (capset(&capheader, &capdata[0]) == -1) {
|
||||
PLOG(FATAL) << "failed to drop capabilities";
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to set PR_SET_NO_NEW_PRIVS";
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
atrace_begin(ATRACE_TAG, "before reparent");
|
||||
|
||||
pid_t target = getppid();
|
||||
bool tombstoned_connected = false;
|
||||
unique_fd tombstoned_socket;
|
||||
unique_fd output_fd;
|
||||
// Globals used by the abort handler.
|
||||
static pid_t g_target_thread = -1;
|
||||
static bool g_tombstoned_connected = false;
|
||||
static unique_fd g_tombstoned_socket;
|
||||
static unique_fd g_output_fd;
|
||||
|
||||
static void Initialize(char** argv) {
|
||||
android::base::InitLogging(argv);
|
||||
android::base::SetAborter([&](const char* abort_msg) {
|
||||
abort_handler(target, tombstoned_connected, tombstoned_socket, output_fd, abort_msg);
|
||||
android::base::SetAborter([](const char* abort_msg) {
|
||||
// If we abort before we get an output fd, contact tombstoned to let any
|
||||
// potential listeners know that we failed.
|
||||
if (!g_tombstoned_connected) {
|
||||
if (!tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,
|
||||
kDebuggerdAnyIntercept)) {
|
||||
// We failed to connect, not much we can do.
|
||||
LOG(ERROR) << "failed to connected to tombstoned to report failure";
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
dprintf(g_output_fd.get(), "crash_dump failed to dump process");
|
||||
if (g_target_thread != 1) {
|
||||
dprintf(g_output_fd.get(), " %d: %s\n", g_target_thread, abort_msg);
|
||||
} else {
|
||||
dprintf(g_output_fd.get(), ": %s\n", abort_msg);
|
||||
}
|
||||
|
||||
_exit(1);
|
||||
});
|
||||
|
||||
// Don't try to dump ourselves.
|
||||
struct sigaction action = {};
|
||||
action.sa_handler = signal_handler;
|
||||
action.sa_handler = SIG_DFL;
|
||||
debuggerd_register_handlers(&action);
|
||||
|
||||
sigset_t mask;
|
||||
|
|
@ -240,216 +224,328 @@ int main(int argc, char** argv) {
|
|||
if (sigprocmask(SIG_SETMASK, &mask, nullptr) != 0) {
|
||||
PLOG(FATAL) << "failed to set signal mask";
|
||||
}
|
||||
}
|
||||
|
||||
static void ParseArgs(int argc, char** argv, pid_t* pseudothread_tid, DebuggerdDumpType* dump_type) {
|
||||
if (argc != 4) {
|
||||
LOG(FATAL) << "Wrong number of args: " << argc << " (expected 4)";
|
||||
LOG(FATAL) << "wrong number of args: " << argc << " (expected 4)";
|
||||
}
|
||||
|
||||
pid_t main_tid;
|
||||
pid_t pseudothread_tid;
|
||||
int dump_type;
|
||||
|
||||
if (!android::base::ParseInt(argv[1], &main_tid, 1, std::numeric_limits<pid_t>::max())) {
|
||||
LOG(FATAL) << "invalid main tid: " << argv[1];
|
||||
if (!android::base::ParseInt(argv[1], &g_target_thread, 1, std::numeric_limits<pid_t>::max())) {
|
||||
LOG(FATAL) << "invalid target tid: " << argv[1];
|
||||
}
|
||||
|
||||
if (!android::base::ParseInt(argv[2], &pseudothread_tid, 1, std::numeric_limits<pid_t>::max())) {
|
||||
if (!android::base::ParseInt(argv[2], pseudothread_tid, 1, std::numeric_limits<pid_t>::max())) {
|
||||
LOG(FATAL) << "invalid pseudothread tid: " << argv[2];
|
||||
}
|
||||
|
||||
if (!android::base::ParseInt(argv[3], &dump_type, 0, 1)) {
|
||||
int dump_type_int;
|
||||
if (!android::base::ParseInt(argv[3], &dump_type_int, 0, 1)) {
|
||||
LOG(FATAL) << "invalid requested dump type: " << argv[3];
|
||||
}
|
||||
*dump_type = static_cast<DebuggerdDumpType>(dump_type_int);
|
||||
}
|
||||
|
||||
if (target == 1) {
|
||||
LOG(FATAL) << "target died before we could attach (received main tid = " << main_tid << ")";
|
||||
static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
|
||||
std::unique_ptr<unwindstack::Regs>* regs, uintptr_t* abort_address) {
|
||||
std::aligned_storage<sizeof(CrashInfo) + 1, alignof(CrashInfo)>::type buf;
|
||||
ssize_t rc = TEMP_FAILURE_RETRY(read(fd.get(), &buf, sizeof(buf)));
|
||||
if (rc == -1) {
|
||||
PLOG(FATAL) << "failed to read target ucontext";
|
||||
} else if (rc != sizeof(CrashInfo)) {
|
||||
LOG(FATAL) << "read " << rc << " bytes when reading target crash information, expected "
|
||||
<< sizeof(CrashInfo);
|
||||
}
|
||||
|
||||
android::procinfo::ProcessInfo target_info;
|
||||
if (!android::procinfo::GetProcessInfo(main_tid, &target_info)) {
|
||||
LOG(FATAL) << "failed to fetch process info for target " << main_tid;
|
||||
CrashInfo* crash_info = reinterpret_cast<CrashInfo*>(&buf);
|
||||
if (crash_info->version != 1) {
|
||||
LOG(FATAL) << "version mismatch, expected 1, received " << crash_info->version;
|
||||
}
|
||||
|
||||
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;
|
||||
*siginfo = crash_info->siginfo;
|
||||
regs->reset(Regs::CreateFromUcontext(Regs::CurrentArch(), &crash_info->ucontext));
|
||||
*abort_address = crash_info->abort_msg_address;
|
||||
}
|
||||
|
||||
// Wait for a process to clone and return the child's pid.
|
||||
// Note: this leaves the parent in PTRACE_EVENT_STOP.
|
||||
static pid_t wait_for_clone(pid_t pid, bool resume_child) {
|
||||
int status;
|
||||
pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, __WALL));
|
||||
if (result == -1) {
|
||||
PLOG(FATAL) << "failed to waitpid";
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (WIFEXITED(status)) {
|
||||
LOG(FATAL) << "traced process exited with status " << WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
LOG(FATAL) << "traced process exited with signal " << WTERMSIG(status);
|
||||
} else if (!WIFSTOPPED(status)) {
|
||||
LOG(FATAL) << "process didn't stop? (status = " << status << ")";
|
||||
}
|
||||
|
||||
if (status >> 8 != (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) {
|
||||
LOG(FATAL) << "process didn't stop due to PTRACE_O_TRACECLONE (status = " << status << ")";
|
||||
}
|
||||
|
||||
pid_t child;
|
||||
if (ptrace(PTRACE_GETEVENTMSG, pid, 0, &child) != 0) {
|
||||
PLOG(FATAL) << "failed to get child pid via PTRACE_GETEVENTMSG";
|
||||
}
|
||||
|
||||
int stop_signal;
|
||||
if (!wait_for_stop(child, &stop_signal)) {
|
||||
PLOG(FATAL) << "failed to waitpid on child";
|
||||
}
|
||||
|
||||
CHECK_EQ(0, stop_signal);
|
||||
|
||||
if (resume_child) {
|
||||
if (ptrace(PTRACE_CONT, child, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to resume child (pid = " << child << ")";
|
||||
}
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
static pid_t wait_for_vm_process(pid_t pseudothread_tid) {
|
||||
// The pseudothread will double-fork, we want its grandchild.
|
||||
pid_t intermediate = wait_for_clone(pseudothread_tid, true);
|
||||
pid_t vm_pid = wait_for_clone(intermediate, false);
|
||||
if (ptrace(PTRACE_DETACH, intermediate, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to detach from intermediate vm process";
|
||||
}
|
||||
|
||||
return vm_pid;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
atrace_begin(ATRACE_TAG, "before reparent");
|
||||
pid_t target_process = getppid();
|
||||
|
||||
// Open /proc/`getppid()` before we daemonize.
|
||||
std::string target_proc_path = "/proc/" + std::to_string(target_process);
|
||||
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;
|
||||
}
|
||||
|
||||
// Make sure our parent didn't die.
|
||||
if (getppid() != target) {
|
||||
PLOG(FATAL) << "parent died";
|
||||
// Make sure getppid() hasn't changed.
|
||||
if (getppid() != target_process) {
|
||||
LOG(FATAL) << "parent died";
|
||||
}
|
||||
|
||||
atrace_end(ATRACE_TAG);
|
||||
|
||||
// Reparent ourselves to init, so that the signal handler can waitpid on the
|
||||
// original process to avoid leaving a zombie for non-fatal dumps.
|
||||
// Move the input/output pipes off of stdout/stderr, out of paranoia.
|
||||
unique_fd output_pipe(dup(STDOUT_FILENO));
|
||||
unique_fd input_pipe(dup(STDIN_FILENO));
|
||||
|
||||
unique_fd fork_exit_read, fork_exit_write;
|
||||
if (!Pipe(&fork_exit_read, &fork_exit_write)) {
|
||||
PLOG(FATAL) << "failed to create pipe";
|
||||
}
|
||||
|
||||
pid_t forkpid = fork();
|
||||
if (forkpid == -1) {
|
||||
PLOG(FATAL) << "fork failed";
|
||||
} else if (forkpid != 0) {
|
||||
exit(0);
|
||||
} else if (forkpid == 0) {
|
||||
fork_exit_read.reset();
|
||||
} else {
|
||||
// We need the pseudothread to live until we get around to verifying the vm pid against it.
|
||||
// The last thing it does is block on a waitpid on us, so wait until our child tells us to die.
|
||||
fork_exit_write.reset();
|
||||
char buf;
|
||||
TEMP_FAILURE_RETRY(read(fork_exit_read.get(), &buf, sizeof(buf)));
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
ATRACE_NAME("after reparent");
|
||||
pid_t pseudothread_tid;
|
||||
DebuggerdDumpType dump_type;
|
||||
uintptr_t abort_address = 0;
|
||||
|
||||
Initialize(argv);
|
||||
ParseArgs(argc, argv, &pseudothread_tid, &dump_type);
|
||||
|
||||
// Die if we take too long.
|
||||
//
|
||||
// Note: processes with many threads and minidebug-info can take a bit to
|
||||
// unwind, do not make this too small. b/62828735
|
||||
alarm(5);
|
||||
alarm(30);
|
||||
|
||||
std::string attach_error;
|
||||
|
||||
std::map<pid_t, std::string> threads;
|
||||
|
||||
{
|
||||
ATRACE_NAME("ptrace");
|
||||
// Seize the main thread.
|
||||
if (!ptrace_seize_thread(target_proc_fd, main_tid, &attach_error)) {
|
||||
LOG(FATAL) << attach_error;
|
||||
}
|
||||
|
||||
// Seize the siblings.
|
||||
{
|
||||
std::set<pid_t> siblings;
|
||||
if (!android::procinfo::GetProcessTids(target, &siblings)) {
|
||||
PLOG(FATAL) << "failed to get process siblings";
|
||||
}
|
||||
|
||||
// but not the already attached main thread.
|
||||
siblings.erase(main_tid);
|
||||
// or the handler pseudothread.
|
||||
siblings.erase(pseudothread_tid);
|
||||
|
||||
for (pid_t sibling_tid : siblings) {
|
||||
if (!ptrace_seize_thread(target_proc_fd, sibling_tid, &attach_error)) {
|
||||
LOG(WARNING) << attach_error;
|
||||
} else {
|
||||
threads.emplace(sibling_tid, get_thread_name(sibling_tid));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the backtrace map, open files, and process/thread names, while we still have caps.
|
||||
std::unique_ptr<BacktraceMap> backtrace_map;
|
||||
{
|
||||
ATRACE_NAME("backtrace map");
|
||||
backtrace_map.reset(BacktraceMap::Create(main_tid));
|
||||
if (!backtrace_map) {
|
||||
LOG(FATAL) << "failed to create backtrace map";
|
||||
}
|
||||
}
|
||||
// Get the process name (aka cmdline).
|
||||
std::string process_name = get_process_name(g_target_thread);
|
||||
|
||||
// Collect the list of open files.
|
||||
OpenFilesList open_files;
|
||||
{
|
||||
ATRACE_NAME("open files");
|
||||
populate_open_files_list(target, &open_files);
|
||||
populate_open_files_list(g_target_thread, &open_files);
|
||||
}
|
||||
|
||||
std::string process_name = get_process_name(main_tid);
|
||||
threads.emplace(main_tid, get_thread_name(main_tid));
|
||||
// In order to reduce the duration that we pause the process for, we ptrace
|
||||
// the threads, fetch their registers and associated information, and then
|
||||
// fork a separate process as a snapshot of the process's address space.
|
||||
std::set<pid_t> threads;
|
||||
if (!android::procinfo::GetProcessTids(g_target_thread, &threads)) {
|
||||
PLOG(FATAL) << "failed to get process threads";
|
||||
}
|
||||
|
||||
// Drop our capabilities now that we've attached to the threads we care about.
|
||||
std::map<pid_t, ThreadInfo> thread_info;
|
||||
siginfo_t siginfo;
|
||||
std::string error;
|
||||
|
||||
{
|
||||
ATRACE_NAME("ptrace");
|
||||
for (pid_t thread : threads) {
|
||||
// Trace the pseudothread separately, so we can use different options.
|
||||
if (thread == pseudothread_tid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ptrace_seize_thread(target_proc_fd, thread, &error)) {
|
||||
bool fatal = thread == g_target_thread;
|
||||
LOG(fatal ? FATAL : WARNING) << error;
|
||||
}
|
||||
|
||||
ThreadInfo info;
|
||||
info.pid = target_process;
|
||||
info.tid = thread;
|
||||
info.process_name = process_name;
|
||||
info.thread_name = get_thread_name(thread);
|
||||
|
||||
if (!ptrace_interrupt(thread, &info.signo)) {
|
||||
PLOG(WARNING) << "failed to ptrace interrupt thread " << thread;
|
||||
ptrace(PTRACE_DETACH, thread, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (thread == g_target_thread) {
|
||||
// Read the thread's registers along with the rest of the crash info out of the pipe.
|
||||
ReadCrashInfo(input_pipe, &siginfo, &info.registers, &abort_address);
|
||||
info.siginfo = &siginfo;
|
||||
info.signo = info.siginfo->si_signo;
|
||||
} else {
|
||||
info.registers.reset(Regs::RemoteGet(thread));
|
||||
if (!info.registers) {
|
||||
PLOG(WARNING) << "failed to fetch registers for thread " << thread;
|
||||
ptrace(PTRACE_DETACH, thread, 0, 0);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
thread_info[thread] = std::move(info);
|
||||
}
|
||||
}
|
||||
|
||||
// Trace the pseudothread with PTRACE_O_TRACECLONE and tell it to fork.
|
||||
if (!ptrace_seize_thread(target_proc_fd, pseudothread_tid, &error, PTRACE_O_TRACECLONE)) {
|
||||
LOG(FATAL) << "failed to seize pseudothread: " << error;
|
||||
}
|
||||
|
||||
if (TEMP_FAILURE_RETRY(write(output_pipe.get(), "\1", 1)) != 1) {
|
||||
PLOG(FATAL) << "failed to write to pseudothread";
|
||||
}
|
||||
|
||||
pid_t vm_pid = wait_for_vm_process(pseudothread_tid);
|
||||
if (ptrace(PTRACE_DETACH, pseudothread_tid, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to detach from pseudothread";
|
||||
}
|
||||
|
||||
// The pseudothread can die now.
|
||||
fork_exit_write.reset();
|
||||
|
||||
// Defer the message until later, for readability.
|
||||
bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
|
||||
if (siginfo.si_signo == DEBUGGER_SIGNAL) {
|
||||
wait_for_gdb = false;
|
||||
}
|
||||
|
||||
// Detach from all of our attached threads before resuming.
|
||||
for (const auto& [tid, thread] : thread_info) {
|
||||
int resume_signal = thread.signo == DEBUGGER_SIGNAL ? 0 : thread.signo;
|
||||
if (wait_for_gdb) {
|
||||
resume_signal = 0;
|
||||
if (tgkill(target_process, tid, SIGSTOP) != 0) {
|
||||
PLOG(WARNING) << "failed to send SIGSTOP to " << tid;
|
||||
}
|
||||
}
|
||||
|
||||
LOG(DEBUG) << "detaching from thread " << tid;
|
||||
if (ptrace(PTRACE_DETACH, tid, 0, resume_signal) != 0) {
|
||||
PLOG(ERROR) << "failed to detach from thread " << tid;
|
||||
}
|
||||
}
|
||||
|
||||
// Drop our capabilities now that we've fetched all of the information we need.
|
||||
drop_capabilities();
|
||||
|
||||
{
|
||||
ATRACE_NAME("tombstoned_connect");
|
||||
const DebuggerdDumpType dump_type_enum = static_cast<DebuggerdDumpType>(dump_type);
|
||||
LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type_enum;
|
||||
tombstoned_connected = tombstoned_connect(target, &tombstoned_socket, &output_fd, dump_type_enum);
|
||||
LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;
|
||||
g_tombstoned_connected =
|
||||
tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, dump_type);
|
||||
}
|
||||
|
||||
// Write a '\1' to stdout to tell the crashing process to resume.
|
||||
// It also restores the value of PR_SET_DUMPABLE at this point.
|
||||
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";
|
||||
if (g_tombstoned_connected) {
|
||||
if (TEMP_FAILURE_RETRY(dup2(g_output_fd.get(), STDOUT_FILENO)) == -1) {
|
||||
PLOG(ERROR) << "failed to dup2 output fd (" << g_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));
|
||||
output_fd = std::move(devnull);
|
||||
g_output_fd = std::move(devnull);
|
||||
}
|
||||
|
||||
LOG(INFO) << "performing dump of process " << target << " (target tid = " << main_tid << ")";
|
||||
|
||||
// At this point, the thread that made the request has been attached and is
|
||||
// in ptrace-stopped state. After resumption, the triggering signal that has
|
||||
// been queued will be delivered.
|
||||
if (ptrace(PTRACE_CONT, main_tid, 0, 0) != 0) {
|
||||
PLOG(ERROR) << "PTRACE_CONT(" << main_tid << ") failed";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
siginfo_t siginfo = {};
|
||||
{
|
||||
ATRACE_NAME("wait_for_signal");
|
||||
if (!wait_for_signal(main_tid, &siginfo)) {
|
||||
printf("failed to wait for signal in tid %d: %s\n", main_tid, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
LOG(INFO) << "performing dump of process " << target_process << " (target tid = " << g_target_thread
|
||||
<< ")";
|
||||
|
||||
int signo = siginfo.si_signo;
|
||||
bool fatal_signal = signo != DEBUGGER_SIGNAL;
|
||||
bool backtrace = false;
|
||||
uintptr_t abort_address = 0;
|
||||
|
||||
// si_value can represent three things:
|
||||
// si_value is special when used with DEBUGGER_SIGNAL.
|
||||
// 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);
|
||||
if (!fatal_signal) {
|
||||
int si_val = siginfo.si_value.sival_int;
|
||||
if (si_val == 0) {
|
||||
backtrace = false;
|
||||
} else if (si_val == 1) {
|
||||
backtrace = true;
|
||||
} else {
|
||||
LOG(WARNING) << "unknown si_value value " << si_val;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use seccomp to lock ourselves down.
|
||||
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(vm_pid, false));
|
||||
if (!map) {
|
||||
LOG(FATAL) << "failed to create backtrace map";
|
||||
}
|
||||
|
||||
std::shared_ptr<unwindstack::Memory> process_memory = map->GetProcessMemory();
|
||||
if (!process_memory) {
|
||||
LOG(FATAL) << "failed to get unwindstack::Memory handle";
|
||||
}
|
||||
|
||||
std::string amfd_data;
|
||||
if (backtrace) {
|
||||
ATRACE_NAME("dump_backtrace");
|
||||
dump_backtrace(output_fd.get(), backtrace_map.get(), target, main_tid, process_name, threads, 0);
|
||||
dump_backtrace(std::move(g_output_fd), map.get(), thread_info, g_target_thread);
|
||||
} else {
|
||||
ATRACE_NAME("engrave_tombstone");
|
||||
engrave_tombstone(output_fd.get(), backtrace_map.get(), &open_files, target, main_tid,
|
||||
process_name, threads, abort_address, fatal_signal ? &amfd_data : nullptr);
|
||||
engrave_tombstone(std::move(g_output_fd), map.get(), process_memory.get(), thread_info,
|
||||
g_target_thread, abort_address, &open_files, &amfd_data);
|
||||
}
|
||||
|
||||
// We don't actually need to PTRACE_DETACH, as long as our tracees aren't in
|
||||
// group-stop state, which is true as long as no stopping signals are sent.
|
||||
|
||||
bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
|
||||
if (!fatal_signal || siginfo.si_code == SI_USER) {
|
||||
// Don't wait_for_gdb when the process didn't actually crash.
|
||||
wait_for_gdb = false;
|
||||
}
|
||||
|
||||
// If the process crashed or we need to send it SIGSTOP for wait_for_gdb,
|
||||
// get it in a state where it can receive signals, and then send the relevant
|
||||
// signal.
|
||||
if (wait_for_gdb || fatal_signal) {
|
||||
if (ptrace(PTRACE_INTERRUPT, main_tid, 0, 0) != 0) {
|
||||
PLOG(ERROR) << "failed to use PTRACE_INTERRUPT on " << main_tid;
|
||||
}
|
||||
|
||||
if (tgkill(target, main_tid, wait_for_gdb ? SIGSTOP : signo) != 0) {
|
||||
PLOG(ERROR) << "failed to resend signal " << signo << " to " << main_tid;
|
||||
if (fatal_signal) {
|
||||
// Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
|
||||
if (thread_info[target_process].thread_name != "system_server") {
|
||||
activity_manager_notify(target_process, signo, amfd_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -463,19 +559,12 @@ int main(int argc, char** argv) {
|
|||
"* gdbclient.py -p %d\n"
|
||||
"*\n"
|
||||
"***********************************************************",
|
||||
target, target);
|
||||
}
|
||||
|
||||
if (fatal_signal) {
|
||||
// Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
|
||||
if (target_info.name != "system_server" || target_info.uid != AID_SYSTEM) {
|
||||
activity_manager_notify(target, signo, amfd_data);
|
||||
}
|
||||
target_process, target_process);
|
||||
}
|
||||
|
||||
// Close stdout before we notify tombstoned of completion.
|
||||
close(STDOUT_FILENO);
|
||||
if (tombstoned_connected && !tombstoned_notify_completion(tombstoned_socket.get())) {
|
||||
if (g_tombstoned_connected && !tombstoned_notify_completion(g_tombstoned_socket.get())) {
|
||||
LOG(ERROR) << "failed to notify tombstoned of completion";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -245,6 +245,8 @@ void CrasherTest::AssertDeath(int signo) {
|
|||
int status;
|
||||
pid_t pid = TIMEOUT(5, waitpid(crasher_pid, &status, 0));
|
||||
if (pid != crasher_pid) {
|
||||
printf("failed to wait for crasher (pid %d)\n", crasher_pid);
|
||||
sleep(100);
|
||||
FAIL() << "failed to wait for crasher: " << strerror(errno);
|
||||
}
|
||||
|
||||
|
|
@ -341,13 +343,12 @@ TEST_F(CrasherTest, signal) {
|
|||
int intercept_result;
|
||||
unique_fd output_fd;
|
||||
StartProcess([]() {
|
||||
abort();
|
||||
while (true) {
|
||||
sleep(1);
|
||||
}
|
||||
});
|
||||
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);
|
||||
FinishCrasher();
|
||||
ASSERT_EQ(0, kill(crasher_pid, SIGSEGV));
|
||||
|
||||
AssertDeath(SIGSEGV);
|
||||
|
|
@ -439,19 +440,6 @@ TEST_F(CrasherTest, wait_for_gdb) {
|
|||
AssertDeath(SIGABRT);
|
||||
}
|
||||
|
||||
// wait_for_gdb shouldn't trigger on manually sent signals.
|
||||
TEST_F(CrasherTest, wait_for_gdb_signal) {
|
||||
if (!android::base::SetProperty(kWaitForGdbKey, "1")) {
|
||||
FAIL() << "failed to enable wait_for_gdb";
|
||||
}
|
||||
|
||||
StartProcess([]() {
|
||||
abort();
|
||||
});
|
||||
ASSERT_EQ(0, kill(crasher_pid, SIGSEGV)) << strerror(errno);
|
||||
AssertDeath(SIGSEGV);
|
||||
}
|
||||
|
||||
TEST_F(CrasherTest, backtrace) {
|
||||
std::string result;
|
||||
int intercept_result;
|
||||
|
|
@ -596,15 +584,13 @@ TEST_F(CrasherTest, competing_tracer) {
|
|||
int intercept_result;
|
||||
unique_fd output_fd;
|
||||
StartProcess([]() {
|
||||
while (true) {
|
||||
}
|
||||
raise(SIGABRT);
|
||||
});
|
||||
|
||||
StartIntercept(&output_fd);
|
||||
FinishCrasher();
|
||||
|
||||
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, crasher_pid, 0, 0));
|
||||
ASSERT_EQ(0, kill(crasher_pid, SIGABRT));
|
||||
FinishCrasher();
|
||||
|
||||
int status;
|
||||
ASSERT_EQ(crasher_pid, waitpid(crasher_pid, &status, 0));
|
||||
|
|
@ -622,6 +608,10 @@ TEST_F(CrasherTest, competing_tracer) {
|
|||
regex += R"( \(.+debuggerd_test)";
|
||||
ASSERT_MATCH(result, regex.c_str());
|
||||
|
||||
ASSERT_EQ(crasher_pid, waitpid(crasher_pid, &status, 0));
|
||||
ASSERT_TRUE(WIFSTOPPED(status));
|
||||
ASSERT_EQ(SIGABRT, WSTOPSIG(status));
|
||||
|
||||
ASSERT_EQ(0, ptrace(PTRACE_DETACH, crasher_pid, 0, SIGABRT));
|
||||
AssertDeath(SIGABRT);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,14 @@
|
|||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <async_safe/log.h>
|
||||
#include <backtrace/BacktraceMap.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
#include <unwindstack/Regs.h>
|
||||
|
||||
#include "debuggerd/handler.h"
|
||||
#include "tombstoned/tombstoned.h"
|
||||
|
|
@ -49,6 +53,7 @@
|
|||
#include "libdebuggerd/tombstone.h"
|
||||
|
||||
using android::base::unique_fd;
|
||||
using unwindstack::Regs;
|
||||
|
||||
extern "C" void __linker_enable_fallback_allocator();
|
||||
extern "C" void __linker_disable_fallback_allocator();
|
||||
|
|
@ -61,7 +66,19 @@ extern "C" void __linker_disable_fallback_allocator();
|
|||
// exhaustion.
|
||||
static void debuggerd_fallback_trace(int output_fd, ucontext_t* ucontext) {
|
||||
__linker_enable_fallback_allocator();
|
||||
dump_backtrace_ucontext(output_fd, ucontext);
|
||||
{
|
||||
std::unique_ptr<Regs> regs;
|
||||
|
||||
ThreadInfo thread;
|
||||
thread.pid = getpid();
|
||||
thread.tid = gettid();
|
||||
thread.thread_name = get_thread_name(gettid());
|
||||
thread.registers.reset(Regs::CreateFromUcontext(Regs::CurrentArch(), ucontext));
|
||||
|
||||
// TODO: Create this once and store it in a global?
|
||||
std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
|
||||
dump_backtrace_thread(output_fd, map.get(), thread);
|
||||
}
|
||||
__linker_disable_fallback_allocator();
|
||||
}
|
||||
|
||||
|
|
@ -162,41 +179,41 @@ static void trace_handler(siginfo_t* info, ucontext_t* ucontext) {
|
|||
|
||||
// Send a signal to all of our siblings, asking them to dump their stack.
|
||||
iterate_siblings(
|
||||
[](pid_t tid, int output_fd) {
|
||||
// Use a pipe, to be able to detect situations where the thread gracefully exits before
|
||||
// receiving our signal.
|
||||
unique_fd pipe_read, pipe_write;
|
||||
if (!Pipe(&pipe_read, &pipe_write)) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s",
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
[](pid_t tid, int output_fd) {
|
||||
// Use a pipe, to be able to detect situations where the thread gracefully exits before
|
||||
// receiving our signal.
|
||||
unique_fd pipe_read, pipe_write;
|
||||
if (!Pipe(&pipe_read, &pipe_write)) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to create pipe: %s",
|
||||
strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
trace_output_fd.store(pipe_write.get());
|
||||
trace_output_fd.store(pipe_write.get());
|
||||
|
||||
siginfo_t siginfo = {};
|
||||
siginfo.si_code = SI_QUEUE;
|
||||
siginfo.si_value.sival_int = ~0;
|
||||
siginfo.si_pid = getpid();
|
||||
siginfo.si_uid = getuid();
|
||||
siginfo_t siginfo = {};
|
||||
siginfo.si_code = SI_QUEUE;
|
||||
siginfo.si_value.sival_int = ~0;
|
||||
siginfo.si_pid = getpid();
|
||||
siginfo.si_uid = getuid();
|
||||
|
||||
if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, DEBUGGER_SIGNAL, &siginfo) != 0) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s",
|
||||
tid, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (syscall(__NR_rt_tgsigqueueinfo, getpid(), tid, DEBUGGER_SIGNAL, &siginfo) != 0) {
|
||||
async_safe_format_log(ANDROID_LOG_ERROR, "libc", "failed to send trace signal to %d: %s",
|
||||
tid, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = forward_output(pipe_read.get(), output_fd);
|
||||
if (success) {
|
||||
// The signaled thread has closed trace_output_fd already.
|
||||
(void)pipe_write.release();
|
||||
} else {
|
||||
trace_output_fd.store(-1);
|
||||
}
|
||||
bool success = forward_output(pipe_read.get(), output_fd);
|
||||
if (success) {
|
||||
// The signaled thread has closed trace_output_fd already.
|
||||
(void)pipe_write.release();
|
||||
} else {
|
||||
trace_output_fd.store(-1);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
output_fd.get());
|
||||
return true;
|
||||
},
|
||||
output_fd.get());
|
||||
|
||||
dump_backtrace_footer(output_fd.get());
|
||||
tombstoned_notify_completion(tombstone_socket.get());
|
||||
|
|
@ -206,7 +223,8 @@ exit:
|
|||
}
|
||||
|
||||
static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_message) {
|
||||
// Only allow one thread to handle a crash.
|
||||
// Only allow one thread to handle a crash at a time (this can happen multiple times without
|
||||
// exit, since tombstones can be requested without a real crash happening.)
|
||||
static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
int ret = pthread_mutex_lock(&crash_mutex);
|
||||
if (ret != 0) {
|
||||
|
|
@ -221,11 +239,13 @@ static void crash_handler(siginfo_t* info, ucontext_t* ucontext, void* abort_mes
|
|||
if (tombstoned_connected) {
|
||||
tombstoned_notify_completion(tombstone_socket.get());
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&crash_mutex);
|
||||
}
|
||||
|
||||
extern "C" void debuggerd_fallback_handler(siginfo_t* info, ucontext_t* ucontext,
|
||||
void* abort_message) {
|
||||
if (info->si_signo == DEBUGGER_SIGNAL) {
|
||||
if (info->si_signo == DEBUGGER_SIGNAL && info->si_value.sival_int != 0) {
|
||||
return trace_handler(info, ucontext);
|
||||
} else {
|
||||
return crash_handler(info, ucontext, abort_message);
|
||||
|
|
|
|||
|
|
@ -44,15 +44,21 @@
|
|||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <async_safe/log.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include <libdebuggerd/utility.h>
|
||||
|
||||
#include "dump_type.h"
|
||||
#include "protocol.h"
|
||||
|
||||
using android::base::Pipe;
|
||||
using android::base::unique_fd;
|
||||
|
||||
// see man(2) prctl, specifically the section about PR_GET_NAME
|
||||
|
|
@ -114,7 +120,7 @@ static void __noreturn __printflike(1, 2) fatal_errno(const char* fmt, ...) {
|
|||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
char buf[4096];
|
||||
char buf[256];
|
||||
async_safe_format_buffer_va_list(buf, sizeof(buf), fmt, args);
|
||||
fatal("%s: %s", buf, strerror(err));
|
||||
}
|
||||
|
|
@ -147,7 +153,7 @@ static bool get_main_thread_name(char* buf, size_t len) {
|
|||
* 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) {
|
||||
static void log_signal_summary(const siginfo_t* info) {
|
||||
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>");
|
||||
|
|
@ -157,57 +163,19 @@ static void log_signal_summary(int signum, const siginfo_t* info) {
|
|||
thread_name[MAX_TASK_NAME_LEN] = 0;
|
||||
}
|
||||
|
||||
if (signum == DEBUGGER_SIGNAL) {
|
||||
if (info->si_signo == DEBUGGER_SIGNAL) {
|
||||
async_safe_format_log(ANDROID_LOG_INFO, "libc", "Requested dump for tid %d (%s)", __gettid(),
|
||||
thread_name);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const char* signal_name = get_signame(info->si_signo);
|
||||
bool has_address = signal_has_si_addr(info->si_signo, info->si_code);
|
||||
|
||||
// "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) {
|
||||
async_safe_format_buffer(code_desc, sizeof(code_desc), ", code %d", info->si_code);
|
||||
if (has_address) {
|
||||
async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr);
|
||||
}
|
||||
// Many signals don't have an address.
|
||||
char addr_desc[32] = ""; // ", fault addr 0x1234"
|
||||
if (has_address) {
|
||||
async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr);
|
||||
}
|
||||
|
||||
char main_thread_name[MAX_TASK_NAME_LEN + 1];
|
||||
|
|
@ -216,8 +184,9 @@ static void log_signal_summary(int signum, const siginfo_t* info) {
|
|||
}
|
||||
|
||||
async_safe_format_log(
|
||||
ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s)%s%s in tid %d (%s), pid %d (%s)", signum,
|
||||
signal_name, code_desc, addr_desc, __gettid(), thread_name, __getpid(), main_thread_name);
|
||||
ANDROID_LOG_FATAL, "libc", "Fatal signal %d (%s), code %d (%s)%s in tid %d (%s), pid %d (%s)",
|
||||
info->si_signo, signal_name, info->si_code, get_sigcode(info->si_signo, info->si_code),
|
||||
addr_desc, __gettid(), thread_name, __getpid(), main_thread_name);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -268,12 +237,44 @@ static void raise_caps() {
|
|||
}
|
||||
}
|
||||
|
||||
static pid_t __fork() {
|
||||
return clone(nullptr, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
// Double-clone, with CLONE_FILES to share the file descriptor table for kcmp validation.
|
||||
// Returns 0 in the orphaned child, the pid of the orphan in the original process, or -1 on failure.
|
||||
static void create_vm_process() {
|
||||
pid_t first = clone(nullptr, nullptr, CLONE_FILES, nullptr);
|
||||
if (first == -1) {
|
||||
fatal_errno("failed to clone vm process");
|
||||
} else if (first == 0) {
|
||||
drop_capabilities();
|
||||
|
||||
if (clone(nullptr, nullptr, CLONE_FILES, nullptr) == -1) {
|
||||
_exit(errno);
|
||||
}
|
||||
|
||||
// Exit immediately on both sides of the fork.
|
||||
// crash_dump is ptracing us, so it'll get to do whatever it wants in between.
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
int status;
|
||||
if (TEMP_FAILURE_RETRY(waitpid(first, &status, __WCLONE)) != first) {
|
||||
fatal_errno("failed to waitpid in double fork");
|
||||
} else if (!WIFEXITED(status)) {
|
||||
fatal("intermediate process didn't exit cleanly in double fork (status = %d)", status);
|
||||
} else if (WEXITSTATUS(status)) {
|
||||
fatal("second clone failed: %s", strerror(WEXITSTATUS(status)));
|
||||
}
|
||||
}
|
||||
|
||||
struct debugger_thread_info {
|
||||
bool crash_dump_started;
|
||||
pid_t crashing_tid;
|
||||
pid_t pseudothread_tid;
|
||||
int signal_number;
|
||||
siginfo_t* info;
|
||||
siginfo_t* siginfo;
|
||||
void* ucontext;
|
||||
uintptr_t abort_msg;
|
||||
};
|
||||
|
||||
// Logging and contacting debuggerd requires free file descriptors, which we might not have.
|
||||
|
|
@ -284,7 +285,8 @@ struct debugger_thread_info {
|
|||
static void* pseudothread_stack;
|
||||
|
||||
static DebuggerdDumpType get_dump_type(const debugger_thread_info* thread_info) {
|
||||
if (thread_info->signal_number == DEBUGGER_SIGNAL && thread_info->info->si_value.sival_int) {
|
||||
if (thread_info->siginfo->si_signo == DEBUGGER_SIGNAL &&
|
||||
thread_info->siginfo->si_value.sival_int) {
|
||||
return kDebuggerdNativeBacktrace;
|
||||
}
|
||||
|
||||
|
|
@ -299,25 +301,58 @@ static int debuggerd_dispatch_pseudothread(void* arg) {
|
|||
}
|
||||
|
||||
int devnull = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
|
||||
if (devnull == -1) {
|
||||
fatal_errno("failed to open /dev/null");
|
||||
} else if (devnull != 0) {
|
||||
fatal_errno("expected /dev/null fd to be 0, actually %d", devnull);
|
||||
}
|
||||
|
||||
// devnull will be 0.
|
||||
TEMP_FAILURE_RETRY(dup2(devnull, STDOUT_FILENO));
|
||||
TEMP_FAILURE_RETRY(dup2(devnull, STDERR_FILENO));
|
||||
TEMP_FAILURE_RETRY(dup2(devnull, 1));
|
||||
TEMP_FAILURE_RETRY(dup2(devnull, 2));
|
||||
|
||||
unique_fd pipe_read, pipe_write;
|
||||
if (!android::base::Pipe(&pipe_read, &pipe_write)) {
|
||||
unique_fd input_read, input_write;
|
||||
unique_fd output_read, output_write;
|
||||
if (!Pipe(&input_read, &input_write) != 0 || !Pipe(&output_read, &output_write)) {
|
||||
fatal_errno("failed to create pipe");
|
||||
}
|
||||
|
||||
// ucontext_t is absurdly large on AArch64, so piece it together manually with writev.
|
||||
uint32_t version = 1;
|
||||
constexpr size_t expected =
|
||||
sizeof(version) + sizeof(siginfo_t) + sizeof(ucontext_t) + sizeof(uintptr_t);
|
||||
|
||||
errno = 0;
|
||||
if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast<int>(expected)) {
|
||||
fatal_errno("failed to set pipe bufer size");
|
||||
}
|
||||
|
||||
struct iovec iovs[4] = {
|
||||
{.iov_base = &version, .iov_len = sizeof(version)},
|
||||
{.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)},
|
||||
{.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_t)},
|
||||
{.iov_base = &thread_info->abort_msg, .iov_len = sizeof(uintptr_t)},
|
||||
};
|
||||
|
||||
ssize_t rc = TEMP_FAILURE_RETRY(writev(output_write.get(), iovs, 4));
|
||||
if (rc == -1) {
|
||||
fatal_errno("failed to write crash info");
|
||||
} else if (rc != expected) {
|
||||
fatal("failed to write crash info, wrote %zd bytes, expected %zd", rc, expected);
|
||||
}
|
||||
|
||||
// Don't use fork(2) to avoid calling pthread_atfork handlers.
|
||||
int forkpid = clone(nullptr, nullptr, 0, nullptr);
|
||||
if (forkpid == -1) {
|
||||
pid_t crash_dump_pid = __fork();
|
||||
if (crash_dump_pid == -1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc",
|
||||
"failed to fork in debuggerd signal handler: %s", strerror(errno));
|
||||
} else if (forkpid == 0) {
|
||||
TEMP_FAILURE_RETRY(dup2(pipe_write.get(), STDOUT_FILENO));
|
||||
pipe_write.reset();
|
||||
pipe_read.reset();
|
||||
} else if (crash_dump_pid == 0) {
|
||||
TEMP_FAILURE_RETRY(dup2(input_write.get(), STDOUT_FILENO));
|
||||
TEMP_FAILURE_RETRY(dup2(output_read.get(), STDIN_FILENO));
|
||||
input_read.reset();
|
||||
input_write.reset();
|
||||
output_read.reset();
|
||||
output_write.reset();
|
||||
|
||||
raise_caps();
|
||||
|
||||
|
|
@ -332,45 +367,49 @@ static int debuggerd_dispatch_pseudothread(void* arg) {
|
|||
|
||||
execle(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, debuggerd_dump_type,
|
||||
nullptr, nullptr);
|
||||
|
||||
fatal_errno("exec failed");
|
||||
} else {
|
||||
pipe_write.reset();
|
||||
char buf[4];
|
||||
ssize_t rc = TEMP_FAILURE_RETRY(read(pipe_read.get(), &buf, sizeof(buf)));
|
||||
if (rc == -1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s",
|
||||
strerror(errno));
|
||||
} else if (rc == 0) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper failed to exec");
|
||||
} else if (rc != 1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc",
|
||||
"read of IPC pipe returned unexpected value: %zd", rc);
|
||||
} else {
|
||||
if (buf[0] != '\1') {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper reported failure");
|
||||
} else {
|
||||
thread_info->crash_dump_started = true;
|
||||
}
|
||||
}
|
||||
pipe_read.reset();
|
||||
|
||||
// Don't leave a zombie child.
|
||||
int status;
|
||||
if (TEMP_FAILURE_RETRY(waitpid(forkpid, &status, 0)) == -1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "failed to wait for crash_dump helper: %s",
|
||||
strerror(errno));
|
||||
} else if (WIFSTOPPED(status) || WIFSIGNALED(status)) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper crashed or stopped");
|
||||
thread_info->crash_dump_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
syscall(__NR_exit, 0);
|
||||
input_write.reset();
|
||||
output_read.reset();
|
||||
|
||||
// crash_dump will ptrace and pause all of our threads, and then write to the pipe to tell
|
||||
// us to fork off a process to read memory from.
|
||||
char buf[4];
|
||||
rc = TEMP_FAILURE_RETRY(read(input_read.get(), &buf, sizeof(buf)));
|
||||
if (rc == -1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s", strerror(errno));
|
||||
return 1;
|
||||
} else if (rc == 0) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper failed to exec");
|
||||
return 1;
|
||||
} else if (rc != 1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc",
|
||||
"read of IPC pipe returned unexpected value: %zd", rc);
|
||||
return 1;
|
||||
} else if (buf[0] != '\1') {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper reported failure");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// crash_dump is ptracing us, fork off a copy of our address space for it to use.
|
||||
create_vm_process();
|
||||
|
||||
input_read.reset();
|
||||
input_write.reset();
|
||||
|
||||
// Don't leave a zombie child.
|
||||
int status;
|
||||
if (TEMP_FAILURE_RETRY(waitpid(crash_dump_pid, &status, 0)) == -1) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "failed to wait for crash_dump helper: %s",
|
||||
strerror(errno));
|
||||
} else if (WIFSTOPPED(status) || WIFSIGNALED(status)) {
|
||||
async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper crashed or stopped");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void resend_signal(siginfo_t* info, bool crash_dump_started) {
|
||||
static void resend_signal(siginfo_t* info) {
|
||||
// Signals can either be fatal or nonfatal.
|
||||
// For fatal signals, crash_dump will send us the signal we crashed with
|
||||
// before resuming us, so that processes using waitpid on us will see that we
|
||||
|
|
@ -379,16 +418,6 @@ static void resend_signal(siginfo_t* info, bool crash_dump_started) {
|
|||
// to deregister our signal handler for that signal before continuing.
|
||||
if (info->si_signo != DEBUGGER_SIGNAL) {
|
||||
signal(info->si_signo, SIG_DFL);
|
||||
}
|
||||
|
||||
// We need to return from our signal handler so that crash_dump can see the
|
||||
// signal via ptrace and dump the thread that crashed. However, returning
|
||||
// does not guarantee that the signal will be thrown again, even for SIGSEGV
|
||||
// and friends, since the signal could have been sent manually. We blocked
|
||||
// all signals when registering the handler, so resending the signal (using
|
||||
// rt_tgsigqueueinfo(2) to preserve SA_SIGINFO) will cause it to be delivered
|
||||
// when our signal handler returns.
|
||||
if (crash_dump_started || info->si_signo != DEBUGGER_SIGNAL) {
|
||||
int rc = syscall(SYS_rt_tgsigqueueinfo, __getpid(), __gettid(), info->si_signo, info);
|
||||
if (rc != 0) {
|
||||
fatal_errno("failed to resend signal during crash");
|
||||
|
|
@ -425,7 +454,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
}
|
||||
|
||||
void* abort_message = nullptr;
|
||||
if (g_callbacks.get_abort_message) {
|
||||
if (signal_number != DEBUGGER_SIGNAL && g_callbacks.get_abort_message) {
|
||||
abort_message = g_callbacks.get_abort_message();
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +468,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
// you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
|
||||
// ANR trace.
|
||||
debuggerd_fallback_handler(info, static_cast<ucontext_t*>(context), abort_message);
|
||||
resend_signal(info, false);
|
||||
resend_signal(info);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -450,20 +479,14 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
return;
|
||||
}
|
||||
|
||||
log_signal_summary(signal_number, info);
|
||||
|
||||
// If this was a fatal crash, populate si_value with the abort message address if possible.
|
||||
// Note that applications can set an abort message without aborting.
|
||||
if (abort_message && signal_number != DEBUGGER_SIGNAL) {
|
||||
info->si_value.sival_ptr = abort_message;
|
||||
}
|
||||
log_signal_summary(info);
|
||||
|
||||
debugger_thread_info thread_info = {
|
||||
.crash_dump_started = false,
|
||||
.pseudothread_tid = -1,
|
||||
.crashing_tid = __gettid(),
|
||||
.signal_number = signal_number,
|
||||
.info = info
|
||||
.pseudothread_tid = -1,
|
||||
.crashing_tid = __gettid(),
|
||||
.siginfo = info,
|
||||
.ucontext = context,
|
||||
.abort_msg = reinterpret_cast<uintptr_t>(abort_message),
|
||||
};
|
||||
|
||||
// Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us.
|
||||
|
|
@ -472,7 +495,8 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
fatal_errno("failed to set dumpable");
|
||||
}
|
||||
|
||||
// Essentially pthread_create without CLONE_FILES (see debuggerd_dispatch_pseudothread).
|
||||
// Essentially pthread_create without CLONE_FILES, so we still work during file descriptor
|
||||
// exhaustion.
|
||||
pid_t child_pid =
|
||||
clone(debuggerd_dispatch_pseudothread, pseudothread_stack,
|
||||
CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
|
||||
|
|
@ -484,7 +508,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
// Wait for the child to start...
|
||||
futex_wait(&thread_info.pseudothread_tid, -1);
|
||||
|
||||
// and then wait for it to finish.
|
||||
// and then wait for it to terminate.
|
||||
futex_wait(&thread_info.pseudothread_tid, child_pid);
|
||||
|
||||
// Restore PR_SET_DUMPABLE to its original value.
|
||||
|
|
@ -492,21 +516,13 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c
|
|||
fatal_errno("failed to restore dumpable");
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
resend_signal(info, thread_info.crash_dump_started);
|
||||
if (info->si_signo == DEBUGGER_SIGNAL) {
|
||||
// If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
|
||||
// starting to dump right before our death.
|
||||
pthread_mutex_unlock(&crash_mutex);
|
||||
} else {
|
||||
// Resend the signal, so that either gdb or the parent's waitpid sees it.
|
||||
resend_signal(info);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2006, 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 "libdebuggerd/machine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
|
||||
pt_regs regs;
|
||||
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, ®s)) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
static const char reg_names[] = "r0r1r2r3r4r5r6r7r8r9slfpipsp";
|
||||
|
||||
for (int reg = 0; reg < 14; reg++) {
|
||||
dump_memory(log, backtrace, regs.uregs[reg], "memory near %.2s:", ®_names[reg * 2]);
|
||||
}
|
||||
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(regs.ARM_pc), "code around pc:");
|
||||
|
||||
if (regs.ARM_pc != regs.ARM_lr) {
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(regs.ARM_lr), "code around lr:");
|
||||
}
|
||||
}
|
||||
|
||||
#define DUMP_GP_REGISTERS(log, reg_prefix) \
|
||||
_LOG(log, logtype::REGISTERS, " r0 %08x r1 %08x r2 %08x r3 %08x\n", \
|
||||
static_cast<uint32_t>(reg_prefix##r0), static_cast<uint32_t>(reg_prefix##r1), \
|
||||
static_cast<uint32_t>(reg_prefix##r2), static_cast<uint32_t>(reg_prefix##r3)); \
|
||||
_LOG(log, logtype::REGISTERS, " r4 %08x r5 %08x r6 %08x r7 %08x\n", \
|
||||
static_cast<uint32_t>(reg_prefix##r4), static_cast<uint32_t>(reg_prefix##r5), \
|
||||
static_cast<uint32_t>(reg_prefix##r6), static_cast<uint32_t>(reg_prefix##r7)); \
|
||||
_LOG(log, logtype::REGISTERS, " r8 %08x r9 %08x sl %08x fp %08x\n", \
|
||||
static_cast<uint32_t>(reg_prefix##r8), static_cast<uint32_t>(reg_prefix##r9), \
|
||||
static_cast<uint32_t>(reg_prefix##r10), static_cast<uint32_t>(reg_prefix##fp)); \
|
||||
_LOG(log, logtype::REGISTERS, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n", \
|
||||
static_cast<uint32_t>(reg_prefix##ip), static_cast<uint32_t>(reg_prefix##sp), \
|
||||
static_cast<uint32_t>(reg_prefix##lr), static_cast<uint32_t>(reg_prefix##pc), \
|
||||
static_cast<uint32_t>(reg_prefix##cpsr))
|
||||
|
||||
void dump_registers(log_t* log, pid_t tid) {
|
||||
pt_regs r;
|
||||
if (ptrace(PTRACE_GETREGS, tid, 0, &r)) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP_GP_REGISTERS(log, r.ARM_);
|
||||
|
||||
user_vfp vfp_regs;
|
||||
if (ptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs)) {
|
||||
ALOGE("cannot get FP registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 32; i += 2) {
|
||||
_LOG(log, logtype::FP_REGISTERS, " d%-2d %016llx d%-2d %016llx\n",
|
||||
i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]);
|
||||
}
|
||||
_LOG(log, logtype::FP_REGISTERS, " scr %08lx\n", vfp_regs.fpscr);
|
||||
}
|
||||
|
||||
void dump_registers(log_t* log, const ucontext_t* uc) {
|
||||
DUMP_GP_REGISTERS(log, uc->uc_mcontext.arm_);
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "DEBUG"
|
||||
|
||||
#include "libdebuggerd/machine.h"
|
||||
|
||||
#include <elf.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
|
||||
struct user_pt_regs regs;
|
||||
struct iovec io;
|
||||
io.iov_base = ®s;
|
||||
io.iov_len = sizeof(regs);
|
||||
|
||||
if (ptrace(PTRACE_GETREGSET, backtrace->Tid(), reinterpret_cast<void*>(NT_PRSTATUS), &io) == -1) {
|
||||
ALOGE("ptrace failed to get registers: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int reg = 0; reg < 31; reg++) {
|
||||
dump_memory(log, backtrace, regs.regs[reg], "memory near x%d:", reg);
|
||||
}
|
||||
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(regs.pc), "code around pc:");
|
||||
|
||||
if (regs.pc != regs.sp) {
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(regs.sp), "code around sp:");
|
||||
}
|
||||
}
|
||||
|
||||
#define DUMP_GP_REGISTERS(log) \
|
||||
for (int i = 0; i < 28; i += 4) { \
|
||||
const char* fmt = " x%-2d %016llx x%-2d %016llx x%-2d %016llx x%-2d %016llx\n"; \
|
||||
_LOG(log, logtype::REGISTERS, fmt, i, r.regs[i], i + 1, r.regs[i + 1], i + 2, r.regs[i + 2], \
|
||||
i + 3, r.regs[i + 3]); \
|
||||
} \
|
||||
_LOG(log, logtype::REGISTERS, " x28 %016llx x29 %016llx x30 %016llx\n", r.regs[28], \
|
||||
r.regs[29], r.regs[30]); \
|
||||
_LOG(log, logtype::REGISTERS, " sp %016llx pc %016llx pstate %016llx\n", r.sp, r.pc, \
|
||||
r.pstate)
|
||||
|
||||
void dump_registers(log_t* log, pid_t tid) {
|
||||
struct user_pt_regs r;
|
||||
struct iovec io;
|
||||
io.iov_base = &r;
|
||||
io.iov_len = sizeof(r);
|
||||
|
||||
if (ptrace(PTRACE_GETREGSET, tid, (void*) NT_PRSTATUS, (void*) &io) == -1) {
|
||||
ALOGE("ptrace error: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
DUMP_GP_REGISTERS(log);
|
||||
|
||||
struct user_fpsimd_state f;
|
||||
io.iov_base = &f;
|
||||
io.iov_len = sizeof(f);
|
||||
|
||||
if (ptrace(PTRACE_GETREGSET, tid, (void*) NT_PRFPREG, (void*) &io) == -1) {
|
||||
ALOGE("ptrace error: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 32; i += 2) {
|
||||
_LOG(log, logtype::FP_REGISTERS,
|
||||
" v%-2d %016" PRIx64 "%016" PRIx64 " v%-2d %016" PRIx64 "%016" PRIx64 "\n",
|
||||
i,
|
||||
static_cast<uint64_t>(f.vregs[i] >> 64),
|
||||
static_cast<uint64_t>(f.vregs[i]),
|
||||
i+1,
|
||||
static_cast<uint64_t>(f.vregs[i+1] >> 64),
|
||||
static_cast<uint64_t>(f.vregs[i+1]));
|
||||
}
|
||||
_LOG(log, logtype::FP_REGISTERS, " fpsr %08x fpcr %08x\n", f.fpsr, f.fpcr);
|
||||
}
|
||||
|
||||
void dump_registers(log_t* log, const ucontext_t* ucontext) {
|
||||
const mcontext_t& r = ucontext->uc_mcontext;
|
||||
DUMP_GP_REGISTERS(log);
|
||||
}
|
||||
|
|
@ -30,12 +30,15 @@
|
|||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/types.h"
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
static void dump_process_header(log_t* log, pid_t pid, const char* process_name) {
|
||||
|
|
@ -56,62 +59,46 @@ static void dump_process_footer(log_t* log, pid_t pid) {
|
|||
_LOG(log, logtype::BACKTRACE, "\n----- end %d -----\n", pid);
|
||||
}
|
||||
|
||||
static void log_thread_name(log_t* log, pid_t tid, const char* thread_name) {
|
||||
_LOG(log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", thread_name, tid);
|
||||
}
|
||||
|
||||
static void dump_thread(log_t* log, BacktraceMap* map, pid_t pid, pid_t tid,
|
||||
const std::string& thread_name) {
|
||||
log_thread_name(log, tid, thread_name.c_str());
|
||||
|
||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
|
||||
if (backtrace->Unwind(0)) {
|
||||
dump_backtrace_to_log(backtrace.get(), log, " ");
|
||||
} else {
|
||||
ALOGE("Unwind failed: tid = %d: %s", tid,
|
||||
backtrace->GetErrorString(backtrace->GetError()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void dump_backtrace(int fd, BacktraceMap* map, pid_t pid, pid_t tid, const std::string& process_name,
|
||||
const std::map<pid_t, std::string>& threads, std::string* amfd_data) {
|
||||
log_t log;
|
||||
log.tfd = fd;
|
||||
log.amfd_data = amfd_data;
|
||||
|
||||
dump_process_header(&log, pid, process_name.c_str());
|
||||
dump_thread(&log, map, pid, tid, threads.find(tid)->second.c_str());
|
||||
|
||||
for (const auto& it : threads) {
|
||||
pid_t thread_tid = it.first;
|
||||
const std::string& thread_name = it.second;
|
||||
if (thread_tid != tid) {
|
||||
dump_thread(&log, map, pid, thread_tid, thread_name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
dump_process_footer(&log, pid);
|
||||
}
|
||||
|
||||
void dump_backtrace_ucontext(int output_fd, ucontext_t* ucontext) {
|
||||
pid_t pid = getpid();
|
||||
pid_t tid = gettid();
|
||||
|
||||
void dump_backtrace_thread(int output_fd, BacktraceMap* map, const ThreadInfo& thread) {
|
||||
log_t log;
|
||||
log.tfd = output_fd;
|
||||
log.amfd_data = nullptr;
|
||||
|
||||
char thread_name[16];
|
||||
read_with_default("/proc/self/comm", thread_name, sizeof(thread_name), "<unknown>");
|
||||
log_thread_name(&log, tid, thread_name);
|
||||
_LOG(&log, logtype::BACKTRACE, "\n\"%s\" sysTid=%d\n", thread.thread_name.c_str(), thread.tid);
|
||||
|
||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid));
|
||||
if (backtrace->Unwind(0, ucontext)) {
|
||||
dump_backtrace_to_log(backtrace.get(), &log, " ");
|
||||
} else {
|
||||
ALOGE("Unwind failed: tid = %d: %s", tid,
|
||||
backtrace->GetErrorString(backtrace->GetError()).c_str());
|
||||
std::vector<backtrace_frame_data_t> frames;
|
||||
if (!Backtrace::Unwind(thread.registers.get(), map, &frames, 0, nullptr)) {
|
||||
_LOG(&log, logtype::THREAD, "Unwind failed: tid = %d", thread.tid);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& frame : frames) {
|
||||
_LOG(&log, logtype::BACKTRACE, " %s\n", Backtrace::FormatFrameData(&frame).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void dump_backtrace(android::base::unique_fd output_fd, BacktraceMap* map,
|
||||
const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread) {
|
||||
log_t log;
|
||||
log.tfd = output_fd.get();
|
||||
log.amfd_data = nullptr;
|
||||
|
||||
auto target = thread_info.find(target_thread);
|
||||
if (target == thread_info.end()) {
|
||||
ALOGE("failed to find target thread in thread info");
|
||||
return;
|
||||
}
|
||||
|
||||
dump_process_header(&log, target->second.pid, target->second.process_name.c_str());
|
||||
|
||||
dump_backtrace_thread(output_fd.get(), map, target->second);
|
||||
for (const auto& [tid, info] : thread_info) {
|
||||
if (tid != target_thread) {
|
||||
dump_backtrace_thread(output_fd.get(), map, info);
|
||||
}
|
||||
}
|
||||
|
||||
dump_process_footer(&log, target->second.pid);
|
||||
}
|
||||
|
||||
void dump_backtrace_header(int output_fd) {
|
||||
|
|
@ -131,9 +118,3 @@ void dump_backtrace_footer(int output_fd) {
|
|||
|
||||
dump_process_footer(&log, getpid());
|
||||
}
|
||||
|
||||
void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, const char* prefix) {
|
||||
for (size_t i = 0; i < backtrace->NumFrames(); i++) {
|
||||
_LOG(log, logtype::BACKTRACE, "%s%s\n", prefix, backtrace->FormatFrameData(i).c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,28 +26,28 @@
|
|||
#include <string>
|
||||
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
|
||||
#define NOTE_ALIGN(size) (((size) + 3) & ~3)
|
||||
|
||||
template <typename HdrType, typename PhdrType, typename NhdrType>
|
||||
static bool get_build_id(
|
||||
Backtrace* backtrace, uintptr_t base_addr, uint8_t* e_ident, std::string* build_id) {
|
||||
static bool get_build_id(unwindstack::Memory* memory, uintptr_t base_addr, uint8_t* e_ident,
|
||||
std::string* build_id) {
|
||||
HdrType hdr;
|
||||
|
||||
memcpy(&hdr.e_ident[0], e_ident, EI_NIDENT);
|
||||
|
||||
// First read the rest of the header.
|
||||
if (backtrace->Read(base_addr + EI_NIDENT, reinterpret_cast<uint8_t*>(&hdr) + EI_NIDENT,
|
||||
sizeof(HdrType) - EI_NIDENT) != sizeof(HdrType) - EI_NIDENT) {
|
||||
if (memory->Read(base_addr + EI_NIDENT, reinterpret_cast<uint8_t*>(&hdr) + EI_NIDENT,
|
||||
sizeof(HdrType) - EI_NIDENT) != sizeof(HdrType) - EI_NIDENT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < hdr.e_phnum; i++) {
|
||||
PhdrType phdr;
|
||||
if (backtrace->Read(base_addr + hdr.e_phoff + i * hdr.e_phentsize,
|
||||
reinterpret_cast<uint8_t*>(&phdr), sizeof(phdr)) != sizeof(phdr)) {
|
||||
if (memory->Read(base_addr + hdr.e_phoff + i * hdr.e_phentsize,
|
||||
reinterpret_cast<uint8_t*>(&phdr), sizeof(phdr)) != sizeof(phdr)) {
|
||||
return false;
|
||||
}
|
||||
// Looking for the .note.gnu.build-id note.
|
||||
|
|
@ -56,7 +56,7 @@ static bool get_build_id(
|
|||
uintptr_t addr = base_addr + phdr.p_offset;
|
||||
while (hdr_size >= sizeof(NhdrType)) {
|
||||
NhdrType nhdr;
|
||||
if (backtrace->Read(addr, reinterpret_cast<uint8_t*>(&nhdr), sizeof(nhdr)) != sizeof(nhdr)) {
|
||||
if (memory->Read(addr, reinterpret_cast<uint8_t*>(&nhdr), sizeof(nhdr)) != sizeof(nhdr)) {
|
||||
return false;
|
||||
}
|
||||
addr += sizeof(nhdr);
|
||||
|
|
@ -69,7 +69,7 @@ static bool get_build_id(
|
|||
nhdr.n_descsz);
|
||||
return false;
|
||||
}
|
||||
if (backtrace->Read(addr, build_id_data, nhdr.n_descsz) != nhdr.n_descsz) {
|
||||
if (memory->Read(addr, build_id_data, nhdr.n_descsz) != nhdr.n_descsz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -95,10 +95,10 @@ static bool get_build_id(
|
|||
return false;
|
||||
}
|
||||
|
||||
bool elf_get_build_id(Backtrace* backtrace, uintptr_t addr, std::string* build_id) {
|
||||
bool elf_get_build_id(unwindstack::Memory* memory, uintptr_t addr, std::string* build_id) {
|
||||
// Read and verify the elf magic number first.
|
||||
uint8_t e_ident[EI_NIDENT];
|
||||
if (backtrace->Read(addr, e_ident, SELFMAG) != SELFMAG) {
|
||||
if (memory->Read(addr, e_ident, SELFMAG) != SELFMAG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -107,14 +107,14 @@ bool elf_get_build_id(Backtrace* backtrace, uintptr_t addr, std::string* build_i
|
|||
}
|
||||
|
||||
// Read the rest of EI_NIDENT.
|
||||
if (backtrace->Read(addr + SELFMAG, e_ident + SELFMAG, EI_NIDENT - SELFMAG) != EI_NIDENT - SELFMAG) {
|
||||
if (memory->Read(addr + SELFMAG, e_ident + SELFMAG, EI_NIDENT - SELFMAG) != EI_NIDENT - SELFMAG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e_ident[EI_CLASS] == ELFCLASS32) {
|
||||
return get_build_id<Elf32_Ehdr, Elf32_Phdr, Elf32_Nhdr>(backtrace, addr, e_ident, build_id);
|
||||
return get_build_id<Elf32_Ehdr, Elf32_Phdr, Elf32_Nhdr>(memory, addr, e_ident, build_id);
|
||||
} else if (e_ident[EI_CLASS] == ELFCLASS64) {
|
||||
return get_build_id<Elf64_Ehdr, Elf64_Phdr, Elf64_Nhdr>(backtrace, addr, e_ident, build_id);
|
||||
return get_build_id<Elf64_Ehdr, Elf64_Phdr, Elf64_Nhdr>(memory, addr, e_ident, build_id);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -23,21 +23,20 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "utility.h"
|
||||
|
||||
class Backtrace;
|
||||
class BacktraceMap;
|
||||
|
||||
// Dumps a backtrace using a format similar to what Dalvik uses so that the result
|
||||
// can be intermixed in a bug report.
|
||||
void dump_backtrace(int fd, BacktraceMap* map, pid_t pid, pid_t tid, const std::string& process_name,
|
||||
const std::map<pid_t, std::string>& threads, std::string* amfd_data);
|
||||
void dump_backtrace(android::base::unique_fd output_fd, BacktraceMap* map,
|
||||
const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread);
|
||||
|
||||
/* Dumps the backtrace in the backtrace data structure to the log. */
|
||||
void dump_backtrace_to_log(Backtrace* backtrace, log_t* log, const char* prefix);
|
||||
|
||||
void dump_backtrace_ucontext(int output_fd, ucontext_t* ucontext);
|
||||
void dump_backtrace_header(int output_fd);
|
||||
void dump_backtrace_thread(int output_fd, BacktraceMap* map, const ThreadInfo& thread);
|
||||
void dump_backtrace_footer(int output_fd);
|
||||
|
||||
#endif // _DEBUGGERD_BACKTRACE_H
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@
|
|||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
class Backtrace;
|
||||
namespace unwindstack {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
bool elf_get_build_id(Backtrace*, uintptr_t, std::string*);
|
||||
bool elf_get_build_id(unwindstack::Memory*, uintptr_t, std::string*);
|
||||
|
||||
#endif // _DEBUGGERD_ELF_UTILS_H
|
||||
|
|
|
|||
|
|
@ -1,30 +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_MACHINE_H
|
||||
#define _DEBUGGERD_MACHINE_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace);
|
||||
void dump_registers(log_t* log, pid_t tid);
|
||||
void dump_registers(log_t* log, const ucontext_t* uc);
|
||||
|
||||
#endif // _DEBUGGERD_MACHINE_H
|
||||
|
|
@ -31,6 +31,6 @@ typedef std::vector<std::pair<int, std::string>> OpenFilesList;
|
|||
void populate_open_files_list(pid_t pid, OpenFilesList* list);
|
||||
|
||||
/* Dumps the open files list to the log. */
|
||||
void dump_open_files_list_to_log(const OpenFilesList& files, log_t* log, const char* prefix);
|
||||
void dump_open_files_list(log_t* log, const OpenFilesList& files, const char* prefix);
|
||||
|
||||
#endif // _DEBUGGERD_OPEN_FILES_LIST_H
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@
|
|||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
|
||||
#include "open_files_list.h"
|
||||
#include "types.h"
|
||||
|
||||
class BacktraceMap;
|
||||
|
||||
|
|
@ -43,11 +46,10 @@ void engrave_tombstone(int tombstone_fd, BacktraceMap* map, const OpenFilesList*
|
|||
void engrave_tombstone_ucontext(int tombstone_fd, uintptr_t abort_msg_address, siginfo_t* siginfo,
|
||||
ucontext_t* ucontext);
|
||||
|
||||
// Compatibility shim.
|
||||
__attribute__((__unused__))
|
||||
static void engrave_tombstone_ucontext(int tombstone_fd, pid_t, pid_t, uintptr_t abort_msg_address,
|
||||
siginfo_t* siginfo, ucontext_t* ucontext) {
|
||||
engrave_tombstone_ucontext(tombstone_fd, abort_msg_address, siginfo, ucontext);
|
||||
}
|
||||
void engrave_tombstone(android::base::unique_fd output_fd, BacktraceMap* map,
|
||||
unwindstack::Memory* process_memory,
|
||||
const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread,
|
||||
uintptr_t abort_msg_address, OpenFilesList* open_files,
|
||||
std::string* amfd_data);
|
||||
|
||||
#endif // _DEBUGGERD_TOMBSTONE_H
|
||||
#endif // _DEBUGGERD_TOMBSTONE_H
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* 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.
|
||||
|
|
@ -14,11 +16,19 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DEBUGGERD_TEST_PTRACE_FAKE_H
|
||||
#define _DEBUGGERD_TEST_PTRACE_FAKE_H
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <signal.h>
|
||||
#include <unwindstack/Regs.h>
|
||||
|
||||
void ptrace_set_fake_getsiginfo(const siginfo_t&);
|
||||
struct ThreadInfo {
|
||||
std::unique_ptr<unwindstack::Regs> registers;
|
||||
pid_t tid;
|
||||
std::string thread_name;
|
||||
|
||||
#endif // _DEBUGGERD_TEST_PTRACE_FAKE_H
|
||||
pid_t pid;
|
||||
std::string process_name;
|
||||
|
||||
int signo = 0;
|
||||
siginfo_t* siginfo = nullptr;
|
||||
};
|
||||
|
|
@ -27,21 +27,24 @@
|
|||
#include <android-base/macros.h>
|
||||
#include <backtrace/Backtrace.h>
|
||||
|
||||
struct log_t{
|
||||
// Tombstone file descriptor.
|
||||
int tfd;
|
||||
// Data to be sent to the Activity Manager.
|
||||
std::string* amfd_data;
|
||||
// The tid of the thread that crashed.
|
||||
pid_t crashed_tid;
|
||||
// The tid of the thread we are currently working with.
|
||||
pid_t current_tid;
|
||||
// logd daemon crash, can block asking for logcat data, allow suppression.
|
||||
bool should_retrieve_logcat;
|
||||
struct log_t {
|
||||
// Tombstone file descriptor.
|
||||
int tfd;
|
||||
// Data to be sent to the Activity Manager.
|
||||
std::string* amfd_data;
|
||||
// The tid of the thread that crashed.
|
||||
pid_t crashed_tid;
|
||||
// The tid of the thread we are currently working with.
|
||||
pid_t current_tid;
|
||||
// logd daemon crash, can block asking for logcat data, allow suppression.
|
||||
bool should_retrieve_logcat;
|
||||
|
||||
log_t()
|
||||
: tfd(-1), amfd_data(nullptr), crashed_tid(-1), current_tid(-1),
|
||||
should_retrieve_logcat(true) {}
|
||||
log_t()
|
||||
: tfd(-1),
|
||||
amfd_data(nullptr),
|
||||
crashed_tid(-1),
|
||||
current_tid(-1),
|
||||
should_retrieve_logcat(true) {}
|
||||
};
|
||||
|
||||
// List of types of logs to simplify the logging decision in _LOG
|
||||
|
|
@ -59,13 +62,20 @@ enum logtype {
|
|||
};
|
||||
|
||||
// Log information onto the tombstone.
|
||||
void _LOG(log_t* log, logtype ltype, const char *fmt, ...)
|
||||
__attribute__ ((format(printf, 3, 4)));
|
||||
void _LOG(log_t* log, logtype ltype, const char* fmt, ...) __attribute__((format(printf, 3, 4)));
|
||||
|
||||
bool wait_for_signal(pid_t tid, siginfo_t* siginfo);
|
||||
namespace unwindstack {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
void dump_memory(log_t* log, Backtrace* backtrace, uintptr_t addr, const char* fmt, ...);
|
||||
void dump_memory(log_t* log, unwindstack::Memory* backtrace, uintptr_t addr, const char* fmt, ...);
|
||||
|
||||
void read_with_default(const char* path, char* buf, size_t len, const char* default_value);
|
||||
|
||||
void drop_capabilities();
|
||||
|
||||
bool signal_has_si_addr(int si_signo, int si_code);
|
||||
const char* get_signame(int sig);
|
||||
const char* get_sigcode(int signo, int code);
|
||||
|
||||
#endif // _DEBUGGERD_UTILITY_H
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright 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 "libdebuggerd/machine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
#define R(x) (static_cast<uintptr_t>(x))
|
||||
|
||||
// If configured to do so, dump memory around *all* registers
|
||||
// for the crashing thread.
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
|
||||
pt_regs r;
|
||||
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r)) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
static const char reg_names[] = "$0atv0v1a0a1a2a3t0t1t2t3t4t5t6t7s0s1s2s3s4s5s6s7t8t9k0k1gpsps8ra";
|
||||
|
||||
for (int reg = 0; reg < 32; reg++) {
|
||||
// skip uninteresting registers
|
||||
if (reg == 0 // $0
|
||||
|| reg == 26 // $k0
|
||||
|| reg == 27 // $k1
|
||||
|| reg == 31 // $ra (done below)
|
||||
)
|
||||
continue;
|
||||
|
||||
dump_memory(log, backtrace, R(r.regs[reg]), "memory near %.2s:", ®_names[reg * 2]);
|
||||
}
|
||||
|
||||
uintptr_t pc = R(r.cp0_epc);
|
||||
uintptr_t ra = R(r.regs[31]);
|
||||
dump_memory(log, backtrace, pc, "code around pc:");
|
||||
if (pc != ra) {
|
||||
dump_memory(log, backtrace, ra, "code around ra:");
|
||||
}
|
||||
}
|
||||
|
||||
void dump_registers(log_t* log, pid_t tid) {
|
||||
pt_regs r;
|
||||
if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(log, logtype::REGISTERS, " zr %08" PRIxPTR " at %08" PRIxPTR
|
||||
" v0 %08" PRIxPTR " v1 %08" PRIxPTR "\n",
|
||||
R(r.regs[0]), R(r.regs[1]), R(r.regs[2]), R(r.regs[3]));
|
||||
_LOG(log, logtype::REGISTERS, " a0 %08" PRIxPTR " a1 %08" PRIxPTR
|
||||
" a2 %08" PRIxPTR " a3 %08" PRIxPTR "\n",
|
||||
R(r.regs[4]), R(r.regs[5]), R(r.regs[6]), R(r.regs[7]));
|
||||
_LOG(log, logtype::REGISTERS, " t0 %08" PRIxPTR " t1 %08" PRIxPTR
|
||||
" t2 %08" PRIxPTR " t3 %08" PRIxPTR "\n",
|
||||
R(r.regs[8]), R(r.regs[9]), R(r.regs[10]), R(r.regs[11]));
|
||||
_LOG(log, logtype::REGISTERS, " t4 %08" PRIxPTR " t5 %08" PRIxPTR
|
||||
" t6 %08" PRIxPTR " t7 %08" PRIxPTR "\n",
|
||||
R(r.regs[12]), R(r.regs[13]), R(r.regs[14]), R(r.regs[15]));
|
||||
_LOG(log, logtype::REGISTERS, " s0 %08" PRIxPTR " s1 %08" PRIxPTR
|
||||
" s2 %08" PRIxPTR " s3 %08" PRIxPTR "\n",
|
||||
R(r.regs[16]), R(r.regs[17]), R(r.regs[18]), R(r.regs[19]));
|
||||
_LOG(log, logtype::REGISTERS, " s4 %08" PRIxPTR " s5 %08" PRIxPTR
|
||||
" s6 %08" PRIxPTR " s7 %08" PRIxPTR "\n",
|
||||
R(r.regs[20]), R(r.regs[21]), R(r.regs[22]), R(r.regs[23]));
|
||||
_LOG(log, logtype::REGISTERS, " t8 %08" PRIxPTR " t9 %08" PRIxPTR
|
||||
" k0 %08" PRIxPTR " k1 %08" PRIxPTR "\n",
|
||||
R(r.regs[24]), R(r.regs[25]), R(r.regs[26]), R(r.regs[27]));
|
||||
_LOG(log, logtype::REGISTERS, " gp %08" PRIxPTR " sp %08" PRIxPTR
|
||||
" s8 %08" PRIxPTR " ra %08" PRIxPTR "\n",
|
||||
R(r.regs[28]), R(r.regs[29]), R(r.regs[30]), R(r.regs[31]));
|
||||
_LOG(log, logtype::REGISTERS, " hi %08" PRIxPTR " lo %08" PRIxPTR
|
||||
" bva %08" PRIxPTR " epc %08" PRIxPTR "\n",
|
||||
R(r.hi), R(r.lo), R(r.cp0_badvaddr), R(r.cp0_epc));
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "DEBUG"
|
||||
|
||||
#include "libdebuggerd/machine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
#define R(x) (static_cast<uintptr_t>(x))
|
||||
|
||||
// If configured to do so, dump memory around *all* registers
|
||||
// for the crashing thread.
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
|
||||
pt_regs r;
|
||||
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r)) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
static const char reg_names[] = "$0atv0v1a0a1a2a3a4a5a6a7t0t1t2t3s0s1s2s3s4s5s6s7t8t9k0k1gpsps8ra";
|
||||
|
||||
for (int reg = 0; reg < 32; reg++) {
|
||||
// skip uninteresting registers
|
||||
if (reg == 0 // $0
|
||||
|| reg == 26 // $k0
|
||||
|| reg == 27 // $k1
|
||||
|| reg == 31 // $ra (done below)
|
||||
)
|
||||
continue;
|
||||
|
||||
dump_memory(log, backtrace, R(r.regs[reg]), "memory near %.2s:", ®_names[reg * 2]);
|
||||
}
|
||||
|
||||
uintptr_t pc = R(r.cp0_epc);
|
||||
uintptr_t ra = R(r.regs[31]);
|
||||
dump_memory(log, backtrace, pc, "code around pc:");
|
||||
if (pc != ra) {
|
||||
dump_memory(log, backtrace, ra, "code around ra:");
|
||||
}
|
||||
}
|
||||
|
||||
void dump_registers(log_t* log, pid_t tid) {
|
||||
pt_regs r;
|
||||
if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(log, logtype::REGISTERS, " zr %016" PRIxPTR " at %016" PRIxPTR
|
||||
" v0 %016" PRIxPTR " v1 %016" PRIxPTR "\n",
|
||||
R(r.regs[0]), R(r.regs[1]), R(r.regs[2]), R(r.regs[3]));
|
||||
_LOG(log, logtype::REGISTERS, " a0 %016" PRIxPTR " a1 %016" PRIxPTR
|
||||
" a2 %016" PRIxPTR " a3 %016" PRIxPTR "\n",
|
||||
R(r.regs[4]), R(r.regs[5]), R(r.regs[6]), R(r.regs[7]));
|
||||
_LOG(log, logtype::REGISTERS, " a4 %016" PRIxPTR " a5 %016" PRIxPTR
|
||||
" a6 %016" PRIxPTR " a7 %016" PRIxPTR "\n",
|
||||
R(r.regs[8]), R(r.regs[9]), R(r.regs[10]), R(r.regs[11]));
|
||||
_LOG(log, logtype::REGISTERS, " t0 %016" PRIxPTR " t1 %016" PRIxPTR
|
||||
" t2 %016" PRIxPTR " t3 %016" PRIxPTR "\n",
|
||||
R(r.regs[12]), R(r.regs[13]), R(r.regs[14]), R(r.regs[15]));
|
||||
_LOG(log, logtype::REGISTERS, " s0 %016" PRIxPTR " s1 %016" PRIxPTR
|
||||
" s2 %016" PRIxPTR " s3 %016" PRIxPTR "\n",
|
||||
R(r.regs[16]), R(r.regs[17]), R(r.regs[18]), R(r.regs[19]));
|
||||
_LOG(log, logtype::REGISTERS, " s4 %016" PRIxPTR " s5 %016" PRIxPTR
|
||||
" s6 %016" PRIxPTR " s7 %016" PRIxPTR "\n",
|
||||
R(r.regs[20]), R(r.regs[21]), R(r.regs[22]), R(r.regs[23]));
|
||||
_LOG(log, logtype::REGISTERS, " t8 %016" PRIxPTR " t9 %016" PRIxPTR
|
||||
" k0 %016" PRIxPTR " k1 %016" PRIxPTR "\n",
|
||||
R(r.regs[24]), R(r.regs[25]), R(r.regs[26]), R(r.regs[27]));
|
||||
_LOG(log, logtype::REGISTERS, " gp %016" PRIxPTR " sp %016" PRIxPTR
|
||||
" s8 %016" PRIxPTR " ra %016" PRIxPTR "\n",
|
||||
R(r.regs[28]), R(r.regs[29]), R(r.regs[30]), R(r.regs[31]));
|
||||
_LOG(log, logtype::REGISTERS, " hi %016" PRIxPTR " lo %016" PRIxPTR
|
||||
" bva %016" PRIxPTR " epc %016" PRIxPTR "\n",
|
||||
R(r.hi), R(r.lo), R(r.cp0_badvaddr), R(r.cp0_epc));
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ void populate_open_files_list(pid_t pid, OpenFilesList* list) {
|
|||
}
|
||||
}
|
||||
|
||||
void dump_open_files_list_to_log(const OpenFilesList& files, log_t* log, const char* prefix) {
|
||||
void dump_open_files_list(log_t* log, const OpenFilesList& files, const char* prefix) {
|
||||
for (auto& file : files) {
|
||||
_LOG(log, logtype::OPEN_FILES, "%sfd %i: %s\n", prefix, file.first, file.second.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,6 @@
|
|||
#ifndef _DEBUGGERD_TEST_BACKTRACE_MOCK_H
|
||||
#define _DEBUGGERD_TEST_BACKTRACE_MOCK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ucontext.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <backtrace/BacktraceMap.h>
|
||||
|
||||
class BacktraceMapMock : public BacktraceMap {
|
||||
|
|
@ -38,69 +29,4 @@ class BacktraceMapMock : public BacktraceMap {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
class BacktraceMock : public Backtrace {
|
||||
public:
|
||||
explicit BacktraceMock(BacktraceMapMock* map) : Backtrace(0, 0, map) {
|
||||
if (map_ == nullptr) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
virtual ~BacktraceMock() {}
|
||||
|
||||
virtual bool Unwind(size_t, ucontext_t*) { return false; }
|
||||
virtual bool ReadWord(uintptr_t, word_t*) { return false;}
|
||||
|
||||
virtual std::string GetFunctionNameRaw(uintptr_t, uintptr_t*) { return ""; }
|
||||
|
||||
virtual size_t Read(uintptr_t addr, uint8_t* buffer, size_t bytes) {
|
||||
size_t offset = 0;
|
||||
if (last_read_addr_ > 0) {
|
||||
offset = addr - last_read_addr_;
|
||||
}
|
||||
size_t bytes_available = buffer_.size() - offset;
|
||||
|
||||
if (do_partial_read_) {
|
||||
// Do a partial read.
|
||||
if (bytes > bytes_partial_read_) {
|
||||
bytes = bytes_partial_read_;
|
||||
}
|
||||
bytes_partial_read_ -= bytes;
|
||||
// Only support a single partial read.
|
||||
do_partial_read_ = false;
|
||||
} else if (bytes > bytes_available) {
|
||||
bytes = bytes_available;
|
||||
}
|
||||
|
||||
if (bytes > 0) {
|
||||
memcpy(buffer, buffer_.data() + offset, bytes);
|
||||
}
|
||||
|
||||
last_read_addr_ = addr;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void SetReadData(uint8_t* buffer, size_t bytes) {
|
||||
buffer_.resize(bytes);
|
||||
memcpy(buffer_.data(), buffer, bytes);
|
||||
bytes_partial_read_ = 0;
|
||||
do_partial_read_ = false;
|
||||
last_read_addr_ = 0;
|
||||
}
|
||||
|
||||
void SetPartialReadAmount(size_t bytes) {
|
||||
if (bytes > buffer_.size()) {
|
||||
abort();
|
||||
}
|
||||
bytes_partial_read_ = bytes;
|
||||
do_partial_read_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buffer_;
|
||||
size_t bytes_partial_read_ = 0;
|
||||
uintptr_t last_read_addr_ = 0;
|
||||
bool do_partial_read_ = false;
|
||||
};
|
||||
|
||||
#endif // _DEBUGGERD_TEST_BACKTRACE_MOCK_H
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <android-base/file.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
#include "BacktraceMock.h"
|
||||
#include "log_fake.h"
|
||||
|
||||
const char g_expected_full_dump[] =
|
||||
|
|
@ -103,11 +103,59 @@ const char g_expected_partial_dump[] = \
|
|||
" 123456d0 -------- -------- -------- -------- ................\n";
|
||||
#endif
|
||||
|
||||
class MemoryMock : public unwindstack::Memory {
|
||||
public:
|
||||
virtual ~MemoryMock() = default;
|
||||
|
||||
virtual size_t Read(uint64_t addr, void* buffer, size_t bytes) override {
|
||||
size_t offset = 0;
|
||||
if (last_read_addr_ > 0) {
|
||||
offset = addr - last_read_addr_;
|
||||
}
|
||||
size_t bytes_available = buffer_.size() - offset;
|
||||
|
||||
if (partial_read_) {
|
||||
bytes = std::min(bytes, bytes_partial_read_);
|
||||
bytes_partial_read_ -= bytes;
|
||||
partial_read_ = bytes_partial_read_;
|
||||
} else if (bytes > bytes_available) {
|
||||
bytes = bytes_available;
|
||||
}
|
||||
|
||||
if (bytes > 0) {
|
||||
memcpy(buffer, buffer_.data() + offset, bytes);
|
||||
}
|
||||
|
||||
last_read_addr_ = addr;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void SetReadData(uint8_t* buffer, size_t bytes) {
|
||||
buffer_.resize(bytes);
|
||||
memcpy(buffer_.data(), buffer, bytes);
|
||||
bytes_partial_read_ = 0;
|
||||
last_read_addr_ = 0;
|
||||
}
|
||||
|
||||
void SetPartialReadAmount(size_t bytes) {
|
||||
if (bytes > buffer_.size()) {
|
||||
abort();
|
||||
}
|
||||
partial_read_ = true;
|
||||
bytes_partial_read_ = bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buffer_;
|
||||
bool partial_read_ = false;
|
||||
size_t bytes_partial_read_ = 0;
|
||||
uintptr_t last_read_addr_ = 0;
|
||||
};
|
||||
|
||||
class DumpMemoryTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
map_mock_.reset(new BacktraceMapMock());
|
||||
backtrace_mock_.reset(new BacktraceMock(map_mock_.get()));
|
||||
memory_mock_ = std::make_unique<MemoryMock>();
|
||||
|
||||
char tmp_file[256];
|
||||
const char data_template[] = "/data/local/tmp/debuggerd_memory_testXXXXXX";
|
||||
|
|
@ -138,10 +186,10 @@ class DumpMemoryTest : public ::testing::Test {
|
|||
if (log_.tfd >= 0) {
|
||||
close(log_.tfd);
|
||||
}
|
||||
memory_mock_.reset();
|
||||
}
|
||||
|
||||
std::unique_ptr<BacktraceMapMock> map_mock_;
|
||||
std::unique_ptr<BacktraceMock> backtrace_mock_;
|
||||
std::unique_ptr<MemoryMock> memory_mock_;
|
||||
|
||||
log_t log_;
|
||||
};
|
||||
|
|
@ -151,9 +199,9 @@ TEST_F(DumpMemoryTest, aligned_addr) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x12345678, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x12345678, "memory near %.2s:", "r1");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -170,10 +218,10 @@ TEST_F(DumpMemoryTest, partial_read) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(96);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(96);
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x12345679, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x12345679, "memory near %.2s:", "r1");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -190,9 +238,9 @@ TEST_F(DumpMemoryTest, unaligned_addr) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x12345679, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x12345679, "memory near %.2s:", "r1");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -205,7 +253,7 @@ TEST_F(DumpMemoryTest, unaligned_addr) {
|
|||
}
|
||||
|
||||
TEST_F(DumpMemoryTest, memory_unreadable) {
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xa2345678, "memory near pc:");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xa2345678, "memory near pc:");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -259,9 +307,9 @@ TEST_F(DumpMemoryTest, memory_partially_unreadable) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x12345600, "memory near pc:");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x12345600, "memory near pc:");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -278,10 +326,10 @@ TEST_F(DumpMemoryTest, memory_partially_unreadable_unaligned_return) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(102);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(102);
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x12345600, "memory near pc:");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x12345600, "memory near pc:");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -303,10 +351,10 @@ TEST_F(DumpMemoryTest, memory_partially_unreadable_two_unaligned_reads) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(45);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(45);
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x12345600, "memory near pc:");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x12345600, "memory near pc:");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -330,9 +378,9 @@ TEST_F(DumpMemoryTest, memory_partially_unreadable_two_unaligned_reads) {
|
|||
TEST_F(DumpMemoryTest, address_low_fence) {
|
||||
uint8_t buffer[256];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x1000, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x1000, "memory near %.2s:", "r1");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -384,9 +432,9 @@ TEST_F(DumpMemoryTest, address_low_fence) {
|
|||
TEST_F(DumpMemoryTest, memory_address_too_low) {
|
||||
uint8_t buffer[256];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0, "memory near %.2s:", "r1");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -401,16 +449,16 @@ TEST_F(DumpMemoryTest, memory_address_too_low) {
|
|||
TEST_F(DumpMemoryTest, memory_address_too_high) {
|
||||
uint8_t buffer[256];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
#if defined(__LP64__)
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x4000000000000000UL, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x4000000000000000UL - 32, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x4000000000000000UL - 216, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL - 32, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL - 216, "memory near %.2s:", "r1");
|
||||
#else
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xffff0000, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xffff0000 - 32, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xffff0000 - 220, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xffff0000, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xffff0000 - 32, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xffff0000 - 220, "memory near %.2s:", "r1");
|
||||
#endif
|
||||
|
||||
std::string tombstone_contents;
|
||||
|
|
@ -426,12 +474,12 @@ TEST_F(DumpMemoryTest, memory_address_too_high) {
|
|||
TEST_F(DumpMemoryTest, memory_address_would_overflow) {
|
||||
uint8_t buffer[256];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
#if defined(__LP64__)
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xfffffffffffffff0, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xfffffffffffffff0, "memory near %.2s:", "r1");
|
||||
#else
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xfffffff0, "memory near %.2s:", "r1");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xfffffff0, "memory near %.2s:", "r1");
|
||||
#endif
|
||||
|
||||
std::string tombstone_contents;
|
||||
|
|
@ -449,12 +497,12 @@ TEST_F(DumpMemoryTest, memory_address_nearly_too_high) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
|
||||
#if defined(__LP64__)
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0x4000000000000000UL - 224, "memory near %.2s:", "r4");
|
||||
dump_memory(&log_, memory_mock_.get(), 0x4000000000000000UL - 224, "memory near %.2s:", "r4");
|
||||
#else
|
||||
dump_memory(&log_, backtrace_mock_.get(), 0xffff0000 - 224, "memory near %.2s:", "r4");
|
||||
dump_memory(&log_, memory_mock_.get(), 0xffff0000 - 224, "memory near %.2s:", "r4");
|
||||
#endif
|
||||
|
||||
std::string tombstone_contents;
|
||||
|
|
@ -509,12 +557,12 @@ TEST_F(DumpMemoryTest, first_read_empty) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(0);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(0);
|
||||
|
||||
size_t page_size = sysconf(_SC_PAGE_SIZE);
|
||||
uintptr_t addr = 0x10000020 + page_size - 120;
|
||||
dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
dump_memory(&log_, memory_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -568,12 +616,12 @@ TEST_F(DumpMemoryTest, first_read_empty_second_read_stops) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(0);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(0);
|
||||
|
||||
size_t page_size = sysconf(_SC_PAGE_SIZE);
|
||||
uintptr_t addr = 0x10000020 + page_size - 192;
|
||||
dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
dump_memory(&log_, memory_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -627,11 +675,11 @@ TEST_F(DumpMemoryTest, first_read_empty_next_page_out_of_range) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(0);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(0);
|
||||
|
||||
uintptr_t addr = 0x10000020;
|
||||
dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
dump_memory(&log_, memory_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -685,13 +733,13 @@ TEST_F(DumpMemoryTest, first_read_empty_next_page_out_of_range_fence_post) {
|
|||
for (size_t i = 0; i < sizeof(buffer); i++) {
|
||||
buffer[i] = i;
|
||||
}
|
||||
backtrace_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
backtrace_mock_->SetPartialReadAmount(0);
|
||||
memory_mock_->SetReadData(buffer, sizeof(buffer));
|
||||
memory_mock_->SetPartialReadAmount(0);
|
||||
|
||||
size_t page_size = sysconf(_SC_PAGE_SIZE);
|
||||
uintptr_t addr = 0x10000020 + page_size - 256;
|
||||
|
||||
dump_memory(&log_, backtrace_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
dump_memory(&log_, memory_mock_.get(), addr, "memory near %.2s:", "r4");
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
class Backtrace;
|
||||
namespace unwindstack {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
std::string g_build_id;
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ void elf_set_fake_build_id(const std::string& build_id) {
|
|||
g_build_id = build_id;
|
||||
}
|
||||
|
||||
bool elf_get_build_id(Backtrace*, uintptr_t, std::string* build_id) {
|
||||
bool elf_get_build_id(unwindstack::Memory*, uintptr_t, std::string* build_id) {
|
||||
if (g_build_id != "") {
|
||||
*build_id = g_build_id;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,53 +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.
|
||||
*/
|
||||
|
||||
#include "ptrace_fake.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
siginfo_t g_fake_si = {.si_signo = 0};
|
||||
|
||||
void ptrace_set_fake_getsiginfo(const siginfo_t& si) {
|
||||
g_fake_si = si;
|
||||
}
|
||||
|
||||
#if !defined(__BIONIC__)
|
||||
extern "C" long ptrace_fake(enum __ptrace_request request, ...) {
|
||||
#else
|
||||
extern "C" long ptrace_fake(int request, ...) {
|
||||
#endif
|
||||
if (request == PTRACE_GETSIGINFO) {
|
||||
if (g_fake_si.si_signo == 0) {
|
||||
errno = EFAULT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, request);
|
||||
va_arg(ap, int);
|
||||
va_arg(ap, int);
|
||||
siginfo_t* si = va_arg(ap, siginfo*);
|
||||
va_end(ap);
|
||||
*si = g_fake_si;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -29,11 +29,6 @@
|
|||
#include "elf_fake.h"
|
||||
#include "host_signal_fixup.h"
|
||||
#include "log_fake.h"
|
||||
#include "ptrace_fake.h"
|
||||
|
||||
// In order to test this code, we need to include the tombstone.cpp code.
|
||||
// Including it, also allows us to override the ptrace function.
|
||||
#define ptrace ptrace_fake
|
||||
|
||||
#include "tombstone.cpp"
|
||||
|
||||
|
|
@ -50,7 +45,6 @@ class TombstoneTest : public ::testing::Test {
|
|||
protected:
|
||||
virtual void SetUp() {
|
||||
map_mock_.reset(new BacktraceMapMock());
|
||||
backtrace_mock_.reset(new BacktraceMock(map_mock_.get()));
|
||||
|
||||
char tmp_file[256];
|
||||
const char data_template[] = "/data/local/tmp/debuggerd_memory_testXXXXXX";
|
||||
|
|
@ -77,11 +71,6 @@ class TombstoneTest : public ::testing::Test {
|
|||
|
||||
resetLogs();
|
||||
elf_set_fake_build_id("");
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.si_signo = SIGABRT;
|
||||
si.si_code = SI_KERNEL;
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
|
|
@ -91,7 +80,6 @@ class TombstoneTest : public ::testing::Test {
|
|||
}
|
||||
|
||||
std::unique_ptr<BacktraceMapMock> map_mock_;
|
||||
std::unique_ptr<BacktraceMock> backtrace_mock_;
|
||||
|
||||
log_t log_;
|
||||
std::string amfd_data_;
|
||||
|
|
@ -108,7 +96,7 @@ TEST_F(TombstoneTest, single_map) {
|
|||
#endif
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -143,7 +131,7 @@ TEST_F(TombstoneTest, single_map_elf_build_id) {
|
|||
map_mock_->AddMap(map);
|
||||
|
||||
elf_set_fake_build_id("abcdef1234567890abcdef1234567890");
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -182,7 +170,7 @@ TEST_F(TombstoneTest, single_map_no_build_id) {
|
|||
map_mock_->AddMap(map);
|
||||
|
||||
elf_set_fake_build_id("abcdef1234567890abcdef1234567890");
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -240,7 +228,7 @@ TEST_F(TombstoneTest, multiple_maps) {
|
|||
map.name = "/system/lib/fake.so";
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -294,13 +282,7 @@ TEST_F(TombstoneTest, multiple_maps_fault_address_before) {
|
|||
map.name = "/system/lib/fake.so";
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.si_signo = SIGBUS;
|
||||
si.si_code = SI_KERNEL;
|
||||
si.si_addr = reinterpret_cast<void*>(0x1000);
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0x1000);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -352,13 +334,7 @@ TEST_F(TombstoneTest, multiple_maps_fault_address_between) {
|
|||
map.name = "/system/lib/fake.so";
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.si_signo = SIGBUS;
|
||||
si.si_code = SI_KERNEL;
|
||||
si.si_addr = reinterpret_cast<void*>(0xa533000);
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0xa533000);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -410,13 +386,7 @@ TEST_F(TombstoneTest, multiple_maps_fault_address_in_map) {
|
|||
map.name = "/system/lib/fake.so";
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.si_signo = SIGBUS;
|
||||
si.si_code = SI_KERNEL;
|
||||
si.si_addr = reinterpret_cast<void*>(0xa534040);
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, 0xa534040);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -466,17 +436,12 @@ TEST_F(TombstoneTest, multiple_maps_fault_address_after) {
|
|||
map.name = "/system/lib/fake.so";
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.si_signo = SIGBUS;
|
||||
si.si_code = SI_KERNEL;
|
||||
#if defined(__LP64__)
|
||||
si.si_addr = reinterpret_cast<void*>(0x12345a534040UL);
|
||||
uintptr_t addr = 0x12345a534040UL;
|
||||
#else
|
||||
si.si_addr = reinterpret_cast<void*>(0xf534040UL);
|
||||
uintptr_t addr = 0xf534040UL;
|
||||
#endif
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
dump_all_maps(&log_, map_mock_.get(), nullptr, addr);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
|
@ -503,124 +468,6 @@ TEST_F(TombstoneTest, multiple_maps_fault_address_after) {
|
|||
ASSERT_STREQ("", getFakeLogPrint().c_str());
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, multiple_maps_getsiginfo_fail) {
|
||||
backtrace_map_t map;
|
||||
|
||||
map.start = 0xa434000;
|
||||
map.end = 0xa435000;
|
||||
map.offset = 0x1000;
|
||||
map.load_bias = 0xd000;
|
||||
map.flags = PROT_WRITE;
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
const char* expected_dump =
|
||||
"\nmemory map (1 entry):\n"
|
||||
#if defined(__LP64__)
|
||||
" 00000000'0a434000-00000000'0a434fff -w- 1000 1000 (load bias 0xd000)\n";
|
||||
#else
|
||||
" 0a434000-0a434fff -w- 1000 1000 (load bias 0xd000)\n";
|
||||
#endif
|
||||
ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
|
||||
|
||||
ASSERT_STREQ("", amfd_data_.c_str());
|
||||
|
||||
// Verify that the log buf is empty, and no error messages.
|
||||
ASSERT_STREQ("", getFakeLogBuf().c_str());
|
||||
ASSERT_STREQ("6 DEBUG Cannot get siginfo for 100: Bad address\n\n", getFakeLogPrint().c_str());
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, multiple_maps_check_signal_has_si_addr) {
|
||||
backtrace_map_t map;
|
||||
|
||||
map.start = 0xa434000;
|
||||
map.end = 0xa435000;
|
||||
map.flags = PROT_WRITE;
|
||||
map_mock_->AddMap(map);
|
||||
|
||||
for (int i = 1; i < 255; i++) {
|
||||
ASSERT_TRUE(ftruncate(log_.tfd, 0) == 0);
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.si_signo = i;
|
||||
si.si_code = SI_KERNEL;
|
||||
si.si_addr = reinterpret_cast<void*>(0x1000);
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
dump_all_maps(backtrace_mock_.get(), map_mock_.get(), &log_, 100);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
bool has_addr = false;
|
||||
switch (si.si_signo) {
|
||||
case SIGBUS:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGSEGV:
|
||||
case SIGTRAP:
|
||||
has_addr = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const char* expected_addr_dump = \
|
||||
"\nmemory map (1 entry):\n"
|
||||
#if defined(__LP64__)
|
||||
"--->Fault address falls at 00000000'00001000 before any mapped regions\n"
|
||||
" 00000000'0a434000-00000000'0a434fff -w- 0 1000\n";
|
||||
#else
|
||||
"--->Fault address falls at 00001000 before any mapped regions\n"
|
||||
" 0a434000-0a434fff -w- 0 1000\n";
|
||||
#endif
|
||||
const char* expected_dump = \
|
||||
"\nmemory map (1 entry):\n"
|
||||
#if defined(__LP64__)
|
||||
" 00000000'0a434000-00000000'0a434fff -w- 0 1000\n";
|
||||
#else
|
||||
" 0a434000-0a434fff -w- 0 1000\n";
|
||||
#endif
|
||||
if (has_addr) {
|
||||
ASSERT_STREQ(expected_addr_dump, tombstone_contents.c_str())
|
||||
<< "Signal " << si.si_signo << " expected to include an address.";
|
||||
} else {
|
||||
ASSERT_STREQ(expected_dump, tombstone_contents.c_str())
|
||||
<< "Signal " << si.si_signo << " is not expected to include an address.";
|
||||
}
|
||||
|
||||
ASSERT_STREQ("", amfd_data_.c_str());
|
||||
|
||||
// Verify that the log buf is empty, and no error messages.
|
||||
ASSERT_STREQ("", getFakeLogBuf().c_str());
|
||||
ASSERT_STREQ("", getFakeLogPrint().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, dump_signal_info_error) {
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
ptrace_set_fake_getsiginfo(si);
|
||||
|
||||
dump_signal_info(&log_, 123);
|
||||
|
||||
std::string tombstone_contents;
|
||||
ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
|
||||
ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
|
||||
ASSERT_STREQ("", tombstone_contents.c_str());
|
||||
|
||||
ASSERT_STREQ("", getFakeLogBuf().c_str());
|
||||
ASSERT_STREQ("6 DEBUG cannot get siginfo: Bad address\n\n", getFakeLogPrint().c_str());
|
||||
|
||||
ASSERT_STREQ("", amfd_data_.c_str());
|
||||
}
|
||||
|
||||
TEST_F(TombstoneTest, dump_log_file_error) {
|
||||
log_.should_retrieve_logcat = true;
|
||||
dump_log_file(&log_, 123, "/fake/filename", 10);
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@
|
|||
#include <string>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/properties.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <android/log.h>
|
||||
#include <backtrace/Backtrace.h>
|
||||
|
|
@ -44,169 +46,27 @@
|
|||
#include <log/log.h>
|
||||
#include <log/logprint.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
#include <unwindstack/Regs.h>
|
||||
|
||||
// Needed to get DEBUGGER_SIGNAL.
|
||||
#include "debuggerd/handler.h"
|
||||
|
||||
#include "libdebuggerd/backtrace.h"
|
||||
#include "libdebuggerd/elf_utils.h"
|
||||
#include "libdebuggerd/machine.h"
|
||||
#include "libdebuggerd/open_files_list.h"
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
using android::base::GetBoolProperty;
|
||||
using android::base::GetProperty;
|
||||
using android::base::StringPrintf;
|
||||
using android::base::unique_fd;
|
||||
|
||||
using unwindstack::Memory;
|
||||
using unwindstack::Regs;
|
||||
|
||||
#define STACK_WORDS 16
|
||||
|
||||
static bool signal_has_si_addr(int si_signo, int si_code) {
|
||||
// Manually sent signals won't have si_addr.
|
||||
if (si_code == SI_USER || si_code == SI_QUEUE || si_code == SI_TKILL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (si_signo) {
|
||||
case SIGBUS:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGSEGV:
|
||||
case SIGTRAP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* get_signame(int sig) {
|
||||
switch (sig) {
|
||||
case SIGABRT: return "SIGABRT";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
case SIGFPE: return "SIGFPE";
|
||||
case SIGILL: return "SIGILL";
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
#if defined(SIGSTKFLT)
|
||||
case SIGSTKFLT: return "SIGSTKFLT";
|
||||
#endif
|
||||
case SIGSTOP: return "SIGSTOP";
|
||||
case SIGSYS: return "SIGSYS";
|
||||
case SIGTRAP: return "SIGTRAP";
|
||||
case DEBUGGER_SIGNAL: return "<debuggerd signal>";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* get_sigcode(int signo, int code) {
|
||||
// Try the signal-specific codes...
|
||||
switch (signo) {
|
||||
case SIGILL:
|
||||
switch (code) {
|
||||
case ILL_ILLOPC: return "ILL_ILLOPC";
|
||||
case ILL_ILLOPN: return "ILL_ILLOPN";
|
||||
case ILL_ILLADR: return "ILL_ILLADR";
|
||||
case ILL_ILLTRP: return "ILL_ILLTRP";
|
||||
case ILL_PRVOPC: return "ILL_PRVOPC";
|
||||
case ILL_PRVREG: return "ILL_PRVREG";
|
||||
case ILL_COPROC: return "ILL_COPROC";
|
||||
case ILL_BADSTK: return "ILL_BADSTK";
|
||||
}
|
||||
static_assert(NSIGILL == ILL_BADSTK, "missing ILL_* si_code");
|
||||
break;
|
||||
case SIGBUS:
|
||||
switch (code) {
|
||||
case BUS_ADRALN: return "BUS_ADRALN";
|
||||
case BUS_ADRERR: return "BUS_ADRERR";
|
||||
case BUS_OBJERR: return "BUS_OBJERR";
|
||||
case BUS_MCEERR_AR: return "BUS_MCEERR_AR";
|
||||
case BUS_MCEERR_AO: return "BUS_MCEERR_AO";
|
||||
}
|
||||
static_assert(NSIGBUS == BUS_MCEERR_AO, "missing BUS_* si_code");
|
||||
break;
|
||||
case SIGFPE:
|
||||
switch (code) {
|
||||
case FPE_INTDIV: return "FPE_INTDIV";
|
||||
case FPE_INTOVF: return "FPE_INTOVF";
|
||||
case FPE_FLTDIV: return "FPE_FLTDIV";
|
||||
case FPE_FLTOVF: return "FPE_FLTOVF";
|
||||
case FPE_FLTUND: return "FPE_FLTUND";
|
||||
case FPE_FLTRES: return "FPE_FLTRES";
|
||||
case FPE_FLTINV: return "FPE_FLTINV";
|
||||
case FPE_FLTSUB: return "FPE_FLTSUB";
|
||||
}
|
||||
static_assert(NSIGFPE == FPE_FLTSUB, "missing FPE_* si_code");
|
||||
break;
|
||||
case SIGSEGV:
|
||||
switch (code) {
|
||||
case SEGV_MAPERR: return "SEGV_MAPERR";
|
||||
case SEGV_ACCERR: return "SEGV_ACCERR";
|
||||
#if defined(SEGV_BNDERR)
|
||||
case SEGV_BNDERR: return "SEGV_BNDERR";
|
||||
#endif
|
||||
#if defined(SEGV_PKUERR)
|
||||
case SEGV_PKUERR: return "SEGV_PKUERR";
|
||||
#endif
|
||||
}
|
||||
#if defined(SEGV_PKUERR)
|
||||
static_assert(NSIGSEGV == SEGV_PKUERR, "missing SEGV_* si_code");
|
||||
#elif defined(SEGV_BNDERR)
|
||||
static_assert(NSIGSEGV == SEGV_BNDERR, "missing SEGV_* si_code");
|
||||
#else
|
||||
static_assert(NSIGSEGV == SEGV_ACCERR, "missing SEGV_* si_code");
|
||||
#endif
|
||||
break;
|
||||
#if defined(SYS_SECCOMP) // Our glibc is too old, and we build this for the host too.
|
||||
case SIGSYS:
|
||||
switch (code) {
|
||||
case SYS_SECCOMP: return "SYS_SECCOMP";
|
||||
}
|
||||
static_assert(NSIGSYS == SYS_SECCOMP, "missing SYS_* si_code");
|
||||
break;
|
||||
#endif
|
||||
case SIGTRAP:
|
||||
switch (code) {
|
||||
case TRAP_BRKPT: return "TRAP_BRKPT";
|
||||
case TRAP_TRACE: return "TRAP_TRACE";
|
||||
case TRAP_BRANCH: return "TRAP_BRANCH";
|
||||
case TRAP_HWBKPT: return "TRAP_HWBKPT";
|
||||
}
|
||||
if ((code & 0xff) == SIGTRAP) {
|
||||
switch ((code >> 8) & 0xff) {
|
||||
case PTRACE_EVENT_FORK:
|
||||
return "PTRACE_EVENT_FORK";
|
||||
case PTRACE_EVENT_VFORK:
|
||||
return "PTRACE_EVENT_VFORK";
|
||||
case PTRACE_EVENT_CLONE:
|
||||
return "PTRACE_EVENT_CLONE";
|
||||
case PTRACE_EVENT_EXEC:
|
||||
return "PTRACE_EVENT_EXEC";
|
||||
case PTRACE_EVENT_VFORK_DONE:
|
||||
return "PTRACE_EVENT_VFORK_DONE";
|
||||
case PTRACE_EVENT_EXIT:
|
||||
return "PTRACE_EVENT_EXIT";
|
||||
case PTRACE_EVENT_SECCOMP:
|
||||
return "PTRACE_EVENT_SECCOMP";
|
||||
case PTRACE_EVENT_STOP:
|
||||
return "PTRACE_EVENT_STOP";
|
||||
}
|
||||
}
|
||||
static_assert(NSIGTRAP == TRAP_HWBKPT, "missing TRAP_* si_code");
|
||||
break;
|
||||
}
|
||||
// Then the other codes...
|
||||
switch (code) {
|
||||
case SI_USER: return "SI_USER";
|
||||
case SI_KERNEL: return "SI_KERNEL";
|
||||
case SI_QUEUE: return "SI_QUEUE";
|
||||
case SI_TIMER: return "SI_TIMER";
|
||||
case SI_MESGQ: return "SI_MESGQ";
|
||||
case SI_ASYNCIO: return "SI_ASYNCIO";
|
||||
case SI_SIGIO: return "SI_SIGIO";
|
||||
case SI_TKILL: return "SI_TKILL";
|
||||
case SI_DETHREAD: return "SI_DETHREAD";
|
||||
}
|
||||
// Then give up...
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void dump_header_info(log_t* log) {
|
||||
auto fingerprint = GetProperty("ro.build.fingerprint", "unknown");
|
||||
auto revision = GetProperty("ro.revision", "unknown");
|
||||
|
|
@ -216,73 +76,64 @@ static void dump_header_info(log_t* log) {
|
|||
_LOG(log, logtype::HEADER, "ABI: '%s'\n", ABI_STRING);
|
||||
}
|
||||
|
||||
static void dump_probable_cause(log_t* log, const siginfo_t& si) {
|
||||
static void dump_probable_cause(log_t* log, const siginfo_t* si) {
|
||||
std::string cause;
|
||||
if (si.si_signo == SIGSEGV && si.si_code == SEGV_MAPERR) {
|
||||
if (si.si_addr < reinterpret_cast<void*>(4096)) {
|
||||
if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
|
||||
if (si->si_addr < reinterpret_cast<void*>(4096)) {
|
||||
cause = StringPrintf("null pointer dereference");
|
||||
} else if (si.si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
|
||||
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
|
||||
cause = "call to kuser_helper_version";
|
||||
} else if (si.si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
|
||||
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
|
||||
cause = "call to kuser_get_tls";
|
||||
} else if (si.si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
|
||||
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
|
||||
cause = "call to kuser_cmpxchg";
|
||||
} else if (si.si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
|
||||
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
|
||||
cause = "call to kuser_memory_barrier";
|
||||
} else if (si.si_addr == reinterpret_cast<void*>(0xffff0f60)) {
|
||||
} else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
|
||||
cause = "call to kuser_cmpxchg64";
|
||||
}
|
||||
} else if (si.si_signo == SIGSYS && si.si_code == SYS_SECCOMP) {
|
||||
cause = StringPrintf("seccomp prevented call to disallowed %s system call %d",
|
||||
ABI_STRING, si.si_syscall);
|
||||
} else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
|
||||
cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
|
||||
si->si_syscall);
|
||||
}
|
||||
|
||||
if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str());
|
||||
}
|
||||
|
||||
static void dump_signal_info(log_t* log, const siginfo_t* siginfo) {
|
||||
const siginfo_t& si = *siginfo;
|
||||
static void dump_signal_info(log_t* log, const siginfo_t* si) {
|
||||
char addr_desc[32]; // ", fault addr 0x1234"
|
||||
if (signal_has_si_addr(si.si_signo, si.si_code)) {
|
||||
snprintf(addr_desc, sizeof(addr_desc), "%p", si.si_addr);
|
||||
if (signal_has_si_addr(si->si_signo, si->si_code)) {
|
||||
snprintf(addr_desc, sizeof(addr_desc), "%p", si->si_addr);
|
||||
} else {
|
||||
snprintf(addr_desc, sizeof(addr_desc), "--------");
|
||||
}
|
||||
|
||||
_LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s), fault addr %s\n", si.si_signo,
|
||||
get_signame(si.si_signo), si.si_code, get_sigcode(si.si_signo, si.si_code), addr_desc);
|
||||
_LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s), fault addr %s\n", si->si_signo,
|
||||
get_signame(si->si_signo), si->si_code, get_sigcode(si->si_signo, si->si_code), addr_desc);
|
||||
|
||||
dump_probable_cause(log, si);
|
||||
}
|
||||
|
||||
static void dump_signal_info(log_t* log, pid_t tid) {
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si) == -1) {
|
||||
ALOGE("cannot get siginfo: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
dump_signal_info(log, &si);
|
||||
}
|
||||
|
||||
static void dump_thread_info(log_t* log, pid_t pid, pid_t tid, const char* process_name,
|
||||
const char* thread_name) {
|
||||
static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) {
|
||||
// Blacklist logd, logd.reader, logd.writer, logd.auditd, logd.control ...
|
||||
// TODO: Why is this controlled by thread name?
|
||||
if (strcmp(thread_name, "logd") == 0 || strncmp(thread_name, "logd.", 4) == 0) {
|
||||
if (thread_info.thread_name == "logd" ||
|
||||
android::base::StartsWith(thread_info.thread_name, "logd.")) {
|
||||
log->should_retrieve_logcat = false;
|
||||
}
|
||||
|
||||
_LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s >>> %s <<<\n", pid, tid, thread_name,
|
||||
process_name);
|
||||
_LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s >>> %s <<<\n", thread_info.pid,
|
||||
thread_info.tid, thread_info.thread_name.c_str(), thread_info.process_name.c_str());
|
||||
}
|
||||
|
||||
static void dump_stack_segment(
|
||||
Backtrace* backtrace, log_t* log, uintptr_t* sp, size_t words, int label) {
|
||||
static void dump_stack_segment(log_t* log, BacktraceMap* backtrace_map, Memory* process_memory,
|
||||
uintptr_t* sp, size_t words, int label) {
|
||||
// Read the data all at once.
|
||||
word_t stack_data[words];
|
||||
size_t bytes_read = backtrace->Read(*sp, reinterpret_cast<uint8_t*>(&stack_data[0]), sizeof(word_t) * words);
|
||||
|
||||
// TODO: Do we need to word align this for crashes caused by a misaligned sp?
|
||||
// The process_vm_readv implementation of Memory should handle this appropriately?
|
||||
size_t bytes_read = process_memory->Read(*sp, stack_data, sizeof(word_t) * words);
|
||||
words = bytes_read / sizeof(word_t);
|
||||
std::string line;
|
||||
for (size_t i = 0; i < words; i++) {
|
||||
|
|
@ -296,11 +147,11 @@ static void dump_stack_segment(
|
|||
line += StringPrintf("%" PRIPTR " %" PRIPTR, *sp, stack_data[i]);
|
||||
|
||||
backtrace_map_t map;
|
||||
backtrace->FillInMap(stack_data[i], &map);
|
||||
backtrace_map->FillIn(stack_data[i], &map);
|
||||
if (BacktraceMap::IsValid(map) && !map.name.empty()) {
|
||||
line += " " + map.name;
|
||||
uintptr_t offset = 0;
|
||||
std::string func_name(backtrace->GetFunctionName(stack_data[i], &offset, &map));
|
||||
std::string func_name = backtrace_map->GetFunctionName(stack_data[i], &offset);
|
||||
if (!func_name.empty()) {
|
||||
line += " (" + func_name;
|
||||
if (offset) {
|
||||
|
|
@ -315,36 +166,38 @@ static void dump_stack_segment(
|
|||
}
|
||||
}
|
||||
|
||||
static void dump_stack(Backtrace* backtrace, log_t* log) {
|
||||
static void dump_stack(log_t* log, BacktraceMap* backtrace_map, Memory* process_memory,
|
||||
std::vector<backtrace_frame_data_t>& frames) {
|
||||
size_t first = 0, last;
|
||||
for (size_t i = 0; i < backtrace->NumFrames(); i++) {
|
||||
const backtrace_frame_data_t* frame = backtrace->GetFrame(i);
|
||||
if (frame->sp) {
|
||||
for (size_t i = 0; i < frames.size(); i++) {
|
||||
const backtrace_frame_data_t& frame = frames[i];
|
||||
if (frame.sp) {
|
||||
if (!first) {
|
||||
first = i+1;
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!first) {
|
||||
return;
|
||||
}
|
||||
first--;
|
||||
|
||||
// Dump a few words before the first frame.
|
||||
word_t sp = backtrace->GetFrame(first)->sp - STACK_WORDS * sizeof(word_t);
|
||||
dump_stack_segment(backtrace, log, &sp, STACK_WORDS, -1);
|
||||
word_t sp = frames[first].sp - STACK_WORDS * sizeof(word_t);
|
||||
dump_stack_segment(log, backtrace_map, process_memory, &sp, STACK_WORDS, -1);
|
||||
|
||||
// Dump a few words from all successive frames.
|
||||
// Only log the first 3 frames, put the rest in the tombstone.
|
||||
for (size_t i = first; i <= last; i++) {
|
||||
const backtrace_frame_data_t* frame = backtrace->GetFrame(i);
|
||||
const backtrace_frame_data_t* frame = &frames[i];
|
||||
if (sp != frame->sp) {
|
||||
_LOG(log, logtype::STACK, " ........ ........\n");
|
||||
sp = frame->sp;
|
||||
}
|
||||
if (i == last) {
|
||||
dump_stack_segment(backtrace, log, &sp, STACK_WORDS, i);
|
||||
dump_stack_segment(log, backtrace_map, process_memory, &sp, STACK_WORDS, i);
|
||||
if (sp < frame->sp + frame->stack_size) {
|
||||
_LOG(log, logtype::STACK, " ........ ........\n");
|
||||
}
|
||||
|
|
@ -355,7 +208,7 @@ static void dump_stack(Backtrace* backtrace, log_t* log) {
|
|||
} else if (words > STACK_WORDS) {
|
||||
words = STACK_WORDS;
|
||||
}
|
||||
dump_stack_segment(backtrace, log, &sp, words, i);
|
||||
dump_stack_segment(log, backtrace_map, process_memory, &sp, words, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -372,44 +225,34 @@ static std::string get_addr_string(uintptr_t addr) {
|
|||
return addr_str;
|
||||
}
|
||||
|
||||
static void dump_abort_message(Backtrace* backtrace, log_t* log, uintptr_t address) {
|
||||
static void dump_abort_message(log_t* log, Memory* process_memory, uintptr_t address) {
|
||||
if (address == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
address += sizeof(size_t); // Skip the buffer length.
|
||||
size_t length;
|
||||
if (!process_memory->ReadFully(address, &length, sizeof(length))) {
|
||||
_LOG(log, logtype::HEADER, "Failed to read abort message header: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
char msg[512];
|
||||
memset(msg, 0, sizeof(msg));
|
||||
char* p = &msg[0];
|
||||
while (p < &msg[sizeof(msg)]) {
|
||||
word_t data;
|
||||
size_t len = sizeof(word_t);
|
||||
if (!backtrace->ReadWord(address, &data)) {
|
||||
break;
|
||||
}
|
||||
address += sizeof(word_t);
|
||||
|
||||
while (len > 0 && (*p++ = (data >> (sizeof(word_t) - len) * 8) & 0xff) != 0) {
|
||||
len--;
|
||||
}
|
||||
if (length >= sizeof(msg)) {
|
||||
_LOG(log, logtype::HEADER, "Abort message too long: claimed length = %zd\n", length);
|
||||
return;
|
||||
}
|
||||
msg[sizeof(msg) - 1] = '\0';
|
||||
|
||||
if (!process_memory->ReadFully(address + sizeof(length), msg, length)) {
|
||||
_LOG(log, logtype::HEADER, "Failed to read abort message: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
msg[length] = '\0';
|
||||
_LOG(log, logtype::HEADER, "Abort message: '%s'\n", msg);
|
||||
}
|
||||
|
||||
static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, pid_t tid) {
|
||||
bool print_fault_address_marker = false;
|
||||
uintptr_t addr = 0;
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si) != -1) {
|
||||
print_fault_address_marker = signal_has_si_addr(si.si_signo, si.si_code);
|
||||
addr = reinterpret_cast<uintptr_t>(si.si_addr);
|
||||
} else {
|
||||
ALOGE("Cannot get siginfo for %d: %s\n", tid, strerror(errno));
|
||||
}
|
||||
static void dump_all_maps(log_t* log, BacktraceMap* map, Memory* process_memory, uintptr_t addr) {
|
||||
bool print_fault_address_marker = addr;
|
||||
|
||||
ScopedBacktraceMapIteratorLock lock(map);
|
||||
_LOG(log, logtype::MAPS,
|
||||
|
|
@ -464,7 +307,7 @@ static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, p
|
|||
space_needed = false;
|
||||
line += " " + entry->name;
|
||||
std::string build_id;
|
||||
if ((entry->flags & PROT_READ) && elf_get_build_id(backtrace, entry->start, &build_id)) {
|
||||
if ((entry->flags & PROT_READ) && elf_get_build_id(process_memory, entry->start, &build_id)) {
|
||||
line += " (BuildId: " + build_id + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -482,50 +325,117 @@ static void dump_all_maps(Backtrace* backtrace, BacktraceMap* map, log_t* log, p
|
|||
}
|
||||
}
|
||||
|
||||
static void dump_backtrace_and_stack(Backtrace* backtrace, log_t* log) {
|
||||
if (backtrace->NumFrames()) {
|
||||
_LOG(log, logtype::BACKTRACE, "\nbacktrace:\n");
|
||||
dump_backtrace_to_log(backtrace, log, " ");
|
||||
|
||||
_LOG(log, logtype::STACK, "\nstack:\n");
|
||||
dump_stack(backtrace, log);
|
||||
void dump_backtrace(log_t* log, std::vector<backtrace_frame_data_t>& frames, const char* prefix) {
|
||||
for (auto& frame : frames) {
|
||||
_LOG(log, logtype::BACKTRACE, "%s%s\n", prefix, Backtrace::FormatFrameData(&frame).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Weak noop implementation, real implementations are in <arch>/machine.cpp.
|
||||
__attribute__((weak)) void dump_registers(log_t* log, const ucontext_t*) {
|
||||
_LOG(log, logtype::REGISTERS, " register dumping unimplemented on this architecture");
|
||||
static void print_register_row(log_t* log,
|
||||
const std::vector<std::pair<std::string, uint64_t>>& registers) {
|
||||
std::string output;
|
||||
for (auto& [name, value] : registers) {
|
||||
output += android::base::StringPrintf(" %-3s %0*" PRIxPTR, name.c_str(),
|
||||
static_cast<int>(2 * sizeof(void*)),
|
||||
static_cast<uintptr_t>(value));
|
||||
}
|
||||
|
||||
_LOG(log, logtype::REGISTERS, " %s\n", output.c_str());
|
||||
}
|
||||
|
||||
static void dump_thread(log_t* log, pid_t pid, pid_t tid, const std::string& process_name,
|
||||
const std::string& thread_name, BacktraceMap* map,
|
||||
uintptr_t abort_msg_address, bool primary_thread) {
|
||||
log->current_tid = tid;
|
||||
void dump_registers(log_t* log, Regs* regs) {
|
||||
// Split lr/sp/pc into their own special row.
|
||||
static constexpr size_t column_count = 4;
|
||||
std::vector<std::pair<std::string, uint64_t>> current_row;
|
||||
std::vector<std::pair<std::string, uint64_t>> special_row;
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
static constexpr const char* special_registers[] = {"ip", "lr", "sp", "pc"};
|
||||
#elif defined(__i386__)
|
||||
static constexpr const char* special_registers[] = {"ebp", "esp", "eip"};
|
||||
#elif defined(__x86_64__)
|
||||
static constexpr const char* special_registers[] = {"rbp", "rsp", "rip"};
|
||||
#else
|
||||
static constexpr const char* special_registers[] = {};
|
||||
#endif
|
||||
|
||||
regs->IterateRegisters([log, ¤t_row, &special_row](const char* name, uint64_t value) {
|
||||
auto row = ¤t_row;
|
||||
for (const char* special_name : special_registers) {
|
||||
if (strcmp(special_name, name) == 0) {
|
||||
row = &special_row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
row->emplace_back(name, value);
|
||||
if (current_row.size() == column_count) {
|
||||
print_register_row(log, current_row);
|
||||
current_row.clear();
|
||||
}
|
||||
});
|
||||
|
||||
if (!current_row.empty()) {
|
||||
print_register_row(log, current_row);
|
||||
}
|
||||
|
||||
print_register_row(log, special_row);
|
||||
}
|
||||
|
||||
void dump_memory_and_code(log_t* log, Memory* memory, Regs* regs) {
|
||||
regs->IterateRegisters([log, memory](const char* name, uint64_t value) {
|
||||
dump_memory(log, memory, value, "memory near %s:", name);
|
||||
});
|
||||
}
|
||||
|
||||
static bool dump_thread(log_t* log, BacktraceMap* map, Memory* process_memory,
|
||||
const ThreadInfo& thread_info, uintptr_t abort_msg_address,
|
||||
bool primary_thread) {
|
||||
UNUSED(process_memory);
|
||||
log->current_tid = thread_info.tid;
|
||||
if (!primary_thread) {
|
||||
_LOG(log, logtype::THREAD, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
|
||||
}
|
||||
dump_thread_info(log, pid, tid, process_name.c_str(), thread_name.c_str());
|
||||
dump_signal_info(log, tid);
|
||||
dump_thread_info(log, thread_info);
|
||||
|
||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
|
||||
if (primary_thread) {
|
||||
dump_abort_message(backtrace.get(), log, abort_msg_address);
|
||||
if (thread_info.siginfo) {
|
||||
dump_signal_info(log, thread_info.siginfo);
|
||||
}
|
||||
dump_registers(log, tid);
|
||||
if (backtrace->Unwind(0)) {
|
||||
dump_backtrace_and_stack(backtrace.get(), log);
|
||||
} else {
|
||||
ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid);
|
||||
|
||||
dump_registers(log, thread_info.registers.get());
|
||||
|
||||
std::vector<backtrace_frame_data_t> frames;
|
||||
if (!Backtrace::Unwind(thread_info.registers.get(), map, &frames, 0, nullptr)) {
|
||||
_LOG(log, logtype::THREAD, "Failed to unwind");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!frames.empty()) {
|
||||
_LOG(log, logtype::BACKTRACE, "\nbacktrace:\n");
|
||||
dump_backtrace(log, frames, " ");
|
||||
|
||||
_LOG(log, logtype::STACK, "\nstack:\n");
|
||||
dump_stack(log, map, process_memory, frames);
|
||||
}
|
||||
|
||||
if (primary_thread) {
|
||||
dump_memory_and_code(log, backtrace.get());
|
||||
dump_abort_message(log, process_memory, abort_msg_address);
|
||||
}
|
||||
|
||||
if (primary_thread) {
|
||||
dump_memory_and_code(log, process_memory, thread_info.registers.get());
|
||||
if (map) {
|
||||
dump_all_maps(backtrace.get(), map, log, tid);
|
||||
uintptr_t addr = 0;
|
||||
siginfo_t* si = thread_info.siginfo;
|
||||
if (signal_has_si_addr(si->si_signo, si->si_code)) {
|
||||
addr = reinterpret_cast<uintptr_t>(si->si_addr);
|
||||
}
|
||||
dump_all_maps(log, map, process_memory, addr);
|
||||
}
|
||||
}
|
||||
|
||||
log->current_tid = log->crashed_tid;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads the contents of the specified log device, filters out the entries
|
||||
|
|
@ -534,8 +444,7 @@ static void dump_thread(log_t* log, pid_t pid, pid_t tid, const std::string& pro
|
|||
// If "tail" is non-zero, log the last "tail" number of lines.
|
||||
static EventTagMap* g_eventTagMap = NULL;
|
||||
|
||||
static void dump_log_file(
|
||||
log_t* log, pid_t pid, const char* filename, unsigned int tail) {
|
||||
static void dump_log_file(log_t* log, pid_t pid, const char* filename, unsigned int tail) {
|
||||
bool first = true;
|
||||
struct logger_list* logger_list;
|
||||
|
||||
|
|
@ -654,56 +563,15 @@ static void dump_log_file(
|
|||
// Dumps the logs generated by the specified pid to the tombstone, from both
|
||||
// "system" and "main" log devices. Ideally we'd interleave the output.
|
||||
static void dump_logs(log_t* log, pid_t pid, unsigned int tail) {
|
||||
if (pid == getpid()) {
|
||||
// Cowardly refuse to dump logs while we're running in-process.
|
||||
return;
|
||||
}
|
||||
|
||||
dump_log_file(log, pid, "system", tail);
|
||||
dump_log_file(log, pid, "main", tail);
|
||||
}
|
||||
|
||||
// Dumps all information about the specified pid to the tombstone.
|
||||
static void dump_crash(log_t* log, BacktraceMap* map, const OpenFilesList* open_files, pid_t pid,
|
||||
pid_t tid, const std::string& process_name,
|
||||
const std::map<pid_t, std::string>& threads, uintptr_t abort_msg_address) {
|
||||
// don't copy log messages to tombstone unless this is a dev device
|
||||
bool want_logs = GetBoolProperty("ro.debuggable", false);
|
||||
|
||||
_LOG(log, logtype::HEADER,
|
||||
"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
|
||||
dump_header_info(log);
|
||||
dump_thread(log, pid, tid, process_name, threads.find(tid)->second, map, abort_msg_address, true);
|
||||
if (want_logs) {
|
||||
dump_logs(log, pid, 5);
|
||||
}
|
||||
|
||||
for (const auto& it : threads) {
|
||||
pid_t thread_tid = it.first;
|
||||
const std::string& thread_name = it.second;
|
||||
|
||||
if (thread_tid != tid) {
|
||||
dump_thread(log, pid, thread_tid, process_name, thread_name, map, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (open_files) {
|
||||
_LOG(log, logtype::OPEN_FILES, "\nopen files:\n");
|
||||
dump_open_files_list_to_log(*open_files, log, " ");
|
||||
}
|
||||
|
||||
if (want_logs) {
|
||||
dump_logs(log, pid, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void engrave_tombstone(int tombstone_fd, BacktraceMap* map, const OpenFilesList* open_files,
|
||||
pid_t pid, pid_t tid, const std::string& process_name,
|
||||
const std::map<pid_t, std::string>& threads, uintptr_t abort_msg_address,
|
||||
std::string* amfd_data) {
|
||||
log_t log;
|
||||
log.current_tid = tid;
|
||||
log.crashed_tid = tid;
|
||||
log.tfd = tombstone_fd;
|
||||
log.amfd_data = amfd_data;
|
||||
dump_crash(&log, map, open_files, pid, tid, process_name, threads, abort_msg_address);
|
||||
}
|
||||
|
||||
void engrave_tombstone_ucontext(int tombstone_fd, uintptr_t abort_msg_address, siginfo_t* siginfo,
|
||||
ucontext_t* ucontext) {
|
||||
pid_t pid = getpid();
|
||||
|
|
@ -721,31 +589,69 @@ void engrave_tombstone_ucontext(int tombstone_fd, uintptr_t abort_msg_address, s
|
|||
read_with_default("/proc/self/comm", thread_name, sizeof(thread_name), "<unknown>");
|
||||
read_with_default("/proc/self/cmdline", process_name, sizeof(process_name), "<unknown>");
|
||||
|
||||
std::unique_ptr<Regs> regs(Regs::CreateFromUcontext(Regs::CurrentArch(), ucontext));
|
||||
|
||||
std::map<pid_t, ThreadInfo> threads;
|
||||
threads[gettid()] = ThreadInfo{
|
||||
.registers = std::move(regs),
|
||||
.tid = tid,
|
||||
.thread_name = thread_name,
|
||||
.pid = pid,
|
||||
.process_name = process_name,
|
||||
.siginfo = siginfo,
|
||||
};
|
||||
|
||||
std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(getpid(), false));
|
||||
if (!backtrace_map) {
|
||||
ALOGE("failed to create backtrace map");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
std::shared_ptr<Memory> process_memory = backtrace_map->GetProcessMemory();
|
||||
engrave_tombstone(unique_fd(dup(tombstone_fd)), backtrace_map.get(), process_memory.get(),
|
||||
threads, tid, abort_msg_address, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void engrave_tombstone(unique_fd output_fd, BacktraceMap* map, Memory* process_memory,
|
||||
const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
|
||||
uintptr_t abort_msg_address, OpenFilesList* open_files,
|
||||
std::string* amfd_data) {
|
||||
// don't copy log messages to tombstone unless this is a dev device
|
||||
bool want_logs = android::base::GetBoolProperty("ro.debuggable", false);
|
||||
|
||||
log_t log;
|
||||
log.current_tid = target_thread;
|
||||
log.crashed_tid = target_thread;
|
||||
log.tfd = output_fd.get();
|
||||
log.amfd_data = amfd_data;
|
||||
|
||||
_LOG(&log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
|
||||
dump_header_info(&log);
|
||||
dump_thread_info(&log, pid, tid, thread_name, process_name);
|
||||
dump_signal_info(&log, siginfo);
|
||||
|
||||
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid));
|
||||
dump_abort_message(backtrace.get(), &log, abort_msg_address);
|
||||
dump_registers(&log, ucontext);
|
||||
auto it = threads.find(target_thread);
|
||||
if (it == threads.end()) {
|
||||
LOG(FATAL) << "failed to find target thread";
|
||||
}
|
||||
dump_thread(&log, map, process_memory, it->second, abort_msg_address, true);
|
||||
|
||||
if (backtrace->Unwind(0, ucontext)) {
|
||||
dump_backtrace_and_stack(backtrace.get(), &log);
|
||||
} else {
|
||||
ALOGE("Unwind failed: pid = %d, tid = %d", pid, tid);
|
||||
if (want_logs) {
|
||||
dump_logs(&log, it->second.pid, 5);
|
||||
}
|
||||
|
||||
// TODO: Make this match the format of dump_all_maps above.
|
||||
_LOG(&log, logtype::MAPS, "memory map:\n");
|
||||
android::base::unique_fd maps_fd(open("/proc/self/maps", O_RDONLY | O_CLOEXEC));
|
||||
if (maps_fd == -1) {
|
||||
_LOG(&log, logtype::MAPS, " failed to open /proc/self/maps: %s", strerror(errno));
|
||||
} else {
|
||||
char buf[256];
|
||||
ssize_t rc;
|
||||
while ((rc = TEMP_FAILURE_RETRY(read(maps_fd.get(), buf, sizeof(buf)))) > 0) {
|
||||
android::base::WriteFully(tombstone_fd, buf, rc);
|
||||
for (auto& [tid, thread_info] : threads) {
|
||||
if (tid == target_thread) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dump_thread(&log, map, process_memory, thread_info, 0, false);
|
||||
}
|
||||
|
||||
if (open_files) {
|
||||
_LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");
|
||||
dump_open_files_list(&log, *open_files, " ");
|
||||
}
|
||||
|
||||
if (want_logs) {
|
||||
dump_logs(&log, it->second.pid, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@
|
|||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
|
|
@ -34,7 +36,9 @@
|
|||
#include <android-base/strings.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <debuggerd/handler.h>
|
||||
#include <log/log.h>
|
||||
#include <unwindstack/Memory.h>
|
||||
|
||||
using android::base::unique_fd;
|
||||
|
||||
|
|
@ -117,34 +121,10 @@ void _LOG(log_t* log, enum logtype ltype, const char* fmt, ...) {
|
|||
}
|
||||
}
|
||||
|
||||
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 false;
|
||||
} else if (n == tid) {
|
||||
if (WIFSTOPPED(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 false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MEMORY_BYTES_TO_DUMP 256
|
||||
#define MEMORY_BYTES_PER_LINE 16
|
||||
|
||||
void dump_memory(log_t* log, Backtrace* backtrace, uintptr_t addr, const char* fmt, ...) {
|
||||
void dump_memory(log_t* log, unwindstack::Memory* memory, uintptr_t addr, const char* fmt, ...) {
|
||||
std::string log_msg;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
|
@ -172,7 +152,7 @@ void dump_memory(log_t* log, Backtrace* backtrace, uintptr_t addr, const char* f
|
|||
// Dump 256 bytes
|
||||
uintptr_t data[MEMORY_BYTES_TO_DUMP/sizeof(uintptr_t)];
|
||||
memset(data, 0, MEMORY_BYTES_TO_DUMP);
|
||||
size_t bytes = backtrace->Read(addr, reinterpret_cast<uint8_t*>(data), sizeof(data));
|
||||
size_t bytes = memory->Read(addr, reinterpret_cast<uint8_t*>(data), sizeof(data));
|
||||
if (bytes % sizeof(uintptr_t) != 0) {
|
||||
// This should never happen, but just in case.
|
||||
ALOGE("Bytes read %zu, is not a multiple of %zu", bytes, sizeof(uintptr_t));
|
||||
|
|
@ -199,8 +179,8 @@ void dump_memory(log_t* log, Backtrace* backtrace, uintptr_t addr, const char* f
|
|||
// into a readable map. Only requires one extra read because a map has
|
||||
// to contain at least one page, and the total number of bytes to dump
|
||||
// is smaller than a page.
|
||||
size_t bytes2 = backtrace->Read(addr + start + bytes, reinterpret_cast<uint8_t*>(data) + bytes,
|
||||
sizeof(data) - bytes - start);
|
||||
size_t bytes2 = memory->Read(addr + start + bytes, reinterpret_cast<uint8_t*>(data) + bytes,
|
||||
sizeof(data) - bytes - start);
|
||||
bytes += bytes2;
|
||||
if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) {
|
||||
// This should never happen, but we'll try and continue any way.
|
||||
|
|
@ -264,3 +244,169 @@ void read_with_default(const char* path, char* buf, size_t len, const char* defa
|
|||
}
|
||||
strcpy(buf, default_value);
|
||||
}
|
||||
|
||||
void drop_capabilities() {
|
||||
__user_cap_header_struct capheader;
|
||||
memset(&capheader, 0, sizeof(capheader));
|
||||
capheader.version = _LINUX_CAPABILITY_VERSION_3;
|
||||
capheader.pid = 0;
|
||||
|
||||
__user_cap_data_struct capdata[2];
|
||||
memset(&capdata, 0, sizeof(capdata));
|
||||
|
||||
if (capset(&capheader, &capdata[0]) == -1) {
|
||||
PLOG(FATAL) << "failed to drop capabilities";
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
|
||||
PLOG(FATAL) << "failed to set PR_SET_NO_NEW_PRIVS";
|
||||
}
|
||||
}
|
||||
|
||||
bool signal_has_si_addr(int si_signo, int si_code) {
|
||||
// Manually sent signals won't have si_addr.
|
||||
if (si_code == SI_USER || si_code == SI_QUEUE || si_code == SI_TKILL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (si_signo) {
|
||||
case SIGBUS:
|
||||
case SIGFPE:
|
||||
case SIGILL:
|
||||
case SIGSEGV:
|
||||
case SIGTRAP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char* get_signame(int sig) {
|
||||
switch (sig) {
|
||||
case SIGABRT: return "SIGABRT";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
case SIGFPE: return "SIGFPE";
|
||||
case SIGILL: return "SIGILL";
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
#if defined(SIGSTKFLT)
|
||||
case SIGSTKFLT: return "SIGSTKFLT";
|
||||
#endif
|
||||
case SIGSTOP: return "SIGSTOP";
|
||||
case SIGSYS: return "SIGSYS";
|
||||
case SIGTRAP: return "SIGTRAP";
|
||||
case DEBUGGER_SIGNAL: return "<debuggerd signal>";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
const char* get_sigcode(int signo, int code) {
|
||||
// Try the signal-specific codes...
|
||||
switch (signo) {
|
||||
case SIGILL:
|
||||
switch (code) {
|
||||
case ILL_ILLOPC: return "ILL_ILLOPC";
|
||||
case ILL_ILLOPN: return "ILL_ILLOPN";
|
||||
case ILL_ILLADR: return "ILL_ILLADR";
|
||||
case ILL_ILLTRP: return "ILL_ILLTRP";
|
||||
case ILL_PRVOPC: return "ILL_PRVOPC";
|
||||
case ILL_PRVREG: return "ILL_PRVREG";
|
||||
case ILL_COPROC: return "ILL_COPROC";
|
||||
case ILL_BADSTK: return "ILL_BADSTK";
|
||||
}
|
||||
static_assert(NSIGILL == ILL_BADSTK, "missing ILL_* si_code");
|
||||
break;
|
||||
case SIGBUS:
|
||||
switch (code) {
|
||||
case BUS_ADRALN: return "BUS_ADRALN";
|
||||
case BUS_ADRERR: return "BUS_ADRERR";
|
||||
case BUS_OBJERR: return "BUS_OBJERR";
|
||||
case BUS_MCEERR_AR: return "BUS_MCEERR_AR";
|
||||
case BUS_MCEERR_AO: return "BUS_MCEERR_AO";
|
||||
}
|
||||
static_assert(NSIGBUS == BUS_MCEERR_AO, "missing BUS_* si_code");
|
||||
break;
|
||||
case SIGFPE:
|
||||
switch (code) {
|
||||
case FPE_INTDIV: return "FPE_INTDIV";
|
||||
case FPE_INTOVF: return "FPE_INTOVF";
|
||||
case FPE_FLTDIV: return "FPE_FLTDIV";
|
||||
case FPE_FLTOVF: return "FPE_FLTOVF";
|
||||
case FPE_FLTUND: return "FPE_FLTUND";
|
||||
case FPE_FLTRES: return "FPE_FLTRES";
|
||||
case FPE_FLTINV: return "FPE_FLTINV";
|
||||
case FPE_FLTSUB: return "FPE_FLTSUB";
|
||||
}
|
||||
static_assert(NSIGFPE == FPE_FLTSUB, "missing FPE_* si_code");
|
||||
break;
|
||||
case SIGSEGV:
|
||||
switch (code) {
|
||||
case SEGV_MAPERR: return "SEGV_MAPERR";
|
||||
case SEGV_ACCERR: return "SEGV_ACCERR";
|
||||
#if defined(SEGV_BNDERR)
|
||||
case SEGV_BNDERR: return "SEGV_BNDERR";
|
||||
#endif
|
||||
#if defined(SEGV_PKUERR)
|
||||
case SEGV_PKUERR: return "SEGV_PKUERR";
|
||||
#endif
|
||||
}
|
||||
#if defined(SEGV_PKUERR)
|
||||
static_assert(NSIGSEGV == SEGV_PKUERR, "missing SEGV_* si_code");
|
||||
#elif defined(SEGV_BNDERR)
|
||||
static_assert(NSIGSEGV == SEGV_BNDERR, "missing SEGV_* si_code");
|
||||
#else
|
||||
static_assert(NSIGSEGV == SEGV_ACCERR, "missing SEGV_* si_code");
|
||||
#endif
|
||||
break;
|
||||
#if defined(SYS_SECCOMP) // Our glibc is too old, and we build this for the host too.
|
||||
case SIGSYS:
|
||||
switch (code) {
|
||||
case SYS_SECCOMP: return "SYS_SECCOMP";
|
||||
}
|
||||
static_assert(NSIGSYS == SYS_SECCOMP, "missing SYS_* si_code");
|
||||
break;
|
||||
#endif
|
||||
case SIGTRAP:
|
||||
switch (code) {
|
||||
case TRAP_BRKPT: return "TRAP_BRKPT";
|
||||
case TRAP_TRACE: return "TRAP_TRACE";
|
||||
case TRAP_BRANCH: return "TRAP_BRANCH";
|
||||
case TRAP_HWBKPT: return "TRAP_HWBKPT";
|
||||
}
|
||||
if ((code & 0xff) == SIGTRAP) {
|
||||
switch ((code >> 8) & 0xff) {
|
||||
case PTRACE_EVENT_FORK:
|
||||
return "PTRACE_EVENT_FORK";
|
||||
case PTRACE_EVENT_VFORK:
|
||||
return "PTRACE_EVENT_VFORK";
|
||||
case PTRACE_EVENT_CLONE:
|
||||
return "PTRACE_EVENT_CLONE";
|
||||
case PTRACE_EVENT_EXEC:
|
||||
return "PTRACE_EVENT_EXEC";
|
||||
case PTRACE_EVENT_VFORK_DONE:
|
||||
return "PTRACE_EVENT_VFORK_DONE";
|
||||
case PTRACE_EVENT_EXIT:
|
||||
return "PTRACE_EVENT_EXIT";
|
||||
case PTRACE_EVENT_SECCOMP:
|
||||
return "PTRACE_EVENT_SECCOMP";
|
||||
case PTRACE_EVENT_STOP:
|
||||
return "PTRACE_EVENT_STOP";
|
||||
}
|
||||
}
|
||||
static_assert(NSIGTRAP == TRAP_HWBKPT, "missing TRAP_* si_code");
|
||||
break;
|
||||
}
|
||||
// Then the other codes...
|
||||
switch (code) {
|
||||
case SI_USER: return "SI_USER";
|
||||
case SI_KERNEL: return "SI_KERNEL";
|
||||
case SI_QUEUE: return "SI_QUEUE";
|
||||
case SI_TIMER: return "SI_TIMER";
|
||||
case SI_MESGQ: return "SI_MESGQ";
|
||||
case SI_ASYNCIO: return "SI_ASYNCIO";
|
||||
case SI_SIGIO: return "SI_SIGIO";
|
||||
case SI_TKILL: return "SI_TKILL";
|
||||
case SI_DETHREAD: return "SI_DETHREAD";
|
||||
}
|
||||
// Then give up...
|
||||
return "?";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright 2006, 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 "libdebuggerd/machine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
|
||||
struct pt_regs r;
|
||||
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r) == -1) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.eax), "memory near eax:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.ebx), "memory near ebx:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.ecx), "memory near ecx:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.edx), "memory near edx:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.esi), "memory near esi:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.edi), "memory near edi:");
|
||||
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.eip), "code around eip:");
|
||||
}
|
||||
|
||||
void dump_registers(log_t* log, pid_t tid) {
|
||||
struct pt_regs r;
|
||||
if (ptrace(PTRACE_GETREGS, tid, 0, &r) == -1) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(log, logtype::REGISTERS, " eax %08lx ebx %08lx ecx %08lx edx %08lx\n",
|
||||
r.eax, r.ebx, r.ecx, r.edx);
|
||||
_LOG(log, logtype::REGISTERS, " esi %08lx edi %08lx\n",
|
||||
r.esi, r.edi);
|
||||
_LOG(log, logtype::REGISTERS, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n",
|
||||
r.xcs, r.xds, r.xes, r.xfs, r.xss);
|
||||
_LOG(log, logtype::REGISTERS, " eip %08lx ebp %08lx esp %08lx flags %08lx\n",
|
||||
r.eip, r.ebp, r.esp, r.eflags);
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
** Copyright 2013, 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 "libdebuggerd/machine.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/user.h>
|
||||
|
||||
#include <backtrace/Backtrace.h>
|
||||
#include <log/log.h>
|
||||
|
||||
#include "libdebuggerd/utility.h"
|
||||
|
||||
void dump_memory_and_code(log_t* log, Backtrace* backtrace) {
|
||||
struct user_regs_struct r;
|
||||
if (ptrace(PTRACE_GETREGS, backtrace->Tid(), 0, &r) == -1) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rax), "memory near rax:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rbx), "memory near rbx:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rcx), "memory near rcx:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rdx), "memory near rdx:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rsi), "memory near rsi:");
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rdi), "memory near rdi:");
|
||||
|
||||
dump_memory(log, backtrace, static_cast<uintptr_t>(r.rip), "code around rip:");
|
||||
}
|
||||
|
||||
void dump_registers(log_t* log, pid_t tid) {
|
||||
struct user_regs_struct r;
|
||||
if (ptrace(PTRACE_GETREGS, tid, 0, &r) == -1) {
|
||||
ALOGE("cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(log, logtype::REGISTERS, " rax %016lx rbx %016lx rcx %016lx rdx %016lx\n",
|
||||
r.rax, r.rbx, r.rcx, r.rdx);
|
||||
_LOG(log, logtype::REGISTERS, " rsi %016lx rdi %016lx\n",
|
||||
r.rsi, r.rdi);
|
||||
_LOG(log, logtype::REGISTERS, " r8 %016lx r9 %016lx r10 %016lx r11 %016lx\n",
|
||||
r.r8, r.r9, r.r10, r.r11);
|
||||
_LOG(log, logtype::REGISTERS, " r12 %016lx r13 %016lx r14 %016lx r15 %016lx\n",
|
||||
r.r12, r.r13, r.r14, r.r15);
|
||||
_LOG(log, logtype::REGISTERS, " cs %016lx ss %016lx\n",
|
||||
r.cs, r.ss);
|
||||
_LOG(log, logtype::REGISTERS, " rip %016lx rbp %016lx rsp %016lx eflags %016lx\n",
|
||||
r.rip, r.rbp, r.rsp, r.eflags);
|
||||
}
|
||||
|
|
@ -16,7 +16,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dump_type.h"
|
||||
|
||||
|
|
@ -76,3 +79,11 @@ struct InterceptResponse {
|
|||
InterceptStatus status;
|
||||
char error_message[127]; // always null-terminated
|
||||
};
|
||||
|
||||
// Sent from handler to crash_dump via pipe.
|
||||
struct __attribute__((__packed__)) CrashInfo {
|
||||
uint32_t version; // must be 1.
|
||||
siginfo_t siginfo;
|
||||
ucontext_t ucontext;
|
||||
uintptr_t abort_msg_address;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@
|
|||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android-base/strings.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <cutils/sockets.h>
|
||||
#include "protocol.h"
|
||||
|
|
@ -86,3 +90,15 @@ ssize_t recv_fd(int sockfd, void* _Nonnull data, size_t len, unique_fd* _Nullabl
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_process_name(pid_t pid) {
|
||||
std::string result = "<unknown>";
|
||||
android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_thread_name(pid_t tid) {
|
||||
std::string result = "<unknown>";
|
||||
android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/comm", tid), &result);
|
||||
return android::base::Trim(result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
|
|
@ -42,3 +44,6 @@ ssize_t send_fd(int sockfd, const void* _Nonnull data, size_t len, android::base
|
|||
// 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);
|
||||
|
||||
std::string get_process_name(pid_t pid);
|
||||
std::string get_thread_name(pid_t tid);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue