blob: dbb9c1b8d7232a333d1afc60f75a2e289ac2df4d [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_x64.h"
#include <lib/stdcompat/vector.h>
#include <memory>
#include "src/developer/debug/shared/register_id.h"
#include "src/developer/debug/shared/register_info.h"
#include "src/developer/debug/zxdb/client/finish_thread_controller.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/common/join_callbacks.h"
namespace zxdb {
namespace {
constexpr uint64_t kRedZoneSize = 128ull;
} // namespace
CallFunctionThreadControllerX64::CallFunctionThreadControllerX64(
const AddressRanges& range, const std::vector<ExprValue>& parameters,
EvalCallback on_function_completed, fit::deferred_callback on_done)
: CallFunctionThreadController(range, parameters, std::move(on_function_completed),
std::move(on_done)),
weak_factory_(this) {}
CallFunctionThreadControllerX64::~CallFunctionThreadControllerX64() = default;
void CallFunctionThreadControllerX64::InitWithThread(Thread* thread,
fit::callback<void(const Err&)> cb) {
SetThread(thread);
Log("CallFunctionThreadControllerX64::InitWithThread");
CollectAllRegisterCategories(thread, [weak_this = weak_factory_.GetWeakPtr(),
weak_thread = thread->GetWeakPtr(),
cb = std::move(cb)](const Err& err) mutable {
if (!weak_this) {
cb(Err("CallFunctionThreadControllerX64 disappeared."));
} else if (!weak_thread) {
return cb(Err("thread disappeared."));
}
// This will be the new PC of the stack frame we create.
uint64_t function_start_addr = weak_this->address_ranges_.begin()->begin();
uint64_t sp = GetRegisterData(weak_this->general_registers_, debug::RegisterID::kX64_rsp);
// Guarantee that SP is aligned.
sp &= ~0xf;
// Build and push a new stack frame to this thread's stack. Note at this point
// this is only local to zxdb and the target has no idea about this frame
// until we write the stack pointer register below.
Frame* frame =
weak_this->PushStackFrame(function_start_addr, sp, weak_this->general_registers_);
if (!frame) {
return cb(Err("Failed to create new stack frame."));
}
weak_this->Log("Pushed new stack frame pc = 0x%x sp = 0x%x", function_start_addr,
sp - kRedZoneSize);
// X64 expects the return address on the stack, so do that first.
sp -= 8;
uint64_t rip = GetRegisterData(weak_this->general_registers_, debug::RegisterID::kX64_rip);
std::vector<uint8_t> data;
data.resize(sizeof(rip));
memcpy(data.data(), &rip, sizeof(rip));
weak_thread->GetProcess()->WriteMemory(
sp, std::move(data),
[weak_this, weak_thread, sp, function_start_addr, cb = std::move(cb)](const Err&) mutable {
if (!weak_this) {
return cb(Err("CallFunctionThreadControllerX64 disappeared."));
}
if (!weak_this->WriteRegister(weak_this->general_registers_, debug::RegisterID::kX64_rip,
function_start_addr)) {
return cb(Err("Failed to find RIP in the register set"));
}
if (!weak_this->WriteRegister(weak_this->general_registers_, debug::RegisterID::kX64_rsp,
sp)) {
return cb(Err("Failed to find RSP in the register set"));
}
// Load all the parameters to registers, and then actually do the bulk register write.
// Currently parameters are only supported when passed via registers.
weak_this->WriteParametersToRegisters();
return weak_this->WriteGeneralRegisters(std::move(cb));
});
});
}
void CallFunctionThreadControllerX64::CollectAllRegisterCategories(
Thread* thread, fit::callback<void(const Err& err)> cb) {
auto joiner = fxl::MakeRefCounted<JoinCallbacks<RegisterCollection>>();
for (auto category : {debug::RegisterCategory::kGeneral, debug::RegisterCategory::kFloatingPoint,
debug::RegisterCategory::kVector}) {
thread->GetStack()[0]->GetRegisterCategoryAsync(
category, true,
[category, cb = joiner->AddCallback()](
const Err& err, const std::vector<debug::RegisterValue>& regs) mutable {
cb(RegisterCollection(err, category, regs));
});
}
joiner->Ready([weak_this = weak_factory_.GetWeakPtr(),
cb = std::move(cb)](const std::vector<RegisterCollection>& results) mutable {
for (auto result : results) {
if (result.err.has_error()) {
return cb(result.err);
}
// Remove non-writeable registers from the register set.
cpp20::erase_if(result.registers, [](const debug::RegisterValue& rv) {
return rv.id == debug::RegisterID::kX64_fsbase || rv.id == debug::RegisterID::kX64_gsbase;
});
weak_this->SetRegisterCategory(result.category, result.registers);
if (cb)
cb(Err());
}
});
}
Frame* CallFunctionThreadControllerX64::PushStackFrame(
uint64_t new_pc, uint64_t old_sp, const std::vector<debug::RegisterValue>& regs) {
debug_ipc::StackFrame synthesized;
synthesized.sp = old_sp - kRedZoneSize; // the new stack pointer.
synthesized.cfa = old_sp; // for the frame fingerprint.
synthesized.ip = new_pc; // this is the start of the function we're about to call.
synthesized.regs = regs;
auto& stack = thread()->GetStack();
const Frame* original_frame = stack[0];
debug_ipc::StackFrame original;
original.sp = original_frame->GetStackPointer();
original.cfa = original_frame->GetCanonicalFrameAddress();
original.ip = original_frame->GetAddress();
original.regs = *original_frame->GetRegisterCategorySync(debug::RegisterCategory::kGeneral);
stack.SetFrames(debug_ipc::ThreadRecord::StackAmount::kMinimal, {synthesized, original});
return stack[0];
}
} // namespace zxdb