| // 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. |
| |
| #ifndef TOOLS_FIDLCAT_INTERCEPTION_TESTS_INTERCEPTION_WORKFLOW_TEST_H_ |
| #define TOOLS_FIDLCAT_INTERCEPTION_TESTS_INTERCEPTION_WORKFLOW_TEST_H_ |
| |
| #include <zircon/fidl.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <memory> |
| #include <sstream> |
| #include <thread> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/developer/debug/ipc/records.h" |
| #include "src/developer/debug/shared/arch.h" |
| #include "src/developer/debug/zxdb/client/frame_impl.h" |
| #include "src/developer/debug/zxdb/client/mock_remote_api.h" |
| #include "src/developer/debug/zxdb/client/remote_api_test.h" |
| #include "src/developer/debug/zxdb/client/target_impl.h" |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/symbols/mock_module_symbols.h" |
| #include "tools/fidlcat/lib/interception_workflow.h" |
| #include "tools/fidlcat/lib/replay.h" |
| |
| namespace fidlcat { |
| |
| class ProcessController; |
| class SyscallDecoderDispatcherTest; |
| |
| constexpr uint64_t kFirstPid = 3141; |
| constexpr uint64_t kSecondPid = 2718; |
| |
| constexpr uint64_t kFirstThreadKoid = 8764; |
| constexpr uint64_t kSecondThreadKoid = 8765; |
| |
| constexpr uint32_t kHandle = 0xcefa1db0; |
| constexpr uint64_t kHandleKoid = 1000828; |
| constexpr uint32_t kHandle2 = 0xcefa1222; |
| constexpr uint64_t kHandle2Koid = 1000829; |
| constexpr uint32_t kHandle3 = 0xcefa1333; |
| constexpr uint32_t kHandleOut = 0xbde90caf; |
| constexpr uint32_t kHandleOut2 = 0xbde90222; |
| constexpr uint32_t kPort = 0xdf0b2ec1; |
| constexpr uint64_t kKey = 1234; |
| constexpr uint64_t kKoid = 4252; |
| constexpr uint64_t kKoid2 = 5242; |
| constexpr zx_futex_t kFutex = 56789; |
| constexpr zx_futex_t kFutex2 = 98765; |
| |
| extern SyscallDecoderDispatcher* global_dispatcher; |
| |
| class SystemCallTest { |
| public: |
| SystemCallTest(const char* name, int64_t result, std::string_view result_name) |
| : name_(name), |
| result_({.first_word = result, .second_word = 0}), |
| enable_overflow_(false), |
| result_name_(result_name) {} |
| |
| SystemCallTest(const char* name, result128_t result, std::string_view result_name) |
| : name_(name), result_(result), enable_overflow_(true), result_name_(result_name) {} |
| |
| const std::string& name() const { return name_; } |
| int64_t result() const { return result_.first_word; } |
| const std::string& result_name() const { return result_name_; } |
| const std::vector<uint64_t>& inputs() const { return inputs_; } |
| |
| void AddInput(uint64_t input) { inputs_.push_back(input); } |
| |
| bool HasOverflow() const { return enable_overflow_; } |
| uint64_t overflow_result() const { return result_.second_word; } |
| |
| private: |
| const std::string name_; |
| const result128_t result_; |
| const bool enable_overflow_; |
| const std::string result_name_; |
| std::vector<uint64_t> inputs_; |
| }; |
| |
| // Data for syscall tests. |
| class DataForSyscallTest { |
| public: |
| explicit DataForSyscallTest(debug::Arch arch); |
| |
| const SystemCallTest* syscall() const { return syscall_.get(); } |
| |
| void set_syscall(std::unique_ptr<SystemCallTest> syscall) { syscall_ = std::move(syscall); } |
| |
| bool use_alternate_data() const { return use_alternate_data_; } |
| |
| void set_use_alternate_data() { use_alternate_data_ = true; } |
| |
| void load_syscall_data() { |
| size_t argument_count = syscall_->inputs().size(); |
| if (argument_count > param_regs_->size()) { |
| argument_count -= param_regs_->size(); |
| for (auto input = syscall_->inputs().crbegin(); |
| (input != syscall_->inputs().crend()) && (argument_count > 0); |
| ++input, --argument_count) { |
| *(--sp_) = *input; |
| } |
| } |
| if (arch_ == debug::Arch::kX64) { |
| *(--sp_) = kReturnAddress; |
| } |
| stepped_processes_.clear(); |
| } |
| |
| uint64_t* sp() const { return sp_; } |
| |
| void set_check_bytes() { check_bytes_ = true; } |
| void set_check_handles() { check_handles_ = true; } |
| |
| uint8_t* bytes() { return reinterpret_cast<uint8_t*>(&header_); } |
| |
| size_t num_bytes() const { return sizeof(header_); } |
| |
| uint8_t* large_bytes() { return large_bytes_.data(); } |
| |
| size_t num_large_bytes() const { return large_bytes_.size(); } |
| |
| zx_handle_t* handles() { return handles_; } |
| |
| size_t num_handles() const { return sizeof(handles_) / sizeof(handles_[0]); } |
| |
| zx_handle_info_t* handle_infos() { return handle_infos_; } |
| |
| size_t num_handle_infos() const { return sizeof(handle_infos_) / sizeof(handle_infos_[0]); } |
| |
| uint8_t* bytes2() { return reinterpret_cast<uint8_t*>(&header2_); } |
| |
| size_t num_bytes2() const { return sizeof(header2_); } |
| |
| zx_handle_t* handles2() { return handles2_; } |
| |
| size_t num_handles2() const { return sizeof(handles2_) / sizeof(handles2_[0]); } |
| |
| void PopulateModules(std::vector<debug_ipc::Module>& modules) { |
| const uint64_t kModuleBase = 0x1000000; |
| debug_ipc::Module load; |
| load.name = "test"; |
| load.base = kModuleBase; |
| load.build_id = kElfSymbolBuildID; |
| modules.push_back(load); |
| } |
| |
| void PopulateMemoryBlockForAddress(uint64_t address, uint64_t size, |
| debug_ipc::MemoryBlock& block) { |
| block.address = address; |
| block.size = size; |
| block.valid = true; |
| std::copy(reinterpret_cast<uint8_t*>(address), reinterpret_cast<uint8_t*>(address + size), |
| std::back_inserter(block.data)); |
| FX_DCHECK(size == block.data.size()) |
| << "expected size: " << size << " and actual size: " << block.data.size(); |
| } |
| |
| void PopulateRegister(debug::RegisterID register_id, uint64_t value, |
| std::vector<debug::RegisterValue>* registers) { |
| debug::RegisterValue& reg = registers->emplace_back(); |
| reg.id = register_id; |
| for (int i = 0; i < 64; i += 8) { |
| reg.data.push_back((value >> i) & 0xff); |
| } |
| } |
| |
| void PopulateRegisters(uint64_t process_koid, std::vector<debug::RegisterValue>* registers) { |
| if (syscall_ != nullptr) { |
| if (stepped_processes_.find(process_koid) == stepped_processes_.end()) { |
| size_t count = std::min(param_regs_->size(), syscall_->inputs().size()); |
| for (size_t i = 0; i < count; ++i) { |
| PopulateRegister((*param_regs_)[i], syscall_->inputs()[i], registers); |
| } |
| } else { |
| if (arch_ == debug::Arch::kArm64) { |
| PopulateRegister(debug::RegisterID::kARMv8_x0, syscall_->result(), registers); |
| if (syscall_->HasOverflow()) { |
| PopulateRegister(debug::RegisterID::kARMv8_x1, syscall_->overflow_result(), registers); |
| } |
| } else { |
| PopulateRegister(debug::RegisterID::kX64_rax, syscall_->result(), registers); |
| if (syscall_->HasOverflow()) { |
| PopulateRegister(debug::RegisterID::kX64_rdx, syscall_->overflow_result(), registers); |
| } |
| } |
| } |
| } |
| |
| if (arch_ == debug::Arch::kArm64) { |
| // stack pointer |
| PopulateRegister(debug::RegisterID::kARMv8_sp, reinterpret_cast<uint64_t>(sp_), registers); |
| // link register |
| PopulateRegister(debug::RegisterID::kARMv8_lr, kReturnAddress, registers); |
| } else if (arch_ == debug::Arch::kX64) { |
| // stack pointer |
| PopulateRegister(debug::RegisterID::kX64_rsp, reinterpret_cast<uint64_t>(sp_), registers); |
| } |
| } |
| |
| void Step(uint64_t process_koid) { |
| // Increment the stack pointer to make it look as if we've stepped out of |
| // the zx_channel function. |
| sp_ = stack_ + kMaxStackSizeInWords; |
| stepped_processes_.insert(process_koid); |
| } |
| |
| template <typename T> |
| void AppendElements(std::string& result, size_t num, const T* a, const T* b) { |
| std::ostringstream os; |
| os << "actual expected\n"; |
| for (size_t i = 0; i < num; i++) { |
| os << std::left << std::setw(11) << static_cast<uint32_t>(a[i]); |
| os << " "; |
| os << std::left << std::setw(11) << static_cast<uint32_t>(b[i]); |
| os << std::endl; |
| } |
| result.append(os.str()); |
| } |
| |
| static constexpr uint64_t kReturnAddress = 0x123456798; |
| static constexpr uint64_t kMaxStackSizeInWords = 0x100; |
| static constexpr zx_txid_t kTxId = 0xaaaaaaaa; |
| static constexpr zx_txid_t kTxId2 = 0x88888888; |
| static constexpr uint32_t kFidlWireFormatMagicNumberInitial = 0x1; |
| static constexpr uint64_t kOrdinal = 0x77e4cceb00000000lu; |
| static constexpr uint64_t kOrdinal2 = 1234567890123456789lu; |
| static constexpr char kElfSymbolBuildID[] = "123412341234"; |
| |
| private: |
| const std::vector<debug::RegisterID>* param_regs_; |
| std::unique_ptr<SystemCallTest> syscall_; |
| bool use_alternate_data_ = false; |
| uint64_t stack_[kMaxStackSizeInWords]; |
| uint64_t* sp_; |
| bool check_bytes_ = false; |
| bool check_handles_ = false; |
| fidl_message_header_t header_; |
| fidl_message_header_t header2_; |
| std::vector<uint8_t> large_bytes_; |
| zx_handle_t handles_[2] = {0x01234567, 0x89abcdef}; |
| zx_handle_info_t handle_infos_[2] = { |
| {0x01234567, ZX_OBJ_TYPE_CHANNEL, |
| ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_SIGNAL | ZX_RIGHT_SIGNAL_PEER | |
| ZX_RIGHT_WAIT | ZX_RIGHT_INSPECT, |
| 0}, |
| {0x89abcdef, ZX_OBJ_TYPE_LOG, |
| ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER | ZX_RIGHT_WRITE | ZX_RIGHT_SIGNAL | ZX_RIGHT_WAIT | |
| ZX_RIGHT_INSPECT, |
| 0}}; |
| zx_handle_t handles2_[2] = {0x76543210, 0xfedcba98}; |
| debug::Arch arch_; |
| std::set<uint64_t> stepped_processes_; |
| }; |
| |
| // Provides the infrastructure needed to provide the data above. |
| class InterceptionRemoteAPI : public zxdb::MockRemoteAPI { |
| public: |
| InterceptionRemoteAPI(DataForSyscallTest& data, bool aborted) : data_(data), aborted_(aborted) {} |
| |
| void AddOrChangeBreakpoint( |
| const debug_ipc::AddOrChangeBreakpointRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::AddOrChangeBreakpointReply)> cb) override { |
| breakpoints_[request.breakpoint.id] = request.breakpoint; |
| MockRemoteAPI::AddOrChangeBreakpoint(request, std::move(cb)); |
| } |
| |
| void Attach(const debug_ipc::AttachRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::AttachReply)> cb) override { |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [cb = std::move(cb)]() mutable { cb(zxdb::Err(), debug_ipc::AttachReply()); }); |
| } |
| |
| void Modules(const debug_ipc::ModulesRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::ModulesReply)> cb) override { |
| debug_ipc::ModulesReply reply; |
| data_.PopulateModules(reply.modules); |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [cb = std::move(cb), reply]() mutable { cb(zxdb::Err(), reply); }); |
| } |
| |
| void ReadMemory(const debug_ipc::ReadMemoryRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::ReadMemoryReply)> cb) override { |
| if (aborted_) { |
| aborted_ = false; |
| Process* process = global_dispatcher->SearchProcess(kFirstPid); |
| FX_DCHECK(process != nullptr); |
| constexpr int64_t kReadFailTimestamp = 1000000000; |
| global_dispatcher->AddStopMonitoringEvent( |
| std::make_shared<StopMonitoringEvent>(kReadFailTimestamp, process)); |
| } |
| debug_ipc::ReadMemoryReply reply; |
| data_.PopulateMemoryBlockForAddress(request.address, request.size, reply.blocks.emplace_back()); |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [cb = std::move(cb), reply]() mutable { cb(zxdb::Err(), reply); }); |
| } |
| |
| void ReadRegisters( |
| const debug_ipc::ReadRegistersRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::ReadRegistersReply)> cb) override { |
| // TODO: Parameterize this so we can have more than one test. |
| debug_ipc::ReadRegistersReply reply; |
| data_.PopulateRegisters(request.id.process, &reply.registers); |
| debug::MessageLoop::Current()->PostTask( |
| FROM_HERE, [cb = std::move(cb), reply]() mutable { cb(zxdb::Err(), reply); }); |
| } |
| |
| void Resume(const debug_ipc::ResumeRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::ResumeReply)> cb) override { |
| debug_ipc::ResumeReply reply; |
| data_.Step(request.ids[0].process); |
| debug::MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb), reply]() mutable { |
| cb(zxdb::Err(), reply); |
| // This is so that the test can inject the next exception. |
| debug::MessageLoop::Current()->QuitNow(); |
| }); |
| } |
| |
| void PopulateBreakpointIds(uint64_t address, debug_ipc::NotifyException& notification) { |
| for (auto& breakpoint : breakpoints_) { |
| if (address == breakpoint.second.locations[0].address) { |
| notification.hit_breakpoints.emplace_back(); |
| notification.hit_breakpoints.back().id = breakpoint.first; |
| } |
| } |
| } |
| |
| void LoadInfoHandleTable( |
| const debug_ipc::LoadInfoHandleTableRequest& request, |
| fit::callback<void(const zxdb::Err&, debug_ipc::LoadInfoHandleTableReply)> cb) override { |
| debug_ipc::LoadInfoHandleTableReply reply; |
| |
| debug_ipc::InfoHandle info; |
| info.type = ZX_OBJ_TYPE_CHANNEL; |
| info.handle_value = kHandle; |
| info.rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_SIGNAL | |
| ZX_RIGHT_SIGNAL_PEER | ZX_RIGHT_WAIT | ZX_RIGHT_INSPECT; |
| info.koid = kHandleKoid; |
| info.related_koid = kHandle2Koid; |
| info.peer_owner_koid = 0; |
| reply.handles.push_back(info); |
| |
| info.type = ZX_OBJ_TYPE_CHANNEL; |
| info.handle_value = kHandle2; |
| info.rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_SIGNAL | |
| ZX_RIGHT_SIGNAL_PEER | ZX_RIGHT_WAIT | ZX_RIGHT_INSPECT; |
| info.koid = kHandle2Koid; |
| info.related_koid = kHandleKoid; |
| info.peer_owner_koid = 0; |
| reply.handles.push_back(info); |
| |
| cb(zxdb::Err(), std::move(reply)); |
| } |
| |
| private: |
| std::map<uint32_t, debug_ipc::BreakpointSettings> breakpoints_; |
| DataForSyscallTest& data_; |
| bool aborted_; |
| }; |
| |
| class InterceptionWorkflowTest : public zxdb::RemoteAPITest { |
| public: |
| InterceptionWorkflowTest(debug::Arch arch, bool aborted) : data_(arch), aborted_(aborted) { |
| decode_options_.output_mode = OutputMode::kStandard; |
| display_options_.pretty_print = true; |
| display_options_.columns = 132; |
| display_options_.needs_colors = true; |
| } |
| ~InterceptionWorkflowTest() override = default; |
| |
| InterceptionRemoteAPI& mock_remote_api() { return *mock_remote_api_; } |
| |
| std::unique_ptr<zxdb::RemoteAPI> GetRemoteAPIImpl() override { |
| auto remote_api = std::make_unique<InterceptionRemoteAPI>(data_, aborted_); |
| mock_remote_api_ = remote_api.get(); |
| return std::move(remote_api); |
| } |
| |
| DataForSyscallTest& data() { return data_; } |
| |
| void set_with_process_info() { display_options_.with_process_info = true; } |
| void set_dump_messages(bool dump_messages) { display_options_.dump_messages = dump_messages; } |
| |
| void set_bad_stack() { bad_stack_ = true; } |
| |
| void AddThread(zxdb::Thread* thread) { threads_[thread->GetKoid()] = thread; } |
| |
| void PerformDisplayTest(const char* syscall_name, std::unique_ptr<SystemCallTest> syscall, |
| const char* expected, fidl_codec::LibraryLoader* loader = nullptr); |
| void PerformDisplayTest(ProcessController* controller, const char* syscall_name, |
| std::unique_ptr<SystemCallTest> syscall, const char* expected, |
| fidl_codec::LibraryLoader* loader = nullptr); |
| |
| void PerformOneThreadDisplayTest(const char* syscall_name, |
| std::unique_ptr<SystemCallTest> syscall, const char* expected); |
| |
| void PerformInterleavedDisplayTest(const char* syscall_name, |
| std::unique_ptr<SystemCallTest> syscall, const char* expected); |
| void PerformInterleavedDisplayTest(ProcessController* controller, const char* syscall_name, |
| std::unique_ptr<SystemCallTest> syscall, const char* expected); |
| |
| void PerformNoReturnDisplayTest(const char* syscall_name, std::unique_ptr<SystemCallTest> syscall, |
| const char* expected); |
| |
| void PerformTest(const char* syscall_name, std::unique_ptr<SystemCallTest> syscall1, |
| std::unique_ptr<SystemCallTest> syscall2, ProcessController* controller, |
| std::unique_ptr<SyscallDecoderDispatcher> dispatcher, bool interleaved_test, |
| bool multi_thread); |
| |
| void PerformAbortedTest(const char* syscall_name, std::unique_ptr<SystemCallTest> syscall, |
| const char* expected); |
| |
| void SimulateSyscall(std::unique_ptr<SystemCallTest> syscall, ProcessController* controller, |
| bool interleaved_test, bool multi_thread); |
| std::vector<std::unique_ptr<zxdb::Frame>> FillBreakpoint(debug_ipc::NotifyException* notification, |
| uint64_t process_koid, |
| uint64_t thread_koid); |
| void TriggerSyscallBreakpoint(uint64_t process_koid, uint64_t thread_koid); |
| void TriggerCallerBreakpoint(uint64_t process_koid, uint64_t thread_koid); |
| |
| void PerformExceptionDisplayTest(debug_ipc::ExceptionType type, const char* expected); |
| void PerformExceptionTest(ProcessController* controller, |
| std::unique_ptr<SyscallDecoderDispatcher> dispatcher, |
| debug_ipc::ExceptionType type); |
| void TriggerException(uint64_t process_koid, uint64_t thread_koid, debug_ipc::ExceptionType type); |
| void PerformFunctionTest(ProcessController* controller, const char* syscall_name, |
| std::unique_ptr<SystemCallTest> syscall, uint64_t pid, uint64_t tid); |
| |
| protected: |
| DataForSyscallTest data_; |
| bool aborted_; |
| InterceptionRemoteAPI* mock_remote_api_; // Owned by the session. |
| DecodeOptions decode_options_; |
| DisplayOptions display_options_; |
| std::stringstream result_; |
| std::map<uint64_t, zxdb::Thread*> threads_; |
| // Function which can simulate the fact that the syscall can modify some data. |
| std::function<void()> update_data_; |
| bool bad_stack_ = false; |
| std::unique_ptr<SyscallDecoderDispatcher> last_decoder_dispatcher_; |
| }; |
| |
| class InterceptionWorkflowTestX64 : public InterceptionWorkflowTest { |
| public: |
| InterceptionWorkflowTestX64() : InterceptionWorkflowTest(GetArch(), false) {} |
| ~InterceptionWorkflowTestX64() override = default; |
| |
| debug::Arch GetArch() const override { return debug::Arch::kX64; } |
| }; |
| |
| class InterceptionWorkflowTestArm : public InterceptionWorkflowTest { |
| public: |
| InterceptionWorkflowTestArm() : InterceptionWorkflowTest(GetArch(), false) {} |
| ~InterceptionWorkflowTestArm() override = default; |
| |
| debug::Arch GetArch() const override { return debug::Arch::kArm64; } |
| }; |
| |
| class InterceptionWorkflowTestX64Aborted : public InterceptionWorkflowTest { |
| public: |
| InterceptionWorkflowTestX64Aborted() : InterceptionWorkflowTest(GetArch(), true) {} |
| ~InterceptionWorkflowTestX64Aborted() override = default; |
| |
| debug::Arch GetArch() const override { return debug::Arch::kX64; } |
| }; |
| |
| class InterceptionWorkflowTestArmAborted : public InterceptionWorkflowTest { |
| public: |
| InterceptionWorkflowTestArmAborted() : InterceptionWorkflowTest(GetArch(), true) {} |
| ~InterceptionWorkflowTestArmAborted() override = default; |
| |
| debug::Arch GetArch() const override { return debug::Arch::kArm64; } |
| }; |
| |
| // This does process setup for the test. It creates fake processes, injects |
| // modules with the appropriate symbols, attaches to the processes, etc. |
| class ProcessController { |
| public: |
| ProcessController(InterceptionWorkflowTest* remote_api, zxdb::Session& session, |
| debug::MessageLoop& loop); |
| ~ProcessController(); |
| |
| InterceptionWorkflowTest* remote_api() const { return remote_api_; } |
| InterceptionWorkflow& workflow() { return workflow_; } |
| const std::vector<uint64_t>& process_koids() { return process_koids_; } |
| uint64_t thread_koid(uint64_t process_koid) { return thread_koids_[process_koid]; } |
| bool initialized() const { return initialized_; } |
| |
| std::unique_ptr<SyscallDecoderDispatcher> GetBackDispatcher() { |
| return workflow_.GetBackDispatcher(); |
| } |
| |
| void InjectProcesses(zxdb::Session& session); |
| |
| // The syscall_name can be the empty string if no mock syscall is needed. |
| void Initialize(zxdb::Session& session, std::unique_ptr<SyscallDecoderDispatcher> dispatcher, |
| const char* syscall_name); |
| void Detach(); |
| |
| private: |
| InterceptionWorkflowTest* remote_api_; |
| std::vector<uint64_t> process_koids_; |
| std::map<uint64_t, uint64_t> thread_koids_; |
| InterceptionWorkflow workflow_; |
| |
| std::vector<zxdb::Process*> processes_; |
| std::vector<zxdb::Target*> targets_; |
| size_t detached_processes_ = 0; |
| bool initialized_ = false; |
| }; |
| |
| class AlwaysQuit { |
| public: |
| AlwaysQuit(ProcessController* controller) : controller_(controller) {} |
| ~AlwaysQuit() { controller_->Detach(); } |
| |
| private: |
| ProcessController* controller_; |
| }; |
| |
| template <typename T> |
| void AppendElements(std::string& result, const T* a, const T* b, size_t num) { |
| std::ostringstream os; |
| os << "actual expected\n"; |
| for (size_t i = 0; i < num; i++) { |
| os << std::left << std::setw(11) << static_cast<uint32_t>(a[i]); |
| os << " "; |
| os << std::left << std::setw(11) << static_cast<uint32_t>(b[i]); |
| os << std::endl; |
| } |
| result.append(os.str()); |
| } |
| |
| class SyscallDisplayDispatcherTest : public SyscallDisplayDispatcher { |
| public: |
| SyscallDisplayDispatcherTest(fidl_codec::LibraryLoader* loader, |
| const DecodeOptions& decode_options, |
| const DisplayOptions& display_options, std::ostream& os, |
| ProcessController* controller, bool aborted) |
| : SyscallDisplayDispatcher(loader, decode_options, display_options, os), |
| controller_(controller), |
| aborted_(aborted), |
| replay_dispatcher_(std::make_unique<SyscallDisplayDispatcher>(loader, decode_options, |
| display_options, os)), |
| replay_(replay_dispatcher_.get()) {} |
| |
| ProcessController* controller() const { return controller_; } |
| |
| void DeleteDecoder(SyscallDecoder* decoder) override { |
| SyscallDisplayDispatcher::DeleteDecoder(decoder); |
| AlwaysQuit aq(controller_); |
| } |
| |
| void DeleteDecoder(ExceptionDecoder* decoder) override { |
| SyscallDecoderDispatcher::DeleteDecoder(decoder); |
| AlwaysQuit aq(controller_); |
| } |
| |
| void AddProcessLaunchedEvent(std::shared_ptr<ProcessLaunchedEvent> event) override {} |
| |
| void AddProcessMonitoredEvent(std::shared_ptr<ProcessMonitoredEvent> event) override {} |
| |
| void AddStopMonitoringEvent(std::shared_ptr<StopMonitoringEvent> event) override { |
| if (aborted_) { |
| SyscallDisplayDispatcher::AddStopMonitoringEvent(std::move(event)); |
| } |
| } |
| |
| // For events, instead of dispatching them using this dispatcher, we dispatch them using the |
| // replay dispatcher. This method ensures that the thread/process used by an event which has |
| // been created in this dispatcher is also created in the replay dispatcher. |
| void CreateReplayThread(Thread* thread) { |
| Thread* replay_thread = replay_dispatcher_->SearchThread(thread->koid()); |
| if (replay_thread == nullptr) { |
| Process* process = thread->process(); |
| Process* replay_process = replay_dispatcher_->SearchProcess(process->koid()); |
| if (replay_process == nullptr) { |
| replay_process = |
| replay_dispatcher_->CreateProcess(process->name(), process->koid(), nullptr); |
| } |
| replay_dispatcher_->CreateThread(thread->koid(), replay_process); |
| } |
| } |
| |
| void AddInvokedEvent(std::shared_ptr<InvokedEvent> invoked_event) override { |
| // Set the invoked event id (this is usually done by SyscallDisplayDispatcher). |
| invoked_event->set_id(GetNextInvokedEventId()); |
| // Ensure that the thread/process are created for the replay dispatcher. |
| CreateReplayThread(invoked_event->thread()); |
| // Create a proto event. |
| proto::Event proto_event; |
| invoked_event->Write(&proto_event); |
| // Replay the proto event. This will dispatch the event to the replay dispatcher. Because both |
| // this dispatcher and the replay dispatcher have the output stream, the output must be |
| // unchanged. |
| replay_.DecodeAndDispatchEvent(proto_event); |
| } |
| |
| void AddOutputEvent(std::shared_ptr<OutputEvent> output_event) override { |
| // Create a proto event. |
| proto::Event proto_event; |
| output_event->Write(&proto_event); |
| // Replay the proto event. This will dispatch the event to the replay dispatcher. Because both |
| // this dispatcher and the replay dispatcher have the output stream, the output must be |
| // unchanged. |
| replay_.DecodeAndDispatchEvent(proto_event); |
| } |
| |
| void AddExceptionEvent(std::shared_ptr<ExceptionEvent> exception_event) override { |
| // Ensure that the thread/process are created for the replay dispatcher. |
| CreateReplayThread(exception_event->thread()); |
| // Create a proto event. |
| proto::Event proto_event; |
| exception_event->Write(&proto_event); |
| // Replay the proto event. This will dispatch the event to the replay dispatcher. Because both |
| // this dispatcher and the replay dispatcher have the output stream, the output must be |
| // unchanged. |
| replay_.DecodeAndDispatchEvent(proto_event); |
| } |
| |
| private: |
| ProcessController* controller_; |
| bool aborted_; |
| // Dispatcher used to test the save/replay of events. |
| std::unique_ptr<SyscallDisplayDispatcher> replay_dispatcher_; |
| // Used to replay saved events. |
| Replay replay_; |
| }; |
| |
| #define AUTOMATION_TEST_CONTENT(syscall_name, errno, expected) \ |
| ProcessController controller(this, session(), loop()); \ |
| controller.Initialize( \ |
| session(), \ |
| std::make_unique<SyscallDisplayDispatcherTest>(nullptr, decode_options_, display_options_, \ |
| result_, &controller, aborted_), \ |
| ""); \ |
| SyscallDecoderDispatcher* dispatcher = controller.workflow().syscall_decoder_dispatcher(); \ |
| Syscall* syscall = dispatcher->SearchSyscall(syscall_name); \ |
| ASSERT_NE(syscall, nullptr); \ |
| std::stringstream output_stream; \ |
| if (syscall->invoked_bp_instructions().size() > 0) { \ |
| output_stream << "Invoked bp instructions:\n"; \ |
| for (auto instr : syscall->invoked_bp_instructions()) { \ |
| output_stream << " " << instr.ToString(); \ |
| } \ |
| } \ |
| if (syscall->exit_bp_instructions().size() > 0) { \ |
| output_stream << "Exit bp instructions:\n"; \ |
| for (auto instr : syscall->exit_bp_instructions()) { \ |
| output_stream << " " << instr.ToString(); \ |
| } \ |
| } \ |
| ASSERT_EQ(output_stream.str(), expected); |
| |
| #define CREATE_AUTOMATION_TEST(test_name, syscall_name, errno, expected_x64, expected_arm) \ |
| TEST_F(InterceptionWorkflowTestX64, test_name) { \ |
| AUTOMATION_TEST_CONTENT(syscall_name, errno, expected_x64); \ |
| } \ |
| TEST_F(InterceptionWorkflowTestArm, test_name) { \ |
| AUTOMATION_TEST_CONTENT(syscall_name, errno, expected_arm); \ |
| } |
| |
| } // namespace fidlcat |
| |
| #endif // TOOLS_FIDLCAT_INTERCEPTION_TESTS_INTERCEPTION_WORKFLOW_TEST_H_ |