blob: 7b871e508df02e8c1e70bad495194115eed65de5 [file] [log] [blame] [edit]
// 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_