blob: 68e7c5beb0b8d4289ea750229a602c437009a345 [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 <zircon/system/public/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 "src/lib/fxl/logging.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_ipc::Register>& general_registers,
const debug_ipc::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()) {
FXL_DCHECK(block.valid);
for (size_t offset = 0; offset < block.size; ++offset) {
output_vector->push_back(block.data[offset]);
}
}
}
void SyscallUse::SyscallInputsDecoded(SyscallDecoder* decoder) {}
void SyscallUse::SyscallOutputsDecoded(SyscallDecoder* decoder) {}
void SyscallUse::SyscallDecodingError(const DecoderError& error, SyscallDecoder* decoder) {
FXL_LOG(ERROR) << error.message();
decoder->Destroy();
}
void SyscallDecoder::DisplayHandle(const zx_handle_info_t& handle_info,
const fidl_codec::Colors& colors, std::ostream& os) {
fidl_codec::DisplayHandle(colors, handle_info, os);
const fidl_codec::semantic::HandleDescription* known_handle =
dispatcher_->inference().GetHandleDescription(process_id_, handle_info.handle);
if (known_handle != nullptr) {
os << '(';
known_handle->Display(colors, os);
os << ')';
}
}
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([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_ipc::Register>* general_registers =
thread->GetStack()[0]->GetRegisterCategorySync(debug_ipc::RegisterCategory::kGeneral);
FXL_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_ipc::RegisterID> amd64_abi = {
debug_ipc::RegisterID::kX64_rdi, debug_ipc::RegisterID::kX64_rsi,
debug_ipc::RegisterID::kX64_rdx, debug_ipc::RegisterID::kX64_rcx,
debug_ipc::RegisterID::kX64_r8, debug_ipc::RegisterID::kX64_r9};
// The order of parameters in the System V AArch64 ABI we use, according to
// Wikipedia:
static std::vector<debug_ipc::RegisterID> aarch64_abi = {
debug_ipc::RegisterID::kARMv8_x0, debug_ipc::RegisterID::kARMv8_x1,
debug_ipc::RegisterID::kARMv8_x2, debug_ipc::RegisterID::kARMv8_x3,
debug_ipc::RegisterID::kARMv8_x4, debug_ipc::RegisterID::kARMv8_x5,
debug_ipc::RegisterID::kARMv8_x6, debug_ipc::RegisterID::kARMv8_x7};
const std::vector<debug_ipc::RegisterID>* abi;
if (arch_ == debug_ipc::Arch::kX64) {
abi = &amd64_abi;
entry_sp_ = GetRegisterValue(*general_registers, debug_ipc::RegisterID::kX64_rsp);
} else if (arch_ == debug_ipc::Arch::kArm64) {
abi = &aarch64_abi;
entry_sp_ = GetRegisterValue(*general_registers, debug_ipc::RegisterID::kARMv8_sp);
return_address_ = GetRegisterValue(*general_registers, debug_ipc::RegisterID::kARMv8_lr);
} else {
Error(DecoderError::Type::kUnknownArchitecture) << "Unknown architecture";
if (pending_request_count_ == 0) {
use_->SyscallDecodingError(error_, this);
}
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_ipc::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_ipc::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) {
use_->SyscallDecodingError(error_, this);
}
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) {
use_->SyscallDecodingError(error_, this);
} 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(thread_id(), this);
thread_observer_->AddExitBreakpoint(thread, syscall_->name(), return_address_);
}
// Restarts the stopped thread. When the breakpoint will be reached (at the
// end of the syscall), LoadSyscallReturnValue will be called.
thread->Continue();
return true;
}
void SyscallDecoder::DecodeInputs() {
if (syscall_->fidl_codec_values_ready()) {
// We are able to create values from the syscall => create the values.
//
// The long term goal is that zxdb gives the timestamp. Currently we only create one when we
// print the syscall.
int64_t timestamp = time(nullptr);
const Thread* thread = dispatcher_->SearchThread(thread_id_);
if (thread == nullptr) {
const Process* process = dispatcher_->SearchProcess(process_id_);
if (process == nullptr) {
process = dispatcher_->CreateProcess(process_name_, process_id_);
}
thread = dispatcher_->CreateThread(thread_id_, process);
}
invoked_event_ = std::make_unique<InvokedEvent>(timestamp << 32, 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)) {
FXL_DCHECK(inline_member != syscall_->input_inline_members().end());
std::unique_ptr<fidl_codec::Value> value = input->GenerateValue(this, Stage::kEntry);
FXL_DCHECK(value != nullptr);
invoked_event_->AddInlineField(inline_member->get(), std::move(value));
}
++inline_member;
} else {
if (input->ConditionsAreTrue(this, Stage::kEntry)) {
FXL_DCHECK(outline_member != syscall_->input_outline_members().end());
std::unique_ptr<fidl_codec::Value> value = input->GenerateValue(this, Stage::kEntry);
FXL_DCHECK(value != nullptr);
invoked_event_->AddOutlineField(outline_member->get(), std::move(value));
}
++outline_member;
}
}
}
UseInputs();
}
void SyscallDecoder::UseInputs() {
// Eventually calls the code before displaying the input (which may invalidate
// the display).
if ((syscall_->inputs_decoded_action() == nullptr) ||
(dispatcher_->*(syscall_->inputs_decoded_action()))(this)) {
use_->SyscallInputsDecoded(this);
}
if (syscall_->return_type() == SyscallReturnType::kNoReturn) {
// We don't expect the syscall to return and it doesn't have any output.
use_->SyscallOutputsDecoded(this);
}
}
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_ipc::Register>* general_registers =
thread->GetStack()[0]->GetRegisterCategorySync(debug_ipc::RegisterCategory::kGeneral);
FXL_DCHECK(general_registers); // General registers should always be available synchronously.
debug_ipc::RegisterID result_register = (arch_ == debug_ipc::Arch::kX64)
? debug_ipc::RegisterID::kX64_rax
: debug_ipc::RegisterID::kARMv8_x0;
syscall_return_value_ = GetRegisterValue(*general_registers, result_register);
LoadOutputs();
}
void SyscallDecoder::LoadOutputs() {
if (error_.type() != DecoderError::Type::kNone) {
if (pending_request_count_ == 0) {
use_->SyscallDecodingError(error_, this);
}
return;
}
for (const auto& output : syscall_->outputs()) {
if ((output->error_code() == static_cast<zx_status_t>(syscall_return_value_)) &&
output->ConditionsAreTrue(this, Stage::kExit)) {
output->Load(this, Stage::kExit);
}
}
if (pending_request_count_ > 0) {
return;
}
if (error_.type() != DecoderError::Type::kNone) {
use_->SyscallDecodingError(error_, this);
} else {
DecodeOutputs();
}
}
void SyscallDecoder::DecodeOutputs() {
if (pending_request_count_ > 0) {
return;
}
if (syscall_->fidl_codec_values_ready()) {
// We are able to create values from the syscall => create the values.
//
// The long term goal is that zxdb gives the timestamp. Currently we only create one when we
// print the syscall.
int64_t timestamp = time(nullptr);
const Thread* thread = dispatcher_->SearchThread(thread_id_);
if (thread == nullptr) {
const Process* process = dispatcher_->SearchProcess(process_id_);
if (process == nullptr) {
process = dispatcher_->CreateProcess(process_name_, process_id_);
}
thread = dispatcher_->CreateThread(thread_id_, process);
}
output_event_ =
std::make_unique<OutputEvent>(timestamp << 32, thread, syscall_, syscall_return_value_);
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_value_)) &&
(output->ConditionsAreTrue(this, Stage::kExit))) {
FXL_DCHECK(inline_member != syscall_->output_inline_members().end());
std::unique_ptr<fidl_codec::Value> value = output->GenerateValue(this, Stage::kExit);
FXL_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_value_)) &&
(output->ConditionsAreTrue(this, Stage::kExit))) {
FXL_DCHECK(outline_member != syscall_->output_outline_members().end());
std::unique_ptr<fidl_codec::Value> value = output->GenerateValue(this, Stage::kExit);
FXL_DCHECK(value != nullptr);
output_event_->AddOutlineField(outline_member->get(), std::move(value));
}
++outline_member;
}
}
}
UseOutputs();
}
void SyscallDecoder::UseOutputs() {
use_->SyscallOutputsDecoded(this);
// Now our job is done, we can destroy the object.
Destroy();
}
void SyscallDecoder::Destroy() {
if (pending_request_count_ == 0) {
if (syscall_->displayed_action() != nullptr) {
// Calls the action associated with the syscall. This is used to infer semantic about handles.
(dispatcher_->*(syscall_->displayed_action()))(this);
}
dispatcher_->DeleteDecoder(this);
}
}
void SyscallDisplay::SyscallInputsDecoded(SyscallDecoder* decoder) {
const fidl_codec::Colors& colors = dispatcher_->colors();
line_header_ = decoder->process_name() + ' ' + colors.red +
std::to_string(decoder->process_id()) + colors.reset + ':' + colors.red +
std::to_string(decoder->thread_id()) + colors.reset + ' ';
if (dispatcher_->with_process_info()) {
os_ << line_header_ << '\n';
} else {
os_ << '\n';
}
if (dispatcher_->decode_options().stack_level != kNoStack) {
// Display caller locations.
DisplayStackFrame(dispatcher_->colors(), line_header_, decoder->caller_locations(), os_);
}
const InvokedEvent* invoked_event = decoder->invoked_event();
if (invoked_event != nullptr) {
// We have been able to create values from the syscall => print them.
const fidl_codec::Colors& colors = dispatcher_->colors();
std::string line_header = invoked_event->thread()->process()->name() + ' ' + colors.red +
std::to_string(invoked_event->thread()->process()->koid()) +
colors.reset + ':' + colors.red +
std::to_string(invoked_event->thread()->koid()) + colors.reset + ' ';
FidlcatPrinter printer(decoder, dispatcher_->dump_messages(),
dispatcher_->message_decoder_dispatcher().display_options().pretty_print,
os_, dispatcher_->colors(), line_header, dispatcher_->columns(),
dispatcher_->with_process_info());
invoked_event->PrettyPrint(printer);
if (!dispatcher_->with_process_info()) {
line_header_ = "";
}
} else {
// This code will be deleted when we will be able to have the two step printing for all the
// syscalls.
//
// Displays the header and the inline input arguments.
os_ << line_header_ << decoder->syscall()->name() << '(';
const char* separator = "";
for (const auto& input : decoder->syscall()->inputs()) {
if (input->ConditionsAreTrue(decoder, Stage::kEntry)) {
separator = input->DisplayInline(dispatcher_, decoder, Stage::kEntry, separator, os_);
}
}
os_ << ")\n";
if (!dispatcher_->with_process_info()) {
line_header_ = "";
}
// Displays the outline input arguments.
for (const auto& input : decoder->syscall()->inputs()) {
if (input->ConditionsAreTrue(decoder, Stage::kEntry)) {
input->DisplayOutline(dispatcher_, decoder, Stage::kEntry, line_header_, /*tabs=*/1, os_);
}
}
}
dispatcher_->set_last_displayed_syscall(this);
}
void SyscallDisplay::SyscallOutputsDecoded(SyscallDecoder* decoder) {
if (decoder->syscall()->return_type() != SyscallReturnType::kNoReturn) {
const fidl_codec::Colors& colors = dispatcher_->colors();
const OutputEvent* output_event = decoder->output_event();
if (output_event != nullptr) {
// We have been able to create values from the syscall => print them.
std::string line_header;
if (dispatcher_->with_process_info() || (dispatcher_->last_displayed_syscall() != this)) {
line_header = output_event->thread()->process()->name() + ' ' + colors.red +
std::to_string(output_event->thread()->process()->koid()) + colors.reset +
':' + colors.red + std::to_string(output_event->thread()->koid()) +
colors.reset + ' ';
}
if (dispatcher_->last_displayed_syscall() != this) {
// Add a blank line to tell the user that this display is not linked to the
// previous displayed lines.
os_ << "\n";
}
FidlcatPrinter printer(
decoder, dispatcher_->dump_messages(),
dispatcher_->message_decoder_dispatcher().display_options().pretty_print, os_,
dispatcher_->colors(), line_header, dispatcher_->columns(),
dispatcher_->with_process_info());
output_event->PrettyPrint(printer);
} else {
// This code will be deleted when we will be able to have the two step printing for all the
// syscalls.
//
// Displays the returned value.
if (dispatcher_->last_displayed_syscall() != this) {
// Add a blank line to tell the user that this display is not linked to the
// previous displayed lines.
os_ << "\n";
// Then always display the process info to be able able to know for which thread
// we are displaying the output.
std::string first_line_header = decoder->process_name() + ' ' + colors.red +
std::to_string(decoder->process_id()) + colors.reset + ':' +
colors.red + std::to_string(decoder->thread_id()) +
colors.reset + ' ';
os_ << first_line_header << " -> ";
} else {
os_ << line_header_ << " -> ";
}
switch (decoder->syscall()->return_type()) {
case SyscallReturnType::kNoReturn:
case SyscallReturnType::kVoid:
break;
case SyscallReturnType::kStatus:
StatusName(colors, static_cast<zx_status_t>(decoder->syscall_return_value()), os_);
break;
case SyscallReturnType::kTicks:
os_ << colors.green << "ticks" << colors.reset << ": " << colors.blue
<< static_cast<uint64_t>(decoder->syscall_return_value()) << colors.reset;
break;
case SyscallReturnType::kTime:
os_ << colors.green << "time" << colors.reset << ": "
<< DisplayTime(colors, static_cast<zx_time_t>(decoder->syscall_return_value()));
break;
case SyscallReturnType::kUint32:
os_ << colors.blue << static_cast<uint32_t>(decoder->syscall_return_value())
<< colors.reset;
break;
case SyscallReturnType::kUint64:
os_ << colors.blue << static_cast<uint64_t>(decoder->syscall_return_value())
<< colors.reset;
break;
}
// And the inline output arguments (if any).
const char* separator = " (";
for (const auto& output : decoder->syscall()->outputs()) {
if ((output->error_code() == static_cast<zx_status_t>(decoder->syscall_return_value())) &&
output->ConditionsAreTrue(decoder, Stage::kExit)) {
separator = output->DisplayInline(dispatcher_, decoder, Stage::kExit, separator, os_);
}
}
if (std::string(" (") != separator) {
os_ << ')';
}
os_ << '\n';
// Displays the outline output arguments.
for (const auto& output : decoder->syscall()->outputs()) {
if ((output->error_code() == static_cast<zx_status_t>(decoder->syscall_return_value())) &&
output->ConditionsAreTrue(decoder, Stage::kExit)) {
output->DisplayOutline(dispatcher_, decoder, Stage::kExit, line_header_, /*tabs=*/2, os_);
}
}
}
dispatcher_->set_last_displayed_syscall(this);
}
}
void SyscallDisplay::SyscallDecodingError(const DecoderError& error, SyscallDecoder* decoder) {
std::string message = error.message();
size_t pos = 0;
for (;;) {
size_t end = message.find('\n', pos);
const fidl_codec::Colors& colors = dispatcher_->colors();
os_ << decoder->process_name() << ' ' << colors.red << decoder->process_id() << colors.reset
<< ':' << colors.red << decoder->thread_id() << colors.reset << ' '
<< decoder->syscall()->name() << ": " << colors.red << error.message().substr(pos, end)
<< colors.reset << '\n';
if (end == std::string::npos) {
break;
}
pos = end + 1;
}
os_ << '\n';
decoder->Destroy();
}
void SyscallCompare::SyscallInputsDecoded(SyscallDecoder* decoder) {
os_.clear();
os_.str("");
SyscallDisplay::SyscallInputsDecoded(decoder);
comparator_->CompareInput(os_.str(), decoder->process_name(), decoder->process_id(),
decoder->thread_id());
}
void SyscallCompare::SyscallOutputsDecoded(SyscallDecoder* decoder) {
os_.clear();
os_.str("");
SyscallDisplay::SyscallOutputsDecoded(decoder);
if (decoder->syscall()->return_type() != SyscallReturnType::kNoReturn) {
comparator_->CompareOutput(os_.str(), decoder->process_name(), decoder->process_id(),
decoder->thread_id());
}
}
void SyscallCompare::SyscallDecodingError(const DecoderError& error, SyscallDecoder* decoder) {
os_.clear();
os_.str("");
SyscallDisplay::SyscallDecodingError(error, decoder);
comparator_->DecodingError(os_.str());
}
} // namespace fidlcat