blob: a41217f9504981a04c1dede2fb9df431333fb571 [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.
#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/protocol.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_(result), result_name_(result_name) {}
const std::string& name() const { return name_; }
int64_t result() const { return result_; }
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); }
private:
const std::string name_;
const int64_t result_;
const std::string result_name_;
std::vector<uint64_t> inputs_;
};
// Data for syscall tests.
class DataForSyscallTest {
public:
DataForSyscallTest(debug_ipc::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_ipc::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_ipc::RegisterID register_id, uint64_t value,
std::vector<debug_ipc::Register>* registers) {
debug_ipc::Register& 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_ipc::Register>* 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_ipc::Arch::kArm64) {
PopulateRegister(debug_ipc::RegisterID::kARMv8_x0, syscall_->result(), registers);
} else {
PopulateRegister(debug_ipc::RegisterID::kX64_rax, syscall_->result(), registers);
}
}
}
if (arch_ == debug_ipc::Arch::kArm64) {
// stack pointer
PopulateRegister(debug_ipc::RegisterID::kARMv8_sp, reinterpret_cast<uint64_t>(sp_),
registers);
// link register
PopulateRegister(debug_ipc::RegisterID::kARMv8_lr, kReturnAddress, registers);
} else if (arch_ == debug_ipc::Arch::kX64) {
// stack pointer
PopulateRegister(debug_ipc::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_ipc::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_ipc::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_ipc::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_ipc::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_ipc::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.process_koid, &reply.registers);
debug_ipc::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.process_koid);
debug_ipc::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_ipc::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_ipc::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 PerformCheckTest(const char* syscall_name, std::unique_ptr<SystemCallTest> syscall1,
std::unique_ptr<SystemCallTest> syscall2);
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;
virtual debug_ipc::Arch GetArch() const override { return debug_ipc::Arch::kX64; }
};
class InterceptionWorkflowTestArm : public InterceptionWorkflowTest {
public:
InterceptionWorkflowTestArm() : InterceptionWorkflowTest(GetArch(), false) {}
~InterceptionWorkflowTestArm() override = default;
virtual debug_ipc::Arch GetArch() const override { return debug_ipc::Arch::kArm64; }
};
class InterceptionWorkflowTestX64Aborted : public InterceptionWorkflowTest {
public:
InterceptionWorkflowTestX64Aborted() : InterceptionWorkflowTest(GetArch(), true) {}
~InterceptionWorkflowTestX64Aborted() override = default;
virtual debug_ipc::Arch GetArch() const override { return debug_ipc::Arch::kX64; }
};
class InterceptionWorkflowTestArmAborted : public InterceptionWorkflowTest {
public:
InterceptionWorkflowTestArmAborted() : InterceptionWorkflowTest(GetArch(), true) {}
~InterceptionWorkflowTestArmAborted() override = default;
virtual debug_ipc::Arch GetArch() const override { return debug_ipc::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_ipc::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 SyscallCheck : public SyscallUse {
public:
explicit SyscallCheck(ProcessController* controller) : controller_(controller) {}
void SyscallOutputsDecoded(SyscallDecoder* decoder) override {
if (decoder->syscall()->name() == "zx_channel_write") {
DataForSyscallTest& data = controller_->remote_api()->data();
FX_DCHECK(decoder->ArgumentValue(0) == kHandle); // handle
FX_DCHECK(decoder->ArgumentValue(1) == 0); // options
FX_DCHECK(decoder->ArgumentLoaded(Stage::kEntry, 2, data.num_bytes()));
uint8_t* bytes = decoder->ArgumentContent(Stage::kEntry, 2);
if (memcmp(bytes, data.bytes(), data.num_bytes()) != 0) {
std::string result = "bytes not equivalent\n";
AppendElements(result, bytes, data.bytes(), data.num_bytes());
FAIL() << result;
}
FX_DCHECK(decoder->ArgumentValue(3) == data.num_bytes()); // num_bytes
FX_DCHECK(
decoder->ArgumentLoaded(Stage::kEntry, 4, data.num_handles() * sizeof(zx_handle_t)));
zx_handle_t* handles =
reinterpret_cast<zx_handle_t*>(decoder->ArgumentContent(Stage::kEntry, 4));
if (memcmp(handles, data.handles(), data.num_handles()) != 0) {
std::string result = "handles not equivalent";
AppendElements(result, handles, data.handles(), data.num_handles());
FAIL() << result;
}
FX_DCHECK(decoder->ArgumentValue(5) == data.num_handles()); // num_handles
} else if (decoder->syscall()->name() == "zx_channel_call") {
DataForSyscallTest& data = controller_->remote_api()->data();
FX_DCHECK(decoder->ArgumentValue(0) == kHandle); // handle
FX_DCHECK(decoder->ArgumentValue(1) == 0); // options
FX_DCHECK(decoder->ArgumentValue(2) == ZX_TIME_INFINITE); // deadline
FX_DCHECK(decoder->ArgumentLoaded(Stage::kEntry, 3, sizeof(zx_channel_call_args_t)));
const zx_channel_call_args_t* args = reinterpret_cast<const zx_channel_call_args_t*>(
decoder->ArgumentContent(Stage::kEntry, 3));
uint8_t* ref_bytes;
uint32_t ref_num_bytes;
if (data.use_alternate_data()) {
ref_bytes = data.bytes2();
ref_num_bytes = data.num_bytes2();
} else {
ref_bytes = data.bytes();
ref_num_bytes = data.num_bytes();
}
FX_DCHECK(args->wr_num_bytes == ref_num_bytes);
FX_DCHECK(decoder->BufferLoaded(Stage::kExit, uint64_t(args->wr_bytes), args->wr_num_bytes));
uint8_t* bytes = decoder->BufferContent(Stage::kExit, uint64_t(args->wr_bytes));
if (memcmp(bytes, ref_bytes, ref_num_bytes) != 0) {
std::string result = "bytes not equivalent\n";
AppendElements(result, bytes, ref_bytes, ref_num_bytes);
FAIL() << result;
}
} else {
FAIL() << "can't check " << decoder->syscall()->name();
}
}
void SyscallDecodingError(const DecoderError& error, SyscallDecoder* decoder) override {
SyscallUse::SyscallDecodingError(error, decoder);
FAIL();
}
private:
ProcessController* controller_;
};
class SyscallDecoderDispatcherTest : public SyscallDecoderDispatcher {
public:
SyscallDecoderDispatcherTest(const DecodeOptions& decode_options, ProcessController* controller)
: SyscallDecoderDispatcher(decode_options), controller_(controller) {}
std::unique_ptr<SyscallDecoder> CreateDecoder(InterceptingThreadObserver* thread_observer,
zxdb::Thread* thread, const Syscall* syscall,
uint64_t timestamp) override {
return std::make_unique<SyscallDecoder>(this, thread_observer, thread, syscall,
std::make_unique<SyscallCheck>(controller_), 0);
}
std::unique_ptr<ExceptionDecoder> CreateDecoder(InterceptionWorkflow* workflow,
zxdb::Thread* thread,
uint64_t timestamp) override {
return nullptr;
}
void DeleteDecoder(SyscallDecoder* decoder) override {
SyscallDecoderDispatcher::DeleteDecoder(decoder);
AlwaysQuit aq(controller_);
}
void DeleteDecoder(ExceptionDecoder* decoder) override {
SyscallDecoderDispatcher::DeleteDecoder(decoder);
AlwaysQuit aq(controller_);
}
private:
ProcessController* controller_;
};
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_; }
std::unique_ptr<SyscallDecoder> CreateDecoder(InterceptingThreadObserver* thread_observer,
zxdb::Thread* thread, const Syscall* syscall,
uint64_t timestamp) override {
return std::make_unique<SyscallDecoder>(this, thread_observer, thread, syscall,
std::make_unique<SyscallDisplay>(this, os()), 0);
}
std::unique_ptr<ExceptionDecoder> CreateDecoder(InterceptionWorkflow* workflow,
zxdb::Thread* thread,
uint64_t timestamp) override {
return std::make_unique<ExceptionDecoder>(workflow, this, thread,
std::make_unique<ExceptionDisplay>(this, os()), 0);
}
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_;
};
} // namespace fidlcat
#endif // TOOLS_FIDLCAT_INTERCEPTION_TESTS_INTERCEPTION_WORKFLOW_TEST_H_