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) {