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