profiling: Add unwinding

Change-Id: I20beef7e9ef0436ef5bf15e3007f7e631e401e5a
diff --git a/Android.bp b/Android.bp
index 50f71c0..5a9e6d7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3688,6 +3688,14 @@
     "src/perfetto_cmd/perfetto_cmd.cc",
     "src/perfetto_cmd/rate_limiter.cc",
     "src/perfetto_cmd/rate_limiter_unittest.cc",
+    "src/profiling/memory/record_reader.cc",
+    "src/profiling/memory/record_reader_unittest.cc",
+    "src/profiling/memory/socket_listener.cc",
+    "src/profiling/memory/socket_listener_unittest.cc",
+    "src/profiling/memory/string_interner.cc",
+    "src/profiling/memory/string_interner_unittest.cc",
+    "src/profiling/memory/unwinding.cc",
+    "src/profiling/memory/unwinding_unittest.cc",
     "src/protozero/message.cc",
     "src/protozero/message_handle.cc",
     "src/protozero/message_handle_unittest.cc",
@@ -3790,9 +3798,12 @@
   ],
   shared_libs: [
     "libandroid",
+    "libbase",
     "liblog",
+    "libprocinfo",
     "libprotobuf-cpp-full",
     "libprotobuf-cpp-lite",
+    "libunwindstack",
   ],
   static_libs: [
     "libgmock",
diff --git a/BUILD.gn b/BUILD.gn
index 5b05523..aee8466 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -33,8 +33,9 @@
   monolithic_binaries = false
 
   # libunwindstack requires API level 26 or newer.
-  should_build_heapprofd = !build_with_chromium && is_clang &&
-                           (!is_android || android_api_level >= 26)
+  should_build_heapprofd =
+      !build_with_chromium && is_clang &&
+      (!is_android || android_api_level >= 26 || build_with_android)
 }
 assert(!monolithic_binaries || !build_with_android)
 
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index f3ce7a0..7990ac2 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("//gn/standalone/libc++/libc++.gni")
+import("../gn/perfetto.gni")
 
 # Used to suppress warnings coming from googletest macros expansions.
 config("test_warning_suppressions") {
@@ -667,6 +668,7 @@
     "-Wno-enum-conversion",
   ]
 }
+
 source_set("libunwindstack") {
   include_dirs = [
     "android-core/libunwindstack/include",
@@ -721,6 +723,7 @@
   configs -= [
     "//gn/standalone:extra_warnings",
     "//gn/standalone:c++11",
+    "//gn/standalone:visibility_hidden",
   ]
   configs += [ "//gn/standalone:c++17" ]
   public_configs = [ ":libunwindstack_config" ]
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 4c1e853..a317600 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -14,8 +14,28 @@
  * limitations under the License.
  */
 
+#include <unwindstack/MachineArm.h>
+#include <unwindstack/MachineArm64.h>
+#include <unwindstack/MachineMips.h>
+#include <unwindstack/MachineMips64.h>
+#include <unwindstack/MachineX86.h>
+#include <unwindstack/MachineX86_64.h>
 #include <unwindstack/Maps.h>
 #include <unwindstack/Memory.h>
+#include <unwindstack/Regs.h>
+#include <unwindstack/RegsArm.h>
+#include <unwindstack/RegsArm64.h>
+#include <unwindstack/RegsMips.h>
+#include <unwindstack/RegsMips64.h>
+#include <unwindstack/RegsX86.h>
+#include <unwindstack/RegsX86_64.h>
+#include <unwindstack/Unwinder.h>
+#include <unwindstack/UserArm.h>
+#include <unwindstack/UserArm64.h>
+#include <unwindstack/UserMips.h>
+#include <unwindstack/UserMips64.h>
+#include <unwindstack/UserX86.h>
+#include <unwindstack/UserX86_64.h>
 
 #include <procinfo/process_map.h>
 
@@ -27,6 +47,62 @@
 
 namespace perfetto {
 
+namespace {
+
+size_t kMaxFrames = 1000;
+
+std::unique_ptr<unwindstack::Regs> CreateFromRawData(unwindstack::ArchEnum arch,
+                                                     void* raw_data) {
+  std::unique_ptr<unwindstack::Regs> ret;
+  // unwindstack::RegsX::Read returns a raw ptr which we are expected to free.
+  switch (arch) {
+    case unwindstack::ARCH_X86:
+      ret.reset(unwindstack::RegsX86::Read(raw_data));
+      break;
+    case unwindstack::ARCH_X86_64:
+      ret.reset(unwindstack::RegsX86_64::Read(raw_data));
+      break;
+    case unwindstack::ARCH_ARM:
+      ret.reset(unwindstack::RegsArm::Read(raw_data));
+      break;
+    case unwindstack::ARCH_ARM64:
+      ret.reset(unwindstack::RegsArm64::Read(raw_data));
+      break;
+    case unwindstack::ARCH_MIPS:
+      ret.reset(unwindstack::RegsMips::Read(raw_data));
+      break;
+    case unwindstack::ARCH_MIPS64:
+      ret.reset(unwindstack::RegsMips64::Read(raw_data));
+      break;
+    case unwindstack::ARCH_UNKNOWN:
+      ret.reset(nullptr);
+      break;
+  }
+  return ret;
+}
+
+}  // namespace
+
+size_t RegSize(unwindstack::ArchEnum arch) {
+  switch (arch) {
+    case unwindstack::ARCH_X86:
+      return unwindstack::X86_REG_LAST * sizeof(uint32_t);
+    case unwindstack::ARCH_X86_64:
+      return unwindstack::X86_64_REG_LAST * sizeof(uint64_t);
+    case unwindstack::ARCH_ARM:
+      return unwindstack::ARM_REG_LAST * sizeof(uint32_t);
+    case unwindstack::ARCH_ARM64:
+      return unwindstack::ARM64_REG_LAST * sizeof(uint64_t);
+    case unwindstack::ARCH_MIPS:
+      return unwindstack::MIPS_REG_LAST * sizeof(uint32_t);
+    case unwindstack::ARCH_MIPS64:
+      return unwindstack::MIPS64_REG_LAST * sizeof(uint64_t);
+    case unwindstack::ARCH_UNKNOWN:
+      PERFETTO_DCHECK(false);
+      return 0;
+  }
+}
+
 StackMemory::StackMemory(int mem_fd, uint64_t sp, uint8_t* stack, size_t size)
     : mem_fd_(mem_fd), sp_(sp), stack_end_(sp + size), stack_(stack) {}
 
@@ -60,7 +136,6 @@
   std::string content;
   if (!base::ReadFileDescriptor(*fd_, &content))
     return false;
-  PERFETTO_DLOG("%s", content.c_str());
   return android::procinfo::ReadMapFileContent(
       &content[0], [&](uint64_t start, uint64_t end, uint16_t flags,
                        uint64_t pgoff, const char* name) {
@@ -80,4 +155,53 @@
   maps_.clear();
 }
 
+bool DoUnwind(void* mem,
+              size_t sz,
+              ProcessMetadata* metadata,
+              std::vector<unwindstack::FrameData>* out) {
+  if (sz < sizeof(AllocMetadata)) {
+    PERFETTO_ELOG("size");
+    return false;
+  }
+  AllocMetadata* alloc_metadata = reinterpret_cast<AllocMetadata*>(mem);
+  if (sizeof(AllocMetadata) + RegSize(alloc_metadata->arch) > sz)
+    return false;
+  void* reg_data = static_cast<uint8_t*>(mem) + sizeof(AllocMetadata);
+  std::unique_ptr<unwindstack::Regs> regs(
+      CreateFromRawData(alloc_metadata->arch, reg_data));
+  if (regs == nullptr) {
+    PERFETTO_ELOG("regs");
+    return false;
+  }
+  if (alloc_metadata->stack_pointer_offset < sizeof(AllocMetadata) ||
+      alloc_metadata->stack_pointer_offset > sz) {
+    PERFETTO_ELOG("out-of-bound stack_pointer_offset");
+    return false;
+  }
+  uint8_t* stack =
+      reinterpret_cast<uint8_t*>(mem) + alloc_metadata->stack_pointer_offset;
+  std::shared_ptr<unwindstack::Memory> mems = std::make_shared<StackMemory>(
+      *metadata->mem_fd, alloc_metadata->stack_pointer, stack,
+      sz - alloc_metadata->stack_pointer_offset);
+  unwindstack::Unwinder unwinder(kMaxFrames, &metadata->maps, regs.get(), mems);
+  // Surpress incorrect "variable may be uninitialized" error for if condition
+  // after this loop. error_code = LastErrorCode gets run at least once.
+  uint8_t error_code = 0;
+  for (int attempt = 0; attempt < 2; ++attempt) {
+    if (attempt > 0) {
+      metadata->maps.Reset();
+      metadata->maps.Parse();
+    }
+    unwinder.Unwind();
+    error_code = unwinder.LastErrorCode();
+    if (error_code != unwindstack::ERROR_INVALID_MAP)
+      break;
+  }
+  if (error_code == 0)
+    *out = unwinder.frames();
+  else
+    PERFETTO_DLOG("unwinding failed %" PRIu8, error_code);
+  return error_code == 0;
+}
+
 }  // namespace perfetto
diff --git a/src/profiling/memory/unwinding.h b/src/profiling/memory/unwinding.h
index f10a361..455be43 100644
--- a/src/profiling/memory/unwinding.h
+++ b/src/profiling/memory/unwinding.h
@@ -60,6 +60,13 @@
   uint8_t* stack_;
 };
 
+size_t RegSize(unwindstack::ArchEnum arch);
+
+bool DoUnwind(void* mem,
+              size_t sz,
+              ProcessMetadata* metadata,
+              std::vector<unwindstack::FrameData>* out);
+
 }  // namespace perfetto
 
 #endif  // SRC_PROFILING_MEMORY_UNWINDING_H_
diff --git a/src/profiling/memory/unwinding_unittest.cc b/src/profiling/memory/unwinding_unittest.cc
index 06fe9b7..06f3ba9 100644
--- a/src/profiling/memory/unwinding_unittest.cc
+++ b/src/profiling/memory/unwinding_unittest.cc
@@ -21,6 +21,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
+#include <cxxabi.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -63,5 +64,82 @@
   ASSERT_EQ(map_info->name, "[stack]");
 }
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+#define MAYBE_DoUnwind DoUnwind
+#else
+#define MAYBE_DoUnwind DISABLED_DoUnwind
+#endif
+
+uint8_t* GetStackBase() {
+  pthread_t t = pthread_self();
+  pthread_attr_t attr;
+  if (pthread_getattr_np(t, &attr) != 0) {
+    return nullptr;
+  }
+  uint8_t* x;
+  size_t s;
+  if (pthread_attr_getstack(&attr, reinterpret_cast<void**>(&x), &s) != 0)
+    return nullptr;
+  pthread_attr_destroy(&attr);
+  return x + s;
+}
+
+// This is needed because ASAN thinks copying the whole stack is a buffer
+// underrun.
+void __attribute__((noinline))
+UnsafeMemcpy(void* dst, const void* src, size_t n)
+    __attribute__((no_sanitize("address"))) {
+  const uint8_t* from = reinterpret_cast<const uint8_t*>(src);
+  uint8_t* to = reinterpret_cast<uint8_t*>(dst);
+  for (size_t i = 0; i < n; ++i)
+    to[i] = from[i];
+}
+
+std::pair<std::unique_ptr<uint8_t[]>, size_t> __attribute__((noinline))
+GetRecord() {
+  const uint8_t* stackbase = GetStackBase();
+  PERFETTO_CHECK(stackbase != nullptr);
+  const uint8_t* stacktop =
+      reinterpret_cast<uint8_t*>(__builtin_frame_address(0));
+  PERFETTO_CHECK(stacktop != nullptr);
+  PERFETTO_CHECK(stacktop < stackbase);
+  const size_t stack_size = static_cast<size_t>(stackbase - stacktop);
+  std::unique_ptr<unwindstack::Regs> regs(unwindstack::Regs::CreateFromLocal());
+  const unwindstack::ArchEnum arch = regs->CurrentArch();
+  const size_t reg_size = RegSize(arch);
+  const size_t total_size = sizeof(AllocMetadata) + reg_size + stack_size;
+  std::unique_ptr<uint8_t[]> buf(new uint8_t[total_size]);
+  AllocMetadata* metadata = reinterpret_cast<AllocMetadata*>(buf.get());
+  metadata->alloc_size = 0;
+  metadata->alloc_address = 0;
+  metadata->stack_pointer = reinterpret_cast<uint64_t>(stacktop);
+  metadata->stack_pointer_offset = sizeof(AllocMetadata) + reg_size;
+  metadata->arch = arch;
+  unwindstack::RegsGetLocal(regs.get());
+  // Make sure nothing above has changed the stack pointer, just for extra
+  // paranoia.
+  PERFETTO_CHECK(stacktop ==
+                 reinterpret_cast<uint8_t*>(__builtin_frame_address(0)));
+  memcpy(buf.get() + sizeof(AllocMetadata), regs->RawData(), reg_size);
+  UnsafeMemcpy(buf.get() + sizeof(AllocMetadata) + reg_size, stacktop,
+               stack_size);
+  return {std::move(buf), total_size};
+}
+
+// TODO(fmayer): Investigate why this fails out of tree.
+TEST(UnwindingTest, MAYBE_DoUnwind) {
+  base::ScopedFile proc_maps(open("/proc/self/maps", O_RDONLY));
+  base::ScopedFile proc_mem(open("/proc/self/mem", O_RDONLY));
+  ProcessMetadata metadata(getpid(), std::move(proc_maps), std::move(proc_mem));
+  auto record = GetRecord();
+  std::vector<unwindstack::FrameData> out;
+  ASSERT_TRUE(DoUnwind(record.first.get(), record.second, &metadata, &out));
+  int st;
+  std::unique_ptr<char> demangled(
+      abi::__cxa_demangle(out[0].function_name.c_str(), nullptr, nullptr, &st));
+  ASSERT_EQ(st, 0);
+  ASSERT_STREQ(demangled.get(), "perfetto::(anonymous namespace)::GetRecord()");
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index a17cf16..e6a6d6c 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -118,12 +118,15 @@
 def enable_protoc_lib(module):
     module.shared_libs.append('libprotoc')
 
+def enable_libunwindstack(module):
+    module.shared_libs.append('libunwindstack')
+    module.shared_libs.append('libprocinfo')
+    module.shared_libs.append('libbase')
 
 def enable_libunwind(module):
     # libunwind is disabled on Darwin so we cannot depend on it.
     pass
 
-
 # Android equivalents for third-party libraries that the upstream project
 # depends on.
 builtin_deps = {
@@ -135,6 +138,7 @@
     '//buildtools:protobuf_full': enable_protobuf_full,
     '//buildtools:protobuf_lite': enable_protobuf_lite,
     '//buildtools:protoc_lib': enable_protoc_lib,
+    '//buildtools:libunwindstack': enable_libunwindstack,
 }
 
 # ----------------------------------------------------------------------------