am bbb4f346: Merge "Add support for explicitly dumping native stacks."

* commit 'bbb4f34640a21d0031ead8ed00845597a89bfcd8':
  Add support for explicitly dumping native stacks.
diff --git a/debuggerd/arm/machine.c b/debuggerd/arm/machine.c
index 891b1ef..d941684 100644
--- a/debuggerd/arm/machine.c
+++ b/debuggerd/arm/machine.c
@@ -121,7 +121,7 @@
         _LOG(tfd, 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\n", vfp_regs.fpscr);
+    _LOG(tfd, only_in_tombstone, "    scr %08lx\n", vfp_regs.fpscr);
 #endif
 }
 
diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c
index 1599439..5a180f1 100644
--- a/debuggerd/debuggerd.c
+++ b/debuggerd/debuggerd.c
@@ -28,6 +28,7 @@
 #include <sys/wait.h>
 #include <sys/exec_elf.h>
 #include <sys/stat.h>
+#include <sys/poll.h>
 
 #include <cutils/sockets.h>
 #include <cutils/logd.h>
@@ -64,6 +65,7 @@
     case SIGFPE:     return "SIGFPE";
     case SIGSEGV:    return "SIGSEGV";
     case SIGSTKFLT:  return "SIGSTKFLT";
+    case SIGSTOP:    return "SIGSTOP";
     default:         return "?";
     }
 }
@@ -156,49 +158,50 @@
 
 /* Return true if some thread is not detached cleanly */
 static bool dump_sibling_thread_report(ptrace_context_t* context,
-        int tfd, pid_t pid, pid_t tid)
-{
-    char task_path[1024];
+        int tfd, pid_t pid, pid_t tid) {
+    char task_path[64];
+    snprintf(task_path, sizeof(task_path), "/proc/%d/task", pid);
 
-    sprintf(task_path, "/proc/%d/task", pid);
-    DIR *d;
-    struct dirent *de;
-    int need_cleanup = 0;
-
-    d = opendir(task_path);
+    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, ".."))
+        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)
+        if (new_tid == tid) {
             continue;
+        }
 
         /* Skip this thread if cannot ptrace it */
-        if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0)
+        if (ptrace(PTRACE_ATTACH, new_tid, 0, 0) < 0) {
             continue;
+        }
 
-        _LOG(tfd, true,
-                "--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
+        _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) {
-            XLOG("detach of tid %d failed: %s\n", new_tid, strerror(errno));
-            need_cleanup = 1;
+            LOG("ptrace detach from %d failed: %s\n", new_tid, strerror(errno));
+            detach_failed = true;
         }
     }
-    closedir(d);
 
-    return need_cleanup != 0;
+    closedir(d);
+    return detach_failed;
 }
 
 /*
@@ -355,7 +358,6 @@
     char value[PROPERTY_VALUE_MAX];
     property_get("ro.debuggable", value, "0");
     bool wantLogs = (value[0] == '1');
-    bool need_cleanup = false;
 
     dump_crash_banner(tfd, pid, tid, signal);
 
@@ -367,8 +369,9 @@
         dump_logs(tfd, pid, true);
     }
 
+    bool detach_failed = false;
     if (dump_sibling_threads) {
-        need_cleanup = dump_sibling_thread_report(context, tfd, pid, tid);
+        detach_failed = dump_sibling_thread_report(context, tfd, pid, tid);
     }
 
     free_ptrace_context(context);
@@ -376,7 +379,7 @@
     if (wantLogs) {
         dump_logs(tfd, pid, false);
     }
-    return need_cleanup;
+    return detach_failed;
 }
 
 #define MAX_TOMBSTONES	10
@@ -443,20 +446,18 @@
 static bool engrave_tombstone(pid_t pid, pid_t tid, int signal,
         bool dump_sibling_threads)
 {
-    int fd;
-    bool need_cleanup = false;
-
     mkdir(TOMBSTONE_DIR, 0755);
     chown(TOMBSTONE_DIR, AID_SYSTEM, AID_SYSTEM);
 
-    fd = find_and_open_tombstone();
-    if (fd < 0)
-        return need_cleanup;
+    int fd = find_and_open_tombstone();
+    if (fd < 0) {
+        return false;
+    }
 
-    need_cleanup = dump_crash(fd, pid, tid, signal, dump_sibling_threads);
+    bool detach_failed = dump_crash(fd, pid, tid, signal, dump_sibling_threads);
 
     close(fd);
-    return need_cleanup;
+    return detach_failed;
 }
 
 static int
@@ -504,21 +505,21 @@
     write_string("/sys/class/leds/left/cadence", "0,0");
 }
 
-static void wait_for_user_action(pid_t tid, struct ucred* cr)
-{
-    (void)tid;
+static void wait_for_user_action(pid_t pid) {
     /* First log a helpful message */
     LOG(    "********************************************************\n"
             "* Process %d has been suspended while crashing.  To\n"
-            "* attach gdbserver for a gdb connection on port 5039:\n"
+            "* attach gdbserver for a gdb connection on port 5039\n"
+            "* and start gdbclient:\n"
             "*\n"
-            "*     adb shell gdbserver :5039 --attach %d &\n"
+            "*     gdbclient app_process :5039 %d\n"
             "*\n"
-            "* Press HOME key to let the process continue crashing.\n"
+            "* Wait for gdb to start, then press HOME or VOLUME DOWN key\n"
+            "* to let the process continue crashing.\n"
             "********************************************************\n",
-            cr->pid, cr->pid);
+            pid, pid);
 
-    /* wait for HOME key (TODO: something useful for devices w/o HOME key) */
+    /* wait for HOME or VOLUME DOWN key */
     if (init_getevent() == 0) {
         int ms = 1200 / 10;
         int dit = 1;
@@ -531,15 +532,18 @@
         };
         size_t s = 0;
         struct input_event e;
-        int home = 0;
+        bool done = false;
         init_debug_led();
         enable_debug_led();
         do {
             int timeout = abs((int)(codes[s])) * ms;
             int res = get_event(&e, timeout);
             if (res == 0) {
-                if (e.type==EV_KEY && e.code==KEY_HOME && e.value==0)
-                    home = 1;
+                if (e.type == EV_KEY
+                        && (e.code == KEY_HOME || e.code == KEY_VOLUMEDOWN)
+                        && e.value == 0) {
+                    done = true;
+                }
             } else if (res == 1) {
                 if (++s >= sizeof(codes)/sizeof(*codes))
                     s = 0;
@@ -549,209 +553,281 @@
                     disable_debug_led();
                 }
             }
-        } while (!home);
+        } while (!done);
         uninit_getevent();
     }
 
     /* don't forget to turn debug led off */
     disable_debug_led();
+    LOG("debuggerd resuming process %d", pid);
+}
 
-    /* close filedescriptor */
-    LOG("debuggerd resuming process %d", cr->pid);
- }
+static int get_process_info(pid_t tid, pid_t* out_pid, uid_t* out_uid, uid_t* out_gid) {
+    char path[64];
+    snprintf(path, sizeof(path), "/proc/%d/status", tid);
 
-static void handle_crashing_process(int fd)
-{
-    char buf[64];
-    struct stat s;
-    pid_t tid;
+    FILE* fp = fopen(path, "r");
+    if (!fp) {
+        return -1;
+    }
+
+    int fields = 0;
+    char line[1024];
+    while (fgets(line, sizeof(line), fp)) {
+        size_t len = strlen(line);
+        if (len > 6 && !memcmp(line, "Tgid:\t", 6)) {
+            *out_pid = atoi(line + 6);
+            fields |= 1;
+        } else if (len > 5 && !memcmp(line, "Uid:\t", 5)) {
+            *out_uid = atoi(line + 5);
+            fields |= 2;
+        } else if (len > 5 && !memcmp(line, "Gid:\t", 5)) {
+            *out_gid = atoi(line + 5);
+            fields |= 4;
+        }
+    }
+    fclose(fp);
+    return fields == 7 ? 0 : -1;
+}
+
+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) {
     struct ucred cr;
-    int n, len, status;
-    int tid_attach_status = -1;
-    unsigned retry = 30;
-    bool need_cleanup = false;
-
-    XLOG("handle_crashing_process(%d)\n", fd);
-
-    char value[PROPERTY_VALUE_MAX];
-    property_get("debug.db.uid", value, "-1");
-    int debug_uid = atoi(value);
-
-    len = sizeof(cr);
-    n = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);
-    if(n != 0) {
+    int len = sizeof(cr);
+    int status = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &len);
+    if (status != 0) {
         LOG("cannot get credentials\n");
-        goto done;
+        return -1;
     }
 
     XLOG("reading tid\n");
     fcntl(fd, F_SETFL, O_NONBLOCK);
-    while((n = read(fd, &tid, sizeof(pid_t))) != sizeof(pid_t)) {
-        if(errno == EINTR) continue;
-        if(errno == EWOULDBLOCK) {
-            if(retry-- > 0) {
-                usleep(100 * 1000);
-                continue;
-            }
-            LOG("timed out reading tid\n");
-            goto done;
-        }
-        LOG("read failure? %s\n", strerror(errno));
-        goto done;
+
+    struct pollfd pollfds[1];
+    pollfds[0].fd = fd;
+    pollfds[0].events = POLLIN;
+    pollfds[0].revents = 0;
+    status = TEMP_FAILURE_RETRY(poll(pollfds, 1, 3000));
+    if (status != 1) {
+        LOG("timed out reading tid\n");
+        return -1;
     }
 
-    snprintf(buf, sizeof buf, "/proc/%d/task/%d", cr.pid, tid);
+    status = TEMP_FAILURE_RETRY(read(fd, &out_request->tid, sizeof(pid_t)));
+    if (status < 0) {
+        LOG("read failure? %s\n", strerror(errno));
+        return -1;
+    }
+    if (status != sizeof(pid_t)) {
+        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;
+        status = get_process_info(out_request->tid, &out_request->pid,
+                &out_request->uid, &out_request->gid);
+        if (status < 0) {
+            LOG("tid %d does not exist. ignoring explicit dump request\n",
+                    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",
-            tid, cr.pid);
-        close(fd);
-        return;
+                out_request->tid, out_request->pid);
+        return -1;
     }
-
-    XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", cr.pid, cr.uid, cr.gid, tid);
-
-    /*
-     * If the user has requested to attach gdb, don't collect the per-thread
-     * information as it increases the chance to lose track of the process.
-     */
-    bool dump_sibling_threads = (signed)cr.pid > debug_uid;
-
-    /* Note that at this point, the target thread's signal handler
-     * is blocked in a read() call. This gives us the time to PTRACE_ATTACH
-     * to it before it has a chance to really fault.
-     *
-     * The PTRACE_ATTACH sends a SIGSTOP to the target process, but it
-     * won't necessarily have stopped by the time ptrace() returns.  (We
-     * currently assume it does.)  We write to the file descriptor to
-     * ensure that it can run as soon as we call PTRACE_CONT below.
-     * See details in bionic/libc/linker/debugger.c, in function
-     * debugger_signal_handler().
-     */
-    tid_attach_status = ptrace(PTRACE_ATTACH, tid, 0, 0);
-    int ptrace_error = errno;
-
-    if (TEMP_FAILURE_RETRY(write(fd, &tid, 1)) != 1) {
-        XLOG("failed responding to client: %s\n",
-            strerror(errno));
-        goto done;
-    }
-
-    if(tid_attach_status < 0) {
-        LOG("ptrace attach failed: %s\n", strerror(ptrace_error));
-        goto done;
-    }
-
-    close(fd);
-    fd = -1;
-
-    const int sleep_time_usec = 200000;         /* 0.2 seconds */
-    const int max_total_sleep_usec = 3000000;   /* 3 seconds */
-    int loop_limit = max_total_sleep_usec / sleep_time_usec;
-    for(;;) {
-        if (loop_limit-- == 0) {
-            LOG("timed out waiting for pid=%d tid=%d uid=%d to die\n",
-                cr.pid, tid, cr.uid);
-            goto done;
-        }
-        n = waitpid(tid, &status, __WALL | WNOHANG);
-
-        if (n == 0) {
-            /* not ready yet */
-            XLOG("not ready yet\n");
-            usleep(sleep_time_usec);
-            continue;
-        }
-
-        if(n < 0) {
-            if(errno == EAGAIN) continue;
-            LOG("waitpid failed: %s\n", strerror(errno));
-            goto done;
-        }
-
-        XLOG("waitpid: n=%d status=%08x\n", n, status);
-
-        if(WIFSTOPPED(status)){
-            n = WSTOPSIG(status);
-            switch(n) {
-            case SIGSTOP:
-                XLOG("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: {
-                XLOG("stopped -- fatal signal\n");
-                need_cleanup = engrave_tombstone(cr.pid, tid, n,
-                        dump_sibling_threads);
-                kill(tid, SIGSTOP);
-                goto done;
-            }
-
-            default:
-                XLOG("stopped -- unexpected signal\n");
-                goto done;
-            }
-        } else {
-            XLOG("unexpected waitpid response\n");
-            goto done;
-        }
-    }
-
-done:
-    XLOG("detaching\n");
-
-    /* stop the process so we can debug */
-    kill(cr.pid, SIGSTOP);
-
-    /*
-     * If a thread has been attached by ptrace, make sure it is detached
-     * successfully otherwise we will get a zombie.
-     */
-    if (tid_attach_status == 0) {
-        int detach_status;
-        /* detach so we can attach gdbserver */
-        detach_status = ptrace(PTRACE_DETACH, tid, 0, 0);
-        need_cleanup |= (detach_status != 0);
-    }
-
-    /*
-     * if debug.db.uid is set, its value indicates if we should wait
-     * for user action for the crashing process.
-     * in this case, we log a message and turn the debug LED on
-     * waiting for a gdb connection (for instance)
-     */
-
-    if ((signed)cr.uid <= debug_uid) {
-        wait_for_user_action(tid, &cr);
-    }
-
-    /*
-     * Resume stopped process (so it can crash in peace).  If we didn't
-     * successfully detach, we're still the parent, and the actual parent
-     * won't receive a death notification via wait(2).  At this point
-     * there's not much we can do about that.
-     */
-    kill(cr.pid, SIGCONT);
-
-    if (need_cleanup) {
-        LOG("debuggerd committing suicide to free the zombie!\n");
-        kill(getpid(), SIGKILL);
-    }
-
-    if(fd != -1) close(fd);
+    return 0;
 }
 
+static bool should_attach_gdb(request_t* request) {
+    if (request->type == REQUEST_TYPE_CRASH) {
+        char value[PROPERTY_VALUE_MAX];
+        property_get("debug.db.uid", value, "-1");
+        int debug_uid = atoi(value);
+        return debug_uid >= 0 && request->uid <= (uid_t)debug_uid;
+    }
+    return false;
+}
 
-int main()
-{
+static void handle_request(int fd) {
+    XLOG("handle_request(%d)\n", fd);
+
+    request_t request;
+    int status = read_request(fd, &request);
+    if (!status) {
+        XLOG("BOOM: pid=%d uid=%d gid=%d tid=%d\n", pid, uid, gid, tid);
+
+        /* At this point, the thread that made the request is blocked in
+         * a read() call.  If the thread has crashed, then this gives us
+         * time to PTRACE_ATTACH to it before it has a chance to really fault.
+         *
+         * The PTRACE_ATTACH sends a SIGSTOP to the target process, but it
+         * won't necessarily have stopped by the time ptrace() returns.  (We
+         * currently assume it does.)  We write to the file descriptor to
+         * ensure that it can run as soon as we call PTRACE_CONT below.
+         * See details in bionic/libc/linker/debugger.c, in function
+         * debugger_signal_handler().
+         */
+        if (ptrace(PTRACE_ATTACH, request.tid, 0, 0)) {
+            LOG("ptrace attach failed: %s\n", strerror(errno));
+        } else {
+            bool detach_failed = false;
+            bool attach_gdb = should_attach_gdb(&request);
+            char response = 0;
+            if (TEMP_FAILURE_RETRY(write(fd, &response, 1)) != 1) {
+                LOG("failed responding to client: %s\n", strerror(errno));
+            } else {
+                close(fd);
+                fd = -1;
+
+                int total_sleep_time_usec = 0;
+                for (;;) {
+                    int signal = wait_for_signal(request.tid, &total_sleep_time_usec);
+                    if (signal < 0) {
+                        break;
+                    }
+
+                    switch (signal) {
+                    case SIGSTOP:
+                        if (request.type == REQUEST_TYPE_DUMP) {
+                            XLOG("stopped -- dumping\n");
+                            detach_failed = engrave_tombstone(request.pid, request.tid,
+                                    signal, true);
+                        } else {
+                            XLOG("stopped -- continuing\n");
+                            status = ptrace(PTRACE_CONT, request.tid, 0, 0);
+                            if (status) {
+                                LOG("ptrace continue failed: %s\n", strerror(errno));
+                            }
+                            continue; /* loop again */
+                        }
+                        break;
+
+                    case SIGILL:
+                    case SIGABRT:
+                    case SIGBUS:
+                    case SIGFPE:
+                    case SIGSEGV:
+                    case SIGSTKFLT: {
+                        XLOG("stopped -- fatal signal\n");
+                        /* don't dump sibling threads when attaching to GDB because it
+                         * makes the process less reliable, apparently... */
+                        detach_failed = engrave_tombstone(request.pid, request.tid,
+                                signal, !attach_gdb);
+                        break;
+                    }
+
+                    default:
+                        XLOG("stopped -- unexpected signal\n");
+                        LOG("process stopped due to unexpected signal %d\n", signal);
+                        break;
+                    }
+                    break;
+                }
+            }
+
+            XLOG("detaching\n");
+            if (attach_gdb) {
+                /* stop the process so we can debug */
+                kill(request.pid, SIGSTOP);
+
+                /* detach so we can attach gdbserver */
+                if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) {
+                    LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno));
+                    detach_failed = true;
+                }
+
+                /*
+                 * if debug.db.uid is set, its value indicates if we should wait
+                 * for user action for the crashing process.
+                 * in this case, we log a message and turn the debug LED on
+                 * waiting for a gdb connection (for instance)
+                 */
+                wait_for_user_action(request.pid);
+            } else {
+                /* just detach */
+                if (ptrace(PTRACE_DETACH, request.tid, 0, 0)) {
+                    LOG("ptrace detach from %d failed: %s\n", request.tid, strerror(errno));
+                    detach_failed = true;
+                }
+            }
+
+            /* resume stopped process (so it can crash in peace). */
+            kill(request.pid, SIGCONT);
+
+            /* If we didn't successfully detach, we're still the parent, and the
+             * actual parent won't receive a death notification via wait(2).  At this point
+             * there's not much we can do about that. */
+            if (detach_failed) {
+                LOG("debuggerd committing suicide to free the zombie!\n");
+                kill(getpid(), SIGKILL);
+            }
+        }
+
+    }
+    if (fd >= 0) {
+        close(fd);
+    }
+}
+
+static int do_server() {
     int s;
     struct sigaction act;
     int logsocket = -1;
@@ -784,7 +860,7 @@
 
     s = socket_local_server("android:debuggerd",
             ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM);
-    if(s < 0) return -1;
+    if(s < 0) return 1;
     fcntl(s, F_SETFD, FD_CLOEXEC);
 
     LOG("debuggerd: " __DATE__ " " __TIME__ "\n");
@@ -804,7 +880,42 @@
 
         fcntl(fd, F_SETFD, FD_CLOEXEC);
 
-        handle_crashing_process(fd);
+        handle_request(fd);
     }
     return 0;
 }
+
+static int do_explicit_dump(pid_t tid) {
+    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);
+    }
+    close(fd);
+    return 0;
+}
+
+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);
+            return 1;
+        }
+        return do_explicit_dump(tid);
+    }
+    return do_server();
+}
diff --git a/libcorkscrew/arch-arm/backtrace-arm.c b/libcorkscrew/arch-arm/backtrace-arm.c
index f2db642..93144f0 100644
--- a/libcorkscrew/arch-arm/backtrace-arm.c
+++ b/libcorkscrew/arch-arm/backtrace-arm.c
@@ -25,23 +25,13 @@
  * the exception handling table of each function, sorted by program
  * counter address.
  *
- * When the executable is statically linked, the EXIDX section can be
- * accessed by querying the values of the __exidx_start and __exidx_end
- * symbols.  That said, this library is currently only compiled as
- * a dynamic library, so we will not trouble ourselves with statically
- * linked executables any further.
- *
- * When the Bionic dynamic linker is used, it exports a function called
- * dl_unwind_find_exidx that obtains the EXIDX section for a given
- * absolute program counter address.
- *
  * This implementation also supports unwinding other processes via ptrace().
  * In that case, the EXIDX section is found by reading the ELF section table
  * structures using ptrace().
  *
  * Because the tables are used for exception handling, it can happen that
  * a given function will not have an exception handling table.  In particular,
- * exceptions are assumes to only ever be thrown at call sites.  Therefore,
+ * exceptions are assumed to only ever be thrown at call sites.  Therefore,
  * by definition leaf functions will not have exception handling tables.
  * This may make unwinding impossible in some cases although we can still get
  * some idea of the call stack by examining the PC and LR registers.
@@ -100,9 +90,29 @@
 /* Special EXIDX value that indicates that a frame cannot be unwound. */
 static const uint32_t EXIDX_CANTUNWIND = 1;
 
-/* The function exported by the Bionic linker to find the EXIDX
- * table for a given program counter address. */
-extern uintptr_t dl_unwind_find_exidx(uintptr_t pc, size_t* out_exidx_size);
+/* Get the EXIDX section start and size for the module that contains a
+ * given program counter address.
+ *
+ * When the executable is statically linked, the EXIDX section can be
+ * accessed by querying the values of the __exidx_start and __exidx_end
+ * symbols.
+ *
+ * When the executable is dynamically linked, the linker exports a function
+ * called dl_unwind_find_exidx that obtains the EXIDX section for a given
+ * absolute program counter address.
+ *
+ * Bionic exports a helpful function called __gnu_Unwind_Find_exidx that
+ * handles both cases, so we use that here.
+ */
+typedef long unsigned int* _Unwind_Ptr;
+extern _Unwind_Ptr __gnu_Unwind_Find_exidx(_Unwind_Ptr pc, int *pcount);
+
+static uintptr_t find_exidx(uintptr_t pc, size_t* out_exidx_size) {
+    int count;
+    uintptr_t start = (uintptr_t)__gnu_Unwind_Find_exidx((_Unwind_Ptr)pc, &count);
+    *out_exidx_size = count;
+    return start;
+}
 
 /* Transforms a 31-bit place-relative offset to an absolute address.
  * We assume the most significant bit is clear. */
@@ -115,7 +125,7 @@
     uintptr_t exidx_start;
     size_t exidx_size;
     if (tid < 0) {
-        exidx_start = dl_unwind_find_exidx(pc, &exidx_size);
+        exidx_start = find_exidx(pc, &exidx_size);
     } else {
         const map_info_t* mi = find_map_info(context->map_info_list, pc);
         if (mi && mi->data) {