| // 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([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; |
| syscall_return_value_ = GetRegisterValue(*general_registers, result_register); |
| |
| 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_value_)) && |
| 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; |
| } |
| // Creates the output event. |
| output_event_ = std::make_shared<OutputEvent>(timestamp(), fidlcat_thread_, syscall_, |
| syscall_return_value_, 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_value_)) && |
| (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_value_)) && |
| (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 |