Append log data to tombstones

The Android Problem Report site shows tombstones uploaded from
devices.  We can see the native stack traces for every thread,
but sometimes there's a very important bit of information sitting
in the log, and without it we can't analyze the failure.

This change modifies debuggerd so that the log contents for the
crashing process are appended to the tombstone.  The format matches
the output of "logcat -v threadtime".  Both "system" and "main" logs
are included (but not interleaved -- we're not that fancy).

This feature is only enabled when the "ro.debuggable" system property
is set to 1 (indicating a development device).

Bug 5456676

Change-Id: I3be1df59813ccf1058cec496a906f6d31fbc7b04
diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c
index 97a6b9e..91d9dda 100644
--- a/debuggerd/debuggerd.c
+++ b/debuggerd/debuggerd.c
@@ -31,6 +31,7 @@
 
 #include <cutils/sockets.h>
 #include <cutils/logd.h>
+#include <cutils/logger.h>
 #include <cutils/properties.h>
 
 #include <linux/input.h>
@@ -413,6 +414,101 @@
     return need_cleanup != 0;
 }
 
+/*
+ * Reads the contents of the specified log device, filters out the entries
+ * that don't match the specified pid, and writes them to the tombstone file.
+ */
+static void dump_log_file(int tfd, unsigned pid, const char* filename)
+{
+    int logfd = open(filename, O_RDONLY | O_NONBLOCK);
+    if (logfd < 0) {
+        XLOG("Unable to open %s: %s\n", filename, strerror(errno));
+        return;
+    }
+    _LOG(tfd, true, "--------- log %s\n", filename);
+
+    union {
+        unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
+        struct logger_entry entry;
+    } log_entry;
+
+    while (true) {
+        ssize_t actual = read(logfd, log_entry.buf, LOGGER_ENTRY_MAX_LEN);
+        if (actual < 0) {
+            if (errno == EINTR) {
+                /* interrupted by signal, retry */
+                continue;
+            } else if (errno == EAGAIN) {
+                /* non-blocking EOF; we're done */
+                break;
+            } else {
+                _LOG(tfd, true, "Error while reading log: %s\n",
+                    strerror(errno));
+                break;
+            }
+        } else if (actual == 0) {
+            _LOG(tfd, true, "Got zero bytes while reading log: %s\n",
+                strerror(errno));
+            break;
+        }
+
+        /*
+         * NOTE: if you XLOG something here, this will spin forever,
+         * because you will be writing as fast as you're reading.  Any
+         * high-frequency debug diagnostics should just be written to
+         * the tombstone file.
+         */
+
+        struct logger_entry* entry = &log_entry.entry;
+
+        if (entry->pid != (int32_t) pid) {
+            /* wrong pid, ignore */
+            continue;
+        }
+
+        /*
+         * Msg format is: <priority:1><tag:N>\0<message:N>\0
+         *
+         * We want to display it in the same format as "logcat -v threadtime"
+         * (although in this case the pid is redundant).
+         *
+         * TODO: scan for line breaks ('\n') and display each text line
+         * on a separate line, prefixed with the header, like logcat does.
+         */
+        static const char* kPrioChars = "!.VDIWEFS";
+        unsigned char prio = entry->msg[0];
+        const char* tag = entry->msg + 1;
+        const char* msg = tag + strlen(tag) + 1;
+
+        log_entry.entry.msg[entry->len] = '\0';
+
+        char timeBuf[32];
+        time_t sec = (time_t) entry->sec;
+        struct tm tmBuf;
+        struct tm* ptm;
+        ptm = localtime_r(&sec, &tmBuf);
+        strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
+
+        _LOG(tfd, true, "%s.%03ld %5d %5d %c %-8s: %s\n",
+            timeBuf, entry->nsec / 1000000,
+            entry->pid, entry->tid,
+            (prio < strlen(kPrioChars) ? kPrioChars[prio] : '?'),
+            tag, msg);
+    }
+
+    close(logfd);
+}
+
+/*
+ * Dumps the logs generated by the specified pid to the tombstone, from both
+ * "system" and "main" log devices.  Ideally we'd interleave the output.
+ */
+static void dump_logs(int tfd, unsigned pid)
+{
+    dump_log_file(tfd, pid, "/dev/log/system");
+    dump_log_file(tfd, pid, "/dev/log/main");
+}
+
 /* Return true if some thread is not detached cleanly */
 static bool engrave_tombstone(unsigned pid, unsigned tid, int debug_uid,
                               int signal)
@@ -437,6 +533,13 @@
         need_cleanup = dump_sibling_thread_report(fd, pid, tid);
     }
 
+    /* don't copy log to tombstone unless this is a dev device */
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.debuggable", value, "0");
+    if (value[0] == '1') {
+        dump_logs(fd, pid);
+    }
+
     close(fd);
     return need_cleanup;
 }