Android use UNIX Domain Socket for get debug log.
usually name "tombstone_0X" and so on in /data/log/logcat/
1. Server
First , it has a socket server. it's a executable program.
The code was in "system/core/debuggerd/debuggerd.c"
in the main, it open a IPC socket
int main() { int s; struct sigaction act; int logsocket = -1; logsocket = socket_local_client("logd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_DGRAM); if(logsocket < 0) { logsocket = -1; } else { fcntl(logsocket, F_SETFD, FD_CLOEXEC); } act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); sigaddset(&act.sa_mask,SIGCHLD); act.sa_flags = SA_NOCLDWAIT; sigaction(SIGCHLD, &act, 0); s = socket_local_server("android:debuggerd", ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); if(s < 0) return -1; fcntl(s, F_SETFD, FD_CLOEXEC); LOG("debuggerd: " __DATE__ " " __TIME__ "\n"); for(;;) { struct sockaddr addr; socklen_t alen; int fd; alen = sizeof(addr); fd = accept(s, &addr, &alen); if(fd < 0) continue; fcntl(fd, F_SETFD, FD_CLOEXEC); handle_crashing_process(fd); } return 0; }
and the log is generated in this function.
2. Client
how could a crashed program connect to the server?
Since every crashed program will receive a signal from the system, so we use "signal" register a signal handle function to the client.
So we can do something before the program die.
the code is "boinic/linker/Debugger.c"
void debugger_init() { signal(SIGILL, debugger_signal_handler); signal(SIGABRT, debugger_signal_handler); signal(SIGBUS, debugger_signal_handler); signal(SIGFPE, debugger_signal_handler); signal(SIGSEGV, debugger_signal_handler); signal(SIGSTKFLT, debugger_signal_handler); signal(SIGPIPE, debugger_signal_handler); }
void debugger_signal_handler(int n) { unsigned tid; int s; /* avoid picking up GC interrupts */ signal(SIGUSR1, SIG_IGN); tid = gettid(); s = socket_abstract_client("android:debuggerd", SOCK_STREAM); if(s >= 0) { /* debugger knows our pid from the credentials on the * local socket but we need to tell it our tid. It * is paranoid and will verify that we are giving a tid * that's actually in our process */ int ret; RETRY_ON_EINTR(ret, write(s, &tid, sizeof(unsigned))); if (ret == sizeof(unsigned)) { /* if the write failed, there is no point to read on * the file descriptor. */ RETRY_ON_EINTR(ret, read(s, &tid, 1)); notify_gdb_of_libraries(); } close(s); } /* remove our net so we fault for real when we return */ signal(n, SIG_IGN); }
3. how is the log file produced
in the client, we just write out tid to the server, of course the log is generated by the server.
in it's "handle_crashing_process" function.
static void handle_crashing_process(int fd) { ..... for(;;) { n = waitpid(tid, &status, __WALL); if(n < 0) { if(errno == EAGAIN) continue; LOG("waitpid failed: %s\n", strerror(errno)); goto done; } LOG("waitpid: n=%d status=%08x\n", n, status); if(WIFSTOPPED(status)){ n = WSTOPSIG(status); switch(n) { case SIGSTOP: LOG("stopped -- continuing\n"); n = ptrace(PTRACE_CONT, tid, 0, 0); if(n) { LOG("ptrace failed: %s\n", strerror(errno)); goto done; } continue; case SIGILL: case SIGABRT: case SIGBUS: case SIGFPE: case SIGSEGV: case SIGSTKFLT: { LOG("stopped -- fatal signal\n"); need_cleanup = engrave_tombstone(cr.pid, tid, debug_uid, n); kill(tid, SIGSTOP); goto done; } default: LOG("stopped -- unexpected signal\n"); goto done; } } else { LOG("unexpected waitpid response\n"); goto done; } } ... }
the function did a lot of things, and we will discuss the detail later.