Merge "Add support for tombstone symbolization to pbtombstone." into main
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index d4dc9a3..3257a2c 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -333,7 +333,10 @@
     name: "pbtombstone",
     host_supported: true,
     defaults: ["debuggerd_defaults"],
-    srcs: ["pbtombstone.cpp"],
+    srcs: [
+        "pbtombstone.cpp",
+        "tombstone_symbolize.cpp",
+    ],
     static_libs: [
         "libbase",
         "libdebuggerd_tombstone_proto_to_text",
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone_proto_to_text.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone_proto_to_text.h
index 515a15f..2de9723 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone_proto_to_text.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone_proto_to_text.h
@@ -19,8 +19,10 @@
 #include <functional>
 #include <string>
 
+class BacktraceFrame;
 class Tombstone;
 
 bool tombstone_proto_to_text(
     const Tombstone& tombstone,
-    std::function<void(const std::string& line, bool should_log)> callback);
+    std::function<void(const std::string& line, bool should_log)> callback,
+    std::function<void(const BacktraceFrame& frame)> symbolize);
diff --git a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
index 3fdb71d..aad209a 100644
--- a/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_proto_to_text_test.cpp
@@ -61,12 +61,16 @@
 
   void ProtoToString() {
     text_ = "";
-    EXPECT_TRUE(
-        tombstone_proto_to_text(*tombstone_, [this](const std::string& line, bool should_log) {
+    EXPECT_TRUE(tombstone_proto_to_text(
+        *tombstone_,
+        [this](const std::string& line, bool should_log) {
           if (should_log) {
             text_ += "LOG ";
           }
           text_ += line + '\n';
+        },
+        [&](const BacktraceFrame& frame) {
+          text_ += "SYMBOLIZE " + frame.build_id() + " " + std::to_string(frame.pc()) + "\n";
         }));
   }
 
@@ -163,3 +167,11 @@
   EXPECT_MATCH(text_, "stack_record fp:0x1 tag:0xb pc:foo\\.so\\+0x567 \\(BuildId: ABC123\\)");
   EXPECT_MATCH(text_, "stack_record fp:0x2 tag:0xc pc:bar\\.so\\+0x678");
 }
+
+TEST_F(TombstoneProtoToTextTest, symbolize) {
+  BacktraceFrame* frame = main_thread_->add_current_backtrace();
+  frame->set_pc(12345);
+  frame->set_build_id("0123456789abcdef");
+  ProtoToString();
+  EXPECT_MATCH(text_, "\\(BuildId: 0123456789abcdef\\)\\nSYMBOLIZE 0123456789abcdef 12345\\n");
+}
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index d483b98..30c6fe4 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -146,7 +146,10 @@
   log.tfd = output_fd.get();
   log.amfd_data = amfd_data;
 
-  tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {
-    _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
-  });
+  tombstone_proto_to_text(
+      tombstone,
+      [&log](const std::string& line, bool should_log) {
+        _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
+      },
+      [](const BacktraceFrame&) {});
 }
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 611e237..fedafc0 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -41,6 +41,7 @@
 #define CBL(...) CB(true, __VA_ARGS__)
 #define CBS(...) CB(false, __VA_ARGS__)
 using CallbackType = std::function<void(const std::string& line, bool should_log)>;
+using SymbolizeCallbackType = std::function<void(const BacktraceFrame& frame)>;
 
 #define DESCRIBE_FLAG(flag) \
   if (value & flag) {       \
@@ -184,7 +185,8 @@
   print_register_row(callback, word_size, special_row, should_log);
 }
 
-static void print_backtrace(CallbackType callback, const Tombstone& tombstone,
+static void print_backtrace(CallbackType callback, SymbolizeCallbackType symbolize,
+                            const Tombstone& tombstone,
                             const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace,
                             bool should_log) {
   int index = 0;
@@ -209,11 +211,14 @@
     }
     line += function + build_id;
     CB(should_log, "%s", line.c_str());
+
+    symbolize(frame);
   }
 }
 
-static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
-                                   const Thread& thread, bool should_log) {
+static void print_thread_backtrace(CallbackType callback, SymbolizeCallbackType symbolize,
+                                   const Tombstone& tombstone, const Thread& thread,
+                                   bool should_log) {
   CBS("");
   CB(should_log, "%d total frames", thread.current_backtrace().size());
   CB(should_log, "backtrace:");
@@ -221,7 +226,7 @@
     CB(should_log, "  NOTE: %s",
        android::base::Join(thread.backtrace_note(), "\n  NOTE: ").c_str());
   }
-  print_backtrace(callback, tombstone, thread.current_backtrace(), should_log);
+  print_backtrace(callback, symbolize, tombstone, thread.current_backtrace(), should_log);
 }
 
 static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone,
@@ -274,10 +279,11 @@
   }
 }
 
-static void print_thread(CallbackType callback, const Tombstone& tombstone, const Thread& thread) {
+static void print_thread(CallbackType callback, SymbolizeCallbackType symbolize,
+                         const Tombstone& tombstone, const Thread& thread) {
   print_thread_header(callback, tombstone, thread, false);
   print_thread_registers(callback, tombstone, thread, false);
-  print_thread_backtrace(callback, tombstone, thread, false);
+  print_thread_backtrace(callback, symbolize, tombstone, thread, false);
   print_thread_memory_dump(callback, tombstone, thread);
 }
 
@@ -433,8 +439,8 @@
   return oct_encoded;
 }
 
-static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
-                              const Thread& thread) {
+static void print_main_thread(CallbackType callback, SymbolizeCallbackType symbolize,
+                              const Tombstone& tombstone, const Thread& thread) {
   print_thread_header(callback, tombstone, thread, true);
 
   const Signal& signal_info = tombstone.signal_info();
@@ -488,7 +494,7 @@
     CBL("      in this process. The stack trace below is the first system call or context");
     CBL("      switch that was executed after the memory corruption happened.");
   }
-  print_thread_backtrace(callback, tombstone, thread, true);
+  print_thread_backtrace(callback, symbolize, tombstone, thread, true);
 
   if (tombstone.causes_size() > 1) {
     CBS("");
@@ -521,13 +527,13 @@
       if (heap_object.deallocation_backtrace_size() != 0) {
         CBS("");
         CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
-        print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), true);
+        print_backtrace(callback, symbolize, tombstone, heap_object.deallocation_backtrace(), true);
       }
 
       if (heap_object.allocation_backtrace_size() != 0) {
         CBS("");
         CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
-        print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), true);
+        print_backtrace(callback, symbolize, tombstone, heap_object.allocation_backtrace(), true);
       }
     }
   }
@@ -576,8 +582,9 @@
   }
 }
 
-static void print_guest_thread(CallbackType callback, const Tombstone& tombstone,
-                               const Thread& guest_thread, pid_t tid, bool should_log) {
+static void print_guest_thread(CallbackType callback, SymbolizeCallbackType symbolize,
+                               const Tombstone& tombstone, const Thread& guest_thread, pid_t tid,
+                               bool should_log) {
   CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
   CBS("Guest thread information for tid: %d", tid);
   print_thread_registers(callback, tombstone, guest_thread, should_log);
@@ -585,12 +592,13 @@
   CBS("");
   CB(true, "%d total frames", guest_thread.current_backtrace().size());
   CB(true, "backtrace:");
-  print_backtrace(callback, tombstone, guest_thread.current_backtrace(), should_log);
+  print_backtrace(callback, symbolize, tombstone, guest_thread.current_backtrace(), should_log);
 
   print_thread_memory_dump(callback, tombstone, guest_thread);
 }
 
-bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback) {
+bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback,
+                             SymbolizeCallbackType symbolize) {
   CBL("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***");
   CBL("Build fingerprint: '%s'", tombstone.build_fingerprint().c_str());
   CBL("Revision: '%s'", tombstone.revision().c_str());
@@ -618,14 +626,15 @@
 
   const auto& main_thread = main_thread_it->second;
 
-  print_main_thread(callback, tombstone, main_thread);
+  print_main_thread(callback, symbolize, tombstone, main_thread);
 
   print_logs(callback, tombstone, 50);
 
   const auto& guest_threads = tombstone.guest_threads();
   auto main_guest_thread_it = guest_threads.find(tombstone.tid());
   if (main_guest_thread_it != threads.end()) {
-    print_guest_thread(callback, tombstone, main_guest_thread_it->second, tombstone.tid(), true);
+    print_guest_thread(callback, symbolize, tombstone, main_guest_thread_it->second,
+                       tombstone.tid(), true);
   }
 
   // protobuf's map is unordered, so sort the keys first.
@@ -638,10 +647,10 @@
 
   for (const auto& tid : thread_ids) {
     CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
-    print_thread(callback, tombstone, threads.find(tid)->second);
+    print_thread(callback, symbolize, tombstone, threads.find(tid)->second);
     auto guest_thread_it = guest_threads.find(tid);
     if (guest_thread_it != guest_threads.end()) {
-      print_guest_thread(callback, tombstone, guest_thread_it->second, tid, false);
+      print_guest_thread(callback, symbolize, tombstone, guest_thread_it->second, tid, false);
     }
   }
 
diff --git a/debuggerd/pbtombstone.cpp b/debuggerd/pbtombstone.cpp
index dcb7c6c..0902b38 100644
--- a/debuggerd/pbtombstone.cpp
+++ b/debuggerd/pbtombstone.cpp
@@ -16,32 +16,55 @@
 
 #include <err.h>
 #include <fcntl.h>
+#include <getopt.h>
 #include <stdio.h>
 #include <unistd.h>
 
+#include <string>
+#include <vector>
+
 #include <android-base/unique_fd.h>
 #include <libdebuggerd/tombstone_proto_to_text.h>
 
 #include "tombstone.pb.h"
+#include "tombstone_symbolize.h"
 
 using android::base::unique_fd;
 
 [[noreturn]] void usage(bool error) {
-  fprintf(stderr, "usage: pbtombstone TOMBSTONE.PB\n");
+  fprintf(stderr, "usage: pbtombstone [OPTION] TOMBSTONE.PB\n");
   fprintf(stderr, "Convert a protobuf tombstone to text.\n");
+  fprintf(stderr, "Arguments:\n");
+  fprintf(stderr, "  -h, --help                   print this message\n");
+  fprintf(stderr, "  --debug-file-directory PATH  specify the path to a symbols directory\n");
   exit(error);
 }
 
-int main(int argc, const char* argv[]) {
-  if (argc != 2) {
+int main(int argc, char* argv[]) {
+  std::vector<std::string> debug_file_directories;
+  static struct option long_options[] = {
+      {"debug-file-directory", required_argument, 0, 0},
+      {"help", no_argument, 0, 'h'},
+      {},
+  };
+  int c;
+  while ((c = getopt_long(argc, argv, "h", long_options, 0)) != -1) {
+    switch (c) {
+      case 0:
+        debug_file_directories.push_back(optarg);
+        break;
+
+      case 'h':
+        usage(false);
+        break;
+    }
+  }
+
+  if (optind != argc-1) {
     usage(true);
   }
 
-  if (strcmp("-h", argv[1]) == 0 || strcmp("--help", argv[1]) == 0) {
-    usage(false);
-  }
-
-  unique_fd fd(open(argv[1], O_RDONLY | O_CLOEXEC));
+  unique_fd fd(open(argv[optind], O_RDONLY | O_CLOEXEC));
   if (fd == -1) {
     err(1, "failed to open tombstone '%s'", argv[1]);
   }
@@ -51,8 +74,11 @@
     err(1, "failed to parse tombstone");
   }
 
+  Symbolizer sym;
+  sym.Start(debug_file_directories);
   bool result = tombstone_proto_to_text(
-      tombstone, [](const std::string& line, bool) { printf("%s\n", line.c_str()); });
+      tombstone, [](const std::string& line, bool) { printf("%s\n", line.c_str()); },
+      [&](const BacktraceFrame& frame) { symbolize_backtrace_frame(frame, sym); });
 
   if (!result) {
     errx(1, "tombstone was malformed");
diff --git a/debuggerd/tombstone_symbolize.cpp b/debuggerd/tombstone_symbolize.cpp
new file mode 100644
index 0000000..07735d0
--- /dev/null
+++ b/debuggerd/tombstone_symbolize.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "tombstone_symbolize.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "android-base/unique_fd.h"
+
+#include "tombstone.pb.h"
+
+using android::base::StringPrintf;
+using android::base::unique_fd;
+
+bool Symbolizer::Start(const std::vector<std::string>& debug_file_directories) {
+  unique_fd parent_in, parent_out, child_in, child_out;
+  if (!Pipe(&parent_in, &child_out) || !Pipe(&child_in, &parent_out)) {
+    return false;
+  }
+
+  std::vector<const char *> args;
+  args.push_back("llvm-symbolizer");
+  for (const std::string &dir : debug_file_directories) {
+    args.push_back("--debug-file-directory");
+    args.push_back(dir.c_str());
+  }
+  args.push_back(0);
+
+  int pid = fork();
+  if (pid == -1) {
+    return false;
+  } else if (pid == 0) {
+    parent_in.reset();
+    parent_out.reset();
+
+    dup2(child_in.get(), STDIN_FILENO);
+    dup2(child_out.get(), STDOUT_FILENO);
+
+    execvp("llvm-symbolizer", const_cast<char *const *>(args.data()));
+
+    fprintf(stderr, "unable to start llvm-symbolizer: %s\n", strerror(errno));
+    _exit(1);
+  } else {
+    child_in.reset();
+    child_out.reset();
+
+    // TODO: Check that llvm-symbolizer started up successfully.
+    // There used to be an easy way to do this, but it was removed in:
+    // https://github.com/llvm/llvm-project/commit/1792852f86dc75efa1f44d46b1a0daf386d64afa
+
+    in_fd = std::move(parent_in);
+    out_fd = std::move(parent_out);
+    return true;
+  }
+}
+
+std::string Symbolizer::read_response() {
+  std::string resp;
+
+  while (resp.size() < 2 || resp[resp.size() - 2] != '\n' || resp[resp.size() - 1] != '\n') {
+    char buf[4096];
+    ssize_t size = read(in_fd, buf, 4096);
+    if (size <= 0) {
+      return "";
+    }
+    resp.append(buf, size);
+  }
+
+  return resp;
+}
+
+std::vector<Symbolizer::Frame> Symbolizer::SymbolizeCode(std::string path, uint64_t rel_pc) {
+  std::string request = StringPrintf("CODE %s 0x%" PRIx64 "\n", path.c_str(), rel_pc);
+  if (write(out_fd, request.c_str(), request.size()) != static_cast<ssize_t>(request.size())) {
+    return {};
+  }
+
+  std::string response = read_response();
+  if (response.empty()) {
+    return {};
+  }
+
+  std::vector<Symbolizer::Frame> frames;
+
+  size_t frame_start = 0;
+  while (frame_start < response.size() - 1) {
+    Symbolizer::Frame frame;
+
+    size_t second_line_start = response.find('\n', frame_start) + 1;
+    if (second_line_start == std::string::npos + 1) {
+      return {};
+    }
+
+    size_t third_line_start = response.find('\n', second_line_start) + 1;
+    if (third_line_start == std::string::npos + 1) {
+      return {};
+    }
+
+    frame.function_name = response.substr(frame_start, second_line_start - frame_start - 1);
+
+    size_t column_number_start = response.rfind(':', third_line_start);
+    if (column_number_start == std::string::npos) {
+      return {};
+    }
+
+    size_t line_number_start = response.rfind(':', column_number_start - 1);
+    if (line_number_start == std::string::npos) {
+      return {};
+    }
+
+    frame.file = response.substr(second_line_start, line_number_start - second_line_start);
+
+    errno = 0;
+    frame.line = strtoull(response.c_str() + line_number_start + 1, 0, 10);
+    frame.column = strtoull(response.c_str() + column_number_start + 1, 0, 10);
+    if (errno != 0) {
+      return {};
+    }
+
+    frames.push_back(frame);
+
+    frame_start = third_line_start;
+  }
+
+  if (frames.size() == 1 && frames[0].file == "??") {
+    return {};
+  }
+
+  return frames;
+}
+
+void symbolize_backtrace_frame(const BacktraceFrame& frame, Symbolizer& sym) {
+  if (frame.build_id().empty()) {
+    return;
+  }
+
+  for (Symbolizer::Frame f : sym.SymbolizeCode("BUILDID:" + frame.build_id(), frame.rel_pc())) {
+    printf("          %s:%" PRId64 ":%" PRId64 " (%s)\n", f.file.c_str(), f.line, f.column,
+           f.function_name.c_str());
+  }
+}
diff --git a/debuggerd/tombstone_symbolize.h b/debuggerd/tombstone_symbolize.h
new file mode 100644
index 0000000..c22d677
--- /dev/null
+++ b/debuggerd/tombstone_symbolize.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "android-base/unique_fd.h"
+
+class BacktraceFrame;
+
+class Symbolizer {
+  android::base::unique_fd in_fd, out_fd;
+
+  std::string read_response();
+
+ public:
+  bool Start(const std::vector<std::string>& debug_file_directories);
+
+  struct Frame {
+    std::string function_name, file;
+    uint64_t line, column;
+  };
+
+  std::vector<Frame> SymbolizeCode(std::string path, uint64_t rel_pc);
+};
+
+void symbolize_backtrace_frame(const BacktraceFrame& frame, Symbolizer& sym);