perf: add userspace frame pointer unwinder
This change introduces the UNWIND_FRAME_POINTER option to the UnwindMode
enum, allowing users to select the frame pointer unwinder for unwinding
userspace stack traces.
github issue: https://github.com/google/perfetto/issues/907
Change-Id: I82b612a7c534f3d3adaa1b0faf28651023429325
diff --git a/Android.bp b/Android.bp
index 2341f3b..2aa73b8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11517,6 +11517,7 @@
name: "perfetto_src_profiling_perf_producer_unittests",
srcs: [
"src/profiling/perf/event_config_unittest.cc",
+ "src/profiling/perf/frame_pointer_unwinder_unittest.cc",
"src/profiling/perf/perf_producer_unittest.cc",
"src/profiling/perf/unwind_queue_unittest.cc",
],
@@ -11542,6 +11543,7 @@
filegroup {
name: "perfetto_src_profiling_perf_unwinding",
srcs: [
+ "src/profiling/perf/frame_pointer_unwinder.cc",
"src/profiling/perf/unwinding.cc",
],
}
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index a87e2a7..c29ca82 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -1957,6 +1957,8 @@
UNWIND_SKIP = 1;
// Use libunwindstack (default):
UNWIND_DWARF = 2;
+ // Use userspace frame pointer unwinder:
+ UNWIND_FRAME_POINTER = 3;
}
}
diff --git a/protos/perfetto/config/profiling/perf_event_config.proto b/protos/perfetto/config/profiling/perf_event_config.proto
index b02aa07..d3cc51c 100644
--- a/protos/perfetto/config/profiling/perf_event_config.proto
+++ b/protos/perfetto/config/profiling/perf_event_config.proto
@@ -217,5 +217,7 @@
UNWIND_SKIP = 1;
// Use libunwindstack (default):
UNWIND_DWARF = 2;
+ // Use userspace frame pointer unwinder:
+ UNWIND_FRAME_POINTER = 3;
}
}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index df04bec..1e8bee7 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -1957,6 +1957,8 @@
UNWIND_SKIP = 1;
// Use libunwindstack (default):
UNWIND_DWARF = 2;
+ // Use userspace frame pointer unwinder:
+ UNWIND_FRAME_POINTER = 3;
}
}
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
index d77cb84..702b798 100644
--- a/src/profiling/perf/BUILD.gn
+++ b/src/profiling/perf/BUILD.gn
@@ -101,6 +101,8 @@
"../common:unwind_support",
]
sources = [
+ "frame_pointer_unwinder.cc",
+ "frame_pointer_unwinder.h",
"unwind_queue.h",
"unwinding.cc",
"unwinding.h",
@@ -147,6 +149,7 @@
]
sources = [
"event_config_unittest.cc",
+ "frame_pointer_unwinder_unittest.cc",
"perf_producer_unittest.cc",
"unwind_queue_unittest.cc",
]
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index 7409c18..94d563c 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -273,6 +273,20 @@
}
}
+bool IsSupportedUnwindMode(
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode) {
+ using protos::gen::PerfEventConfig;
+ switch (static_cast<int>(unwind_mode)) { // cast to pacify -Wswitch-enum
+ case PerfEventConfig::UNWIND_UNKNOWN:
+ case PerfEventConfig::UNWIND_SKIP:
+ case PerfEventConfig::UNWIND_DWARF:
+ case PerfEventConfig::UNWIND_FRAME_POINTER:
+ return true;
+ default:
+ return false;
+ }
+}
+
} // namespace
// static
@@ -371,32 +385,18 @@
}
// Callstack sampling.
- bool user_frames = false;
bool kernel_frames = false;
+ // Disable user_frames by default.
+ auto unwind_mode = protos::gen::PerfEventConfig::UNWIND_SKIP;
+
TargetFilter target_filter;
bool legacy_config = pb_config.all_cpus(); // all_cpus was mandatory before
if (pb_config.has_callstack_sampling() || legacy_config) {
- user_frames = true;
-
// Userspace callstacks.
- using protos::gen::PerfEventConfig;
- switch (static_cast<int>(pb_config.callstack_sampling().user_frames())) {
- case PerfEventConfig::UNWIND_UNKNOWN:
- // default to true, both for backwards compatibility and because it's
- // almost always what the user wants.
- user_frames = true;
- break;
- case PerfEventConfig::UNWIND_SKIP:
- user_frames = false;
- break;
- case PerfEventConfig::UNWIND_DWARF:
- user_frames = true;
- break;
- default:
- // enum value from the future that we don't yet know, refuse the config
- // TODO(rsavitski): double-check that both pbzero and ::gen propagate
- // unknown enum values.
- return std::nullopt;
+ unwind_mode = pb_config.callstack_sampling().user_frames();
+ if (!IsSupportedUnwindMode(unwind_mode)) {
+ // enum value from the future that we don't yet know, refuse the config
+ return std::nullopt;
}
// Process scoping. Sharding parameter is supplied from outside as it is
@@ -482,7 +482,7 @@
pe.clockid = ToClockId(pb_config.timebase().timestamp_clock());
pe.use_clockid = true;
- if (user_frames) {
+ if (IsUserFramesEnabled(unwind_mode)) {
pe.sample_type |= PERF_SAMPLE_STACK_USER | PERF_SAMPLE_REGS_USER;
// PERF_SAMPLE_STACK_USER:
// Needs to be < ((u16)(~0u)), and have bottom 8 bits clear.
@@ -529,19 +529,35 @@
return EventConfig(
raw_ds_config, pe, std::move(pe_followers), timebase_event, followers,
- user_frames, kernel_frames, std::move(target_filter),
+ kernel_frames, unwind_mode, std::move(target_filter),
ring_buffer_pages.value(), read_tick_period_ms, samples_per_tick_limit,
remote_descriptor_timeout_ms, pb_config.unwind_state_clear_period_ms(),
max_enqueued_footprint_bytes, pb_config.target_installed_by());
}
+// static
+bool EventConfig::IsUserFramesEnabled(
+ const protos::gen::PerfEventConfig::UnwindMode unwind_mode) {
+ using protos::gen::PerfEventConfig;
+ switch (unwind_mode) {
+ case PerfEventConfig::UNWIND_UNKNOWN:
+ // default to true, both for backwards compatibility and because it's
+ // almost always what the user wants.
+ case PerfEventConfig::UNWIND_DWARF:
+ case PerfEventConfig::UNWIND_FRAME_POINTER:
+ return true;
+ case PerfEventConfig::UNWIND_SKIP:
+ return false;
+ }
+}
+
EventConfig::EventConfig(const DataSourceConfig& raw_ds_config,
const perf_event_attr& pe_timebase,
std::vector<perf_event_attr> pe_followers,
const PerfCounter& timebase_event,
std::vector<PerfCounter> follower_events,
- bool user_frames,
bool kernel_frames,
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode,
TargetFilter target_filter,
uint32_t ring_buffer_pages,
uint32_t read_tick_period_ms,
@@ -554,8 +570,8 @@
perf_event_followers_(std::move(pe_followers)),
timebase_event_(timebase_event),
follower_events_(std::move(follower_events)),
- user_frames_(user_frames),
kernel_frames_(kernel_frames),
+ unwind_mode_(unwind_mode),
target_filter_(std::move(target_filter)),
ring_buffer_pages_(ring_buffer_pages),
read_tick_period_ms_(read_tick_period_ms),
diff --git a/src/profiling/perf/event_config.h b/src/profiling/perf/event_config.h
index fc498f7..a87429d 100644
--- a/src/profiling/perf/event_config.h
+++ b/src/profiling/perf/event_config.h
@@ -31,6 +31,7 @@
#include "perfetto/tracing/core/data_source_config.h"
#include "protos/perfetto/common/perf_events.gen.h"
+#include "protos/perfetto/config/profiling/perf_event_config.gen.h"
namespace perfetto {
namespace protos {
@@ -136,9 +137,12 @@
uint64_t max_enqueued_footprint_bytes() const {
return max_enqueued_footprint_bytes_;
}
- bool sample_callstacks() const { return user_frames_ || kernel_frames_; }
- bool user_frames() const { return user_frames_; }
+ bool sample_callstacks() const { return user_frames() || kernel_frames_; }
+ bool user_frames() const { return IsUserFramesEnabled(unwind_mode_); }
bool kernel_frames() const { return kernel_frames_; }
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode() const {
+ return unwind_mode_;
+ }
const TargetFilter& filter() const { return target_filter_; }
perf_event_attr* perf_attr() const {
return const_cast<perf_event_attr*>(&perf_event_attr_);
@@ -158,13 +162,16 @@
const DataSourceConfig& raw_ds_config() const { return raw_ds_config_; }
private:
+ static bool IsUserFramesEnabled(
+ const protos::gen::PerfEventConfig::UnwindMode unwind_mode);
+
EventConfig(const DataSourceConfig& raw_ds_config,
const perf_event_attr& pe_timebase,
std::vector<perf_event_attr> pe_followers,
const PerfCounter& timebase_event,
std::vector<PerfCounter> follower_events,
- bool user_frames,
bool kernel_frames,
+ protos::gen::PerfEventConfig::UnwindMode unwind_mode,
TargetFilter target_filter,
uint32_t ring_buffer_pages,
uint32_t read_tick_period_ms,
@@ -187,12 +194,12 @@
// Timebase event, which are already described by |perf_event_followers_|.
std::vector<PerfCounter> follower_events_;
- // If true, include userspace frames in sampled callstacks.
- const bool user_frames_;
-
// If true, include kernel frames in sampled callstacks.
const bool kernel_frames_;
+ // Userspace unwinding mode.
+ const protos::gen::PerfEventConfig::UnwindMode unwind_mode_;
+
// Parsed allow/deny-list for filtering samples.
const TargetFilter target_filter_;
diff --git a/src/profiling/perf/frame_pointer_unwinder.cc b/src/profiling/perf/frame_pointer_unwinder.cc
new file mode 100644
index 0000000..6841dc6
--- /dev/null
+++ b/src/profiling/perf/frame_pointer_unwinder.cc
@@ -0,0 +1,156 @@
+/*
+ * 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 "src/profiling/perf/frame_pointer_unwinder.h"
+
+#include <cinttypes>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace profiling {
+
+void FramePointerUnwinder::Unwind() {
+ if (!IsArchSupported()) {
+ PERFETTO_ELOG("Unsupported architecture: %d", arch_);
+ last_error_.code = unwindstack::ErrorCode::ERROR_UNSUPPORTED;
+ return;
+ }
+
+ if (maps_ == nullptr || maps_->Total() == 0) {
+ PERFETTO_ELOG("No maps provided");
+ last_error_.code = unwindstack::ErrorCode::ERROR_INVALID_MAP;
+ return;
+ }
+
+ PERFETTO_DCHECK(stack_size_ > 0u);
+
+ frames_.reserve(max_frames_);
+ ClearErrors();
+ TryUnwind();
+}
+
+void FramePointerUnwinder::TryUnwind() {
+ uint64_t fp = 0;
+ switch (arch_) {
+ case unwindstack::ARCH_ARM64:
+ fp = reinterpret_cast<uint64_t*>(
+ regs_->RawData())[unwindstack::Arm64Reg::ARM64_REG_R29];
+ break;
+ case unwindstack::ARCH_X86_64:
+ fp = reinterpret_cast<uint64_t*>(
+ regs_->RawData())[unwindstack::X86_64Reg::X86_64_REG_RBP];
+ break;
+ case unwindstack::ARCH_RISCV64:
+ fp = reinterpret_cast<uint64_t*>(
+ regs_->RawData())[unwindstack::Riscv64Reg::RISCV64_REG_S0];
+ break;
+ case unwindstack::ARCH_UNKNOWN:
+ case unwindstack::ARCH_ARM:
+ case unwindstack::ARCH_X86:
+ // not supported
+ ;
+ }
+ uint64_t sp = regs_->sp();
+ uint64_t pc = regs_->pc();
+ for (size_t i = 0; i < max_frames_; i++) {
+ if (!IsFrameValid(fp, sp))
+ return;
+
+ // retrive the map info and elf info
+ std::shared_ptr<unwindstack::MapInfo> map_info = maps_->Find(pc);
+ if (map_info == nullptr) {
+ last_error_.code = unwindstack::ErrorCode::ERROR_INVALID_MAP;
+ return;
+ }
+
+ unwindstack::FrameData frame;
+ frame.num = i;
+ frame.rel_pc = pc;
+ frame.pc = pc;
+ frame.map_info = map_info;
+ unwindstack::Elf* elf = map_info->GetElf(process_memory_, arch_);
+ if (elf != nullptr) {
+ uint64_t relative_pc = elf->GetRelPc(pc, map_info.get());
+ uint64_t pc_adjustment = GetPcAdjustment(relative_pc, elf, arch_);
+ frame.rel_pc = relative_pc - pc_adjustment;
+ frame.pc = pc - pc_adjustment;
+ if (!resolve_names_ ||
+ !elf->GetFunctionName(frame.rel_pc, &frame.function_name,
+ &frame.function_offset)) {
+ frame.function_name = "";
+ frame.function_offset = 0;
+ }
+ }
+ frames_.push_back(frame);
+ // move to the next frame
+ fp = DecodeFrame(fp, &pc, &sp);
+ }
+}
+
+uint64_t FramePointerUnwinder::DecodeFrame(uint64_t fp,
+ uint64_t* next_pc,
+ uint64_t* next_sp) {
+ uint64_t next_fp;
+ if (!process_memory_->ReadFully(static_cast<uint64_t>(fp), &next_fp,
+ sizeof(next_fp)))
+ return 0;
+
+ uint64_t pc;
+ if (!process_memory_->ReadFully(static_cast<uint64_t>(fp + sizeof(uint64_t)),
+ &pc, sizeof(pc)))
+ return 0;
+
+ // Ensure there's not a stack overflow.
+ if (__builtin_add_overflow(fp, sizeof(uint64_t) * 2, next_sp))
+ return 0;
+
+ *next_pc = static_cast<uint64_t>(pc);
+ return next_fp;
+}
+
+bool FramePointerUnwinder::IsFrameValid(uint64_t fp, uint64_t sp) {
+ uint64_t align_mask = 0;
+ switch (arch_) {
+ case unwindstack::ARCH_ARM64:
+ align_mask = 0x1;
+ break;
+ case unwindstack::ARCH_X86_64:
+ align_mask = 0xf;
+ break;
+ case unwindstack::ARCH_RISCV64:
+ align_mask = 0x7;
+ break;
+ case unwindstack::ARCH_UNKNOWN:
+ case unwindstack::ARCH_ARM:
+ case unwindstack::ARCH_X86:
+ // not supported
+ ;
+ }
+
+ if (fp == 0 || fp <= sp)
+ return false;
+
+ // Ensure there's space on the stack to read two values: the caller's
+ // frame pointer and the return address.
+ uint64_t result;
+ if (__builtin_add_overflow(fp, sizeof(uint64_t) * 2, &result))
+ return false;
+
+ return result <= stack_end_ && (fp & align_mask) == 0;
+}
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/frame_pointer_unwinder.h b/src/profiling/perf/frame_pointer_unwinder.h
new file mode 100644
index 0000000..14534d9
--- /dev/null
+++ b/src/profiling/perf/frame_pointer_unwinder.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+#ifndef SRC_PROFILING_PERF_FRAME_POINTER_UNWINDER_H_
+#define SRC_PROFILING_PERF_FRAME_POINTER_UNWINDER_H_
+
+#include <stdint.h>
+#include <memory>
+#include <vector>
+
+#include <unwindstack/Error.h>
+#include <unwindstack/MachineArm64.h>
+#include <unwindstack/MachineRiscv64.h>
+#include <unwindstack/MachineX86_64.h>
+#include <unwindstack/Unwinder.h>
+
+namespace perfetto {
+namespace profiling {
+
+class FramePointerUnwinder {
+ public:
+ FramePointerUnwinder(size_t max_frames,
+ unwindstack::Maps* maps,
+ unwindstack::Regs* regs,
+ std::shared_ptr<unwindstack::Memory> process_memory,
+ size_t stack_size)
+ : max_frames_(max_frames),
+ maps_(maps),
+ regs_(regs),
+ process_memory_(process_memory),
+ stack_size_(stack_size),
+ arch_(regs->Arch()) {
+ stack_end_ = regs->sp() + stack_size;
+ }
+
+ FramePointerUnwinder(const FramePointerUnwinder&) = delete;
+ FramePointerUnwinder& operator=(const FramePointerUnwinder&) = delete;
+
+ void Unwind();
+
+ // Disabling the resolving of names results in the function name being
+ // set to an empty string and the function offset being set to zero.
+ void SetResolveNames(bool resolve) { resolve_names_ = resolve; }
+
+ unwindstack::ErrorCode LastErrorCode() const { return last_error_.code; }
+ uint64_t warnings() const { return warnings_; }
+
+ std::vector<unwindstack::FrameData> ConsumeFrames() {
+ std::vector<unwindstack::FrameData> frames = std::move(frames_);
+ frames_.clear();
+ return frames;
+ }
+
+ bool IsArchSupported() const {
+ return arch_ == unwindstack::ARCH_ARM64 ||
+ arch_ == unwindstack::ARCH_X86_64;
+ }
+
+ void ClearErrors() {
+ warnings_ = unwindstack::WARNING_NONE;
+ last_error_.code = unwindstack::ERROR_NONE;
+ last_error_.address = 0;
+ }
+
+ protected:
+ const size_t max_frames_;
+ unwindstack::Maps* maps_;
+ unwindstack::Regs* regs_;
+ std::vector<unwindstack::FrameData> frames_;
+ std::shared_ptr<unwindstack::Memory> process_memory_;
+ const size_t stack_size_;
+ unwindstack::ArchEnum arch_ = unwindstack::ARCH_UNKNOWN;
+ bool resolve_names_ = false;
+ size_t stack_end_;
+
+ unwindstack::ErrorData last_error_;
+ uint64_t warnings_ = 0;
+
+ private:
+ void TryUnwind();
+ // Given a frame pointer, returns the frame pointer of the calling stack
+ // frame, places the return address of the calling stack frame into
+ // `ret_addr` and stack pointer into `sp`.
+ uint64_t DecodeFrame(uint64_t fp, uint64_t* ret_addr, uint64_t* sp);
+ bool IsFrameValid(uint64_t fp, uint64_t sp);
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_PERF_FRAME_POINTER_UNWINDER_H_
diff --git a/src/profiling/perf/frame_pointer_unwinder_unittest.cc b/src/profiling/perf/frame_pointer_unwinder_unittest.cc
new file mode 100644
index 0000000..c494fd7
--- /dev/null
+++ b/src/profiling/perf/frame_pointer_unwinder_unittest.cc
@@ -0,0 +1,229 @@
+/*
+ * 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 "src/profiling/perf/frame_pointer_unwinder.h"
+
+#include <sys/mman.h>
+#include <unwindstack/Unwinder.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+class RegsFake : public unwindstack::Regs {
+ public:
+ RegsFake(uint16_t total_regs)
+ : unwindstack::Regs(
+ total_regs,
+ unwindstack::Regs::Location(unwindstack::Regs::LOCATION_UNKNOWN,
+ 0)) {
+ fake_data_ = std::make_unique<uint64_t[]>(total_regs);
+ }
+ ~RegsFake() override = default;
+
+ unwindstack::ArchEnum Arch() override { return fake_arch_; }
+ void* RawData() override { return fake_data_.get(); }
+ uint64_t pc() override { return fake_pc_; }
+ uint64_t sp() override { return fake_sp_; }
+ void set_pc(uint64_t pc) override { fake_pc_ = pc; }
+ void set_sp(uint64_t sp) override { fake_sp_ = sp; }
+
+ void set_fp(uint64_t fp) {
+ switch (fake_arch_) {
+ case unwindstack::ARCH_ARM64:
+ fake_data_[unwindstack::Arm64Reg::ARM64_REG_R29] = fp;
+ break;
+ case unwindstack::ARCH_X86_64:
+ fake_data_[unwindstack::X86_64Reg::X86_64_REG_RBP] = fp;
+ break;
+ case unwindstack::ARCH_RISCV64:
+ fake_data_[unwindstack::Riscv64Reg::RISCV64_REG_S0] = fp;
+ break;
+ case unwindstack::ARCH_UNKNOWN:
+ case unwindstack::ARCH_ARM:
+ case unwindstack::ARCH_X86:
+ // not supported
+ ;
+ }
+ }
+
+ bool SetPcFromReturnAddress(unwindstack::Memory*) override { return false; }
+
+ void IterateRegisters(std::function<void(const char*, uint64_t)>) override {}
+
+ bool StepIfSignalHandler(uint64_t,
+ unwindstack::Elf*,
+ unwindstack::Memory*) override {
+ return false;
+ }
+
+ void FakeSetArch(unwindstack::ArchEnum arch) { fake_arch_ = arch; }
+
+ Regs* Clone() override { return nullptr; }
+
+ private:
+ unwindstack::ArchEnum fake_arch_ = unwindstack::ARCH_UNKNOWN;
+ uint64_t fake_pc_ = 0;
+ uint64_t fake_sp_ = 0;
+ std::unique_ptr<uint64_t[]> fake_data_;
+};
+
+class MemoryFake : public unwindstack::Memory {
+ public:
+ MemoryFake() = default;
+ ~MemoryFake() override = default;
+
+ size_t Read(uint64_t addr, void* memory, size_t size) override {
+ uint8_t* dst = reinterpret_cast<uint8_t*>(memory);
+ for (size_t i = 0; i < size; i++, addr++) {
+ auto value = data_.find(addr);
+ if (value == data_.end()) {
+ return i;
+ }
+ dst[i] = value->second;
+ }
+ return size;
+ }
+
+ void SetMemory(uint64_t addr, const void* memory, size_t length) {
+ const uint8_t* src = reinterpret_cast<const uint8_t*>(memory);
+ for (size_t i = 0; i < length; i++, addr++) {
+ auto value = data_.find(addr);
+ if (value != data_.end()) {
+ value->second = src[i];
+ } else {
+ data_.insert({addr, src[i]});
+ }
+ }
+ }
+
+ void SetData8(uint64_t addr, uint8_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetData16(uint64_t addr, uint16_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetData32(uint64_t addr, uint32_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetData64(uint64_t addr, uint64_t value) {
+ SetMemory(addr, &value, sizeof(value));
+ }
+
+ void SetMemory(uint64_t addr, std::vector<uint8_t> values) {
+ SetMemory(addr, values.data(), values.size());
+ }
+
+ void SetMemory(uint64_t addr, std::string string) {
+ SetMemory(addr, string.c_str(), string.size() + 1);
+ }
+
+ void Clear() override { data_.clear(); }
+
+ private:
+ std::unordered_map<uint64_t, uint8_t> data_;
+};
+
+constexpr static uint64_t kMaxFrames = 64;
+constexpr static uint64_t kStackSize = 0xFFFFFFF;
+
+class FramePointerUnwinderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memory_fake_ = new MemoryFake;
+ maps_.reset(new unwindstack::Maps);
+ regs_fake_ = std::make_unique<RegsFake>(64);
+ regs_fake_->FakeSetArch(unwindstack::ARCH_X86_64);
+ process_memory_.reset(memory_fake_);
+
+ unwinder_ = std::make_unique<FramePointerUnwinder>(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize);
+ }
+
+ MemoryFake* memory_fake_;
+ std::unique_ptr<unwindstack::Maps> maps_;
+ std::unique_ptr<RegsFake> regs_fake_;
+ std::shared_ptr<unwindstack::Memory> process_memory_;
+
+ std::unique_ptr<FramePointerUnwinder> unwinder_;
+};
+
+TEST_F(FramePointerUnwinderTest, UnwindUnsupportedArch) {
+ regs_fake_->FakeSetArch(unwindstack::ARCH_UNKNOWN);
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_UNSUPPORTED);
+
+ regs_fake_->FakeSetArch(unwindstack::ARCH_X86);
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_UNSUPPORTED);
+
+ regs_fake_->FakeSetArch(unwindstack::ARCH_ARM);
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_UNSUPPORTED);
+}
+
+TEST_F(FramePointerUnwinderTest, UnwindInvalidMaps) {
+ // Set up a valid stack frame
+ regs_fake_->set_pc(0x1000);
+ regs_fake_->set_sp(0x2000);
+ memory_fake_->SetData64(0x2000, 0x3000);
+ memory_fake_->SetData64(0x2008, 0x2000);
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(),
+ unwindstack::ErrorCode::ERROR_INVALID_MAP);
+ EXPECT_EQ(unwinder_->ConsumeFrames().size(), 0UL);
+}
+
+TEST_F(FramePointerUnwinderTest, UnwindValidStack) {
+ regs_fake_->set_pc(0x1900);
+ regs_fake_->set_sp(0x1800);
+ regs_fake_->set_fp(0x2000);
+
+ memory_fake_->SetData64(0x2000, 0x2200); // mock next_fp
+ memory_fake_->SetData64(0x2000 + sizeof(uint64_t),
+ 0x2100); // mock return_address(next_pc)
+
+ memory_fake_->SetData64(0x2200, 0);
+
+ maps_->Add(0x1000, 0x12000, 0, PROT_READ | PROT_WRITE, "libmock.so");
+
+ unwinder_.reset(new FramePointerUnwinder(
+ kMaxFrames, maps_.get(), regs_fake_.get(), process_memory_, kStackSize));
+ unwinder_->Unwind();
+ EXPECT_EQ(unwinder_->LastErrorCode(), unwindstack::ErrorCode::ERROR_NONE);
+ EXPECT_EQ(unwinder_->ConsumeFrames().size(), 2UL);
+}
+
+} // namespace
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 492907e..86d7b15 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -500,8 +500,12 @@
// Inform unwinder of the new data source instance, and optionally start a
// periodic task to clear its cached state.
- unwinding_worker_->PostStartDataSource(ds_id,
- ds.event_config.kernel_frames());
+ auto unwind_mode = (ds.event_config.unwind_mode() ==
+ protos::gen::PerfEventConfig::UNWIND_FRAME_POINTER)
+ ? Unwinder::UnwindMode::kFramePointer
+ : Unwinder::UnwindMode::kUnwindStack;
+ unwinding_worker_->PostStartDataSource(ds_id, ds.event_config.kernel_frames(),
+ unwind_mode);
if (ds.event_config.unwind_state_clear_period_ms()) {
unwinding_worker_->PostClearCachedStatePeriodic(
ds_id, ds.event_config.unwind_state_clear_period_ms());
diff --git a/src/profiling/perf/unwinding.cc b/src/profiling/perf/unwinding.cc
index 53023a8..7ead30f 100644
--- a/src/profiling/perf/unwinding.cc
+++ b/src/profiling/perf/unwinding.cc
@@ -25,6 +25,7 @@
#include "perfetto/ext/base/no_destructor.h"
#include "perfetto/ext/base/thread_utils.h"
#include "perfetto/ext/base/utils.h"
+#include "src/profiling/perf/frame_pointer_unwinder.h"
namespace {
constexpr size_t kUnwindingMaxFrames = 1000;
@@ -43,18 +44,23 @@
}
void Unwinder::PostStartDataSource(DataSourceInstanceID ds_id,
- bool kernel_frames) {
+ bool kernel_frames,
+ UnwindMode unwind_mode) {
// No need for a weak pointer as the associated task runner quits (stops
// running tasks) strictly before the Unwinder's destruction.
- task_runner_->PostTask(
- [this, ds_id, kernel_frames] { StartDataSource(ds_id, kernel_frames); });
+ task_runner_->PostTask([this, ds_id, kernel_frames, unwind_mode] {
+ StartDataSource(ds_id, kernel_frames, unwind_mode);
+ });
}
-void Unwinder::StartDataSource(DataSourceInstanceID ds_id, bool kernel_frames) {
+void Unwinder::StartDataSource(DataSourceInstanceID ds_id,
+ bool kernel_frames,
+ UnwindMode unwind_mode) {
PERFETTO_DCHECK_THREAD(thread_checker_);
PERFETTO_DLOG("Unwinder::StartDataSource(%zu)", static_cast<size_t>(ds_id));
- auto it_and_inserted = data_sources_.emplace(ds_id, DataSourceState{});
+ auto it_and_inserted =
+ data_sources_.emplace(ds_id, DataSourceState{unwind_mode});
PERFETTO_DCHECK(it_and_inserted.second);
if (kernel_frames) {
@@ -297,8 +303,9 @@
(proc_state.unwind_state.has_value()
? &proc_state.unwind_state.value()
: nullptr);
- CompletedSample unwound_sample = UnwindSample(
- entry.sample, opt_user_state, proc_state.attempted_unwinding);
+ CompletedSample unwound_sample =
+ UnwindSample(entry.sample, opt_user_state,
+ proc_state.attempted_unwinding, ds.unwind_mode);
proc_state.attempted_unwinding = true;
PERFETTO_METATRACE_COUNTER(TAG_PRODUCER, PROFILER_UNWIND_CURRENT_PID, 0);
@@ -334,7 +341,8 @@
CompletedSample Unwinder::UnwindSample(const ParsedSample& sample,
UnwindingMetadata* opt_user_state,
- bool pid_unwound_before) {
+ bool pid_unwound_before,
+ UnwindMode unwind_mode) {
PERFETTO_DCHECK_THREAD(thread_checker_);
CompletedSample ret;
@@ -375,7 +383,7 @@
UnwindResult& operator=(UnwindResult&&) = default;
};
auto attempt_unwind = [&sample, unwind_state, pid_unwound_before,
- &overlay_memory]() -> UnwindResult {
+ &overlay_memory, unwind_mode]() -> UnwindResult {
metatrace::ScopedEvent m(metatrace::TAG_PRODUCER,
pid_unwound_before
? metatrace::PROFILER_UNWIND_ATTEMPT
@@ -384,16 +392,29 @@
// Unwindstack clobbers registers, so make a copy in case of retries.
auto regs_copy = std::unique_ptr<unwindstack::Regs>{sample.regs->Clone()};
- unwindstack::Unwinder unwinder(kUnwindingMaxFrames, &unwind_state->fd_maps,
- regs_copy.get(), overlay_memory);
+ switch (unwind_mode) {
+ case UnwindMode::kFramePointer: {
+ FramePointerUnwinder unwinder(kUnwindingMaxFrames,
+ &unwind_state->fd_maps, regs_copy.get(),
+ overlay_memory, sample.stack.size());
+ unwinder.Unwind();
+ return {unwinder.LastErrorCode(), unwinder.warnings(),
+ unwinder.ConsumeFrames()};
+ }
+ case UnwindMode::kUnwindStack: {
+ unwindstack::Unwinder unwinder(kUnwindingMaxFrames,
+ &unwind_state->fd_maps, regs_copy.get(),
+ overlay_memory);
#if PERFETTO_BUILDFLAG(PERFETTO_ANDROID_BUILD)
- unwinder.SetJitDebug(unwind_state->GetJitDebug(regs_copy->Arch()));
- unwinder.SetDexFiles(unwind_state->GetDexFiles(regs_copy->Arch()));
+ unwinder.SetJitDebug(unwind_state->GetJitDebug(regs_copy->Arch()));
+ unwinder.SetDexFiles(unwind_state->GetDexFiles(regs_copy->Arch()));
#endif
- unwinder.Unwind(/*initial_map_names_to_skip=*/nullptr,
- /*map_suffixes_to_ignore=*/nullptr);
- return {unwinder.LastErrorCode(), unwinder.warnings(),
- unwinder.ConsumeFrames()};
+ unwinder.Unwind(/*initial_map_names_to_skip=*/nullptr,
+ /*map_suffixes_to_ignore=*/nullptr);
+ return {unwinder.LastErrorCode(), unwinder.warnings(),
+ unwinder.ConsumeFrames()};
+ }
+ }
};
// first unwind attempt
diff --git a/src/profiling/perf/unwinding.h b/src/profiling/perf/unwinding.h
index 8295fb8..13a7d6d 100644
--- a/src/profiling/perf/unwinding.h
+++ b/src/profiling/perf/unwinding.h
@@ -75,6 +75,8 @@
public:
friend class UnwinderHandle;
+ enum class UnwindMode { kUnwindStack, kFramePointer };
+
// Callbacks from the unwinder to the primary producer thread.
class Delegate {
public:
@@ -89,7 +91,9 @@
~Unwinder() { PERFETTO_DCHECK_THREAD(thread_checker_); }
- void PostStartDataSource(DataSourceInstanceID ds_id, bool kernel_frames);
+ void PostStartDataSource(DataSourceInstanceID ds_id,
+ bool kernel_frames,
+ UnwindMode unwind_mode);
void PostAdoptProcDescriptors(DataSourceInstanceID ds_id,
pid_t pid,
base::ScopedFile maps_fd,
@@ -144,8 +148,11 @@
struct DataSourceState {
enum class Status { kActive, kShuttingDown };
+ explicit DataSourceState(UnwindMode _unwind_mode)
+ : unwind_mode(_unwind_mode) {}
Status status = Status::kActive;
+ const UnwindMode unwind_mode;
std::map<pid_t, ProcessState> process_states;
};
@@ -163,7 +170,9 @@
// Marks the data source as valid and active at the unwinding stage.
// Initializes kernel address symbolization if needed.
- void StartDataSource(DataSourceInstanceID ds_id, bool kernel_frames);
+ void StartDataSource(DataSourceInstanceID ds_id,
+ bool kernel_frames,
+ UnwindMode unwind_mode);
void AdoptProcDescriptors(DataSourceInstanceID ds_id,
pid_t pid,
@@ -184,7 +193,8 @@
CompletedSample UnwindSample(const ParsedSample& sample,
UnwindingMetadata* opt_user_state,
- bool pid_unwound_before);
+ bool pid_unwound_before,
+ UnwindMode unwind_mode);
// Returns a list of symbolized kernel frames in the sample (if any).
std::vector<unwindstack::FrameData> SymbolizeKernelCallchain(