Add support for explicitly dumping native stacks.

This change modifies debuggerd so that it can be used to grab
the native stacks of a process that has hung and not just crashed.
Note that only the root user can do this (for now).

adb shell debuggerd <tid>

Then use logcat to find the tombstone file that was generated
which will have the native stacks of all threads in the
requested process.  The specified thread will be shown first
and will also appear in the main log.

Also made some minor tweaks to libcorkscrew so that we
could handle statically compiled executables in the future
if we compiled the library statically.

Improved the "wait_for_user_action" function to support
volume down as an alternative for devices that do not
have home keys.

Removed a mess of gotos.

Change-Id: Ic149653986b0c2f503c7f0e8b7cb1f3be7c84d1e
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();
+}