blob: 6da7d7aff1eef225712ee7044d4e048f6826a53d [file] [log] [blame]
// Copyright 2023 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/call_function_thread_controller.h"
#include <map>
#include <utility>
#include <vector>
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/shared/register_info.h"
#include "src/developer/debug/shared/register_value.h"
#include "src/developer/debug/zxdb/client/call_function_thread_controller_arm64.h"
#include "src/developer/debug/zxdb/client/call_function_thread_controller_x64.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/remote_api.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/step_thread_controller.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/client/thread_controller.h"
#include "src/developer/debug/zxdb/expr/return_value.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/function.h"
namespace zxdb {
CallFunctionThreadController::CallFunctionThreadController(const AddressRanges& ranges,
const std::vector<ExprValue>& parameters,
EvalCallback on_function_completed,
fit::deferred_callback on_done)
: ThreadController(std::move(on_done)),
address_ranges_(ranges),
parameters_(parameters),
on_function_completed_(std::move(on_function_completed)),
weak_factory_(this) {}
CallFunctionThreadController::~CallFunctionThreadController() {
if (on_function_completed_) {
on_function_completed_(
Err("CallFunctionThreadController destroyed before function call was completed."));
}
}
// static.
bool CallFunctionThreadController::WriteRegister(std::vector<debug::RegisterValue>& regs,
debug::RegisterID id, uint64_t value) {
auto reg_it = std::find_if(regs.begin(), regs.end(),
[id](const debug::RegisterValue& rv) { return rv.id == id; });
if (reg_it == regs.end())
return false;
*reg_it = debug::RegisterValue(id, value);
return true;
}
// static.
uint64_t CallFunctionThreadController::GetRegisterData(
const std::vector<debug::RegisterValue>& regs, debug::RegisterID id) {
auto reg_it = std::find_if(regs.begin(), regs.end(),
[id](const debug::RegisterValue& rv) { return rv.id == id; });
if (reg_it == regs.end())
return 0;
return reg_it->GetValue();
}
Err CallFunctionThreadController::WriteParametersToRegisters() {
auto arg_regs_opt = thread()->session()->arch_info().abi()->GetFunctionParameterRegisters();
if (!arg_regs_opt) {
return Err("Unknown ");
}
auto argument_registers = *arg_regs_opt;
if (parameters_.size() > argument_registers.size()) {
return Err("Only %zu arguments are supported functions on %s, but was given %zu",
argument_registers.size(), thread()->session()->arch_info().triple_name().c_str(),
parameters_.size());
}
for (size_t i = 0; i < parameters_.size(); i++) {
uint64_t promoted = 0;
if (parameters_[i].data().size() > sizeof(promoted)) {
return Err("Parameter %zu cannot fit in target register. Size is: %zu", i,
parameters_[i].data().size());
}
if (auto err = parameters_[i].PromoteTo64(&promoted); err.has_error()) {
return err;
}
if (!WriteRegister(general_registers_, argument_registers[i], promoted)) {
return Err("Failed writing register %s", debug::RegisterIDToString(argument_registers[i]));
}
}
return Err();
}
void CallFunctionThreadController::WriteGeneralRegisters(fit::callback<void(const Err&)> cb) {
debug_ipc::WriteRegistersRequest request;
request.id = {thread()->GetProcess()->GetKoid(), thread()->GetKoid()};
request.registers = general_registers_;
thread()->GetProcess()->session()->remote_api()->WriteRegisters(
request,
[weak_this = weak_factory_.GetWeakPtr(), weak_thread = thread()->GetWeakPtr(),
cb = std::move(cb)](const Err& err, const debug_ipc::WriteRegistersReply& reply) mutable {
if (err.has_error()) {
return cb(err);
} else if (reply.status.has_error()) {
return cb(Err(reply.status.message()));
} else if (!weak_thread) {
return cb(Err("Thread disappeared."));
} else if (!weak_this) {
return cb(Err("CallFunctionThreadController disappeared."));
}
weak_this->Log("Spawning finish thread controller to complete synthetic frame.");
auto& stack = weak_thread->GetStack();
weak_this->finish_controller_ = std::make_unique<FinishThreadController>(
stack, *stack.IndexForFrame(stack[0]), [weak_this](const FunctionReturnInfo& info) {
if (weak_this)
weak_this->return_info_ = info;
});
weak_this->finish_controller_->InitWithThread(weak_thread.get(), std::move(cb));
});
}
void CallFunctionThreadController::ResolveReturnValue(const FunctionReturnInfo& return_info,
EvalCallback cb) {
// If FunctionReturnCallback was never called or gave us bad info then bail early since we can
// never get the return value.
if (!return_info.thread || !return_info.symbol) {
return cb(Err("Frame exited with invalid return info"));
}
auto eval_context = return_info.thread->GetStack()[0]->GetEvalContext();
const Function* fn = return_info.symbol.Get()->As<Function>();
if (!fn) {
return cb(Err("Function symbol disappeared"));
}
GetReturnValue(eval_context, fn,
[weak_this = weak_factory_.GetWeakPtr(),
cb = std::move(cb)](ErrOrValue err_or_value) mutable {
if (!weak_this) {
return;
}
ExprValue value;
if (err_or_value.ok()) {
if (!err_or_value.value_or_empty().type()) {
// Void return.
auto voidtype =
fxl::MakeRefCounted<BaseType>(BaseType::kBaseTypeNone, 0, "");
value = ExprValue(std::move(voidtype), {});
} else {
value = err_or_value.take_value();
}
} else {
return cb(err_or_value.err());
}
return cb(std::move(value));
});
}
void CallFunctionThreadController::CleanupFunction(fit::callback<void(const Err&)> cb) {
Log("CallFunctionThreadController::CleanupFunction");
debug_ipc::WriteRegistersRequest request;
request.id = {.process = thread()->GetProcess()->GetKoid(), .thread = thread()->GetKoid()};
for (auto [cat, regs] : saved_register_state_) {
request.registers.insert(request.registers.end(), regs.begin(), regs.end());
}
thread()->session()->remote_api()->WriteRegisters(
request, [weak_this = weak_factory_.GetWeakPtr(), cb = std::move(cb)](
const Err& err, const debug_ipc::WriteRegistersReply& reply) mutable {
if (!weak_this) {
return cb(Err("CallFunctionThreadController disappeared."));
} else if (err.has_error()) {
weak_this->saved_register_state_.clear();
return cb(Err("Got error while restoring registers: " + err.msg()));
} else if (reply.status.has_error()) {
weak_this->saved_register_state_.clear();
return cb(Err("Got WriteRegistersReply status error: " + reply.status.message()));
}
// Force sync the registers to the top most frame, which should be the
// same as what we just restored. This ensures that if the user types
// "regs" after calling the function they should be identical to what
// they were before. Once this completes, we're done.
weak_this->thread()->GetStack()[0]->GetRegisterCategoryAsync(
debug::RegisterCategory::kGeneral, true,
[weak_this, cb = std::move(cb)](const Err& err,
const std::vector<debug::RegisterValue>& regs) mutable {
if (cb)
return cb(err);
});
});
}
ThreadController::ContinueOp CallFunctionThreadController::GetContinueOp() {
return finish_controller_->GetContinueOp();
}
ThreadController::StopOp CallFunctionThreadController::OnThreadStop(
debug_ipc::ExceptionType stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) {
Log("CallFunctionThreadController::OnThreadStop");
if (finish_controller_) {
auto op = finish_controller_->OnThreadStop(stop_type, hit_breakpoints);
if (op == kStopDone) {
// Okay the finish controller is done, now we need to cleanup the
// register state which will happen asynchronously. We should have already
// captured the return info from the FunctionReturnCallback provided to
// this controller, so now all we need to do is cleanup.
finish_controller_.reset();
// When the finish controller is finished, the synthetic frame that was
// created by the ABI instance of this class has been completed.
// First collect the return value, then cleanup the register state and
// report that we're finished.
ResolveReturnValue(return_info_, [stop_type, weak_this = weak_factory_.GetWeakPtr()](
ErrOrValue err_or_value) mutable {
if (weak_this) {
weak_this->CleanupFunction([err_or_value, stop_type, weak_this](const Err& err) mutable {
if (!weak_this)
return;
// An error restoring the thread state is more important to
// surface to the user than anything to do with the return
// value since this would leave the debugger in a weird state.
if (err.has_error())
err_or_value = err;
weak_this->on_function_completed_(std::move(err_or_value));
weak_this->thread()->ResumeFromAsyncThreadController(stop_type);
});
}
});
return kFuture;
}
// The finish controller is not finished yet forward that back up to the
// thread.
return op;
}
// We are only considered done once we've reported back the return value and
// have completed restoring the thread state.
return on_function_completed_ ? kFuture : kStopDone;
}
} // namespace zxdb