resolved conflicts for merge of 053b8654
to jb-dev-plus-aosp
Change-Id: Idfef8c26b7a9e1a1a202e21dc5d34022bbaa92cc
This commit is contained in:
commit
536b81afed
|
@ -5,9 +5,15 @@ ifneq ($(filter arm x86,$(TARGET_ARCH)),)
|
|||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= debuggerd.c utility.c getevent.c $(TARGET_ARCH)/machine.c
|
||||
LOCAL_SRC_FILES:= \
|
||||
backtrace.c \
|
||||
debuggerd.c \
|
||||
getevent.c \
|
||||
tombstone.c \
|
||||
utility.c \
|
||||
$(TARGET_ARCH)/machine.c
|
||||
|
||||
LOCAL_CFLAGS := -Wall -Werror -std=gnu99
|
||||
LOCAL_CFLAGS := -Wall -Wno-unused-parameter -std=gnu99
|
||||
LOCAL_MODULE := debuggerd
|
||||
|
||||
ifeq ($(ARCH_ARM_HAVE_VFP),true)
|
||||
|
|
|
@ -15,23 +15,17 @@
|
|||
** limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/exec_elf.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <cutils/sockets.h>
|
||||
#include <cutils/properties.h>
|
||||
#include <corkscrew/ptrace.h>
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/user.h>
|
||||
|
||||
#include "../utility.h"
|
||||
|
@ -48,11 +42,72 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
static void dump_memory(log_t* log, pid_t tid, uintptr_t addr, bool at_fault) {
|
||||
char code_buffer[64]; /* actual 8+1+((8+1)*4) + 1 == 45 */
|
||||
char ascii_buffer[32]; /* actual 16 + 1 == 17 */
|
||||
uintptr_t p, end;
|
||||
|
||||
p = addr & ~3;
|
||||
p -= 32;
|
||||
if (p > addr) {
|
||||
/* catch underflow */
|
||||
p = 0;
|
||||
}
|
||||
end = p + 80;
|
||||
/* catch overflow; 'end - p' has to be multiples of 16 */
|
||||
while (end < p)
|
||||
end -= 16;
|
||||
|
||||
/* Dump the code around PC as:
|
||||
* addr contents ascii
|
||||
* 00008d34 ef000000 e8bd0090 e1b00000 512fff1e ............../Q
|
||||
* 00008d44 ea00b1f9 e92d0090 e3a070fc ef000000 ......-..p......
|
||||
*/
|
||||
while (p < end) {
|
||||
char* asc_out = ascii_buffer;
|
||||
|
||||
sprintf(code_buffer, "%08x ", p);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
/*
|
||||
* If we see (data == -1 && errno != 0), we know that the ptrace
|
||||
* call failed, probably because we're dumping memory in an
|
||||
* unmapped or inaccessible page. I don't know if there's
|
||||
* value in making that explicit in the output -- it likely
|
||||
* just complicates parsing and clarifies nothing for the
|
||||
* enlightened reader.
|
||||
*/
|
||||
long data = ptrace(PTRACE_PEEKTEXT, tid, (void*)p, NULL);
|
||||
sprintf(code_buffer + strlen(code_buffer), "%08lx ", data);
|
||||
|
||||
int j;
|
||||
for (j = 0; j < 4; j++) {
|
||||
/*
|
||||
* Our isprint() allows high-ASCII characters that display
|
||||
* differently (often badly) in different viewers, so we
|
||||
* just use a simpler test.
|
||||
*/
|
||||
char val = (data >> (j*8)) & 0xff;
|
||||
if (val >= 0x20 && val < 0x7f) {
|
||||
*asc_out++ = val;
|
||||
} else {
|
||||
*asc_out++ = '.';
|
||||
}
|
||||
}
|
||||
p += 4;
|
||||
}
|
||||
*asc_out = '\0';
|
||||
_LOG(log, !at_fault, " %s %s\n", code_buffer, ascii_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If configured to do so, dump memory around *all* registers
|
||||
* for the crashing thread.
|
||||
*/
|
||||
static void dump_memory_and_code(int tfd, pid_t tid, bool at_fault) {
|
||||
void dump_memory_and_code(const ptrace_context_t* context __attribute((unused)),
|
||||
log_t* log, pid_t tid, bool at_fault) {
|
||||
struct pt_regs regs;
|
||||
if(ptrace(PTRACE_GETREGS, tid, 0, ®s)) {
|
||||
return;
|
||||
|
@ -73,38 +128,38 @@ static void dump_memory_and_code(int tfd, pid_t tid, bool at_fault) {
|
|||
continue;
|
||||
}
|
||||
|
||||
_LOG(tfd, false, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]);
|
||||
dump_memory(tfd, tid, addr, at_fault);
|
||||
_LOG(log, false, "\nmemory near %.2s:\n", ®_NAMES[reg * 2]);
|
||||
dump_memory(log, tid, addr, at_fault);
|
||||
}
|
||||
}
|
||||
|
||||
_LOG(tfd, !at_fault, "\ncode around pc:\n");
|
||||
dump_memory(tfd, tid, (uintptr_t)regs.ARM_pc, at_fault);
|
||||
_LOG(log, !at_fault, "\ncode around pc:\n");
|
||||
dump_memory(log, tid, (uintptr_t)regs.ARM_pc, at_fault);
|
||||
|
||||
if (regs.ARM_pc != regs.ARM_lr) {
|
||||
_LOG(tfd, !at_fault, "\ncode around lr:\n");
|
||||
dump_memory(tfd, tid, (uintptr_t)regs.ARM_lr, at_fault);
|
||||
_LOG(log, !at_fault, "\ncode around lr:\n");
|
||||
dump_memory(log, tid, (uintptr_t)regs.ARM_lr, at_fault);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_registers(const ptrace_context_t* context __attribute((unused)),
|
||||
int tfd, pid_t tid, bool at_fault)
|
||||
log_t* log, pid_t tid, bool at_fault)
|
||||
{
|
||||
struct pt_regs r;
|
||||
bool only_in_tombstone = !at_fault;
|
||||
|
||||
if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
|
||||
_LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
|
||||
_LOG(log, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(tfd, only_in_tombstone, " r0 %08x r1 %08x r2 %08x r3 %08x\n",
|
||||
_LOG(log, only_in_tombstone, " r0 %08x r1 %08x r2 %08x r3 %08x\n",
|
||||
(uint32_t)r.ARM_r0, (uint32_t)r.ARM_r1, (uint32_t)r.ARM_r2, (uint32_t)r.ARM_r3);
|
||||
_LOG(tfd, only_in_tombstone, " r4 %08x r5 %08x r6 %08x r7 %08x\n",
|
||||
_LOG(log, only_in_tombstone, " r4 %08x r5 %08x r6 %08x r7 %08x\n",
|
||||
(uint32_t)r.ARM_r4, (uint32_t)r.ARM_r5, (uint32_t)r.ARM_r6, (uint32_t)r.ARM_r7);
|
||||
_LOG(tfd, only_in_tombstone, " r8 %08x r9 %08x sl %08x fp %08x\n",
|
||||
_LOG(log, only_in_tombstone, " r8 %08x r9 %08x sl %08x fp %08x\n",
|
||||
(uint32_t)r.ARM_r8, (uint32_t)r.ARM_r9, (uint32_t)r.ARM_r10, (uint32_t)r.ARM_fp);
|
||||
_LOG(tfd, only_in_tombstone, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n",
|
||||
_LOG(log, only_in_tombstone, " ip %08x sp %08x lr %08x pc %08x cpsr %08x\n",
|
||||
(uint32_t)r.ARM_ip, (uint32_t)r.ARM_sp, (uint32_t)r.ARM_lr,
|
||||
(uint32_t)r.ARM_pc, (uint32_t)r.ARM_cpsr);
|
||||
|
||||
|
@ -113,25 +168,14 @@ void dump_registers(const ptrace_context_t* context __attribute((unused)),
|
|||
int i;
|
||||
|
||||
if(ptrace(PTRACE_GETVFPREGS, tid, 0, &vfp_regs)) {
|
||||
_LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
|
||||
_LOG(log, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_VFP_REGS; i += 2) {
|
||||
_LOG(tfd, only_in_tombstone, " d%-2d %016llx d%-2d %016llx\n",
|
||||
_LOG(log, only_in_tombstone, " d%-2d %016llx d%-2d %016llx\n",
|
||||
i, vfp_regs.fpregs[i], i+1, vfp_regs.fpregs[i+1]);
|
||||
}
|
||||
_LOG(tfd, only_in_tombstone, " scr %08lx\n", vfp_regs.fpscr);
|
||||
_LOG(log, only_in_tombstone, " scr %08lx\n", vfp_regs.fpscr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
|
||||
dump_registers(context, tfd, tid, at_fault);
|
||||
|
||||
dump_backtrace_and_stack(context, tfd, tid, at_fault);
|
||||
|
||||
if (at_fault) {
|
||||
dump_memory_and_code(tfd, tid, at_fault);
|
||||
dump_nearby_maps(context, tfd, tid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
||||
#include <corkscrew/backtrace.h>
|
||||
|
||||
#include "tombstone.h"
|
||||
#include "utility.h"
|
||||
|
||||
#define STACK_DEPTH 32
|
||||
|
||||
static void dump_process_header(log_t* log, pid_t pid) {
|
||||
char path[PATH_MAX];
|
||||
char procnamebuf[1024];
|
||||
char* procname = NULL;
|
||||
FILE* fp;
|
||||
|
||||
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
|
||||
if ((fp = fopen(path, "r"))) {
|
||||
procname = fgets(procnamebuf, sizeof(procnamebuf), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
time_t t = time(NULL);
|
||||
struct tm tm;
|
||||
localtime_r(&t, &tm);
|
||||
char timestr[64];
|
||||
strftime(timestr, sizeof(timestr), "%F %T", &tm);
|
||||
_LOG(log, false, "\n\n----- pid %d at %s -----\n", pid, timestr);
|
||||
|
||||
if (procname) {
|
||||
_LOG(log, false, "\nCmd line: %s\n", procname);
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_process_footer(log_t* log, pid_t pid) {
|
||||
_LOG(log, false, "\n----- end %d -----\n", pid);
|
||||
}
|
||||
|
||||
static void dump_thread(log_t* log, pid_t tid, ptrace_context_t* context, bool attached,
|
||||
bool* detach_failed, int* total_sleep_time_usec) {
|
||||
char path[PATH_MAX];
|
||||
char threadnamebuf[1024];
|
||||
char* threadname = NULL;
|
||||
FILE* fp;
|
||||
|
||||
snprintf(path, sizeof(path), "/proc/%d/comm", tid);
|
||||
if ((fp = fopen(path, "r"))) {
|
||||
threadname = fgets(threadnamebuf, sizeof(threadnamebuf), fp);
|
||||
fclose(fp);
|
||||
if (threadname) {
|
||||
size_t len = strlen(threadname);
|
||||
if (len && threadname[len - 1] == '\n') {
|
||||
threadname[len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_LOG(log, false, "\n\"%s\" sysTid=%d\n", threadname ? threadname : "<unknown>", tid);
|
||||
|
||||
if (!attached && ptrace(PTRACE_ATTACH, tid, 0, 0) < 0) {
|
||||
_LOG(log, false, "Could not attach to thread: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
wait_for_stop(tid, total_sleep_time_usec);
|
||||
|
||||
backtrace_frame_t backtrace[STACK_DEPTH];
|
||||
ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH);
|
||||
if (frames <= 0) {
|
||||
_LOG(log, false, "Could not obtain stack trace for thread.\n");
|
||||
} else {
|
||||
backtrace_symbol_t backtrace_symbols[STACK_DEPTH];
|
||||
get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols);
|
||||
for (size_t i = 0; i < (size_t)frames; i++) {
|
||||
char line[MAX_BACKTRACE_LINE_LENGTH];
|
||||
format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i],
|
||||
line, MAX_BACKTRACE_LINE_LENGTH);
|
||||
_LOG(log, false, " %s\n", line);
|
||||
}
|
||||
free_backtrace_symbols(backtrace_symbols, frames);
|
||||
}
|
||||
|
||||
if (!attached && ptrace(PTRACE_DETACH, tid, 0, 0) != 0) {
|
||||
LOG("ptrace detach from %d failed: %s\n", tid, strerror(errno));
|
||||
*detach_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void dump_backtrace(int fd, pid_t pid, pid_t tid, bool* detach_failed,
|
||||
int* total_sleep_time_usec) {
|
||||
log_t log;
|
||||
log.tfd = fd;
|
||||
log.quiet = true;
|
||||
|
||||
ptrace_context_t* context = load_ptrace_context(tid);
|
||||
dump_process_header(&log, pid);
|
||||
dump_thread(&log, tid, context, true, detach_failed, total_sleep_time_usec);
|
||||
|
||||
char task_path[64];
|
||||
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
|
||||
DIR* d = opendir(task_path);
|
||||
if (d) {
|
||||
struct dirent debuf;
|
||||
struct dirent *de;
|
||||
while (!readdir_r(d, &debuf, &de) && de) {
|
||||
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char* end;
|
||||
pid_t new_tid = strtoul(de->d_name, &end, 10);
|
||||
if (*end || new_tid == tid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dump_thread(&log, new_tid, context, false, detach_failed, total_sleep_time_usec);
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
dump_process_footer(&log, pid);
|
||||
free_ptrace_context(context);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DEBUGGERD_BACKTRACE_H
|
||||
#define _DEBUGGERD_BACKTRACE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <corkscrew/ptrace.h>
|
||||
|
||||
/* 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, pid_t pid, pid_t tid, bool* detach_failed,
|
||||
int* total_sleep_time_usec);
|
||||
|
||||
#endif // _DEBUGGERD_BACKTRACE_H
|
|
@ -23,6 +23,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
|
@ -34,6 +35,7 @@
|
|||
#include <cutils/logd.h>
|
||||
#include <cutils/logger.h>
|
||||
#include <cutils/properties.h>
|
||||
#include <cutils/debugger.h>
|
||||
|
||||
#include <corkscrew/backtrace.h>
|
||||
|
||||
|
@ -41,432 +43,16 @@
|
|||
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include "backtrace.h"
|
||||
#include "getevent.h"
|
||||
#include "machine.h"
|
||||
#include "tombstone.h"
|
||||
#include "utility.h"
|
||||
|
||||
#define ANDROID_LOG_INFO 4
|
||||
|
||||
static void dump_build_info(int tfd)
|
||||
{
|
||||
char fingerprint[PROPERTY_VALUE_MAX];
|
||||
|
||||
property_get("ro.build.fingerprint", fingerprint, "unknown");
|
||||
|
||||
_LOG(tfd, false, "Build fingerprint: '%s'\n", fingerprint);
|
||||
}
|
||||
|
||||
static const char *get_signame(int sig)
|
||||
{
|
||||
switch(sig) {
|
||||
case SIGILL: return "SIGILL";
|
||||
case SIGABRT: return "SIGABRT";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
case SIGFPE: return "SIGFPE";
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
case SIGPIPE: return "SIGPIPE";
|
||||
case SIGSTKFLT: return "SIGSTKFLT";
|
||||
case SIGSTOP: return "SIGSTOP";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_sigcode(int signo, int code)
|
||||
{
|
||||
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";
|
||||
}
|
||||
break;
|
||||
case SIGBUS:
|
||||
switch (code) {
|
||||
case BUS_ADRALN: return "BUS_ADRALN";
|
||||
case BUS_ADRERR: return "BUS_ADRERR";
|
||||
case BUS_OBJERR: return "BUS_OBJERR";
|
||||
}
|
||||
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";
|
||||
}
|
||||
break;
|
||||
case SIGSEGV:
|
||||
switch (code) {
|
||||
case SEGV_MAPERR: return "SEGV_MAPERR";
|
||||
case SEGV_ACCERR: return "SEGV_ACCERR";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void dump_fault_addr(int tfd, pid_t tid, int sig)
|
||||
{
|
||||
siginfo_t si;
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
if(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)){
|
||||
_LOG(tfd, false, "cannot get siginfo: %s\n", strerror(errno));
|
||||
} else if (signal_has_address(sig)) {
|
||||
_LOG(tfd, false, "signal %d (%s), code %d (%s), fault addr %08x\n",
|
||||
sig, get_signame(sig),
|
||||
si.si_code, get_sigcode(sig, si.si_code),
|
||||
(uintptr_t) si.si_addr);
|
||||
} else {
|
||||
_LOG(tfd, false, "signal %d (%s), code %d (%s), fault addr --------\n",
|
||||
sig, get_signame(sig), si.si_code, get_sigcode(sig, si.si_code));
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_crash_banner(int tfd, pid_t pid, pid_t tid, int sig)
|
||||
{
|
||||
char data[1024];
|
||||
char *x = 0;
|
||||
FILE *fp;
|
||||
|
||||
sprintf(data, "/proc/%d/cmdline", pid);
|
||||
fp = fopen(data, "r");
|
||||
if(fp) {
|
||||
x = fgets(data, 1024, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
_LOG(tfd, false,
|
||||
"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
|
||||
dump_build_info(tfd);
|
||||
_LOG(tfd, false, "pid: %d, tid: %d >>> %s <<<\n",
|
||||
pid, tid, x ? x : "UNKNOWN");
|
||||
|
||||
if(sig) {
|
||||
dump_fault_addr(tfd, tid, sig);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if some thread is not detached cleanly */
|
||||
static bool dump_sibling_thread_report(const ptrace_context_t* context,
|
||||
int tfd, pid_t pid, pid_t tid) {
|
||||
char task_path[64];
|
||||
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
|
||||
|
||||
DIR* d = opendir(task_path);
|
||||
/* Bail early if cannot open the task directory */
|
||||
if (d == NULL) {
|
||||
XLOG("Cannot open /proc/%d/task\n", pid);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool detach_failed = false;
|
||||
struct dirent *de;
|
||||
while ((de = readdir(d)) != NULL) {
|
||||
pid_t new_tid;
|
||||
/* Ignore "." and ".." */
|
||||
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_tid = atoi(de->d_name);
|
||||
/* The main thread at fault has been handled individually */
|
||||
if (new_tid == tid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip this thread if cannot ptrace it */
|
||||
if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_LOG(tfd, true, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
|
||||
_LOG(tfd, true, "pid: %d, tid: %d\n", pid, new_tid);
|
||||
|
||||
dump_thread(context, tfd, new_tid, false);
|
||||
|
||||
if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) {
|
||||
LOG("ptrace detach from %d failed: %s\n", new_tid, strerror(errno));
|
||||
detach_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
return detach_failed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the contents of the specified log device, filters out the entries
|
||||
* that don't match the specified pid, and writes them to the tombstone file.
|
||||
*
|
||||
* If "tailOnly" is set, we only print the last few lines.
|
||||
*/
|
||||
static void dump_log_file(int tfd, pid_t pid, const char* filename,
|
||||
bool tailOnly)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
/* circular buffer, for "tailOnly" mode */
|
||||
const int kShortLogMaxLines = 5;
|
||||
const int kShortLogLineLen = 256;
|
||||
char shortLog[kShortLogMaxLines][kShortLogLineLen];
|
||||
int shortLogCount = 0;
|
||||
int shortLogNext = 0;
|
||||
|
||||
int logfd = open(filename, O_RDONLY | O_NONBLOCK);
|
||||
if (logfd < 0) {
|
||||
XLOG("Unable to open %s: %s\n", filename, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
union {
|
||||
unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
|
||||
struct logger_entry entry;
|
||||
} log_entry;
|
||||
|
||||
while (true) {
|
||||
ssize_t actual = read(logfd, log_entry.buf, LOGGER_ENTRY_MAX_LEN);
|
||||
if (actual < 0) {
|
||||
if (errno == EINTR) {
|
||||
/* interrupted by signal, retry */
|
||||
continue;
|
||||
} else if (errno == EAGAIN) {
|
||||
/* non-blocking EOF; we're done */
|
||||
break;
|
||||
} else {
|
||||
_LOG(tfd, true, "Error while reading log: %s\n",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
} else if (actual == 0) {
|
||||
_LOG(tfd, true, "Got zero bytes while reading log: %s\n",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: if you XLOG something here, this will spin forever,
|
||||
* because you will be writing as fast as you're reading. Any
|
||||
* high-frequency debug diagnostics should just be written to
|
||||
* the tombstone file.
|
||||
*/
|
||||
|
||||
struct logger_entry* entry = &log_entry.entry;
|
||||
|
||||
if (entry->pid != (int32_t) pid) {
|
||||
/* wrong pid, ignore */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
_LOG(tfd, true, "--------- %slog %s\n",
|
||||
tailOnly ? "tail end of " : "", filename);
|
||||
first = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Msg format is: <priority:1><tag:N>\0<message:N>\0
|
||||
*
|
||||
* We want to display it in the same format as "logcat -v threadtime"
|
||||
* (although in this case the pid is redundant).
|
||||
*
|
||||
* TODO: scan for line breaks ('\n') and display each text line
|
||||
* on a separate line, prefixed with the header, like logcat does.
|
||||
*/
|
||||
static const char* kPrioChars = "!.VDIWEFS";
|
||||
unsigned char prio = entry->msg[0];
|
||||
char* tag = entry->msg + 1;
|
||||
char* msg = tag + strlen(tag) + 1;
|
||||
|
||||
/* consume any trailing newlines */
|
||||
char* eatnl = msg + strlen(msg) - 1;
|
||||
while (eatnl >= msg && *eatnl == '\n') {
|
||||
*eatnl-- = '\0';
|
||||
}
|
||||
|
||||
char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?');
|
||||
|
||||
char timeBuf[32];
|
||||
time_t sec = (time_t) entry->sec;
|
||||
struct tm tmBuf;
|
||||
struct tm* ptm;
|
||||
ptm = localtime_r(&sec, &tmBuf);
|
||||
strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
|
||||
|
||||
if (tailOnly) {
|
||||
snprintf(shortLog[shortLogNext], kShortLogLineLen,
|
||||
"%s.%03d %5d %5d %c %-8s: %s",
|
||||
timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
|
||||
prioChar, tag, msg);
|
||||
shortLogNext = (shortLogNext + 1) % kShortLogMaxLines;
|
||||
shortLogCount++;
|
||||
} else {
|
||||
_LOG(tfd, true, "%s.%03d %5d %5d %c %-8s: %s\n",
|
||||
timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
|
||||
prioChar, tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (tailOnly) {
|
||||
int i;
|
||||
|
||||
/*
|
||||
* If we filled the buffer, we want to start at "next", which has
|
||||
* the oldest entry. If we didn't, we want to start at zero.
|
||||
*/
|
||||
if (shortLogCount < kShortLogMaxLines) {
|
||||
shortLogNext = 0;
|
||||
} else {
|
||||
shortLogCount = kShortLogMaxLines; /* cap at window size */
|
||||
}
|
||||
|
||||
for (i = 0; i < shortLogCount; i++) {
|
||||
_LOG(tfd, true, "%s\n", shortLog[shortLogNext]);
|
||||
shortLogNext = (shortLogNext + 1) % kShortLogMaxLines;
|
||||
}
|
||||
}
|
||||
|
||||
close(logfd);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(int tfd, pid_t pid, bool tailOnly)
|
||||
{
|
||||
dump_log_file(tfd, pid, "/dev/log/system", tailOnly);
|
||||
dump_log_file(tfd, pid, "/dev/log/main", tailOnly);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dumps all information about the specified pid to the tombstone.
|
||||
*/
|
||||
static bool dump_crash(int tfd, pid_t pid, pid_t tid, int signal,
|
||||
bool dump_sibling_threads)
|
||||
{
|
||||
/* don't copy log messages to tombstone unless this is a dev device */
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.debuggable", value, "0");
|
||||
bool wantLogs = (value[0] == '1');
|
||||
|
||||
dump_crash_banner(tfd, pid, tid, signal);
|
||||
|
||||
ptrace_context_t* context = load_ptrace_context(tid);
|
||||
|
||||
dump_thread(context, tfd, tid, true);
|
||||
|
||||
if (wantLogs) {
|
||||
dump_logs(tfd, pid, true);
|
||||
}
|
||||
|
||||
bool detach_failed = false;
|
||||
if (dump_sibling_threads) {
|
||||
detach_failed = dump_sibling_thread_report(context, tfd, pid, tid);
|
||||
}
|
||||
|
||||
free_ptrace_context(context);
|
||||
|
||||
if (wantLogs) {
|
||||
dump_logs(tfd, pid, false);
|
||||
}
|
||||
return detach_failed;
|
||||
}
|
||||
|
||||
#define MAX_TOMBSTONES 10
|
||||
|
||||
#define typecheck(x,y) { \
|
||||
typeof(x) __dummy1; \
|
||||
typeof(y) __dummy2; \
|
||||
(void)(&__dummy1 == &__dummy2); }
|
||||
|
||||
#define TOMBSTONE_DIR "/data/tombstones"
|
||||
|
||||
/*
|
||||
* find_and_open_tombstone - find an available tombstone slot, if any, of the
|
||||
* form tombstone_XX where XX is 00 to MAX_TOMBSTONES-1, inclusive. If no
|
||||
* file is available, we reuse the least-recently-modified file.
|
||||
*
|
||||
* Returns the path of the tombstone file, allocated using malloc(). Caller must free() it.
|
||||
*/
|
||||
static char* find_and_open_tombstone(int* fd)
|
||||
{
|
||||
unsigned long mtime = ULONG_MAX;
|
||||
struct stat sb;
|
||||
|
||||
/*
|
||||
* XXX: Our stat.st_mtime isn't time_t. If it changes, as it probably ought
|
||||
* to, our logic breaks. This check will generate a warning if that happens.
|
||||
*/
|
||||
typecheck(mtime, sb.st_mtime);
|
||||
|
||||
/*
|
||||
* In a single wolf-like pass, find an available slot and, in case none
|
||||
* exist, find and record the least-recently-modified file.
|
||||
*/
|
||||
char path[128];
|
||||
int oldest = 0;
|
||||
for (int i = 0; i < MAX_TOMBSTONES; i++) {
|
||||
snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", i);
|
||||
|
||||
if (!stat(path, &sb)) {
|
||||
if (sb.st_mtime < mtime) {
|
||||
oldest = i;
|
||||
mtime = sb.st_mtime;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (errno != ENOENT)
|
||||
continue;
|
||||
|
||||
*fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0600);
|
||||
if (*fd < 0)
|
||||
continue; /* raced ? */
|
||||
|
||||
fchown(*fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
/* we didn't find an available file, so we clobber the oldest one */
|
||||
snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", oldest);
|
||||
*fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
|
||||
if (*fd < 0) {
|
||||
LOG("failed to open tombstone file '%s': %s\n", path, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
fchown(*fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
/* Return true if some thread is not detached cleanly */
|
||||
static char* engrave_tombstone(pid_t pid, pid_t tid, int signal, bool dump_sibling_threads,
|
||||
bool* detach_failed)
|
||||
{
|
||||
mkdir(TOMBSTONE_DIR, 0755);
|
||||
chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM);
|
||||
|
||||
int fd;
|
||||
char* path = find_and_open_tombstone(&fd);
|
||||
if (!path) {
|
||||
*detach_failed = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*detach_failed = dump_crash(fd, pid, tid, signal, dump_sibling_threads);
|
||||
|
||||
close(fd);
|
||||
return path;
|
||||
}
|
||||
typedef struct {
|
||||
debugger_action_t action;
|
||||
pid_t pid, tid;
|
||||
uid_t uid, gid;
|
||||
} debugger_request_t;
|
||||
|
||||
static int
|
||||
write_string(const char* file, const char* string)
|
||||
|
@ -598,50 +184,7 @@ static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* ou
|
|||
return fields == 7 ? 0 : -1;
|
||||
}
|
||||
|
||||
static int wait_for_signal(pid_t tid, int* total_sleep_time_usec) {
|
||||
const int sleep_time_usec = 200000; /* 0.2 seconds */
|
||||
const int max_total_sleep_usec = 3000000; /* 3 seconds */
|
||||
for (;;) {
|
||||
int status;
|
||||
pid_t n = waitpid(tid, &status, __WALL | WNOHANG);
|
||||
if (n < 0) {
|
||||
if(errno == EAGAIN) continue;
|
||||
LOG("waitpid failed: %s\n", strerror(errno));
|
||||
return -1;
|
||||
} else if (n > 0) {
|
||||
XLOG("waitpid: n=%d status=%08x\n", n, status);
|
||||
if (WIFSTOPPED(status)) {
|
||||
return WSTOPSIG(status);
|
||||
} else {
|
||||
LOG("unexpected waitpid response: n=%d, status=%08x\n", n, status);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (*total_sleep_time_usec > max_total_sleep_usec) {
|
||||
LOG("timed out waiting for tid=%d to die\n", tid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* not ready yet */
|
||||
XLOG("not ready yet\n");
|
||||
usleep(sleep_time_usec);
|
||||
*total_sleep_time_usec += sleep_time_usec;
|
||||
}
|
||||
}
|
||||
|
||||
enum {
|
||||
REQUEST_TYPE_CRASH,
|
||||
REQUEST_TYPE_DUMP,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
pid_t pid, tid;
|
||||
uid_t uid, gid;
|
||||
} request_t;
|
||||
|
||||
static int read_request(int fd, request_t* out_request) {
|
||||
static int read_request(int fd, debugger_request_t* out_request) {
|
||||
struct ucred cr;
|
||||
int len = sizeof(cr);
|
||||
int status = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);
|
||||
|
@ -663,20 +206,37 @@ static int read_request(int fd, request_t* out_request) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
status = TEMP_FAILURE_RETRY(read(fd, &out_request->tid, sizeof(pid_t)));
|
||||
debugger_msg_t msg;
|
||||
status = TEMP_FAILURE_RETRY(read(fd, &msg, sizeof(msg)));
|
||||
if (status < 0) {
|
||||
LOG("read failure? %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (status != sizeof(pid_t)) {
|
||||
if (status != sizeof(msg)) {
|
||||
LOG("invalid crash request of size %d\n", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out_request->tid < 0 && cr.uid == 0) {
|
||||
/* Root can ask us to attach to any process and dump it explicitly. */
|
||||
out_request->type = REQUEST_TYPE_DUMP;
|
||||
out_request->tid = -out_request->tid;
|
||||
out_request->action = msg.action;
|
||||
out_request->tid = msg.tid;
|
||||
out_request->pid = cr.pid;
|
||||
out_request->uid = cr.uid;
|
||||
out_request->gid = cr.gid;
|
||||
|
||||
if (msg.action == DEBUGGER_ACTION_CRASH) {
|
||||
/* Ensure that the tid reported by the crashing process is valid. */
|
||||
char buf[64];
|
||||
struct stat s;
|
||||
snprintf(buf, sizeof buf, "/proc/%d/task/%d", out_request->pid, out_request->tid);
|
||||
if(stat(buf, &s)) {
|
||||
LOG("tid %d does not exist in pid %d. ignoring debug request\n",
|
||||
out_request->tid, out_request->pid);
|
||||
return -1;
|
||||
}
|
||||
} else if (cr.uid == 0
|
||||
|| (cr.uid == AID_SYSTEM && msg.action == DEBUGGER_ACTION_DUMP_BACKTRACE)) {
|
||||
/* Only root or system can ask us to attach to any process and dump it explicitly.
|
||||
* However, system is only allowed to collect backtraces but cannot dump tombstones. */
|
||||
status = get_process_info(out_request->tid, &out_request->pid,
|
||||
&out_request->uid, &out_request->gid);
|
||||
if (status < 0) {
|
||||
|
@ -684,28 +244,15 @@ static int read_request(int fd, request_t* out_request) {
|
|||
out_request->tid);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Ensure that the tid reported by the crashing process is valid. */
|
||||
out_request->type = REQUEST_TYPE_CRASH;
|
||||
out_request->pid = cr.pid;
|
||||
out_request->uid = cr.uid;
|
||||
out_request->gid = cr.gid;
|
||||
|
||||
char buf[64];
|
||||
struct stat s;
|
||||
snprintf(buf, sizeof buf, "/proc/%d/task/%d", out_request->pid, out_request->tid);
|
||||
if(stat(buf, &s)) {
|
||||
LOG("tid %d does not exist in pid %d. ignoring debug request\n",
|
||||
out_request->tid, out_request->pid);
|
||||
} else {
|
||||
/* No one else is not allowed to dump arbitrary processes. */
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool should_attach_gdb(request_t* request) {
|
||||
if (request->type == REQUEST_TYPE_CRASH) {
|
||||
static bool should_attach_gdb(debugger_request_t* request) {
|
||||
if (request->action == DEBUGGER_ACTION_CRASH) {
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
property_get("debug.db.uid", value, "-1");
|
||||
int debug_uid = atoi(value);
|
||||
|
@ -717,7 +264,7 @@ static bool should_attach_gdb(request_t* request) {
|
|||
static void handle_request(int fd) {
|
||||
XLOG("handle_request(%d)\n", fd);
|
||||
|
||||
request_t request;
|
||||
debugger_request_t request;
|
||||
int status = read_request(fd, &request);
|
||||
if (!status) {
|
||||
XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n",
|
||||
|
@ -739,13 +286,12 @@ static void handle_request(int fd) {
|
|||
} else {
|
||||
bool detach_failed = false;
|
||||
bool attach_gdb = should_attach_gdb(&request);
|
||||
char response = 0;
|
||||
if (TEMP_FAILURE_RETRY(write(fd, &response, 1)) != 1) {
|
||||
if (TEMP_FAILURE_RETRY(write(fd, "\0", 1)) != 1) {
|
||||
LOG("failed responding to client: %s\n", strerror(errno));
|
||||
} else {
|
||||
char* tombstone_path = NULL;
|
||||
|
||||
if (request.type != REQUEST_TYPE_DUMP) {
|
||||
if (request.action == DEBUGGER_ACTION_CRASH) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
@ -759,10 +305,15 @@ static void handle_request(int fd) {
|
|||
|
||||
switch (signal) {
|
||||
case SIGSTOP:
|
||||
if (request.type == REQUEST_TYPE_DUMP) {
|
||||
XLOG("stopped -- dumping\n");
|
||||
if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
|
||||
XLOG("stopped -- dumping to tombstone\n");
|
||||
tombstone_path = engrave_tombstone(request.pid, request.tid,
|
||||
signal, true, &detach_failed);
|
||||
signal, true, true, &detach_failed,
|
||||
&total_sleep_time_usec);
|
||||
} else if (request.action == DEBUGGER_ACTION_DUMP_BACKTRACE) {
|
||||
XLOG("stopped -- dumping to fd\n");
|
||||
dump_backtrace(fd, request.pid, request.tid, &detach_failed,
|
||||
&total_sleep_time_usec);
|
||||
} else {
|
||||
XLOG("stopped -- continuing\n");
|
||||
status = ptrace(PTRACE_CONT, request.tid, 0, 0);
|
||||
|
@ -791,7 +342,8 @@ static void handle_request(int fd) {
|
|||
/* don't dump sibling threads when attaching to GDB because it
|
||||
* makes the process less reliable, apparently... */
|
||||
tombstone_path = engrave_tombstone(request.pid, request.tid,
|
||||
signal, !attach_gdb, &detach_failed);
|
||||
signal, !attach_gdb, false, &detach_failed,
|
||||
&total_sleep_time_usec);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -803,7 +355,7 @@ static void handle_request(int fd) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (request.type == REQUEST_TYPE_DUMP) {
|
||||
if (request.action == DEBUGGER_ACTION_DUMP_TOMBSTONE) {
|
||||
if (tombstone_path) {
|
||||
write(fd, tombstone_path, strlen(tombstone_path));
|
||||
}
|
||||
|
@ -888,7 +440,7 @@ static int do_server() {
|
|||
act.sa_flags = SA_NOCLDWAIT;
|
||||
sigaction(SIGCHLD, &act, 0);
|
||||
|
||||
s = socket_local_server("android:debuggerd",
|
||||
s = socket_local_server(DEBUGGER_SOCKET_NAME,
|
||||
ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
|
||||
if(s < 0) return 1;
|
||||
fcntl(s, F_SETFD, FD_CLOEXEC);
|
||||
|
@ -915,47 +467,56 @@ static int do_server() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int do_explicit_dump(pid_t tid) {
|
||||
static int do_explicit_dump(pid_t tid, bool dump_backtrace) {
|
||||
fprintf(stdout, "Sending request to dump task %d.\n", tid);
|
||||
|
||||
int fd = socket_local_client("android:debuggerd",
|
||||
ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
|
||||
if (fd < 0) {
|
||||
fputs("Error opening local socket to debuggerd.\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pid_t request = -tid;
|
||||
write(fd, &request, sizeof(pid_t));
|
||||
if (read(fd, &request, 1) != 1) {
|
||||
/* did not get expected reply, debuggerd must have closed the socket */
|
||||
fputs("Error sending request. Did not receive reply from debuggerd.\n", stderr);
|
||||
if (dump_backtrace) {
|
||||
fflush(stdout);
|
||||
if (dump_backtrace_to_file(tid, fileno(stdout)) < 0) {
|
||||
fputs("Error dumping backtrace.\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
char tombstone_path[PATH_MAX];
|
||||
ssize_t n = read(fd, &tombstone_path, sizeof(tombstone_path) - 1);
|
||||
if (n <= 0) {
|
||||
fputs("Error dumping process. Check log for details.\n", stderr);
|
||||
} else {
|
||||
tombstone_path[n] = '\0';
|
||||
fprintf(stderr, "Tombstone written to: %s\n", tombstone_path);
|
||||
if (dump_tombstone(tid, tombstone_path, sizeof(tombstone_path)) < 0) {
|
||||
fputs("Error dumping tombstone.\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Tombstone written to: %s\n", tombstone_path);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
fputs("Usage: -b [<tid>]\n"
|
||||
" -b dump backtrace to console, otherwise dump full tombstone file\n"
|
||||
"\n"
|
||||
"If tid specified, sends a request to debuggerd to dump that task.\n"
|
||||
"Otherwise, starts the debuggerd server.\n", stderr);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc == 2) {
|
||||
pid_t tid = atoi(argv[1]);
|
||||
if (!tid) {
|
||||
fputs("Usage: [<tid>]\n"
|
||||
"\n"
|
||||
"If tid specified, sends a request to debuggerd to dump that task.\n"
|
||||
"Otherwise, starts the debuggerd server.\n", stderr);
|
||||
if (argc == 1) {
|
||||
return do_server();
|
||||
}
|
||||
|
||||
bool dump_backtrace = false;
|
||||
bool have_tid = false;
|
||||
pid_t tid = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "-b")) {
|
||||
dump_backtrace = true;
|
||||
} else if (!have_tid) {
|
||||
tid = atoi(argv[i]);
|
||||
have_tid = true;
|
||||
} else {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
return do_explicit_dump(tid);
|
||||
}
|
||||
return do_server();
|
||||
if (!have_tid) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
return do_explicit_dump(tid, dump_backtrace);
|
||||
}
|
||||
|
|
|
@ -17,9 +17,15 @@
|
|||
#ifndef _DEBUGGERD_MACHINE_H
|
||||
#define _DEBUGGERD_MACHINE_H
|
||||
|
||||
#include <corkscrew/backtrace.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault);
|
||||
#include <corkscrew/ptrace.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
void dump_memory_and_code(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault);
|
||||
void dump_registers(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault);
|
||||
|
||||
#endif // _DEBUGGERD_MACHINE_H
|
||||
|
|
|
@ -0,0 +1,698 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <time.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include <cutils/logger.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include <corkscrew/demangle.h>
|
||||
#include <corkscrew/backtrace.h>
|
||||
|
||||
#include "machine.h"
|
||||
#include "tombstone.h"
|
||||
#include "utility.h"
|
||||
|
||||
#define STACK_DEPTH 32
|
||||
#define STACK_WORDS 16
|
||||
|
||||
#define MAX_TOMBSTONES 10
|
||||
#define TOMBSTONE_DIR "/data/tombstones"
|
||||
|
||||
#define typecheck(x,y) { \
|
||||
typeof(x) __dummy1; \
|
||||
typeof(y) __dummy2; \
|
||||
(void)(&__dummy1 == &__dummy2); }
|
||||
|
||||
|
||||
static bool signal_has_address(int sig) {
|
||||
switch (sig) {
|
||||
case SIGILL:
|
||||
case SIGFPE:
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_signame(int sig)
|
||||
{
|
||||
switch(sig) {
|
||||
case SIGILL: return "SIGILL";
|
||||
case SIGABRT: return "SIGABRT";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
case SIGFPE: return "SIGFPE";
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
case SIGPIPE: return "SIGPIPE";
|
||||
case SIGSTKFLT: return "SIGSTKFLT";
|
||||
case SIGSTOP: return "SIGSTOP";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *get_sigcode(int signo, int code)
|
||||
{
|
||||
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";
|
||||
}
|
||||
break;
|
||||
case SIGBUS:
|
||||
switch (code) {
|
||||
case BUS_ADRALN: return "BUS_ADRALN";
|
||||
case BUS_ADRERR: return "BUS_ADRERR";
|
||||
case BUS_OBJERR: return "BUS_OBJERR";
|
||||
}
|
||||
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";
|
||||
}
|
||||
break;
|
||||
case SIGSEGV:
|
||||
switch (code) {
|
||||
case SEGV_MAPERR: return "SEGV_MAPERR";
|
||||
case SEGV_ACCERR: return "SEGV_ACCERR";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static void dump_build_info(log_t* log)
|
||||
{
|
||||
char fingerprint[PROPERTY_VALUE_MAX];
|
||||
|
||||
property_get("ro.build.fingerprint", fingerprint, "unknown");
|
||||
|
||||
_LOG(log, false, "Build fingerprint: '%s'\n", fingerprint);
|
||||
}
|
||||
|
||||
static void dump_fault_addr(log_t* log, pid_t tid, int sig)
|
||||
{
|
||||
siginfo_t si;
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
if(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)){
|
||||
_LOG(log, false, "cannot get siginfo: %s\n", strerror(errno));
|
||||
} else if (signal_has_address(sig)) {
|
||||
_LOG(log, false, "signal %d (%s), code %d (%s), fault addr %08x\n",
|
||||
sig, get_signame(sig),
|
||||
si.si_code, get_sigcode(sig, si.si_code),
|
||||
(uintptr_t) si.si_addr);
|
||||
} else {
|
||||
_LOG(log, false, "signal %d (%s), code %d (%s), fault addr --------\n",
|
||||
sig, get_signame(sig), si.si_code, get_sigcode(sig, si.si_code));
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_thread_info(log_t* log, pid_t pid, pid_t tid, bool at_fault) {
|
||||
char path[64];
|
||||
char threadnamebuf[1024];
|
||||
char* threadname = NULL;
|
||||
FILE *fp;
|
||||
|
||||
snprintf(path, sizeof(path), "/proc/%d/comm", tid);
|
||||
if ((fp = fopen(path, "r"))) {
|
||||
threadname = fgets(threadnamebuf, sizeof(threadnamebuf), fp);
|
||||
fclose(fp);
|
||||
if (threadname) {
|
||||
size_t len = strlen(threadname);
|
||||
if (len && threadname[len - 1] == '\n') {
|
||||
threadname[len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (at_fault) {
|
||||
char procnamebuf[1024];
|
||||
char* procname = NULL;
|
||||
|
||||
snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
|
||||
if ((fp = fopen(path, "r"))) {
|
||||
procname = fgets(procnamebuf, sizeof(procnamebuf), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
_LOG(log, false, "pid: %d, tid: %d, name: %s >>> %s <<<\n", pid, tid,
|
||||
threadname ? threadname : "UNKNOWN",
|
||||
procname ? procname : "UNKNOWN");
|
||||
} else {
|
||||
_LOG(log, true, "pid: %d, tid: %d, name: %s\n", pid, tid,
|
||||
threadname ? threadname : "UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_backtrace(const ptrace_context_t* context __attribute((unused)),
|
||||
log_t* log, pid_t tid __attribute((unused)), bool at_fault,
|
||||
const backtrace_frame_t* backtrace, size_t frames) {
|
||||
_LOG(log, !at_fault, "\nbacktrace:\n");
|
||||
|
||||
backtrace_symbol_t backtrace_symbols[STACK_DEPTH];
|
||||
get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols);
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
char line[MAX_BACKTRACE_LINE_LENGTH];
|
||||
format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i],
|
||||
line, MAX_BACKTRACE_LINE_LENGTH);
|
||||
_LOG(log, !at_fault, " %s\n", line);
|
||||
}
|
||||
free_backtrace_symbols(backtrace_symbols, frames);
|
||||
}
|
||||
|
||||
static void dump_stack_segment(const ptrace_context_t* context, log_t* log, pid_t tid,
|
||||
bool only_in_tombstone, uintptr_t* sp, size_t words, int label) {
|
||||
for (size_t i = 0; i < words; i++) {
|
||||
uint32_t stack_content;
|
||||
if (!try_get_word_ptrace(tid, *sp, &stack_content)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const map_info_t* mi;
|
||||
const symbol_t* symbol;
|
||||
find_symbol_ptrace(context, stack_content, &mi, &symbol);
|
||||
|
||||
if (symbol) {
|
||||
char* demangled_name = demangle_symbol_name(symbol->name);
|
||||
const char* symbol_name = demangled_name ? demangled_name : symbol->name;
|
||||
uint32_t offset = stack_content - (mi->start + symbol->start);
|
||||
if (!i && label >= 0) {
|
||||
if (offset) {
|
||||
_LOG(log, only_in_tombstone, " #%02d %08x %08x %s (%s+%u)\n",
|
||||
label, *sp, stack_content, mi ? mi->name : "", symbol_name, offset);
|
||||
} else {
|
||||
_LOG(log, only_in_tombstone, " #%02d %08x %08x %s (%s)\n",
|
||||
label, *sp, stack_content, mi ? mi->name : "", symbol_name);
|
||||
}
|
||||
} else {
|
||||
if (offset) {
|
||||
_LOG(log, only_in_tombstone, " %08x %08x %s (%s+%u)\n",
|
||||
*sp, stack_content, mi ? mi->name : "", symbol_name, offset);
|
||||
} else {
|
||||
_LOG(log, only_in_tombstone, " %08x %08x %s (%s)\n",
|
||||
*sp, stack_content, mi ? mi->name : "", symbol_name);
|
||||
}
|
||||
}
|
||||
free(demangled_name);
|
||||
} else {
|
||||
if (!i && label >= 0) {
|
||||
_LOG(log, only_in_tombstone, " #%02d %08x %08x %s\n",
|
||||
label, *sp, stack_content, mi ? mi->name : "");
|
||||
} else {
|
||||
_LOG(log, only_in_tombstone, " %08x %08x %s\n",
|
||||
*sp, stack_content, mi ? mi->name : "");
|
||||
}
|
||||
}
|
||||
|
||||
*sp += sizeof(uint32_t);
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_stack(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault,
|
||||
const backtrace_frame_t* backtrace, size_t frames) {
|
||||
bool have_first = false;
|
||||
size_t first, last;
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
if (backtrace[i].stack_top) {
|
||||
if (!have_first) {
|
||||
have_first = true;
|
||||
first = i;
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
if (!have_first) {
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(log, !at_fault, "\nstack:\n");
|
||||
|
||||
// Dump a few words before the first frame.
|
||||
bool only_in_tombstone = !at_fault;
|
||||
uintptr_t sp = backtrace[first].stack_top - STACK_WORDS * sizeof(uint32_t);
|
||||
dump_stack_segment(context, log, tid, only_in_tombstone, &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_t* frame = &backtrace[i];
|
||||
if (sp != frame->stack_top) {
|
||||
_LOG(log, only_in_tombstone, " ........ ........\n");
|
||||
sp = frame->stack_top;
|
||||
}
|
||||
if (i - first == 3) {
|
||||
only_in_tombstone = true;
|
||||
}
|
||||
if (i == last) {
|
||||
dump_stack_segment(context, log, tid, only_in_tombstone, &sp, STACK_WORDS, i);
|
||||
if (sp < frame->stack_top + frame->stack_size) {
|
||||
_LOG(log, only_in_tombstone, " ........ ........\n");
|
||||
}
|
||||
} else {
|
||||
size_t words = frame->stack_size / sizeof(uint32_t);
|
||||
if (words == 0) {
|
||||
words = 1;
|
||||
} else if (words > STACK_WORDS) {
|
||||
words = STACK_WORDS;
|
||||
}
|
||||
dump_stack_segment(context, log, tid, only_in_tombstone, &sp, words, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_backtrace_and_stack(const ptrace_context_t* context, log_t* log, pid_t tid,
|
||||
bool at_fault) {
|
||||
backtrace_frame_t backtrace[STACK_DEPTH];
|
||||
ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH);
|
||||
if (frames > 0) {
|
||||
dump_backtrace(context, log, tid, at_fault, backtrace, frames);
|
||||
dump_stack(context, log, tid, at_fault, backtrace, frames);
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_nearby_maps(const ptrace_context_t* context, log_t* log, pid_t tid) {
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) {
|
||||
_LOG(log, false, "cannot get siginfo for %d: %s\n",
|
||||
tid, strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (!signal_has_address(si.si_signo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t addr = (uintptr_t) si.si_addr;
|
||||
addr &= ~0xfff; /* round to 4K page boundary */
|
||||
if (addr == 0) { /* null-pointer deref */
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(log, false, "\nmemory map around fault addr %08x:\n", (int)si.si_addr);
|
||||
|
||||
/*
|
||||
* Search for a match, or for a hole where the match would be. The list
|
||||
* is backward from the file content, so it starts at high addresses.
|
||||
*/
|
||||
map_info_t* map = context->map_info_list;
|
||||
map_info_t *next = NULL;
|
||||
map_info_t *prev = NULL;
|
||||
while (map != NULL) {
|
||||
if (addr >= map->start && addr < map->end) {
|
||||
next = map->next;
|
||||
break;
|
||||
} else if (addr >= map->end) {
|
||||
/* map would be between "prev" and this entry */
|
||||
next = map;
|
||||
map = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
prev = map;
|
||||
map = map->next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Show "next" then "match" then "prev" so that the addresses appear in
|
||||
* ascending order (like /proc/pid/maps).
|
||||
*/
|
||||
if (next != NULL) {
|
||||
_LOG(log, false, " %08x-%08x %s\n", next->start, next->end, next->name);
|
||||
} else {
|
||||
_LOG(log, false, " (no map below)\n");
|
||||
}
|
||||
if (map != NULL) {
|
||||
_LOG(log, false, " %08x-%08x %s\n", map->start, map->end, map->name);
|
||||
} else {
|
||||
_LOG(log, false, " (no map for address)\n");
|
||||
}
|
||||
if (prev != NULL) {
|
||||
_LOG(log, false, " %08x-%08x %s\n", prev->start, prev->end, prev->name);
|
||||
} else {
|
||||
_LOG(log, false, " (no map above)\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_thread(const ptrace_context_t* context, log_t* log, pid_t tid, bool at_fault,
|
||||
int* total_sleep_time_usec) {
|
||||
wait_for_stop(tid, total_sleep_time_usec);
|
||||
|
||||
dump_registers(context, log, tid, at_fault);
|
||||
dump_backtrace_and_stack(context, log, tid, at_fault);
|
||||
if (at_fault) {
|
||||
dump_memory_and_code(context, log, tid, at_fault);
|
||||
dump_nearby_maps(context, log, tid);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if some thread is not detached cleanly */
|
||||
static bool dump_sibling_thread_report(const ptrace_context_t* context,
|
||||
log_t* log, pid_t pid, pid_t tid, int* total_sleep_time_usec) {
|
||||
char task_path[64];
|
||||
snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
|
||||
|
||||
DIR* d = opendir(task_path);
|
||||
/* Bail early if cannot open the task directory */
|
||||
if (d == NULL) {
|
||||
XLOG("Cannot open /proc/%d/task\n", pid);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool detach_failed = false;
|
||||
struct dirent debuf;
|
||||
struct dirent *de;
|
||||
while (!readdir_r(d, &debuf, &de) && de) {
|
||||
/* Ignore "." and ".." */
|
||||
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The main thread at fault has been handled individually */
|
||||
char* end;
|
||||
pid_t new_tid = strtoul(de->d_name, &end, 10);
|
||||
if (*end || new_tid == tid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip this thread if cannot ptrace it */
|
||||
if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_LOG(log, true, "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
|
||||
dump_thread_info(log, pid, new_tid, false);
|
||||
dump_thread(context, log, new_tid, false, total_sleep_time_usec);
|
||||
|
||||
if (ptrace(PTRACE_DETACH, new_tid, 0, 0) != 0) {
|
||||
LOG("ptrace detach from %d failed: %s\n", new_tid, strerror(errno));
|
||||
detach_failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
return detach_failed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads the contents of the specified log device, filters out the entries
|
||||
* that don't match the specified pid, and writes them to the tombstone file.
|
||||
*
|
||||
* If "tailOnly" is set, we only print the last few lines.
|
||||
*/
|
||||
static void dump_log_file(log_t* log, pid_t pid, const char* filename,
|
||||
bool tailOnly)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
/* circular buffer, for "tailOnly" mode */
|
||||
const int kShortLogMaxLines = 5;
|
||||
const int kShortLogLineLen = 256;
|
||||
char shortLog[kShortLogMaxLines][kShortLogLineLen];
|
||||
int shortLogCount = 0;
|
||||
int shortLogNext = 0;
|
||||
|
||||
int logfd = open(filename, O_RDONLY | O_NONBLOCK);
|
||||
if (logfd < 0) {
|
||||
XLOG("Unable to open %s: %s\n", filename, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
union {
|
||||
unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
|
||||
struct logger_entry entry;
|
||||
} log_entry;
|
||||
|
||||
while (true) {
|
||||
ssize_t actual = read(logfd, log_entry.buf, LOGGER_ENTRY_MAX_LEN);
|
||||
if (actual < 0) {
|
||||
if (errno == EINTR) {
|
||||
/* interrupted by signal, retry */
|
||||
continue;
|
||||
} else if (errno == EAGAIN) {
|
||||
/* non-blocking EOF; we're done */
|
||||
break;
|
||||
} else {
|
||||
_LOG(log, true, "Error while reading log: %s\n",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
} else if (actual == 0) {
|
||||
_LOG(log, true, "Got zero bytes while reading log: %s\n",
|
||||
strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: if you XLOG something here, this will spin forever,
|
||||
* because you will be writing as fast as you're reading. Any
|
||||
* high-frequency debug diagnostics should just be written to
|
||||
* the tombstone file.
|
||||
*/
|
||||
|
||||
struct logger_entry* entry = &log_entry.entry;
|
||||
|
||||
if (entry->pid != (int32_t) pid) {
|
||||
/* wrong pid, ignore */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
_LOG(log, true, "--------- %slog %s\n",
|
||||
tailOnly ? "tail end of " : "", filename);
|
||||
first = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Msg format is: <priority:1><tag:N>\0<message:N>\0
|
||||
*
|
||||
* We want to display it in the same format as "logcat -v threadtime"
|
||||
* (although in this case the pid is redundant).
|
||||
*
|
||||
* TODO: scan for line breaks ('\n') and display each text line
|
||||
* on a separate line, prefixed with the header, like logcat does.
|
||||
*/
|
||||
static const char* kPrioChars = "!.VDIWEFS";
|
||||
unsigned char prio = entry->msg[0];
|
||||
char* tag = entry->msg + 1;
|
||||
char* msg = tag + strlen(tag) + 1;
|
||||
|
||||
/* consume any trailing newlines */
|
||||
char* eatnl = msg + strlen(msg) - 1;
|
||||
while (eatnl >= msg && *eatnl == '\n') {
|
||||
*eatnl-- = '\0';
|
||||
}
|
||||
|
||||
char prioChar = (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?');
|
||||
|
||||
char timeBuf[32];
|
||||
time_t sec = (time_t) entry->sec;
|
||||
struct tm tmBuf;
|
||||
struct tm* ptm;
|
||||
ptm = localtime_r(&sec, &tmBuf);
|
||||
strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
|
||||
|
||||
if (tailOnly) {
|
||||
snprintf(shortLog[shortLogNext], kShortLogLineLen,
|
||||
"%s.%03d %5d %5d %c %-8s: %s",
|
||||
timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
|
||||
prioChar, tag, msg);
|
||||
shortLogNext = (shortLogNext + 1) % kShortLogMaxLines;
|
||||
shortLogCount++;
|
||||
} else {
|
||||
_LOG(log, true, "%s.%03d %5d %5d %c %-8s: %s\n",
|
||||
timeBuf, entry->nsec / 1000000, entry->pid, entry->tid,
|
||||
prioChar, tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (tailOnly) {
|
||||
int i;
|
||||
|
||||
/*
|
||||
* If we filled the buffer, we want to start at "next", which has
|
||||
* the oldest entry. If we didn't, we want to start at zero.
|
||||
*/
|
||||
if (shortLogCount < kShortLogMaxLines) {
|
||||
shortLogNext = 0;
|
||||
} else {
|
||||
shortLogCount = kShortLogMaxLines; /* cap at window size */
|
||||
}
|
||||
|
||||
for (i = 0; i < shortLogCount; i++) {
|
||||
_LOG(log, true, "%s\n", shortLog[shortLogNext]);
|
||||
shortLogNext = (shortLogNext + 1) % kShortLogMaxLines;
|
||||
}
|
||||
}
|
||||
|
||||
close(logfd);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, bool tailOnly)
|
||||
{
|
||||
dump_log_file(log, pid, "/dev/log/system", tailOnly);
|
||||
dump_log_file(log, pid, "/dev/log/main", tailOnly);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dumps all information about the specified pid to the tombstone.
|
||||
*/
|
||||
static bool dump_crash(log_t* log, pid_t pid, pid_t tid, int signal,
|
||||
bool dump_sibling_threads, int* total_sleep_time_usec)
|
||||
{
|
||||
/* don't copy log messages to tombstone unless this is a dev device */
|
||||
char value[PROPERTY_VALUE_MAX];
|
||||
property_get("ro.debuggable", value, "0");
|
||||
bool want_logs = (value[0] == '1');
|
||||
|
||||
_LOG(log, false,
|
||||
"*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
|
||||
dump_build_info(log);
|
||||
dump_thread_info(log, pid, tid, true);
|
||||
if(signal) {
|
||||
dump_fault_addr(log, tid, signal);
|
||||
}
|
||||
|
||||
ptrace_context_t* context = load_ptrace_context(tid);
|
||||
dump_thread(context, log, tid, true, total_sleep_time_usec);
|
||||
|
||||
if (want_logs) {
|
||||
dump_logs(log, pid, true);
|
||||
}
|
||||
|
||||
bool detach_failed = false;
|
||||
if (dump_sibling_threads) {
|
||||
detach_failed = dump_sibling_thread_report(context, log, pid, tid, total_sleep_time_usec);
|
||||
}
|
||||
|
||||
free_ptrace_context(context);
|
||||
|
||||
if (want_logs) {
|
||||
dump_logs(log, pid, false);
|
||||
}
|
||||
return detach_failed;
|
||||
}
|
||||
|
||||
/*
|
||||
* find_and_open_tombstone - find an available tombstone slot, if any, of the
|
||||
* form tombstone_XX where XX is 00 to MAX_TOMBSTONES-1, inclusive. If no
|
||||
* file is available, we reuse the least-recently-modified file.
|
||||
*
|
||||
* Returns the path of the tombstone file, allocated using malloc(). Caller must free() it.
|
||||
*/
|
||||
static char* find_and_open_tombstone(int* fd)
|
||||
{
|
||||
unsigned long mtime = ULONG_MAX;
|
||||
struct stat sb;
|
||||
|
||||
/*
|
||||
* XXX: Our stat.st_mtime isn't time_t. If it changes, as it probably ought
|
||||
* to, our logic breaks. This check will generate a warning if that happens.
|
||||
*/
|
||||
typecheck(mtime, sb.st_mtime);
|
||||
|
||||
/*
|
||||
* In a single wolf-like pass, find an available slot and, in case none
|
||||
* exist, find and record the least-recently-modified file.
|
||||
*/
|
||||
char path[128];
|
||||
int oldest = 0;
|
||||
for (int i = 0; i < MAX_TOMBSTONES; i++) {
|
||||
snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", i);
|
||||
|
||||
if (!stat(path, &sb)) {
|
||||
if (sb.st_mtime < mtime) {
|
||||
oldest = i;
|
||||
mtime = sb.st_mtime;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (errno != ENOENT)
|
||||
continue;
|
||||
|
||||
*fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0600);
|
||||
if (*fd < 0)
|
||||
continue; /* raced ? */
|
||||
|
||||
fchown(*fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
/* we didn't find an available file, so we clobber the oldest one */
|
||||
snprintf(path, sizeof(path), TOMBSTONE_DIR"/tombstone_%02d", oldest);
|
||||
*fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
|
||||
if (*fd < 0) {
|
||||
LOG("failed to open tombstone file '%s': %s\n", path, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
fchown(*fd, AID_SYSTEM, AID_SYSTEM);
|
||||
return strdup(path);
|
||||
}
|
||||
|
||||
char* engrave_tombstone(pid_t pid, pid_t tid, int signal,
|
||||
bool dump_sibling_threads, bool quiet, bool* detach_failed,
|
||||
int* total_sleep_time_usec) {
|
||||
mkdir(TOMBSTONE_DIR, 0755);
|
||||
chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM);
|
||||
|
||||
int fd;
|
||||
char* path = find_and_open_tombstone(&fd);
|
||||
if (!path) {
|
||||
*detach_failed = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
log_t log;
|
||||
log.tfd = fd;
|
||||
log.quiet = quiet;
|
||||
*detach_failed = dump_crash(&log, pid, tid, signal, dump_sibling_threads,
|
||||
total_sleep_time_usec);
|
||||
|
||||
close(fd);
|
||||
return path;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _DEBUGGERD_TOMBSTONE_H
|
||||
#define _DEBUGGERD_TOMBSTONE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <corkscrew/ptrace.h>
|
||||
|
||||
/* Creates a tombstone file and writes the crash dump to it.
|
||||
* Returns the path of the tombstone, which must be freed using free(). */
|
||||
char* engrave_tombstone(pid_t pid, pid_t tid, int signal,
|
||||
bool dump_sibling_threads, bool quiet, bool* detach_failed, int* total_sleep_time_usec);
|
||||
|
||||
#endif // _DEBUGGERD_TOMBSTONE_H
|
|
@ -15,293 +15,80 @@
|
|||
** limitations under the License.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <cutils/logd.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <errno.h>
|
||||
#include <corkscrew/demangle.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
#define STACK_DEPTH 32
|
||||
#define STACK_WORDS 16
|
||||
const int sleep_time_usec = 50000; /* 0.05 seconds */
|
||||
const int max_total_sleep_usec = 10000000; /* 10 seconds */
|
||||
|
||||
void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) {
|
||||
void _LOG(log_t* log, bool in_tombstone_only, const char *fmt, ...) {
|
||||
char buf[512];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
if (tfd >= 0) {
|
||||
if (log && log->tfd >= 0) {
|
||||
int len;
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
len = strlen(buf);
|
||||
if(tfd >= 0) write(tfd, buf, len);
|
||||
write(log->tfd, buf, len);
|
||||
}
|
||||
|
||||
if (!in_tombstone_only)
|
||||
if (!in_tombstone_only && (!log || !log->quiet)) {
|
||||
__android_log_vprint(ANDROID_LOG_INFO, "DEBUG", fmt, ap);
|
||||
}
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
bool signal_has_address(int sig) {
|
||||
switch (sig) {
|
||||
case SIGILL:
|
||||
case SIGFPE:
|
||||
case SIGSEGV:
|
||||
case SIGBUS:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_backtrace(const ptrace_context_t* context __attribute((unused)),
|
||||
int tfd, pid_t tid __attribute((unused)), bool at_fault,
|
||||
const backtrace_frame_t* backtrace, size_t frames) {
|
||||
_LOG(tfd, !at_fault, "\nbacktrace:\n");
|
||||
|
||||
backtrace_symbol_t backtrace_symbols[STACK_DEPTH];
|
||||
get_backtrace_symbols_ptrace(context, backtrace, frames, backtrace_symbols);
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
char line[MAX_BACKTRACE_LINE_LENGTH];
|
||||
format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i],
|
||||
line, MAX_BACKTRACE_LINE_LENGTH);
|
||||
_LOG(tfd, !at_fault, " %s\n", line);
|
||||
}
|
||||
free_backtrace_symbols(backtrace_symbols, frames);
|
||||
}
|
||||
|
||||
static void dump_stack_segment(const ptrace_context_t* context, int tfd, pid_t tid,
|
||||
bool only_in_tombstone, uintptr_t* sp, size_t words, int label) {
|
||||
for (size_t i = 0; i < words; i++) {
|
||||
uint32_t stack_content;
|
||||
if (!try_get_word_ptrace(tid, *sp, &stack_content)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const map_info_t* mi;
|
||||
const symbol_t* symbol;
|
||||
find_symbol_ptrace(context, stack_content, &mi, &symbol);
|
||||
|
||||
if (symbol) {
|
||||
char* demangled_name = demangle_symbol_name(symbol->name);
|
||||
const char* symbol_name = demangled_name ? demangled_name : symbol->name;
|
||||
uint32_t offset = stack_content - (mi->start + symbol->start);
|
||||
if (!i && label >= 0) {
|
||||
if (offset) {
|
||||
_LOG(tfd, only_in_tombstone, " #%02d %08x %08x %s (%s+%u)\n",
|
||||
label, *sp, stack_content, mi ? mi->name : "", symbol_name, offset);
|
||||
} else {
|
||||
_LOG(tfd, only_in_tombstone, " #%02d %08x %08x %s (%s)\n",
|
||||
label, *sp, stack_content, mi ? mi->name : "", symbol_name);
|
||||
}
|
||||
int wait_for_signal(pid_t tid, int* total_sleep_time_usec) {
|
||||
for (;;) {
|
||||
int status;
|
||||
pid_t n = waitpid(tid, &status, __WALL | WNOHANG);
|
||||
if (n < 0) {
|
||||
if(errno == EAGAIN) continue;
|
||||
LOG("waitpid failed: %s\n", strerror(errno));
|
||||
return -1;
|
||||
} else if (n > 0) {
|
||||
XLOG("waitpid: n=%d status=%08x\n", n, status);
|
||||
if (WIFSTOPPED(status)) {
|
||||
return WSTOPSIG(status);
|
||||
} else {
|
||||
if (offset) {
|
||||
_LOG(tfd, only_in_tombstone, " %08x %08x %s (%s+%u)\n",
|
||||
*sp, stack_content, mi ? mi->name : "", symbol_name, offset);
|
||||
} else {
|
||||
_LOG(tfd, only_in_tombstone, " %08x %08x %s (%s)\n",
|
||||
*sp, stack_content, mi ? mi->name : "", symbol_name);
|
||||
}
|
||||
}
|
||||
free(demangled_name);
|
||||
} else {
|
||||
if (!i && label >= 0) {
|
||||
_LOG(tfd, only_in_tombstone, " #%02d %08x %08x %s\n",
|
||||
label, *sp, stack_content, mi ? mi->name : "");
|
||||
} else {
|
||||
_LOG(tfd, only_in_tombstone, " %08x %08x %s\n",
|
||||
*sp, stack_content, mi ? mi->name : "");
|
||||
LOG("unexpected waitpid response: n=%d, status=%08x\n", n, status);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
*sp += sizeof(uint32_t);
|
||||
if (*total_sleep_time_usec > max_total_sleep_usec) {
|
||||
LOG("timed out waiting for tid=%d to die\n", tid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* not ready yet */
|
||||
XLOG("not ready yet\n");
|
||||
usleep(sleep_time_usec);
|
||||
*total_sleep_time_usec += sleep_time_usec;
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_stack(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault,
|
||||
const backtrace_frame_t* backtrace, size_t frames) {
|
||||
bool have_first = false;
|
||||
size_t first, last;
|
||||
for (size_t i = 0; i < frames; i++) {
|
||||
if (backtrace[i].stack_top) {
|
||||
if (!have_first) {
|
||||
have_first = true;
|
||||
first = i;
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
if (!have_first) {
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(tfd, !at_fault, "\nstack:\n");
|
||||
|
||||
// Dump a few words before the first frame.
|
||||
bool only_in_tombstone = !at_fault;
|
||||
uintptr_t sp = backtrace[first].stack_top - STACK_WORDS * sizeof(uint32_t);
|
||||
dump_stack_segment(context, tfd, tid, only_in_tombstone, &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_t* frame = &backtrace[i];
|
||||
if (sp != frame->stack_top) {
|
||||
_LOG(tfd, only_in_tombstone, " ........ ........\n");
|
||||
sp = frame->stack_top;
|
||||
}
|
||||
if (i - first == 3) {
|
||||
only_in_tombstone = true;
|
||||
}
|
||||
if (i == last) {
|
||||
dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, STACK_WORDS, i);
|
||||
if (sp < frame->stack_top + frame->stack_size) {
|
||||
_LOG(tfd, only_in_tombstone, " ........ ........\n");
|
||||
}
|
||||
} else {
|
||||
size_t words = frame->stack_size / sizeof(uint32_t);
|
||||
if (words == 0) {
|
||||
words = 1;
|
||||
} else if (words > STACK_WORDS) {
|
||||
words = STACK_WORDS;
|
||||
}
|
||||
dump_stack_segment(context, tfd, tid, only_in_tombstone, &sp, words, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dump_backtrace_and_stack(const ptrace_context_t* context, int tfd, pid_t tid,
|
||||
bool at_fault) {
|
||||
backtrace_frame_t backtrace[STACK_DEPTH];
|
||||
ssize_t frames = unwind_backtrace_ptrace(tid, context, backtrace, 0, STACK_DEPTH);
|
||||
if (frames > 0) {
|
||||
dump_backtrace(context, tfd, tid, at_fault, backtrace, frames);
|
||||
dump_stack(context, tfd, tid, at_fault, backtrace, frames);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_memory(int tfd, pid_t tid, uintptr_t addr, bool at_fault) {
|
||||
char code_buffer[64]; /* actual 8+1+((8+1)*4) + 1 == 45 */
|
||||
char ascii_buffer[32]; /* actual 16 + 1 == 17 */
|
||||
uintptr_t p, end;
|
||||
|
||||
p = addr & ~3;
|
||||
p -= 32;
|
||||
if (p > addr) {
|
||||
/* catch underflow */
|
||||
p = 0;
|
||||
}
|
||||
end = p + 80;
|
||||
/* catch overflow; 'end - p' has to be multiples of 16 */
|
||||
while (end < p)
|
||||
end -= 16;
|
||||
|
||||
/* Dump the code around PC as:
|
||||
* addr contents ascii
|
||||
* 00008d34 ef000000 e8bd0090 e1b00000 512fff1e ............../Q
|
||||
* 00008d44 ea00b1f9 e92d0090 e3a070fc ef000000 ......-..p......
|
||||
*/
|
||||
while (p < end) {
|
||||
char* asc_out = ascii_buffer;
|
||||
|
||||
sprintf(code_buffer, "%08x ", p);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
/*
|
||||
* If we see (data == -1 && errno != 0), we know that the ptrace
|
||||
* call failed, probably because we're dumping memory in an
|
||||
* unmapped or inaccessible page. I don't know if there's
|
||||
* value in making that explicit in the output -- it likely
|
||||
* just complicates parsing and clarifies nothing for the
|
||||
* enlightened reader.
|
||||
*/
|
||||
long data = ptrace(PTRACE_PEEKTEXT, tid, (void*)p, NULL);
|
||||
sprintf(code_buffer + strlen(code_buffer), "%08lx ", data);
|
||||
|
||||
int j;
|
||||
for (j = 0; j < 4; j++) {
|
||||
/*
|
||||
* Our isprint() allows high-ASCII characters that display
|
||||
* differently (often badly) in different viewers, so we
|
||||
* just use a simpler test.
|
||||
*/
|
||||
char val = (data >> (j*8)) & 0xff;
|
||||
if (val >= 0x20 && val < 0x7f) {
|
||||
*asc_out++ = val;
|
||||
} else {
|
||||
*asc_out++ = '.';
|
||||
}
|
||||
}
|
||||
p += 4;
|
||||
}
|
||||
*asc_out = '\0';
|
||||
_LOG(tfd, !at_fault, " %s %s\n", code_buffer, ascii_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_nearby_maps(const ptrace_context_t* context, int tfd, pid_t tid) {
|
||||
void wait_for_stop(pid_t tid, int* total_sleep_time_usec) {
|
||||
siginfo_t si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
if (ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) {
|
||||
_LOG(tfd, false, "cannot get siginfo for %d: %s\n",
|
||||
tid, strerror(errno));
|
||||
return;
|
||||
}
|
||||
if (!signal_has_address(si.si_signo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t addr = (uintptr_t) si.si_addr;
|
||||
addr &= ~0xfff; /* round to 4K page boundary */
|
||||
if (addr == 0) { /* null-pointer deref */
|
||||
return;
|
||||
}
|
||||
|
||||
_LOG(tfd, false, "\nmemory map around fault addr %08x:\n", (int)si.si_addr);
|
||||
|
||||
/*
|
||||
* Search for a match, or for a hole where the match would be. The list
|
||||
* is backward from the file content, so it starts at high addresses.
|
||||
*/
|
||||
map_info_t* map = context->map_info_list;
|
||||
map_info_t *next = NULL;
|
||||
map_info_t *prev = NULL;
|
||||
while (map != NULL) {
|
||||
if (addr >= map->start && addr < map->end) {
|
||||
next = map->next;
|
||||
break;
|
||||
} else if (addr >= map->end) {
|
||||
/* map would be between "prev" and this entry */
|
||||
next = map;
|
||||
map = NULL;
|
||||
while (TEMP_FAILURE_RETRY(ptrace(PTRACE_GETSIGINFO, tid, 0, &si)) < 0 && errno == ESRCH) {
|
||||
if (*total_sleep_time_usec > max_total_sleep_usec) {
|
||||
LOG("timed out waiting for tid=%d to stop\n", tid);
|
||||
break;
|
||||
}
|
||||
|
||||
prev = map;
|
||||
map = map->next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Show "next" then "match" then "prev" so that the addresses appear in
|
||||
* ascending order (like /proc/pid/maps).
|
||||
*/
|
||||
if (next != NULL) {
|
||||
_LOG(tfd, false, " %08x-%08x %s\n", next->start, next->end, next->name);
|
||||
} else {
|
||||
_LOG(tfd, false, " (no map below)\n");
|
||||
}
|
||||
if (map != NULL) {
|
||||
_LOG(tfd, false, " %08x-%08x %s\n", map->start, map->end, map->name);
|
||||
} else {
|
||||
_LOG(tfd, false, " (no map for address)\n");
|
||||
}
|
||||
if (prev != NULL) {
|
||||
_LOG(tfd, false, " %08x-%08x %s\n", prev->start, prev->end, prev->name);
|
||||
} else {
|
||||
_LOG(tfd, false, " (no map above)\n");
|
||||
usleep(sleep_time_usec);
|
||||
*total_sleep_time_usec += sleep_time_usec;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,53 +20,35 @@
|
|||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <corkscrew/backtrace.h>
|
||||
|
||||
typedef struct {
|
||||
/* tombstone file descriptor */
|
||||
int tfd;
|
||||
/* if true, does not log anything to the Android logcat */
|
||||
bool quiet;
|
||||
} log_t;
|
||||
|
||||
/* Log information onto the tombstone. */
|
||||
void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...)
|
||||
void _LOG(log_t* log, bool in_tombstone_only, const char *fmt, ...)
|
||||
__attribute__ ((format(printf, 3, 4)));
|
||||
|
||||
#define LOG(fmt...) _LOG(-1, 0, fmt)
|
||||
#define LOG(fmt...) _LOG(NULL, 0, fmt)
|
||||
|
||||
/* Set to 1 for normal debug traces */
|
||||
#if 0
|
||||
#define XLOG(fmt...) _LOG(-1, 0, fmt)
|
||||
#define XLOG(fmt...) _LOG(NULL, 0, fmt)
|
||||
#else
|
||||
#define XLOG(fmt...) do {} while(0)
|
||||
#endif
|
||||
|
||||
/* Set to 1 for chatty debug traces. Includes all resolved dynamic symbols */
|
||||
#if 0
|
||||
#define XLOG2(fmt...) _LOG(-1, 0, fmt)
|
||||
#define XLOG2(fmt...) _LOG(NULL, 0, fmt)
|
||||
#else
|
||||
#define XLOG2(fmt...) do {} while(0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Returns true if the specified signal has an associated address.
|
||||
* (i.e. it sets siginfo_t.si_addr).
|
||||
*/
|
||||
bool signal_has_address(int sig);
|
||||
|
||||
/*
|
||||
* Dumps the backtrace and contents of the stack.
|
||||
*/
|
||||
void dump_backtrace_and_stack(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault);
|
||||
|
||||
/*
|
||||
* Dumps a few bytes of memory, starting a bit before and ending a bit
|
||||
* after the specified address.
|
||||
*/
|
||||
void dump_memory(int tfd, pid_t tid, uintptr_t addr, bool at_fault);
|
||||
|
||||
/*
|
||||
* If this isn't clearly a null pointer dereference, dump the
|
||||
* /proc/maps entries near the fault address.
|
||||
*
|
||||
* This only makes sense to do on the thread that crashed.
|
||||
*/
|
||||
void dump_nearby_maps(const ptrace_context_t* context, int tfd, pid_t tid);
|
||||
|
||||
int wait_for_signal(pid_t tid, int* total_sleep_time_usec);
|
||||
void wait_for_stop(pid_t tid, int* total_sleep_time_usec);
|
||||
|
||||
#endif // _DEBUGGERD_UTILITY_H
|
||||
|
|
|
@ -15,59 +15,44 @@
|
|||
** limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/exec_elf.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <cutils/sockets.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
#include <corkscrew/backtrace.h>
|
||||
#include <corkscrew/ptrace.h>
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/user.h>
|
||||
|
||||
#include "../machine.h"
|
||||
#include "../utility.h"
|
||||
#include "../machine.h"
|
||||
|
||||
static void dump_registers(const ptrace_context_t* context __attribute((unused)),
|
||||
int tfd, pid_t tid, bool at_fault) {
|
||||
void dump_memory_and_code(const ptrace_context_t* context __attribute((unused)),
|
||||
log_t* log, pid_t tid, bool at_fault) {
|
||||
}
|
||||
|
||||
void dump_registers(const ptrace_context_t* context __attribute((unused)),
|
||||
log_t* log, pid_t tid, bool at_fault) {
|
||||
struct pt_regs_x86 r;
|
||||
bool only_in_tombstone = !at_fault;
|
||||
|
||||
if(ptrace(PTRACE_GETREGS, tid, 0, &r)) {
|
||||
_LOG(tfd, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
|
||||
_LOG(log, only_in_tombstone, "cannot get registers: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
//if there is no stack, no print just like arm
|
||||
if(!r.ebp)
|
||||
return;
|
||||
_LOG(tfd, only_in_tombstone, " eax %08x ebx %08x ecx %08x edx %08x\n",
|
||||
_LOG(log, only_in_tombstone, " eax %08x ebx %08x ecx %08x edx %08x\n",
|
||||
r.eax, r.ebx, r.ecx, r.edx);
|
||||
_LOG(tfd, only_in_tombstone, " esi %08x edi %08x\n",
|
||||
_LOG(log, only_in_tombstone, " esi %08x edi %08x\n",
|
||||
r.esi, r.edi);
|
||||
_LOG(tfd, only_in_tombstone, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n",
|
||||
_LOG(log, only_in_tombstone, " xcs %08x xds %08x xes %08x xfs %08x xss %08x\n",
|
||||
r.xcs, r.xds, r.xes, r.xfs, r.xss);
|
||||
_LOG(tfd, only_in_tombstone, " eip %08x ebp %08x esp %08x flags %08x\n",
|
||||
_LOG(log, only_in_tombstone, " eip %08x ebp %08x esp %08x flags %08x\n",
|
||||
r.eip, r.ebp, r.esp, r.eflags);
|
||||
}
|
||||
|
||||
void dump_thread(const ptrace_context_t* context, int tfd, pid_t tid, bool at_fault) {
|
||||
dump_registers(context, tfd, tid, at_fault);
|
||||
|
||||
dump_backtrace_and_stack(context, tfd, tid, at_fault);
|
||||
|
||||
if (at_fault) {
|
||||
dump_nearby_maps(context, tfd, tid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef __CUTILS_DEBUGGER_H
|
||||
#define __CUTILS_DEBUGGER_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DEBUGGER_SOCKET_NAME "android:debuggerd"
|
||||
|
||||
typedef enum {
|
||||
// dump a crash
|
||||
DEBUGGER_ACTION_CRASH,
|
||||
// dump a tombstone file
|
||||
DEBUGGER_ACTION_DUMP_TOMBSTONE,
|
||||
// dump a backtrace only back to the socket
|
||||
DEBUGGER_ACTION_DUMP_BACKTRACE,
|
||||
} debugger_action_t;
|
||||
|
||||
/* message sent over the socket */
|
||||
typedef struct {
|
||||
debugger_action_t action;
|
||||
pid_t tid;
|
||||
} debugger_msg_t;
|
||||
|
||||
/* Dumps a process backtrace, registers, and stack to a tombstone file (requires root).
|
||||
* Stores the tombstone path in the provided buffer.
|
||||
* Returns 0 on success, -1 on error.
|
||||
*/
|
||||
int dump_tombstone(pid_t tid, char* pathbuf, size_t pathlen);
|
||||
|
||||
/* Dumps a process backtrace only to the specified file (requires root).
|
||||
* Returns 0 on success, -1 on error.
|
||||
*/
|
||||
int dump_backtrace_to_file(pid_t tid, int fd);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __CUTILS_DEBUGGER_H */
|
|
@ -116,7 +116,15 @@ libcutils_c_includes := bionic/libc/private
|
|||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := libcutils
|
||||
LOCAL_SRC_FILES := $(commonSources) ashmem-dev.c mq.c android_reboot.c partition_utils.c uevent.c qtaguid.c klog.c
|
||||
LOCAL_SRC_FILES := $(commonSources) \
|
||||
android_reboot.c \
|
||||
ashmem-dev.c \
|
||||
debugger.c \
|
||||
klog.c \
|
||||
mq.c \
|
||||
partition_utils.c \
|
||||
qtaguid.c \
|
||||
uevent.c
|
||||
|
||||
ifeq ($(TARGET_ARCH),arm)
|
||||
LOCAL_SRC_FILES += arch-arm/memset32.S
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cutils/debugger.h>
|
||||
#include <cutils/sockets.h>
|
||||
|
||||
int dump_tombstone(pid_t tid, char* pathbuf, size_t pathlen) {
|
||||
int s = socket_local_client(DEBUGGER_SOCKET_NAME,
|
||||
ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
|
||||
if (s < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
debugger_msg_t msg;
|
||||
msg.tid = tid;
|
||||
msg.action = DEBUGGER_ACTION_DUMP_TOMBSTONE;
|
||||
|
||||
int result = 0;
|
||||
if (TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))) != sizeof(msg)) {
|
||||
result = -1;
|
||||
} else {
|
||||
char ack;
|
||||
if (TEMP_FAILURE_RETRY(read(s, &ack, 1)) != 1) {
|
||||
result = -1;
|
||||
} else {
|
||||
if (pathbuf && pathlen) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(read(s, pathbuf, pathlen - 1));
|
||||
if (n <= 0) {
|
||||
result = -1;
|
||||
} else {
|
||||
pathbuf[n] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TEMP_FAILURE_RETRY(close(s));
|
||||
return result;
|
||||
}
|
||||
|
||||
int dump_backtrace_to_file(pid_t tid, int fd) {
|
||||
int s = socket_local_client(DEBUGGER_SOCKET_NAME,
|
||||
ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
|
||||
if (s < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
debugger_msg_t msg;
|
||||
msg.tid = tid;
|
||||
msg.action = DEBUGGER_ACTION_DUMP_BACKTRACE;
|
||||
|
||||
int result = 0;
|
||||
if (TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))) != sizeof(msg)) {
|
||||
result = -1;
|
||||
} else {
|
||||
char ack;
|
||||
if (TEMP_FAILURE_RETRY(read(s, &ack, 1)) != 1) {
|
||||
result = -1;
|
||||
} else {
|
||||
char buffer[4096];
|
||||
ssize_t n;
|
||||
while ((n = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)))) > 0) {
|
||||
if (TEMP_FAILURE_RETRY(write(fd, buffer, n)) != n) {
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TEMP_FAILURE_RETRY(close(s));
|
||||
return result;
|
||||
}
|
Loading…
Reference in New Issue