ART: Improve addr2line

Change addr2line handling: use the tool in "server" mode of
operation, keeping a connection open for the same file. This
reduces the time of a dump of sleeping 001-HelloWorld from fifteen
to ten seconds.

Bug: 30351473
Test: m test-art-host
Test: manual host testing with a test that times out
Change-Id: Idbe424f85c566f5bb39d12644ce104fff54a2146
diff --git a/runtime/native_stack_dump.cc b/runtime/native_stack_dump.cc
index 8c5b386..c20c8b8 100644
--- a/runtime/native_stack_dump.cc
+++ b/runtime/native_stack_dump.cc
@@ -41,7 +41,9 @@
 #include "base/memory_tool.h"
 #include "base/mutex.h"
 #include "base/stringprintf.h"
+#include "base/unix_file/fd_file.h"
 #include "oat_quick_method_header.h"
+#include "os.h"
 #include "thread-inl.h"
 #include "utils.h"
 
@@ -54,56 +56,209 @@
 static constexpr bool kUseAddr2line = !kIsTargetBuild;
 
 ALWAYS_INLINE
-static inline void WritePrefix(std::ostream* os, const char* prefix, bool odd) {
+static inline void WritePrefix(std::ostream& os, const char* prefix, bool odd) {
   if (prefix != nullptr) {
-    *os << prefix;
+    os << prefix;
   }
-  *os << "  ";
+  os << "  ";
   if (!odd) {
-    *os << " ";
+    os << " ";
   }
 }
 
-static bool RunCommand(std::string cmd, std::ostream* os, const char* prefix) {
-  FILE* stream = popen(cmd.c_str(), "r");
-  if (stream) {
-    if (os != nullptr) {
-      bool odd_line = true;               // We indent them differently.
-      bool wrote_prefix = false;          // Have we already written a prefix?
-      constexpr size_t kMaxBuffer = 128;  // Relatively small buffer. Should be OK as we're on an
-                                          // alt stack, but just to be sure...
-      char buffer[kMaxBuffer];
-      while (!feof(stream)) {
-        if (fgets(buffer, kMaxBuffer, stream) != nullptr) {
-          // Split on newlines.
-          char* tmp = buffer;
-          for (;;) {
-            char* new_line = strchr(tmp, '\n');
-            if (new_line == nullptr) {
-              // Print the rest.
-              if (*tmp != 0) {
-                if (!wrote_prefix) {
-                  WritePrefix(os, prefix, odd_line);
-                }
-                wrote_prefix = true;
-                *os << tmp;
-              }
-              break;
-            }
-            if (!wrote_prefix) {
-              WritePrefix(os, prefix, odd_line);
-            }
-            char saved = *(new_line + 1);
-            *(new_line + 1) = 0;
-            *os << tmp;
-            *(new_line + 1) = saved;
-            tmp = new_line + 1;
-            odd_line = !odd_line;
-            wrote_prefix = false;
-          }
+// The state of an open pipe to addr2line. In "server" mode, addr2line takes input on stdin
+// and prints the result to stdout. This struct keeps the state of the open connection.
+struct Addr2linePipe {
+  Addr2linePipe(int in_fd, int out_fd, const std::string& file_name, pid_t pid)
+      : in(in_fd, false), out(out_fd, false), file(file_name), child_pid(pid), odd(true) {}
+
+  ~Addr2linePipe() {
+    kill(child_pid, SIGKILL);
+  }
+
+  File in;      // The file descriptor that is connected to the output of addr2line.
+  File out;     // The file descriptor that is connected to the input of addr2line.
+
+  const std::string file;     // The file addr2line is working on, so that we know when to close
+                              // and restart.
+  const pid_t child_pid;      // The pid of the child, which we should kill when we're done.
+  bool odd;                   // Print state for indentation of lines.
+};
+
+static std::unique_ptr<Addr2linePipe> Connect(const std::string& name, const char* args[]) {
+  int caller_to_addr2line[2];
+  int addr2line_to_caller[2];
+
+  if (pipe(caller_to_addr2line) == -1) {
+    return nullptr;
+  }
+  if (pipe(addr2line_to_caller) == -1) {
+    close(caller_to_addr2line[0]);
+    close(caller_to_addr2line[1]);
+    return nullptr;
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) {
+    close(caller_to_addr2line[0]);
+    close(caller_to_addr2line[1]);
+    close(addr2line_to_caller[1]);
+    close(addr2line_to_caller[1]);
+    return nullptr;
+  }
+
+  if (pid == 0) {
+    dup2(caller_to_addr2line[0], STDIN_FILENO);
+    dup2(addr2line_to_caller[1], STDOUT_FILENO);
+
+    close(caller_to_addr2line[0]);
+    close(caller_to_addr2line[1]);
+    close(addr2line_to_caller[0]);
+    close(addr2line_to_caller[1]);
+
+    execv(args[0], const_cast<char* const*>(args));
+    exit(1);
+  } else {
+    close(caller_to_addr2line[0]);
+    close(addr2line_to_caller[1]);
+    return std::unique_ptr<Addr2linePipe>(new Addr2linePipe(addr2line_to_caller[0],
+                                                            caller_to_addr2line[1],
+                                                            name,
+                                                            pid));
+  }
+}
+
+static void Drain(size_t expected,
+                  const char* prefix,
+                  std::unique_ptr<Addr2linePipe>* pipe /* inout */,
+                  std::ostream& os) {
+  DCHECK(pipe != nullptr);
+  DCHECK(pipe->get() != nullptr);
+  int in = pipe->get()->in.Fd();
+  DCHECK_GE(in, 0);
+
+  bool prefix_written = false;
+
+  for (;;) {
+    constexpr uint32_t kWaitTimeExpectedMicros = 500 * 1000;
+    constexpr uint32_t kWaitTimeUnexpectedMicros = 50 * 1000;
+
+    struct timeval tv;
+    tv.tv_sec = 0;
+    tv.tv_usec = expected > 0 ? kWaitTimeExpectedMicros : kWaitTimeUnexpectedMicros;
+
+    fd_set rfds;
+    FD_ZERO(&rfds);
+    FD_SET(in, &rfds);
+
+    int retval = TEMP_FAILURE_RETRY(select(in + 1, &rfds, nullptr, nullptr, &tv));
+
+    if (retval < 0) {
+      // Other side may have crashed or other errors.
+      pipe->reset();
+      return;
+    }
+
+    if (retval == 0) {
+      // Timeout.
+      return;
+    }
+
+    DCHECK_EQ(retval, 1);
+
+    constexpr size_t kMaxBuffer = 128;  // Relatively small buffer. Should be OK as we're on an
+    // alt stack, but just to be sure...
+    char buffer[kMaxBuffer];
+    memset(buffer, 0, kMaxBuffer);
+    int bytes_read = TEMP_FAILURE_RETRY(read(in, buffer, kMaxBuffer - 1));
+
+    if (bytes_read < 0) {
+      // This should not really happen...
+      pipe->reset();
+      return;
+    }
+
+    char* tmp = buffer;
+    while (*tmp != 0) {
+      if (!prefix_written) {
+        WritePrefix(os, prefix, (*pipe)->odd);
+        prefix_written = true;
+      }
+      char* new_line = strchr(tmp, '\n');
+      if (new_line == nullptr) {
+        os << tmp;
+
+        break;
+      } else {
+        char saved = *(new_line + 1);
+        *(new_line + 1) = 0;
+        os << tmp;
+        *(new_line + 1) = saved;
+
+        tmp = new_line + 1;
+        prefix_written = false;
+        (*pipe)->odd = !(*pipe)->odd;
+
+        if (expected > 0) {
+          expected--;
         }
       }
     }
+  }
+}
+
+static void Addr2line(const std::string& map_src,
+                      uintptr_t offset,
+                      std::ostream& os,
+                      const char* prefix,
+                      std::unique_ptr<Addr2linePipe>* pipe /* inout */) {
+  DCHECK(pipe != nullptr);
+
+  if (map_src == "[vdso]") {
+    // Special-case this, our setup has problems with this.
+    return;
+  }
+
+  if (*pipe == nullptr || (*pipe)->file != map_src) {
+    if (*pipe != nullptr) {
+      Drain(0, prefix, pipe, os);
+    }
+    pipe->reset();  // Close early.
+
+    const char* args[7] = {
+        "/usr/bin/addr2line",
+        "--functions",
+        "--inlines",
+        "--demangle",
+        "-e",
+        map_src.c_str(),
+        nullptr
+    };
+    *pipe = Connect(map_src, args);
+  }
+
+  Addr2linePipe* pipe_ptr = pipe->get();
+  if (pipe_ptr == nullptr) {
+    // Failed...
+    return;
+  }
+
+  // Send the offset.
+  const std::string hex_offset = StringPrintf("%zx\n", offset);
+
+  if (!pipe_ptr->out.WriteFully(hex_offset.data(), hex_offset.length())) {
+    // Error. :-(
+    pipe->reset();
+    return;
+  }
+
+  // Now drain (expecting two lines).
+  Drain(2U, prefix, pipe, os);
+}
+
+static bool RunCommand(std::string cmd) {
+  FILE* stream = popen(cmd.c_str(), "r");
+  if (stream) {
     pclose(stream);
     return true;
   } else {
@@ -111,13 +266,6 @@
   }
 }
 
-static void Addr2line(const std::string& map_src, uintptr_t offset, std::ostream& os,
-                      const char* prefix) {
-  std::string cmdline(StringPrintf("addr2line --functions --inlines --demangle -e %s %zx",
-                                   map_src.c_str(), offset));
-  RunCommand(cmdline.c_str(), &os, prefix);
-}
-
 static bool PcIsWithinQuickCode(ArtMethod* method, uintptr_t pc) NO_THREAD_SAFETY_ANALYSIS {
   uintptr_t code = reinterpret_cast<uintptr_t>(EntryPointToCodePointer(
       method->GetEntryPointFromQuickCompiledCode()));
@@ -128,8 +276,12 @@
   return code <= pc && pc <= (code + code_size);
 }
 
-void DumpNativeStack(std::ostream& os, pid_t tid, BacktraceMap* existing_map, const char* prefix,
-    ArtMethod* current_method, void* ucontext_ptr) {
+void DumpNativeStack(std::ostream& os,
+                     pid_t tid,
+                     BacktraceMap* existing_map,
+                     const char* prefix,
+                     ArtMethod* current_method,
+                     void* ucontext_ptr) {
   // b/18119146
   if (RUNNING_ON_MEMORY_TOOL != 0) {
     return;
@@ -156,11 +308,13 @@
   if (kUseAddr2line) {
     // Try to run it to see whether we have it. Push an argument so that it doesn't assume a.out
     // and print to stderr.
-    use_addr2line = (gAborting > 0) && RunCommand("addr2line -h", nullptr, nullptr);
+    use_addr2line = (gAborting > 0) && RunCommand("addr2line -h");
   } else {
     use_addr2line = false;
   }
 
+  std::unique_ptr<Addr2linePipe> addr2line_state;
+
   for (Backtrace::const_iterator it = backtrace->begin();
        it != backtrace->end(); ++it) {
     // We produce output like this:
@@ -202,9 +356,13 @@
     }
     os << "\n";
     if (try_addr2line && use_addr2line) {
-      Addr2line(it->map.name, it->pc - it->map.start, os, prefix);
+      Addr2line(it->map.name, it->pc - it->map.start, os, prefix, &addr2line_state);
     }
   }
+
+  if (addr2line_state != nullptr) {
+    Drain(0, prefix, &addr2line_state, os);
+  }
 }
 
 void DumpKernelStack(std::ostream& os, pid_t tid, const char* prefix, bool include_count) {