| // Copyright 2018 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 "src/developer/debug/zxdb/client/frame_impl.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "src/developer/debug/shared/message_loop.h" |
| #include "src/developer/debug/shared/zx_status.h" |
| #include "src/developer/debug/zxdb/client/client_eval_context_impl.h" |
| #include "src/developer/debug/zxdb/client/frame_symbol_data_provider.h" |
| #include "src/developer/debug/zxdb/client/process_impl.h" |
| #include "src/developer/debug/zxdb/client/remote_api.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/client/setting_schema_definition.h" |
| #include "src/developer/debug/zxdb/client/thread_impl.h" |
| #include "src/developer/debug/zxdb/symbols/dwarf_expr_eval.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/input_location.h" |
| #include "src/developer/debug/zxdb/symbols/symbol.h" |
| #include "src/developer/debug/zxdb/symbols/variable_location.h" |
| |
| namespace zxdb { |
| |
| using debug::RegisterCategory; |
| |
| FrameImpl::FrameImpl(Thread* thread, const debug_ipc::StackFrame& stack_frame, Location location) |
| : Frame(thread->session()), |
| thread_(thread), |
| sp_(stack_frame.sp), |
| cfa_(stack_frame.cfa), |
| location_(std::move(location)), |
| weak_factory_(this) { |
| registers_[static_cast<size_t>(RegisterCategory::kGeneral)] = stack_frame.regs; |
| } |
| |
| FrameImpl::~FrameImpl() = default; |
| |
| Thread* FrameImpl::GetThread() const { return thread_; } |
| |
| bool FrameImpl::IsInline() const { return false; } |
| |
| const Frame* FrameImpl::GetPhysicalFrame() const { return this; } |
| |
| const Location& FrameImpl::GetLocation() const { |
| EnsureSymbolized(); |
| return location_; |
| } |
| |
| uint64_t FrameImpl::GetAddress() const { return location_.address(); } |
| |
| const std::vector<debug::RegisterValue>* FrameImpl::GetRegisterCategorySync( |
| RegisterCategory category) const { |
| FX_DCHECK(category <= RegisterCategory::kLast); |
| |
| size_t category_index = static_cast<size_t>(category); |
| FX_DCHECK(category_index < static_cast<size_t>(RegisterCategory::kLast)); |
| |
| if (registers_[category_index]) |
| return &*registers_[category_index]; |
| return nullptr; |
| } |
| |
| void FrameImpl::GetRegisterCategoryAsync( |
| RegisterCategory category, bool always_request, |
| fit::function<void(const Err&, const std::vector<debug::RegisterValue>&)> cb) { |
| FX_DCHECK(category < RegisterCategory::kLast && category != RegisterCategory::kNone); |
| |
| size_t category_index = static_cast<size_t>(category); |
| FX_DCHECK(category_index < static_cast<size_t>(RegisterCategory::kLast)); |
| |
| if (!always_request && registers_[category_index]) { |
| // Registers known already, asynchronously return the result. |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [cb = std::move(cb), weak_frame = weak_factory_.GetWeakPtr(), category_index]() { |
| if (weak_frame) |
| cb(Err(), *weak_frame->registers_[category_index]); |
| else |
| cb(Err("Frame destroyed before registers could be retrieved."), {}); |
| }); |
| return; |
| } |
| |
| // The CPU registers will always refer to the top physical frame so don't fetch them otherwise. |
| if (!IsInTopmostPhysicalFrame()) { |
| debug::MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb)]() { |
| cb(Err("This type of register is unavailable in non-topmost stack frames."), {}); |
| }); |
| return; |
| } |
| |
| debug_ipc::ReadRegistersRequest request; |
| request.id = {.process = thread_->GetProcess()->GetKoid(), .thread = thread_->GetKoid()}; |
| request.categories.push_back(category); |
| |
| session()->remote_api()->ReadRegisters( |
| request, [weak_frame = weak_factory_.GetWeakPtr(), category, cb = std::move(cb)]( |
| const Err& err, debug_ipc::ReadRegistersReply reply) mutable { |
| if (!weak_frame) |
| return cb(Err("Frame destroyed before registers could be retrieved."), {}); |
| |
| weak_frame->registers_[static_cast<size_t>(category)] = reply.registers; |
| cb(Err(), std::move(reply.registers)); |
| }); |
| return; |
| } |
| |
| void FrameImpl::WriteRegister(debug::RegisterID id, std::vector<uint8_t> data, |
| fit::callback<void(const Err&)> cb) { |
| const debug::RegisterInfo* info = debug::InfoForRegister(id); |
| FX_DCHECK(info); // Should always be a valid register. |
| FX_DCHECK(info->canonical_id == id); // Should only write full canonical registers. |
| |
| if (!IsInTopmostPhysicalFrame()) { |
| debug::MessageLoop::Current()->PostTask(FROM_HERE, [id, cb = std::move(cb)]() mutable { |
| cb(Err("Register %s can't be written when the frame is not the topmost.", |
| debug::RegisterIDToString(id))); |
| }); |
| return; |
| } |
| |
| debug_ipc::WriteRegistersRequest request; |
| request.id = {.process = thread_->GetProcess()->GetKoid(), .thread = thread_->GetKoid()}; |
| request.registers.emplace_back(id, data); // Don't move, used below. |
| |
| session()->remote_api()->WriteRegisters( |
| request, [weak_frame = weak_factory_.GetWeakPtr(), data, cb = std::move(cb)]( |
| const Err& err, debug_ipc::WriteRegistersReply reply) mutable { |
| if (err.has_error()) |
| return cb(err); // Transport error. |
| |
| if (reply.status.has_error()) { |
| // Agent error. |
| return cb(Err("Error writing register: " + reply.status.message())); |
| } |
| |
| if (weak_frame) |
| weak_frame->SaveRegisterUpdates(std::move(reply.registers)); |
| cb(Err()); |
| }); |
| } |
| |
| std::optional<uint64_t> FrameImpl::GetBasePointer() const { |
| // This function is logically const even though EnsureBasePointer does some potentially mutating |
| // things underneath (calling callbacks and such). |
| if (const_cast<FrameImpl*>(this)->EnsureBasePointer()) { |
| FX_DCHECK(computed_base_pointer_); |
| return computed_base_pointer_; |
| } |
| return std::nullopt; |
| } |
| |
| void FrameImpl::GetBasePointerAsync(fit::callback<void(uint64_t bp)> cb) { |
| if (EnsureBasePointer()) { |
| // BP available synchronously but we don't want to reenter the caller. |
| FX_DCHECK(computed_base_pointer_); |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [bp = *computed_base_pointer_, cb = std::move(cb)]() mutable { cb(bp); }); |
| } else { |
| // Add pending request for when evaluation is complete. |
| FX_DCHECK(base_pointer_eval_ && !base_pointer_eval_->is_complete()); |
| base_pointer_requests_.push_back(std::move(cb)); |
| } |
| } |
| |
| uint64_t FrameImpl::GetStackPointer() const { return sp_; } |
| |
| uint64_t FrameImpl::GetCanonicalFrameAddress() const { return cfa_; } |
| |
| bool FrameImpl::IsInTopmostPhysicalFrame() const { |
| const Stack& stack = GetThread()->GetStack(); |
| if (stack.empty()) |
| return false; |
| |
| // Search for the first physical frame, and return true if it or anything above it matches the |
| // current frame. |
| for (size_t i = 0; i < stack.size(); i++) { |
| if (stack[i] == this) |
| return true; |
| if (!stack[i]->IsInline()) |
| break; |
| } |
| return false; |
| } |
| |
| void FrameImpl::EnsureSymbolized() const { |
| if (location_.is_symbolized()) |
| return; |
| auto vect = |
| thread_->GetProcess()->GetSymbols()->ResolveInputLocation(InputLocation(location_.address())); |
| // Should always return 1 result for symbolizing addresses. |
| FX_DCHECK(vect.size() == 1); |
| location_ = std::move(vect[0]); |
| } |
| |
| fxl::RefPtr<SymbolDataProvider> FrameImpl::GetSymbolDataProvider() const { |
| if (!symbol_data_provider_) { |
| symbol_data_provider_ = |
| fxl::MakeRefCounted<FrameSymbolDataProvider>(const_cast<FrameImpl*>(this)->GetWeakPtr()); |
| } |
| return symbol_data_provider_; |
| } |
| |
| fxl::RefPtr<EvalContext> FrameImpl::GetEvalContext() const { |
| auto language_setting = |
| thread_->session()->system().settings().GetString(ClientSettings::System::kLanguage); |
| |
| std::optional<ExprLanguage> language = std::nullopt; |
| if (language_setting == ClientSettings::System::kLanguage_Rust) { |
| language = ExprLanguage::kRust; |
| } else if (language_setting == ClientSettings::System::kLanguage_Cpp) { |
| language = ExprLanguage::kC; |
| } else { |
| FX_DCHECK(language_setting == ClientSettings::System::kLanguage_Auto); |
| } |
| |
| if (!symbol_eval_context_) |
| symbol_eval_context_ = fxl::MakeRefCounted<ClientEvalContextImpl>(this, language); |
| return symbol_eval_context_; |
| } |
| |
| bool FrameImpl::IsAmbiguousInlineLocation() const { |
| // This object always represents physical frames which aren't ambiguous. |
| return false; |
| } |
| |
| bool FrameImpl::EnsureBasePointer() { |
| if (computed_base_pointer_) |
| return true; // Already have it available synchronously. |
| |
| if (base_pointer_eval_) { |
| // Already happening asynchronously. |
| FX_DCHECK(!base_pointer_eval_->is_complete()); |
| return false; |
| } |
| |
| const Location& loc = GetLocation(); |
| if (!loc.symbol()) { |
| // Unsymbolized. |
| computed_base_pointer_ = 0; |
| return true; |
| } |
| |
| const Function* function = loc.symbol().Get()->As<Function>(); |
| const DwarfExpr* location_expr = nullptr; |
| if (!function || |
| !(location_expr = function->frame_base().ExprForIP(loc.symbol_context(), GetAddress()))) { |
| // No frame base declared for this function. |
| computed_base_pointer_ = 0; |
| return true; |
| } |
| |
| // Try to evaluate the location. |
| base_pointer_eval_ = std::make_unique<DwarfExprEval>(); |
| |
| // Callback when the expression is done. Will normally get called reentrantly by |
| // DwarfExprEval::Eval(). |
| // |
| // Binding |this| here is OK because the DwarfExprEval is owned by us and won't give callbacks |
| // after it's destroyed. |
| auto save_result = [this](DwarfExprEval* eval, const Err&) { |
| // We don't currently report errors for frame base requests, but instead just fall back on |
| // what was computed by the backend. |
| computed_base_pointer_ = 0; |
| if (eval->is_success() && eval->GetResultType() == DwarfExprEval::ResultType::kValue) { |
| DwarfStackEntry result = eval->GetResult(); |
| if (result.TreatAsUnsigned()) |
| computed_base_pointer_ = result.unsigned_value(); |
| } |
| |
| // Issue callbacks for everybody waiting. Moving to a local here prevents weirdness if a |
| // callback calls back into us, and also clears the vector. |
| std::vector<fit::callback<void(uint64_t)>> callbacks = std::move(base_pointer_requests_); |
| for (auto& cb : callbacks) |
| cb(*computed_base_pointer_); |
| }; |
| |
| auto eval_result = base_pointer_eval_->Eval(GetSymbolDataProvider(), loc.symbol_context(), |
| *location_expr, std::move(save_result)); |
| |
| // In the common case this will complete synchronously and the above callback will have put the |
| // result into base_pointer_requests_ before this code is executed. |
| return eval_result == DwarfExprEval::Completion::kSync; |
| } |
| |
| void FrameImpl::SaveRegisterUpdates(std::vector<debug::RegisterValue> regs) { |
| std::map<RegisterCategory, std::vector<debug::RegisterValue>> categorized; |
| for (auto& reg : regs) { |
| RegisterCategory cat = debug::RegisterIDToCategory(reg.id); |
| FX_DCHECK(cat != RegisterCategory::kNone); |
| categorized[cat].push_back(std::move(reg)); |
| } |
| |
| // This function replaces entire categories so we want to clear old registers as we go. |
| for (auto& [cat, update] : categorized) |
| registers_[static_cast<size_t>(cat)] = std::move(update); |
| } |
| |
| } // namespace zxdb |