Show maps near native fault address

This adds some additional output to native crashes.  For example, if
something tried to access a bit of mmap(/dev/zero) memory that had
been mprotect()ed, you might see output like this:

I DEBUG   : memory map around addr 4015a00c:
I DEBUG   : 40159000-4015a000 /system/lib/libstdc++.so
I DEBUG   : 4015a000-40162000 /dev/zero
I DEBUG   : b0001000-b0009000 /system/bin/linker

The idea is to see what's in and around the fault address to make it
easier to identify bus errors due to file truncation and segmentation
faults caused by buffer over/underruns.

No output is generated for accesses below 0x1000 (which are likely
NULL pointer dereferences) or for signals that don't set si_addr.

Also, suppress the fault address for signals that don't set si_addr:
I DEBUG   : signal 6 (SIGABRT), code 0 (?), fault addr --------

We still print "fault addr" followed by 8 characters for anything
that is parsing the contents.  The "address" shown for signals like
SIGABRT was meaningless and possibly confusing.

Bug 5358516

Change-Id: Icae8ef309ea2d89b129f68d30f96b2ca8a69cc6c
diff --git a/debuggerd/arm/machine.c b/debuggerd/arm/machine.c
index 88bf054..58a7839 100644
--- a/debuggerd/arm/machine.c
+++ b/debuggerd/arm/machine.c
@@ -50,6 +50,74 @@
                                         int *frame0_pc_sane,
                                         bool at_fault);
 
+/*
+ * If this isn't clearly a null pointer dereference, dump the
+ * /proc/maps entries near the fault address.
+ */
+static void show_nearby_maps(int tfd, int pid, mapinfo *map)
+{
+    siginfo_t si;
+
+    memset(&si, 0, sizeof(si));
+    if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si)) {
+        _LOG(tfd, false, "cannot get siginfo: %s\n", 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 addr %08x:\n", 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.
+     */
+    bool found = false;
+    mapinfo *next = NULL;
+    mapinfo *prev = NULL;
+    while (map != NULL) {
+        if (addr >= map->start && addr < map->end) {
+            found = true;
+            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(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");
+    }
+}
+
+
 void dump_stack_and_code(int tfd, int pid, mapinfo *map,
                          int unwind_depth, unsigned int sp_list[],
                          bool at_fault)
@@ -123,6 +191,8 @@
         }
     }
 
+    show_nearby_maps(tfd, pid, map);
+
     p = sp - 64;
     if (p > sp)
         p = 0;
diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c
index d03c214..97a6b9e 100644
--- a/debuggerd/debuggerd.c
+++ b/debuggerd/debuggerd.c
@@ -70,14 +70,17 @@
     mapinfo *mi;
     int len = strlen(line);
 
-    if(len < 1) return 0;
+    if (len < 1) return 0;      /* not expected */
     line[--len] = 0;
 
-    if(len < 50) return 0;
-    if(line[20] != 'x') return 0;
+    if (len < 50) {
+        mi = malloc(sizeof(mapinfo) + 1);
+    } else {
+        mi = malloc(sizeof(mapinfo) + (len - 47));
+    }
+    if (mi == 0) return 0;
 
-    mi = malloc(sizeof(mapinfo) + (len - 47));
-    if(mi == 0) return 0;
+    mi->isExecutable = (line[20] == 'x');
 
     mi->start = strtoul(line, 0, 16);
     mi->end = strtoul(line + 9, 0, 16);
@@ -87,7 +90,11 @@
     mi->exidx_start = mi->exidx_end = 0;
     mi->symbols = 0;
     mi->next = 0;
-    strcpy(mi->name, line + 49);
+    if (len < 50) {
+        mi->name[0] = '\0';
+    } else {
+        strcpy(mi->name, line + 49);
+    }
 
     return mi;
 }
@@ -165,11 +172,14 @@
     memset(&si, 0, sizeof(si));
     if(ptrace(PTRACE_GETSIGINFO, pid, 0, &si)){
         _LOG(tfd, false, "cannot get siginfo: %s\n", strerror(errno));
-    } else {
+    } 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),
              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));
     }
 }
 
@@ -199,6 +209,9 @@
 {
     mapinfo *mi;
     for (mi = milist; mi != NULL; mi = mi->next) {
+        if (!mi->isExecutable)
+            continue;
+
         Elf32_Ehdr ehdr;
 
         memset(&ehdr, 0, sizeof(Elf32_Ehdr));
diff --git a/debuggerd/utility.c b/debuggerd/utility.c
index 2afdb46..409209c 100644
--- a/debuggerd/utility.c
+++ b/debuggerd/utility.c
@@ -17,6 +17,7 @@
 
 #include <sys/ptrace.h>
 #include <sys/exec_elf.h>
+#include <signal.h>
 #include <assert.h>
 #include <string.h>
 #include <errno.h>
@@ -82,3 +83,20 @@
     }
     return NULL;
 }
+
+/*
+ * Returns true if the specified signal has an associated address (i.e. it
+ * sets siginfo_t.si_addr).
+ */
+bool signal_has_address(int sig)
+{
+    switch (sig) {
+        case SIGILL:
+        case SIGFPE:
+        case SIGSEGV:
+        case SIGBUS:
+            return true;
+        default:
+            return false;
+    }
+}
diff --git a/debuggerd/utility.h b/debuggerd/utility.h
index 45e2067..4a935d2 100644
--- a/debuggerd/utility.h
+++ b/debuggerd/utility.h
@@ -36,6 +36,7 @@
     unsigned exidx_start;
     unsigned exidx_end;
     struct symbol_table *symbols;
+    bool isExecutable;
     char name[];
 } mapinfo;
 
@@ -56,6 +57,9 @@
 /* Log information onto the tombstone */
 extern void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...);
 
+/* Determine whether si_addr is valid for this signal */
+bool signal_has_address(int sig);
+
 #define LOG(fmt...) _LOG(-1, 0, fmt)
 
 /* Set to 1 for normal debug traces */