diff options
| -rw-r--r-- | runtime/native_stack_dump.cc | 262 |
1 files changed, 210 insertions, 52 deletions
diff --git a/runtime/native_stack_dump.cc b/runtime/native_stack_dump.cc index 8c5b386932..c20c8b88a4 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 @@ namespace art { 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 bool RunCommand(std::string cmd, std::ostream* os, const char* prefix) { } } -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 @@ static bool PcIsWithinQuickCode(ArtMethod* method, uintptr_t pc) NO_THREAD_SAFET 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 @@ void DumpNativeStack(std::ostream& os, pid_t tid, BacktraceMap* existing_map, co 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 @@ void DumpNativeStack(std::ostream& os, pid_t tid, BacktraceMap* existing_map, co } 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) { |