blob: a991ecac94b118af21c3789a656bc7c3dd9c98ea [file] [log] [blame]
// 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 "tools/fidlcat/lib/syscall_decoder.h"
#include <lib/syslog/cpp/macros.h>
#include <sys/time.h>
#include <zircon/types.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>
#include "src/developer/debug/zxdb/client/breakpoint.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/memory_dump.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/step_thread_controller.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/symbols/symbol.h"
#include "tools/fidlcat/lib/interception_workflow.h"
#include "tools/fidlcat/lib/type_decoder.h"
namespace fidlcat {
constexpr int kBitsPerByte = 8;
// Helper function to convert a vector of bytes to a T.
template <typename T>
T GetValueFromBytes(const std::vector<uint8_t>& bytes, size_t offset) {
T ret = 0;
for (size_t i = 0; (i < sizeof(ret)) && (offset < bytes.size()); i++) {
ret |= static_cast<uint64_t>(bytes[offset++]) << (i * kBitsPerByte);
}
return ret;
}
uint64_t GetRegisterValue(const std::vector<debug::RegisterValue>& general_registers,
const debug::RegisterID register_id) {
for (const auto& reg : general_registers) {
if (reg.id == register_id) {
return GetValueFromBytes<uint64_t>(reg.data, 0);
}
}
return 0;
}
void MemoryDumpToVector(const zxdb::MemoryDump& dump, std::vector<uint8_t>* output_vector) {
output_vector->reserve(dump.size());
for (const debug_ipc::MemoryBlock& block : dump.blocks()) {
FX_DCHECK(block.valid);
for (size_t offset = 0; offset < block.size; ++offset) {
output_vector->push_back(block.data[offset]);
}
}
}
SyscallDecoder::SyscallDecoder(SyscallDecoderDispatcher* dispatcher,
InterceptingThreadObserver* thread_observer, zxdb::Thread* thread,
const Syscall* syscall, int64_t timestamp)
: SyscallDecoderInterface(dispatcher, thread),
thread_observer_(thread_observer),
weak_thread_(thread->GetWeakPtr()),
syscall_(syscall),
timestamp_(timestamp) {
if (fidlcat_thread_ == nullptr) {
Process* fidlcat_process = dispatcher_->SearchProcess(thread->GetProcess()->GetKoid());
if (fidlcat_process == nullptr) {
fidlcat_process = dispatcher_->CreateProcess(thread->GetProcess()->GetName(),
thread->GetProcess()->GetKoid(),
thread->GetProcess()->GetWeakPtr());
}
fidlcat_thread_ = dispatcher_->CreateThread(thread->GetKoid(), fidlcat_process);
}
}
void SyscallDecoder::SyscallDecodingError() {
dispatcher_->SyscallDecodingError(fidlcat_thread_, syscall_, error_);
Destroy();
}
void SyscallDecoder::LoadMemory(uint64_t address, size_t size, std::vector<uint8_t>* destination) {
if (address == 0) {
// Null pointer => don't load anything.
return;
}
zxdb::Thread* thread = get_thread();
if (thread == nullptr) {
aborted_ = true;
Destroy();
}
++pending_request_count_;
thread->GetProcess()->ReadMemory(
address, size,
[this, address, size, destination](const zxdb::Err& err, zxdb::MemoryDump dump) {
--pending_request_count_;
if (aborted()) {
Destroy();
} else {
if (!err.ok()) {
Error(DecoderError::Type::kCantReadMemory)
<< "Can't load memory at " << address << ": " << err.msg();
} else if ((dump.size() != size) || !dump.AllValid()) {
Error(DecoderError::Type::kCantReadMemory)
<< "Can't load memory at " << address << ": not enough data";
} else {
MemoryDumpToVector(dump, destination);
}
if (input_arguments_loaded_) {
LoadOutputs();
} else {
LoadInputs();
}
}
});
}
void SyscallDecoder::LoadArgument(Stage stage, int argument_index, size_t size) {
if (decoded_arguments_[argument_index].loading(stage)) {
return;
}
decoded_arguments_[argument_index].set_loading(stage);
LoadMemory(ArgumentValue(argument_index), size,
&decoded_arguments_[argument_index].loaded_values(stage));
}
void SyscallDecoder::LoadBuffer(Stage stage, uint64_t address, size_t size) {
if (address == 0) {
return;
}
SyscallDecoderBuffer& buffer = buffers_[std::make_pair(stage, address)];
if (buffer.loading()) {
return;
}
buffer.set_loading();
LoadMemory(address, size, &buffer.loaded_values());
}
void SyscallDecoder::Decode() {
zxdb::Thread* thread = weak_thread_.get();
if (aborted_ || (thread == nullptr) || (thread->GetStack().size() == 0)) {
aborted_ = true;
Destroy();
return;
}
if (dispatcher_->decode_options().stack_level >= kFullStack) {
thread->GetStack().SyncFrames(false, [this](const zxdb::Err& /*err*/) { DoDecode(); });
} else {
DoDecode();
}
}
void SyscallDecoder::DoDecode() {
zxdb::Thread* thread = weak_thread_.get();
if (aborted_ || (thread == nullptr) || (thread->GetStack().size() == 0)) {
aborted_ = true;
Destroy();
return;
}
const zxdb::Stack& stack = thread->GetStack();
// Don't keep the inner frame which is the syscall and is not useful.
for (size_t i = stack.size() - 1; i > 0; --i) {
const zxdb::Frame* caller = stack[i];
caller_locations_.push_back(caller->GetLocation());
}
const std::vector<debug::RegisterValue>* general_registers =
thread->GetStack()[0]->GetRegisterCategorySync(debug::RegisterCategory::kGeneral);
FX_DCHECK(general_registers); // General registers should always be available synchronously.
// The order of parameters in the System V AMD64 ABI we use, according to
// Wikipedia:
static std::vector<debug::RegisterID> amd64_abi = {
debug::RegisterID::kX64_rdi, debug::RegisterID::kX64_rsi, debug::RegisterID::kX64_rdx,
debug::RegisterID::kX64_rcx, debug::RegisterID::kX64_r8, debug::RegisterID::kX64_r9};
// The order of parameters in the System V AArch64 ABI we use, according to
// Wikipedia:
static std::vector<debug::RegisterID> aarch64_abi = {
debug::RegisterID::kARMv8_x0, debug::RegisterID::kARMv8_x1, debug::RegisterID::kARMv8_x2,
debug::RegisterID::kARMv8_x3, debug::RegisterID::kARMv8_x4, debug::RegisterID::kARMv8_x5,
debug::RegisterID::kARMv8_x6, debug::RegisterID::kARMv8_x7};
const std::vector<debug::RegisterID>* abi;
if (arch_ == debug::Arch::kX64) {
abi = &amd64_abi;
entry_sp_ = GetRegisterValue(*general_registers, debug::RegisterID::kX64_rsp);
} else if (arch_ == debug::Arch::kArm64) {
abi = &aarch64_abi;
entry_sp_ = GetRegisterValue(*general_registers, debug::RegisterID::kARMv8_sp);
return_address_ = GetRegisterValue(*general_registers, debug::RegisterID::kARMv8_lr);
} else {
Error(DecoderError::Type::kUnknownArchitecture) << "Unknown architecture";
if (pending_request_count_ == 0) {
SyscallDecodingError();
}
return;
}
size_t argument_count = syscall_->arguments().size();
decoded_arguments_.reserve(argument_count);
size_t register_count = std::min(argument_count, abi->size());
for (size_t i = 0; i < register_count; i++) {
decoded_arguments_.emplace_back(GetRegisterValue(*general_registers, (*abi)[i]));
}
LoadStack();
}
void SyscallDecoder::LoadStack() {
zxdb::Thread* thread = weak_thread_.get();
if (aborted_ || (thread == nullptr) || (thread->GetStack().size() == 0)) {
aborted_ = true;
Destroy();
return;
}
size_t stack_size = (syscall_->arguments().size() - decoded_arguments_.size()) * sizeof(uint64_t);
if (arch_ == debug::Arch::kX64) {
stack_size += sizeof(uint64_t);
}
if (stack_size == 0) {
LoadInputs();
return;
}
uint64_t address = entry_sp_;
++pending_request_count_;
thread->GetProcess()->ReadMemory(
address, stack_size,
[this, address, stack_size](const zxdb::Err& err, zxdb::MemoryDump dump) {
--pending_request_count_;
if (aborted()) {
Destroy();
} else {
if (!err.ok()) {
Error(DecoderError::Type::kCantReadMemory)
<< "Can't load stack at " << address << '/' << stack_size << ": " << err.msg();
} else if ((dump.size() != stack_size) || !dump.AllValid()) {
Error(DecoderError::Type::kCantReadMemory)
<< "Can't load stack at " << address << '/' << stack_size << ": not enough data";
} else {
std::vector<uint8_t> data;
MemoryDumpToVector(dump, &data);
size_t offset = 0;
if (arch_ == debug::Arch::kX64) {
return_address_ = GetValueFromBytes<uint64_t>(data, 0);
offset += sizeof(uint64_t);
}
while (offset < data.size()) {
decoded_arguments_.emplace_back(GetValueFromBytes<uint64_t>(data, offset));
offset += sizeof(uint64_t);
}
}
LoadInputs();
}
});
}
void SyscallDecoder::LoadInputs() {
if (error_.type() != DecoderError::Type::kNone) {
if (pending_request_count_ == 0) {
SyscallDecodingError();
}
return;
}
for (const auto& input : syscall_->inputs()) {
if (input->ConditionsAreTrue(this, Stage::kEntry)) {
input->Load(this, Stage::kEntry);
}
}
if (pending_request_count_ > 0) {
return;
}
input_arguments_loaded_ = true;
if (error_.type() != DecoderError::Type::kNone) {
SyscallDecodingError();
} else {
if (StepToReturnAddress()) {
DecodeInputs();
}
}
}
bool SyscallDecoder::StepToReturnAddress() {
zxdb::Thread* thread = weak_thread_.get();
if (aborted_ || (thread == nullptr) || (thread->GetStack().size() == 0)) {
aborted_ = true;
Destroy();
return false;
}
if (syscall_->return_type() != SyscallReturnType::kNoReturn) {
thread_observer_->Register(fidlcat_thread()->koid(), this);
thread_observer_->AddExitBreakpoint(thread, *syscall_, return_address_);
}
// Restarts the stopped thread. When the breakpoint will be reached (at the
// end of the syscall), LoadSyscallReturnValue will be called.
thread->Continue(false);
return true;
}
void SyscallDecoder::DecodeInputs() {
// Creates the invoked event.
invoked_event_ = std::make_shared<InvokedEvent>(timestamp(), fidlcat_thread_, syscall_);
auto inline_member = syscall_->input_inline_members().begin();
auto outline_member = syscall_->input_outline_members().begin();
for (const auto& input : syscall_->inputs()) {
if (input->InlineValue()) {
if (input->ConditionsAreTrue(this, Stage::kEntry)) {
FX_DCHECK(inline_member != syscall_->input_inline_members().end());
std::unique_ptr<fidl_codec::Value> value = input->GenerateValue(this, Stage::kEntry);
FX_DCHECK(value != nullptr);
invoked_event_->AddInlineField(inline_member->get(), std::move(value));
}
++inline_member;
} else {
if (input->ConditionsAreTrue(this, Stage::kEntry)) {
FX_DCHECK(outline_member != syscall_->input_outline_members().end());
std::unique_ptr<fidl_codec::Value> value = input->GenerateValue(this, Stage::kEntry);
FX_DCHECK(value != nullptr);
invoked_event_->AddOutlineField(outline_member->get(), std::move(value));
}
++outline_member;
}
}
if (dispatcher_->needs_stack_frame()) {
CopyStackFrame(caller_locations(), &invoked_event_->stack_frame());
}
if (invoked_event_->NeedsToLoadHandleInfo(&dispatcher_->inference())) {
fidlcat_thread_->process()->LoadHandleInfo(&dispatcher_->inference());
}
// Eventually calls the code before displaying the input (which may invalidate
// the display).
if ((syscall_->inputs_decoded_action() == nullptr) ||
(dispatcher_->*(syscall_->inputs_decoded_action()))(timestamp(), this)) {
dispatcher_->AddInvokedEvent(invoked_event_);
}
if (syscall_->return_type() == SyscallReturnType::kNoReturn) {
// We already called Continue in StepToReturnAddress. We don't want to call it twice. We set
// aborted_ to avoid that.
aborted_ = true;
// We don't expect the syscall to return and it doesn't have any output. We can now destroy
// the decoder.
Destroy();
}
}
void SyscallDecoder::LoadSyscallReturnValue() {
zxdb::Thread* thread = weak_thread_.get();
if (aborted_ || (thread == nullptr) || (thread->GetStack().size() == 0)) {
aborted_ = true;
Destroy();
return;
}
const std::vector<debug::RegisterValue>* general_registers =
thread->GetStack()[0]->GetRegisterCategorySync(debug::RegisterCategory::kGeneral);
FX_DCHECK(general_registers); // General registers should always be available synchronously.
debug::RegisterID result_register =
(arch_ == debug::Arch::kX64) ? debug::RegisterID::kX64_rax : debug::RegisterID::kARMv8_x0;
uint64_t syscall_result_status = GetRegisterValue(*general_registers, result_register);
// This could probably use a layer of abstraction around it, but it is for
// the only case where syscalls don't return an int, so it's not clear it
// is worth the cost.
if (syscall_->return_type() == SyscallReturnType::kStringView) {
debug::RegisterID length_register =
(arch_ == debug::Arch::kX64) ? debug::RegisterID::kX64_rdx : debug::RegisterID::kARMv8_x1;
result128_t retval;
retval.first_word = syscall_result_status;
retval.second_word = GetRegisterValue(*general_registers, length_register);
syscall_return_event_ =
std::make_shared<ReturnEvent>(timestamp_, fidlcat_thread_, syscall_, retval);
// String length does not include the null terminator, but string is
// guaranteed to be null terminated, so we grab the NUL for ease of internal
// handling.
LoadBuffer(Stage::kExit, syscall_result_status, retval.second_word + 1);
} else {
syscall_return_event_ =
std::make_shared<ReturnEvent>(timestamp_, fidlcat_thread_, syscall_, syscall_result_status);
}
LoadOutputs();
}
void SyscallDecoder::LoadOutputs() {
if (error_.type() != DecoderError::Type::kNone) {
if (pending_request_count_ == 0) {
SyscallDecodingError();
}
return;
}
for (const auto& output : syscall_->outputs()) {
if ((output->error_code() == static_cast<zx_status_t>(syscall_return_event_->return_code())) &&
output->ConditionsAreTrue(this, Stage::kExit)) {
output->Load(this, Stage::kExit);
}
}
if (pending_request_count_ > 0) {
return;
}
if (error_.type() != DecoderError::Type::kNone) {
SyscallDecodingError();
} else {
DecodeOutputs();
}
}
void SyscallDecoder::DecodeOutputs() {
if (pending_request_count_ > 0) {
return;
}
syscall_return_event_->SetValue(this);
// Creates the output event.
output_event_ = std::make_shared<OutputEvent>(timestamp(), fidlcat_thread_, syscall_,
syscall_return_event_, invoked_event_);
auto inline_member = syscall_->output_inline_members().begin();
auto outline_member = syscall_->output_outline_members().begin();
for (const auto& output : syscall_->outputs()) {
if (output->InlineValue()) {
if ((output->error_code() ==
static_cast<zx_status_t>(syscall_return_event_->return_code())) &&
(output->ConditionsAreTrue(this, Stage::kExit))) {
FX_DCHECK(inline_member != syscall_->output_inline_members().end());
std::unique_ptr<fidl_codec::Value> value = output->GenerateValue(this, Stage::kExit);
FX_DCHECK(value != nullptr);
output_event_->AddInlineField(inline_member->get(), std::move(value));
}
++inline_member;
} else {
if ((output->error_code() ==
static_cast<zx_status_t>(syscall_return_event_->return_code())) &&
(output->ConditionsAreTrue(this, Stage::kExit))) {
FX_DCHECK(outline_member != syscall_->output_outline_members().end());
std::unique_ptr<fidl_codec::Value> value = output->GenerateValue(this, Stage::kExit);
FX_DCHECK(value != nullptr);
output_event_->AddOutlineField(outline_member->get(), std::move(value));
}
++outline_member;
}
}
if (output_event_->NeedsToLoadHandleInfo(&dispatcher_->inference())) {
fidlcat_thread_->process()->LoadHandleInfo(&dispatcher_->inference());
}
if (syscall_->inference() != nullptr) {
// Executes the inference associated with the syscall.
// This is used to infer semantic about handles.
(dispatcher_->*(syscall_->inference()))(output_event_.get(), semantic());
}
// If we have been able to generate an invoked event, directly call the dispatcher.
dispatcher_->AddOutputEvent(output_event_);
// Now our job is done, we can destroy the object.
Destroy();
}
void SyscallDecoder::Destroy() {
if (pending_request_count_ == 0) {
dispatcher_->DeleteDecoder(this);
}
}
} // namespace fidlcat