| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "garnet/bin/zxdb/client/stack.h" |
| |
| #include <map> |
| |
| #include "garnet/bin/zxdb/client/frame.h" |
| #include "garnet/bin/zxdb/client/frame_fingerprint.h" |
| #include "garnet/bin/zxdb/common/err.h" |
| #include "garnet/bin/zxdb/expr/expr_eval_context.h" |
| #include "garnet/bin/zxdb/symbols/function.h" |
| #include "garnet/lib/debug_ipc/helper/message_loop.h" |
| #include "garnet/lib/debug_ipc/records.h" |
| #include "lib/fxl/logging.h" |
| #include "lib/fxl/macros.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // Implementation of Frame for inlined frames. Inlined frames have a different |
| // location in the source code, but refer to the underlying physical frame for |
| // most data. |
| class InlineFrame final : public Frame { |
| public: |
| // The physical_frame must outlive this class. Normally both are owned by the |
| // Stack and have the same lifetime. |
| InlineFrame(Frame* physical_frame, Location loc) |
| : Frame(physical_frame->session()), |
| physical_frame_(physical_frame), |
| location_(loc) {} |
| ~InlineFrame() override = default; |
| |
| // Frame implementation. |
| Thread* GetThread() const override { return physical_frame_->GetThread(); } |
| bool IsInline() const override { return true; } |
| const Frame* GetPhysicalFrame() const override { return physical_frame_; } |
| const Location& GetLocation() const override { return location_; } |
| uint64_t GetAddress() const override { return location_.address(); } |
| uint64_t GetBasePointerRegister() const override { |
| return physical_frame_->GetBasePointerRegister(); |
| } |
| std::optional<uint64_t> GetBasePointer() const override { |
| return physical_frame_->GetBasePointer(); |
| } |
| void GetBasePointerAsync(std::function<void(uint64_t bp)> cb) override { |
| return physical_frame_->GetBasePointerAsync(std::move(cb)); |
| } |
| uint64_t GetStackPointer() const override { |
| return physical_frame_->GetStackPointer(); |
| } |
| fxl::RefPtr<SymbolDataProvider> GetSymbolDataProvider() const override { |
| return physical_frame_->GetSymbolDataProvider(); |
| } |
| fxl::RefPtr<ExprEvalContext> GetExprEvalContext() const override { |
| return physical_frame_->GetExprEvalContext(); |
| } |
| |
| private: |
| Frame* physical_frame_; // Non-owning. |
| Location location_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(InlineFrame); |
| }; |
| |
| // Returns a fixed-up location referring to an indexed element in an inlined |
| // function call chain. This also handles the case where there are no inline |
| // calls and the function is the only one (this returns the same location). |
| // |
| // The main_location is the location returned by symbol lookup for the |
| // current address. |
| Location LocationForInlineFrameChain( |
| const std::vector<const Function*>& inline_chain, size_t chain_index, |
| const Location& main_location) { |
| // The file/line is the call location of the next (into the future) inlined |
| // function. Fall back on the file/line from the main lookup. |
| const FileLine* new_line = &main_location.file_line(); |
| int new_column = main_location.column(); |
| if (chain_index > 0) { |
| const Function* next_call = inline_chain[chain_index - 1]; |
| if (next_call->call_line().is_valid()) { |
| new_line = &next_call->call_line(); |
| new_column = 0; // DWARF doesn't contain inline call column. |
| } |
| } |
| |
| return Location(main_location.address(), *new_line, new_column, |
| main_location.symbol_context(), |
| LazySymbol(inline_chain[chain_index])); |
| } |
| |
| } // namespace |
| |
| Stack::Stack(Delegate* delegate) : delegate_(delegate), weak_factory_(this) {} |
| |
| Stack::~Stack() = default; |
| |
| fxl::WeakPtr<Stack> Stack::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } |
| |
| std::optional<size_t> Stack::IndexForFrame(const Frame* frame) const { |
| for (size_t i = 0; i < frames_.size(); i++) { |
| if (frames_[i].get() == frame) |
| return i; |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<FrameFingerprint> Stack::GetFrameFingerprint( |
| size_t virtual_frame_index) const { |
| size_t frame_index = virtual_frame_index + hide_top_inline_frame_count_; |
| |
| // Should reference a valid index in the array. |
| if (frame_index >= frames_.size()) { |
| FXL_NOTREACHED(); |
| return FrameFingerprint(); |
| } |
| |
| // Compute the inline frame count. This is the number of steps from the |
| // requested frame index to the current physical frame. |
| uint32_t inline_count = 0; |
| while (frame_index + inline_count < frames_.size() && |
| frames_[frame_index + inline_count]->IsInline()) |
| inline_count++; |
| |
| // The stack pointer we want is the one from right before the current |
| // physical frame (see frame_fingerprint.h). |
| size_t before_physical_frame_index = frame_index + inline_count + 1; |
| if (before_physical_frame_index == frames_.size()) { |
| if (!has_all_frames()) |
| return std::nullopt; // Not synchronously available. |
| |
| // For the bottom frame, this returns the frame base pointer instead which |
| // will at least identify the frame in some ways, and can be used to see if |
| // future frames are younger. |
| return FrameFingerprint(frames_[frame_index]->GetStackPointer(), 0); |
| } |
| |
| return FrameFingerprint( |
| frames_[before_physical_frame_index]->GetStackPointer(), inline_count); |
| } |
| |
| void Stack::GetFrameFingerprint( |
| size_t virtual_frame_index, |
| std::function<void(const Err&, size_t new_index, FrameFingerprint)> cb) { |
| size_t frame_index = virtual_frame_index + hide_top_inline_frame_count_; |
| FXL_DCHECK(frame_index < frames_.size()); |
| |
| // Identify the frame in question across the async call by its combination of |
| // IP, SP, and inline nesting count. |
| uint64_t ip = frames_[frame_index]->GetAddress(); |
| uint64_t sp = frames_[frame_index]->GetStackPointer(); |
| uint32_t inline_count = 0; |
| while (frame_index + inline_count < frames_.size() && |
| frames_[frame_index + inline_count]->IsInline()) |
| inline_count++; |
| |
| // This callback is issued when the full stack is available. |
| auto on_full_stack = |
| [ weak_stack = GetWeakPtr(), ip, sp, inline_count, |
| cb = std::move(cb) ](const Err& err) { |
| if (err.has_error()) { |
| cb(err, 0, FrameFingerprint()); |
| return; |
| } |
| if (!weak_stack) { |
| cb(Err("Thread destroyed."), 0, FrameFingerprint()); |
| return; |
| } |
| const auto& frames = weak_stack->frames_; |
| |
| // Re-find the first frame index with matching IP/SP. |
| bool found_frame = false; |
| int new_index = 0; |
| for (int i = static_cast<int>(frames.size()) - 1; i >= 0; i--) { |
| if (frames[i]->GetAddress() == ip && frames[i]->GetStackPointer() == sp) { |
| new_index = i; |
| found_frame = true; |
| break; |
| } |
| } |
| |
| if (found_frame && inline_count) { |
| // Check inline frames. |
| new_index -= inline_count; |
| if (new_index >= 0) { |
| // Inline frame still in range, it must have the same IP/SP. |
| if (frames[new_index]->GetAddress() != ip || |
| frames[new_index]->GetStackPointer() != sp) |
| found_frame = false; |
| } else { |
| found_frame = false; |
| } |
| } |
| if (!found_frame) { |
| cb(Err("Frame destroyed."), 0, FrameFingerprint()); |
| return; |
| } |
| |
| // Should always have a fingerprint after syncing the stack. |
| auto found_fingerprint = weak_stack->GetFrameFingerprint(new_index); |
| FXL_DCHECK(found_fingerprint); |
| cb(Err(), new_index, *found_fingerprint); |
| }; |
| |
| if (has_all_frames()) { |
| // All frames are available, don't force a recomputation of the stack. But |
| // the caller still expects an async response. |
| debug_ipc::MessageLoop::Current()->PostTask( |
| FROM_HERE, |
| [on_full_stack = std::move(on_full_stack)]() { on_full_stack(Err()); }); |
| } else { |
| SyncFrames(std::move(on_full_stack)); |
| } |
| } |
| |
| size_t Stack::GetTopInlineFrameCount() const { |
| for (size_t i = 0; i < frames_.size(); i++) { |
| if (!frames_[i]->IsInline()) |
| return i; |
| } |
| |
| // Should always have a non-inline frame if there are any. |
| FXL_DCHECK(frames_.empty()); |
| return 0; |
| } |
| |
| void Stack::SetHideTopInlineFrameCount(size_t hide_count) { |
| FXL_DCHECK(hide_count <= GetTopInlineFrameCount()); |
| hide_top_inline_frame_count_ = hide_count; |
| } |
| |
| void Stack::SyncFrames(std::function<void(const Err&)> callback) { |
| delegate_->SyncFramesForStack(std::move(callback)); |
| } |
| |
| void Stack::SetFrames(debug_ipc::ThreadRecord::StackAmount amount, |
| const std::vector<debug_ipc::StackFrame>& frames) { |
| frames_.clear(); |
| for (const debug_ipc::StackFrame& frame : frames) |
| AppendFrame(frame); |
| has_all_frames_ = amount == debug_ipc::ThreadRecord::StackAmount::kFull; |
| } |
| |
| void Stack::SetFramesForTest(std::vector<std::unique_ptr<Frame>> frames, |
| bool has_all) { |
| frames_ = std::move(frames); |
| has_all_frames_ = has_all; |
| } |
| |
| bool Stack::ClearFrames() { |
| has_all_frames_ = false; |
| |
| if (frames_.empty()) |
| return false; // Nothing to do. |
| |
| frames_.clear(); |
| return true; |
| } |
| |
| void Stack::AppendFrame(const debug_ipc::StackFrame& record) { |
| // This symbolizes all stack frames since the expansion of inline frames |
| // depends on the symbols. Its possible some stack objects will never have |
| // their frames queried which makes this duplicate work. A possible addition |
| // is to just save the debug_ipc::StackFrames and only expand the inline |
| // frames when the frame list is accessed. |
| |
| // The symbols will provide the location for the innermost inlined function. |
| Location inner_loc = delegate_->GetSymbolizedLocationForStackFrame(record); |
| |
| const Function* cur_func = inner_loc.symbol().Get()->AsFunction(); |
| if (!cur_func) { |
| // No function associated with this location. |
| frames_.push_back(delegate_->MakeFrameForStack(record, inner_loc)); |
| return; |
| } |
| |
| // The Location object will reference the most-specific inline function but |
| // we need the whole chain. |
| std::vector<const Function*> inline_chain = cur_func->GetInlineChain(); |
| if (inline_chain.back()->is_inline()) { |
| // A non-inline frame was not found. The symbols are corrupt so give up |
| // on inline processing and add the physical frame only. |
| frames_.push_back(delegate_->MakeFrameForStack(record, inner_loc)); |
| return; |
| } |
| |
| // Need to make the base "physical" frame first because all of the inline |
| // frames refer to it. |
| auto physical_frame = delegate_->MakeFrameForStack( |
| record, LocationForInlineFrameChain(inline_chain, inline_chain.size() - 1, |
| inner_loc)); |
| |
| // Add all inline functions (skipping the last which is the physical frame |
| // made above). |
| for (size_t i = 0; i < inline_chain.size() - 1; i++) { |
| frames_.push_back(std::make_unique<InlineFrame>( |
| physical_frame.get(), |
| LocationForInlineFrameChain(inline_chain, i, inner_loc))); |
| } |
| |
| // Physical frame goes last (back in time). |
| frames_.push_back(std::move(physical_frame)); |
| } |
| |
| } // namespace zxdb |