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,
}
# ----------------------------------------------------------------------------