From 2686251fde60de8ec92f800458ad14577ab37031 Mon Sep 17 00:00:00 2001 From: Maurice Heumann Date: Mon, 10 Feb 2025 13:42:49 +0100 Subject: [PATCH] Cleanup windows-emulator and extract classes --- src/common/utils/moved_marker.hpp | 46 ++ src/windows-emulator/emulator_thread.cpp | 243 ++++++++++ src/windows-emulator/emulator_thread.hpp | 199 ++++++++ src/windows-emulator/exception_dispatch.cpp | 199 ++++++++ src/windows-emulator/exception_dispatch.hpp | 24 + src/windows-emulator/handles.hpp | 2 + src/windows-emulator/memory_manager.cpp | 4 - src/windows-emulator/memory_manager.hpp | 4 + src/windows-emulator/process_context.hpp | 484 +------------------- src/windows-emulator/syscalls.cpp | 12 +- src/windows-emulator/windows_emulator.cpp | 434 +----------------- src/windows-emulator/windows_objects.hpp | 252 ++++++++++ 12 files changed, 980 insertions(+), 923 deletions(-) create mode 100644 src/common/utils/moved_marker.hpp create mode 100644 src/windows-emulator/emulator_thread.cpp create mode 100644 src/windows-emulator/emulator_thread.hpp create mode 100644 src/windows-emulator/exception_dispatch.cpp create mode 100644 src/windows-emulator/exception_dispatch.hpp create mode 100644 src/windows-emulator/windows_objects.hpp diff --git a/src/common/utils/moved_marker.hpp b/src/common/utils/moved_marker.hpp new file mode 100644 index 00000000..8453cc57 --- /dev/null +++ b/src/common/utils/moved_marker.hpp @@ -0,0 +1,46 @@ +#pragma once +#include + +namespace utils +{ + class moved_marker + { + public: + moved_marker() = default; + + moved_marker(const moved_marker& copy) = default; + moved_marker& operator=(const moved_marker&) = default; + + moved_marker(moved_marker&& obj) noexcept + : moved_marker() + { + this->operator=(std::move(obj)); + } + + moved_marker& operator=(moved_marker&& obj) noexcept + { + if (this != &obj) + { + this->was_moved_ = obj.was_moved_; + obj.was_moved_ = true; + } + + return *this; + } + + ~moved_marker() = default; + + bool was_moved() const + { + return this->was_moved_; + } + + void mark_as_moved() + { + this->was_moved_ = true; + } + + private: + bool was_moved_{false}; + }; +} diff --git a/src/windows-emulator/emulator_thread.cpp b/src/windows-emulator/emulator_thread.cpp new file mode 100644 index 00000000..0ba0fde2 --- /dev/null +++ b/src/windows-emulator/emulator_thread.cpp @@ -0,0 +1,243 @@ +#include "emulator_thread.hpp" + +#include "context_frame.hpp" +#include "process_context.hpp" + +namespace +{ + template + emulator_object allocate_object_on_stack(x64_emulator& emu) + { + const auto old_sp = emu.reg(x64_register::rsp); + const auto new_sp = align_down(old_sp - sizeof(T), std::max(alignof(T), alignof(x64_emulator::pointer_type))); + emu.reg(x64_register::rsp, new_sp); + return {emu, new_sp}; + } + + void unalign_stack(x64_emulator& emu) + { + auto sp = emu.reg(x64_register::rsp); + sp = align_down(sp - 0x10, 0x10) + 8; + emu.reg(x64_register::rsp, sp); + } + + void setup_stack(x64_emulator& emu, const uint64_t stack_base, const size_t stack_size) + { + const uint64_t stack_end = stack_base + stack_size; + emu.reg(x64_register::rsp, stack_end); + } + + void setup_gs_segment(x64_emulator& emu, const emulator_allocator& allocator) + { + struct msr_value + { + uint32_t id; + uint64_t value; + }; + + const msr_value value{IA32_GS_BASE_MSR, allocator.get_base()}; + + emu.write_register(x64_register::msr, &value, sizeof(value)); + } + + bool is_object_signaled(process_context& c, const handle h, const uint32_t current_thread_id) + { + const auto type = h.value.type; + + switch (type) + { + default: + break; + + case handle_types::event: { + if (h.value.is_pseudo) + { + return true; + } + + auto* e = c.events.get(h); + if (e) + { + return e->is_signaled(); + } + + break; + } + + case handle_types::mutant: { + auto* e = c.mutants.get(h); + return !e || e->try_lock(current_thread_id); + } + + case handle_types::semaphore: { + auto* s = c.semaphores.get(h); + if (s) + { + return s->try_lock(); + } + + break; + } + + case handle_types::thread: { + const auto* t = c.threads.get(h); + if (t) + { + return t->is_terminated(); + } + + break; + } + } + + throw std::runtime_error("Bad object: " + std::to_string(h.value.type)); + } +} + +emulator_thread::emulator_thread(memory_manager& memory, const process_context& context, const uint64_t start_address, + const uint64_t argument, const uint64_t stack_size, const uint32_t id) + : memory_ptr(&memory), + stack_size(page_align_up(std::max(stack_size, static_cast(STACK_SIZE)))), + start_address(start_address), + argument(argument), + id(id), + last_registers(context.default_register_set) +{ + this->stack_base = memory.allocate_memory(this->stack_size, memory_permission::read_write); + + this->gs_segment = emulator_allocator{ + memory, + memory.allocate_memory(GS_SEGMENT_SIZE, memory_permission::read_write), + GS_SEGMENT_SIZE, + }; + + this->teb = this->gs_segment->reserve(); + + this->teb->access([&](TEB64& teb_obj) { + // Skips GetCurrentNlsCache + // This hack can be removed once this is fixed: + // https://github.com/momo5502/emulator/issues/128 + reinterpret_cast(&teb_obj)[0x179C] = 1; + + teb_obj.ClientId.UniqueProcess = 1ul; + teb_obj.ClientId.UniqueThread = static_cast(this->id); + teb_obj.NtTib.StackLimit = reinterpret_cast(this->stack_base); + teb_obj.NtTib.StackBase = reinterpret_cast(this->stack_base + this->stack_size); + teb_obj.NtTib.Self = &this->teb->ptr()->NtTib; + teb_obj.ProcessEnvironmentBlock = context.peb.ptr(); + }); +} + +void emulator_thread::mark_as_ready(const NTSTATUS status) +{ + this->pending_status = status; + this->await_time = {}; + this->await_objects = {}; + + // TODO: Find out if this is correct + if (this->waiting_for_alert) + { + this->alerted = false; + } + + this->waiting_for_alert = false; +} + +bool emulator_thread::is_terminated() const +{ + return this->exit_status.has_value(); +} + +bool emulator_thread::is_thread_ready(process_context& process) +{ + if (this->is_terminated()) + { + return false; + } + + if (this->waiting_for_alert) + { + if (this->alerted) + { + this->mark_as_ready(STATUS_ALERTED); + return true; + } + if (this->is_await_time_over()) + { + this->mark_as_ready(STATUS_TIMEOUT); + return true; + } + + return false; + } + + if (!this->await_objects.empty()) + { + bool all_signaled = true; + for (uint32_t i = 0; i < this->await_objects.size(); ++i) + { + const auto& obj = this->await_objects[i]; + + const auto signaled = is_object_signaled(process, obj, this->id); + all_signaled &= signaled; + + if (signaled && this->await_any) + { + this->mark_as_ready(STATUS_WAIT_0 + i); + return true; + } + } + + if (!this->await_any && all_signaled) + { + this->mark_as_ready(STATUS_SUCCESS); + return true; + } + + if (this->is_await_time_over()) + { + this->mark_as_ready(STATUS_TIMEOUT); + return true; + } + + return false; + } + + if (this->await_time.has_value()) + { + if (this->is_await_time_over()) + { + this->mark_as_ready(STATUS_SUCCESS); + return true; + } + + return false; + } + + return true; +} + +void emulator_thread::setup_registers(x64_emulator& emu, const process_context& context) const +{ + setup_stack(emu, this->stack_base, this->stack_size); + setup_gs_segment(emu, *this->gs_segment); + + CONTEXT64 ctx{}; + ctx.ContextFlags = CONTEXT64_ALL; + + unalign_stack(emu); + context_frame::save(emu, ctx); + + ctx.Rip = context.rtl_user_thread_start; + ctx.Rcx = this->start_address; + ctx.Rdx = this->argument; + + const auto ctx_obj = allocate_object_on_stack(emu); + ctx_obj.write(ctx); + + unalign_stack(emu); + + emu.reg(x64_register::rcx, ctx_obj.value()); + emu.reg(x64_register::rdx, context.ntdll->image_base); + emu.reg(x64_register::rip, context.ldr_initialize_thunk); +} diff --git a/src/windows-emulator/emulator_thread.hpp b/src/windows-emulator/emulator_thread.hpp new file mode 100644 index 00000000..2908b9a9 --- /dev/null +++ b/src/windows-emulator/emulator_thread.hpp @@ -0,0 +1,199 @@ +#pragma once + +#include "handles.hpp" +#include "emulator_utils.hpp" +#include "memory_manager.hpp" + +#include + +struct process_context; + +class emulator_thread : public ref_counted_object +{ + public: + emulator_thread(memory_manager& memory) + : memory_ptr(&memory) + { + } + + emulator_thread(utils::buffer_deserializer& buffer) + : emulator_thread(buffer.read().get()) + { + } + + emulator_thread(memory_manager& memory, const process_context& context, uint64_t start_address, uint64_t argument, + uint64_t stack_size, uint32_t id); + + emulator_thread(const emulator_thread&) = delete; + emulator_thread& operator=(const emulator_thread&) = delete; + + emulator_thread(emulator_thread&& obj) noexcept = default; + emulator_thread& operator=(emulator_thread&& obj) noexcept = default; + + ~emulator_thread() override + { + this->release(); + } + + utils::moved_marker marker{}; + + memory_manager* memory_ptr{}; + + uint64_t stack_base{}; + uint64_t stack_size{}; + uint64_t start_address{}; + uint64_t argument{}; + uint64_t executed_instructions{0}; + + uint32_t id{}; + + std::u16string name{}; + + std::optional exit_status{}; + std::vector await_objects{}; + bool await_any{false}; + bool waiting_for_alert{false}; + bool alerted{false}; + std::optional await_time{}; + + std::optional pending_status{}; + + std::optional gs_segment; + std::optional> teb; + + std::vector last_registers{}; + + void mark_as_ready(NTSTATUS status); + + bool is_await_time_over() const + { + return this->await_time.has_value() && this->await_time.value() < std::chrono::steady_clock::now(); + } + + bool is_terminated() const; + + bool is_thread_ready(process_context& process); + + void save(x64_emulator& emu) + { + this->last_registers = emu.save_registers(); + } + + void restore(x64_emulator& emu) const + { + emu.restore_registers(this->last_registers); + } + + void setup_if_necessary(x64_emulator& emu, const process_context& context) + { + if (!this->executed_instructions) + { + this->setup_registers(emu, context); + } + + if (this->pending_status.has_value()) + { + const auto status = *this->pending_status; + this->pending_status = {}; + + emu.reg(x64_register::rax, static_cast(status)); + } + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + if (this->marker.was_moved()) + { + throw std::runtime_error("Object was moved!"); + } + + buffer.write(this->stack_base); + buffer.write(this->stack_size); + buffer.write(this->start_address); + buffer.write(this->argument); + buffer.write(this->executed_instructions); + buffer.write(this->id); + + buffer.write_string(this->name); + + buffer.write_optional(this->exit_status); + buffer.write_vector(this->await_objects); + buffer.write(this->await_any); + + buffer.write(this->waiting_for_alert); + buffer.write(this->alerted); + + buffer.write_optional(this->await_time); + buffer.write_optional(this->pending_status); + buffer.write_optional(this->gs_segment); + buffer.write_optional(this->teb); + + buffer.write_vector(this->last_registers); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + if (this->marker.was_moved()) + { + throw std::runtime_error("Object was moved!"); + } + + this->release(); + + buffer.read(this->stack_base); + buffer.read(this->stack_size); + buffer.read(this->start_address); + buffer.read(this->argument); + buffer.read(this->executed_instructions); + buffer.read(this->id); + + buffer.read_string(this->name); + + buffer.read_optional(this->exit_status); + buffer.read_vector(this->await_objects); + buffer.read(this->await_any); + + buffer.read(this->waiting_for_alert); + buffer.read(this->alerted); + + buffer.read_optional(this->await_time); + buffer.read_optional(this->pending_status); + buffer.read_optional(this->gs_segment, [this] { return emulator_allocator(*this->memory_ptr); }); + buffer.read_optional(this->teb, [this] { return emulator_object(*this->memory_ptr); }); + + buffer.read_vector(this->last_registers); + } + + void leak_memory() + { + this->marker.mark_as_moved(); + } + + private: + void setup_registers(x64_emulator& emu, const process_context& context) const; + + void release() + { + if (this->marker.was_moved()) + { + return; + } + + if (this->stack_base) + { + if (!this->memory_ptr) + { + throw std::runtime_error("Emulator was never assigned!"); + } + + this->memory_ptr->release_memory(this->stack_base, this->stack_size); + this->stack_base = 0; + } + + if (this->gs_segment) + { + this->gs_segment->release(*this->memory_ptr); + this->gs_segment = {}; + } + } +}; diff --git a/src/windows-emulator/exception_dispatch.cpp b/src/windows-emulator/exception_dispatch.cpp new file mode 100644 index 00000000..d5ff06b8 --- /dev/null +++ b/src/windows-emulator/exception_dispatch.cpp @@ -0,0 +1,199 @@ +#include "exception_dispatch.hpp" +#include "process_context.hpp" +#include "context_frame.hpp" + +#include + +namespace +{ + using exception_record = EMU_EXCEPTION_RECORD>; + using exception_record_map = std::unordered_map>; + + emulator_object save_exception_record(emulator_allocator& allocator, + const exception_record& record, + exception_record_map& record_mapping) + { + const auto record_obj = allocator.reserve(); + record_obj.write(record); + + if (record.ExceptionRecord) + { + record_mapping.emplace(&record, record_obj); + + emulator_object nested_record_obj{allocator.get_memory()}; + const auto nested_record = record_mapping.find(reinterpret_cast(record.ExceptionRecord)); + + if (nested_record != record_mapping.end()) + { + nested_record_obj = nested_record->second; + } + else + { + nested_record_obj = save_exception_record( + allocator, *reinterpret_cast(record.ExceptionRecord), record_mapping); + } + + record_obj.access([&](exception_record& r) { + r.ExceptionRecord = reinterpret_cast::PVOID>(nested_record_obj.ptr()); + }); + } + + return record_obj; + } + + emulator_object save_exception_record(emulator_allocator& allocator, + const exception_record& record) + { + exception_record_map record_mapping{}; + return save_exception_record(allocator, record, record_mapping); + } + + uint32_t map_violation_operation_to_parameter(const memory_operation operation) + { + switch (operation) + { + default: + case memory_operation::read: + return 0; + case memory_operation::write: + case memory_operation::exec: + return 1; + } + } + + size_t calculate_exception_record_size(const exception_record& record) + { + std::unordered_set records{}; + size_t total_size = 0; + + const exception_record* current_record = &record; + while (current_record) + { + if (!records.insert(current_record).second) + { + break; + } + + total_size += sizeof(*current_record); + current_record = reinterpret_cast(record.ExceptionRecord); + } + + return total_size; + } + + struct machine_frame + { + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + }; + + void dispatch_exception_pointers(x64_emulator& emu, const uint64_t dispatcher, + const EMU_EXCEPTION_POINTERS> pointers) + { + constexpr auto mach_frame_size = 0x40; + constexpr auto context_record_size = 0x4F0; + const auto exception_record_size = + calculate_exception_record_size(*reinterpret_cast(pointers.ExceptionRecord)); + const auto combined_size = align_up(exception_record_size + context_record_size, 0x10); + + assert(combined_size == 0x590); + + const auto allocation_size = combined_size + mach_frame_size; + + const auto initial_sp = emu.reg(x64_register::rsp); + const auto new_sp = align_down(initial_sp - allocation_size, 0x100); + + const auto total_size = initial_sp - new_sp; + assert(total_size >= allocation_size); + + std::vector zero_memory{}; + zero_memory.resize(total_size, 0); + + emu.write_memory(new_sp, zero_memory.data(), zero_memory.size()); + + emu.reg(x64_register::rsp, new_sp); + emu.reg(x64_register::rip, dispatcher); + + const emulator_object context_record_obj{emu, new_sp}; + context_record_obj.write(*reinterpret_cast(pointers.ContextRecord)); + + emulator_allocator allocator{emu, new_sp + context_record_size, exception_record_size}; + const auto exception_record_obj = + save_exception_record(allocator, *reinterpret_cast(pointers.ExceptionRecord)); + + if (exception_record_obj.value() != allocator.get_base()) + { + throw std::runtime_error("Bad exception record position on stack"); + } + + const emulator_object machine_frame_obj{emu, new_sp + combined_size}; + machine_frame_obj.access([&](machine_frame& frame) { + const auto& record = *reinterpret_cast(pointers.ContextRecord); + frame.rip = record.Rip; + frame.rsp = record.Rsp; + frame.ss = record.SegSs; + frame.cs = record.SegCs; + frame.eflags = record.EFlags; + }); + } +} + +void dispatch_exception(x64_emulator& emu, const process_context& proc, const DWORD status, + const std::vector::ULONG_PTR>& parameters) +{ + CONTEXT64 ctx{}; + ctx.ContextFlags = CONTEXT64_ALL; + context_frame::save(emu, ctx); + + exception_record record{}; + memset(&record, 0, sizeof(record)); + record.ExceptionCode = status; + record.ExceptionFlags = 0; + record.ExceptionRecord = 0; + record.ExceptionAddress = emu.read_instruction_pointer(); + record.NumberParameters = static_cast(parameters.size()); + + if (parameters.size() > 15) + { + throw std::runtime_error("Too many exception parameters"); + } + + for (size_t i = 0; i < parameters.size(); ++i) + { + record.ExceptionInformation[i] = parameters[i]; + } + + EMU_EXCEPTION_POINTERS> pointers{}; + pointers.ContextRecord = reinterpret_cast::PVOID>(&ctx); + pointers.ExceptionRecord = reinterpret_cast::PVOID>(&record); + + dispatch_exception_pointers(emu, proc.ki_user_exception_dispatcher, pointers); +} + +void dispatch_access_violation(x64_emulator& emu, const process_context& proc, const uint64_t address, + const memory_operation operation) +{ + dispatch_exception(emu, proc, STATUS_ACCESS_VIOLATION, + { + map_violation_operation_to_parameter(operation), + address, + }); +} + +void dispatch_illegal_instruction_violation(x64_emulator& emu, const process_context& proc) +{ + dispatch_exception(emu, proc, STATUS_ILLEGAL_INSTRUCTION, {}); +} + +void dispatch_integer_division_by_zero(x64_emulator& emu, const process_context& proc) +{ + dispatch_exception(emu, proc, STATUS_INTEGER_DIVIDE_BY_ZERO, {}); +} + +void dispatch_single_step(x64_emulator& emu, const process_context& proc) +{ + dispatch_exception(emu, proc, STATUS_SINGLE_STEP, {}); +} diff --git a/src/windows-emulator/exception_dispatch.hpp b/src/windows-emulator/exception_dispatch.hpp new file mode 100644 index 00000000..7befde5c --- /dev/null +++ b/src/windows-emulator/exception_dispatch.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include +#include + +struct process_context; + +void dispatch_exception(x64_emulator& emu, const process_context& proc, DWORD status, + const std::vector::ULONG_PTR>& parameters); +template + requires(std::is_integral_v && !std::is_same_v) +void dispatch_exception(x64_emulator& emu, const process_context& proc, const T status, + const std::vector::ULONG_PTR>& parameters) +{ + dispatch_exception(emu, proc, static_cast(status), parameters); +} + +void dispatch_access_violation(x64_emulator& emu, const process_context& proc, uint64_t address, + memory_operation operation); +void dispatch_illegal_instruction_violation(x64_emulator& emu, const process_context& proc); +void dispatch_integer_division_by_zero(x64_emulator& emu, const process_context& proc); +void dispatch_single_step(x64_emulator& emu, const process_context& proc); diff --git a/src/windows-emulator/handles.hpp b/src/windows-emulator/handles.hpp index 4646bd3e..de78b99b 100644 --- a/src/windows-emulator/handles.hpp +++ b/src/windows-emulator/handles.hpp @@ -1,5 +1,7 @@ #pragma once +#include + struct handle_types { enum type : uint16_t diff --git a/src/windows-emulator/memory_manager.cpp b/src/windows-emulator/memory_manager.cpp index 8af1222a..214cfaa2 100644 --- a/src/windows-emulator/memory_manager.cpp +++ b/src/windows-emulator/memory_manager.cpp @@ -10,10 +10,6 @@ namespace { - constexpr auto ALLOCATION_GRANULARITY = 0x0000000000010000ULL; - constexpr auto MIN_ALLOCATION_ADDRESS = 0x0000000000010000ULL; - constexpr auto MAX_ALLOCATION_ADDRESS = 0x00007ffffffeffffULL; - void split_regions(memory_manager::committed_region_map& regions, const std::vector& split_points) { for (auto i = regions.begin(); i != regions.end(); ++i) diff --git a/src/windows-emulator/memory_manager.hpp b/src/windows-emulator/memory_manager.hpp index 1ab6aa23..02e0aa07 100644 --- a/src/windows-emulator/memory_manager.hpp +++ b/src/windows-emulator/memory_manager.hpp @@ -8,6 +8,10 @@ #include +constexpr auto ALLOCATION_GRANULARITY = 0x0000000000010000ULL; +constexpr auto MIN_ALLOCATION_ADDRESS = 0x0000000000010000ULL; +constexpr auto MAX_ALLOCATION_ADDRESS = 0x00007ffffffeffffULL; + struct region_info : basic_memory_region { uint64_t allocation_base{}; diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index 9144ef26..dec59678 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -6,13 +6,13 @@ #include "module/module_manager.hpp" #include -#include #include -#include #include "io_device.hpp" #include "kusd_mmio.hpp" +#include "windows_objects.hpp" +#include "emulator_thread.hpp" #define PEB_SEGMENT_SIZE (20 << 20) // 20 MB #define GS_SEGMENT_SIZE (1 << 20) // 1 MB @@ -25,486 +25,6 @@ #define GDT_LIMIT 0x1000 #define GDT_ENTRY_SIZE 0x8 -class windows_emulator; - -struct event : ref_counted_object -{ - bool signaled{}; - EVENT_TYPE type{}; - std::u16string name{}; - - bool is_signaled() - { - const auto res = this->signaled; - - if (this->type == SynchronizationEvent) - { - this->signaled = false; - } - - return res; - } - - void serialize_object(utils::buffer_serializer& buffer) const override - { - buffer.write(this->signaled); - buffer.write(this->type); - buffer.write(this->name); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - buffer.read(this->signaled); - buffer.read(this->type); - buffer.read(this->name); - } -}; - -struct mutant : ref_counted_object -{ - uint32_t locked_count{0}; - uint32_t owning_thread_id{}; - std::u16string name{}; - - bool try_lock(const uint32_t thread_id) - { - if (this->locked_count == 0) - { - ++this->locked_count; - this->owning_thread_id = thread_id; - return true; - } - - if (this->owning_thread_id != thread_id) - { - return false; - } - - ++this->locked_count; - return true; - } - - std::pair release(const uint32_t thread_id) - { - const auto old_count = this->locked_count; - - if (this->locked_count <= 0 || this->owning_thread_id != thread_id) - { - return {old_count, false}; - } - - --this->locked_count; - return {old_count, true}; - } - - void serialize_object(utils::buffer_serializer& buffer) const override - { - buffer.write(this->locked_count); - buffer.write(this->owning_thread_id); - buffer.write(this->name); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - buffer.read(this->locked_count); - buffer.read(this->owning_thread_id); - buffer.read(this->name); - } -}; - -struct file_entry -{ - std::filesystem::path file_path{}; - - void serialize(utils::buffer_serializer& buffer) const - { - buffer.write(this->file_path); - } - - void deserialize(utils::buffer_deserializer& buffer) - { - buffer.read(this->file_path); - } -}; - -struct file_enumeration_state -{ - size_t current_index{0}; - std::vector files{}; - - void serialize(utils::buffer_serializer& buffer) const - { - buffer.write(this->current_index); - buffer.write_vector(this->files); - } - - void deserialize(utils::buffer_deserializer& buffer) - { - buffer.read(this->current_index); - buffer.read_vector(this->files); - } -}; - -struct file : ref_counted_object -{ - utils::file_handle handle{}; - std::u16string name{}; - std::optional enumeration_state{}; - - bool is_file() const - { - return this->handle; - } - - bool is_directory() const - { - return !this->is_file(); - } - - void serialize_object(utils::buffer_serializer& buffer) const override - { - // TODO: Serialize handle - buffer.write(this->name); - buffer.write_optional(this->enumeration_state); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - buffer.read(this->name); - buffer.read_optional(this->enumeration_state); - this->handle = {}; - } -}; - -struct section : ref_counted_object -{ - std::u16string name{}; - std::u16string file_name{}; - uint64_t maximum_size{}; - uint32_t section_page_protection{}; - uint32_t allocation_attributes{}; - - bool is_image() const - { - return this->allocation_attributes & SEC_IMAGE; - } - - void serialize_object(utils::buffer_serializer& buffer) const override - { - buffer.write(this->name); - buffer.write(this->file_name); - buffer.write(this->maximum_size); - buffer.write(this->section_page_protection); - buffer.write(this->allocation_attributes); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - buffer.read(this->name); - buffer.read(this->file_name); - buffer.read(this->maximum_size); - buffer.read(this->section_page_protection); - buffer.read(this->allocation_attributes); - } -}; - -struct semaphore : ref_counted_object -{ - std::u16string name{}; - uint32_t current_count{}; - uint32_t max_count{}; - - bool try_lock() - { - if (this->current_count > 0) - { - --this->current_count; - return true; - } - - return false; - } - - std::pair release(const uint32_t release_count) - { - const auto old_count = this->current_count; - - if (this->current_count + release_count > this->max_count) - { - return {old_count, false}; - } - - this->current_count += release_count; - - return {old_count, true}; - } - - void serialize_object(utils::buffer_serializer& buffer) const override - { - buffer.write(this->name); - buffer.write(this->current_count); - buffer.write(this->max_count); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - buffer.read(this->name); - buffer.read(this->current_count); - buffer.read(this->max_count); - } -}; - -struct port : ref_counted_object -{ - std::u16string name{}; - uint64_t view_base{}; - - void serialize_object(utils::buffer_serializer& buffer) const override - { - buffer.write(this->name); - buffer.write(this->view_base); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - buffer.read(this->name); - buffer.read(this->view_base); - } -}; - -struct process_context; - -class moved_marker -{ - public: - moved_marker() = default; - - moved_marker(const moved_marker& copy) = default; - moved_marker& operator=(const moved_marker&) = default; - - moved_marker(moved_marker&& obj) noexcept - : moved_marker() - { - this->operator=(std::move(obj)); - } - - moved_marker& operator=(moved_marker&& obj) noexcept - { - if (this != &obj) - { - this->was_moved_ = obj.was_moved_; - obj.was_moved_ = true; - } - - return *this; - } - - ~moved_marker() = default; - - bool was_moved() const - { - return this->was_moved_; - } - - void mark_as_moved() - { - this->was_moved_ = true; - } - - private: - bool was_moved_{false}; -}; - -class emulator_thread : public ref_counted_object -{ - public: - emulator_thread(memory_manager& memory) - : memory_ptr(&memory) - { - } - - emulator_thread(utils::buffer_deserializer& buffer) - : emulator_thread(buffer.read().get()) - { - } - - emulator_thread(memory_manager& memory, const process_context& context, uint64_t start_address, uint64_t argument, - uint64_t stack_size, uint32_t id); - - emulator_thread(const emulator_thread&) = delete; - emulator_thread& operator=(const emulator_thread&) = delete; - - emulator_thread(emulator_thread&& obj) noexcept = default; - emulator_thread& operator=(emulator_thread&& obj) noexcept = default; - - ~emulator_thread() override - { - this->release(); - } - - moved_marker marker{}; - - memory_manager* memory_ptr{}; - - uint64_t stack_base{}; - uint64_t stack_size{}; - uint64_t start_address{}; - uint64_t argument{}; - uint64_t executed_instructions{0}; - - uint32_t id{}; - - std::u16string name{}; - - std::optional exit_status{}; - std::vector await_objects{}; - bool await_any{false}; - bool waiting_for_alert{false}; - bool alerted{false}; - std::optional await_time{}; - - std::optional pending_status{}; - - std::optional gs_segment; - std::optional> teb; - - std::vector last_registers{}; - - void mark_as_ready(NTSTATUS status); - - bool is_await_time_over() const - { - return this->await_time.has_value() && this->await_time.value() < std::chrono::steady_clock::now(); - } - - bool is_terminated() const; - - bool is_thread_ready(windows_emulator& win_emu); - - void save(x64_emulator& emu) - { - this->last_registers = emu.save_registers(); - } - - void restore(x64_emulator& emu) const - { - emu.restore_registers(this->last_registers); - } - - void setup_if_necessary(x64_emulator& emu, const process_context& context) - { - if (!this->executed_instructions) - { - this->setup_registers(emu, context); - } - - if (this->pending_status.has_value()) - { - const auto status = *this->pending_status; - this->pending_status = {}; - - emu.reg(x64_register::rax, static_cast(status)); - } - } - - void serialize_object(utils::buffer_serializer& buffer) const override - { - if (this->marker.was_moved()) - { - throw std::runtime_error("Object was moved!"); - } - - buffer.write(this->stack_base); - buffer.write(this->stack_size); - buffer.write(this->start_address); - buffer.write(this->argument); - buffer.write(this->executed_instructions); - buffer.write(this->id); - - buffer.write_string(this->name); - - buffer.write_optional(this->exit_status); - buffer.write_vector(this->await_objects); - buffer.write(this->await_any); - - buffer.write(this->waiting_for_alert); - buffer.write(this->alerted); - - buffer.write_optional(this->await_time); - buffer.write_optional(this->pending_status); - buffer.write_optional(this->gs_segment); - buffer.write_optional(this->teb); - - buffer.write_vector(this->last_registers); - } - - void deserialize_object(utils::buffer_deserializer& buffer) override - { - if (this->marker.was_moved()) - { - throw std::runtime_error("Object was moved!"); - } - - this->release(); - - buffer.read(this->stack_base); - buffer.read(this->stack_size); - buffer.read(this->start_address); - buffer.read(this->argument); - buffer.read(this->executed_instructions); - buffer.read(this->id); - - buffer.read_string(this->name); - - buffer.read_optional(this->exit_status); - buffer.read_vector(this->await_objects); - buffer.read(this->await_any); - - buffer.read(this->waiting_for_alert); - buffer.read(this->alerted); - - buffer.read_optional(this->await_time); - buffer.read_optional(this->pending_status); - buffer.read_optional(this->gs_segment, [this] { return emulator_allocator(*this->memory_ptr); }); - buffer.read_optional(this->teb, [this] { return emulator_object(*this->memory_ptr); }); - - buffer.read_vector(this->last_registers); - } - - void leak_memory() - { - this->marker.mark_as_moved(); - } - - private: - void setup_registers(x64_emulator& emu, const process_context& context) const; - - void release() - { - if (this->marker.was_moved()) - { - return; - } - - if (this->stack_base) - { - if (!this->memory_ptr) - { - throw std::runtime_error("Emulator was never assigned!"); - } - - this->memory_ptr->release_memory(this->stack_base, this->stack_size); - this->stack_base = 0; - } - - if (this->gs_segment) - { - this->gs_segment->release(*this->memory_ptr); - this->gs_segment = {}; - } - } -}; - struct process_context { process_context(x64_emulator& emu, memory_manager& memory, file_system& file_sys) diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 9c9a4552..4695e0d8 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -1111,9 +1111,9 @@ namespace basic_info.PageSize = 0x1000; basic_info.LowestPhysicalPageNumber = 0x00000001; basic_info.HighestPhysicalPageNumber = 0x00c9c7ff; - basic_info.AllocationGranularity = 0x10000; - basic_info.MinimumUserModeAddress = 0x0000000000010000; - basic_info.MaximumUserModeAddress = 0x00007ffffffeffff; + basic_info.AllocationGranularity = ALLOCATION_GRANULARITY; + basic_info.MinimumUserModeAddress = MIN_ALLOCATION_ADDRESS; + basic_info.MaximumUserModeAddress = MAX_ALLOCATION_ADDRESS; basic_info.ActiveProcessorsAffinityMask = 0x0000000000000fff; basic_info.NumberOfProcessors = 1; }); @@ -1267,9 +1267,9 @@ namespace basic_info.PageSize = 0x1000; basic_info.LowestPhysicalPageNumber = 0x00000001; basic_info.HighestPhysicalPageNumber = 0x00c9c7ff; - basic_info.AllocationGranularity = 0x10000; - basic_info.MinimumUserModeAddress = 0x0000000000010000; - basic_info.MaximumUserModeAddress = 0x00007ffffffeffff; + basic_info.AllocationGranularity = ALLOCATION_GRANULARITY; + basic_info.MinimumUserModeAddress = MIN_ALLOCATION_ADDRESS; + basic_info.MaximumUserModeAddress = MAX_ALLOCATION_ADDRESS; basic_info.ActiveProcessorsAffinityMask = 0x0000000000000fff; basic_info.NumberOfProcessors = 1; }); diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 4652d847..358ef143 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -12,46 +12,12 @@ #include #include "apiset.hpp" +#include "exception_dispatch.hpp" constexpr auto MAX_INSTRUCTIONS_PER_TIME_SLICE = 100000; namespace { - template - emulator_object allocate_object_on_stack(x64_emulator& emu) - { - const auto old_sp = emu.reg(x64_register::rsp); - const auto new_sp = align_down(old_sp - sizeof(T), std::max(alignof(T), alignof(x64_emulator::pointer_type))); - emu.reg(x64_register::rsp, new_sp); - return {emu, new_sp}; - } - - void unalign_stack(x64_emulator& emu) - { - auto sp = emu.reg(x64_register::rsp); - sp = align_down(sp - 0x10, 0x10) + 8; - emu.reg(x64_register::rsp, sp); - } - - void setup_stack(x64_emulator& emu, const uint64_t stack_base, const size_t stack_size) - { - const uint64_t stack_end = stack_base + stack_size; - emu.reg(x64_register::rsp, stack_end); - } - - void setup_gs_segment(x64_emulator& emu, const emulator_allocator& allocator) - { - struct msr_value - { - uint32_t id; - uint64_t value; - }; - - const msr_value value{IA32_GS_BASE_MSR, allocator.get_base()}; - - emu.write_register(x64_register::msr, &value, sizeof(value)); - } - uint64_t copy_string(x64_emulator& emu, emulator_allocator& allocator, const void* base_ptr, const uint64_t offset, const size_t length) { @@ -318,199 +284,6 @@ namespace }); } - using exception_record = EMU_EXCEPTION_RECORD>; - using exception_record_map = std::unordered_map>; - - emulator_object save_exception_record(emulator_allocator& allocator, - const exception_record& record, - exception_record_map& record_mapping) - { - const auto record_obj = allocator.reserve(); - record_obj.write(record); - - if (record.ExceptionRecord) - { - record_mapping.emplace(&record, record_obj); - - emulator_object nested_record_obj{allocator.get_memory()}; - const auto nested_record = record_mapping.find(reinterpret_cast(record.ExceptionRecord)); - - if (nested_record != record_mapping.end()) - { - nested_record_obj = nested_record->second; - } - else - { - nested_record_obj = save_exception_record( - allocator, *reinterpret_cast(record.ExceptionRecord), record_mapping); - } - - record_obj.access([&](exception_record& r) { - r.ExceptionRecord = reinterpret_cast::PVOID>(nested_record_obj.ptr()); - }); - } - - return record_obj; - } - - emulator_object save_exception_record(emulator_allocator& allocator, - const exception_record& record) - { - exception_record_map record_mapping{}; - return save_exception_record(allocator, record, record_mapping); - } - - uint32_t map_violation_operation_to_parameter(const memory_operation operation) - { - switch (operation) - { - default: - case memory_operation::read: - return 0; - case memory_operation::write: - case memory_operation::exec: - return 1; - } - } - - size_t calculate_exception_record_size(const exception_record& record) - { - std::unordered_set records{}; - size_t total_size = 0; - - const exception_record* current_record = &record; - while (current_record) - { - if (!records.insert(current_record).second) - { - break; - } - - total_size += sizeof(*current_record); - current_record = reinterpret_cast(record.ExceptionRecord); - } - - return total_size; - } - - struct machine_frame - { - uint64_t rip; - uint64_t cs; - uint64_t eflags; - uint64_t rsp; - uint64_t ss; - }; - - void dispatch_exception_pointers(x64_emulator& emu, const uint64_t dispatcher, - const EMU_EXCEPTION_POINTERS> pointers) - { - constexpr auto mach_frame_size = 0x40; - constexpr auto context_record_size = 0x4F0; - const auto exception_record_size = - calculate_exception_record_size(*reinterpret_cast(pointers.ExceptionRecord)); - const auto combined_size = align_up(exception_record_size + context_record_size, 0x10); - - assert(combined_size == 0x590); - - const auto allocation_size = combined_size + mach_frame_size; - - const auto initial_sp = emu.reg(x64_register::rsp); - const auto new_sp = align_down(initial_sp - allocation_size, 0x100); - - const auto total_size = initial_sp - new_sp; - assert(total_size >= allocation_size); - - std::vector zero_memory{}; - zero_memory.resize(total_size, 0); - - emu.write_memory(new_sp, zero_memory.data(), zero_memory.size()); - - emu.reg(x64_register::rsp, new_sp); - emu.reg(x64_register::rip, dispatcher); - - const emulator_object context_record_obj{emu, new_sp}; - context_record_obj.write(*reinterpret_cast(pointers.ContextRecord)); - - emulator_allocator allocator{emu, new_sp + context_record_size, exception_record_size}; - const auto exception_record_obj = - save_exception_record(allocator, *reinterpret_cast(pointers.ExceptionRecord)); - - if (exception_record_obj.value() != allocator.get_base()) - { - throw std::runtime_error("Bad exception record position on stack"); - } - - const emulator_object machine_frame_obj{emu, new_sp + combined_size}; - machine_frame_obj.access([&](machine_frame& frame) { - const auto& record = *reinterpret_cast(pointers.ContextRecord); - frame.rip = record.Rip; - frame.rsp = record.Rsp; - frame.ss = record.SegSs; - frame.cs = record.SegCs; - frame.eflags = record.EFlags; - }); - } - - template - requires(std::is_integral_v) - void dispatch_exception(x64_emulator& emu, const process_context& proc, const T status, - const std::vector::ULONG_PTR>& parameters) - { - CONTEXT64 ctx{}; - ctx.ContextFlags = CONTEXT64_ALL; - context_frame::save(emu, ctx); - - exception_record record{}; - memset(&record, 0, sizeof(record)); - record.ExceptionCode = static_cast(status); - record.ExceptionFlags = 0; - record.ExceptionRecord = 0; - record.ExceptionAddress = emu.read_instruction_pointer(); - record.NumberParameters = static_cast(parameters.size()); - - if (parameters.size() > 15) - { - throw std::runtime_error("Too many exception parameters"); - } - - for (size_t i = 0; i < parameters.size(); ++i) - { - record.ExceptionInformation[i] = parameters[i]; - } - - EMU_EXCEPTION_POINTERS> pointers{}; - pointers.ContextRecord = reinterpret_cast::PVOID>(&ctx); - pointers.ExceptionRecord = reinterpret_cast::PVOID>(&record); - - dispatch_exception_pointers(emu, proc.ki_user_exception_dispatcher, pointers); - } - - void dispatch_access_violation(x64_emulator& emu, const process_context& proc, const uint64_t address, - const memory_operation operation) - { - dispatch_exception(emu, proc, STATUS_ACCESS_VIOLATION, - { - map_violation_operation_to_parameter(operation), - address, - }); - } - - void dispatch_illegal_instruction_violation(x64_emulator& emu, const process_context& proc) - { - dispatch_exception(emu, proc, STATUS_ILLEGAL_INSTRUCTION, {}); - } - - void dispatch_integer_division_by_zero(x64_emulator& emu, const process_context& proc) - { - dispatch_exception(emu, proc, STATUS_INTEGER_DIVIDE_BY_ZERO, {}); - } - - void dispatch_single_step(x64_emulator& emu, const process_context& proc) - { - dispatch_exception(emu, proc, STATUS_SINGLE_STEP, {}); - } - void perform_context_switch_work(windows_emulator& win_emu) { auto& devices = win_emu.process().devices; @@ -548,7 +321,7 @@ namespace auto& emu = win_emu.emu(); auto& context = win_emu.process(); - const auto is_ready = thread.is_thread_ready(win_emu); + const auto is_ready = thread.is_thread_ready(context); if (!is_ready && !force) { @@ -623,207 +396,6 @@ namespace return false; } - - bool is_object_signaled(process_context& c, const handle h, const uint32_t current_thread_id) - { - const auto type = h.value.type; - - switch (type) - { - default: - break; - - case handle_types::event: { - if (h.value.is_pseudo) - { - return true; - } - - auto* e = c.events.get(h); - if (e) - { - return e->is_signaled(); - } - - break; - } - - case handle_types::mutant: { - auto* e = c.mutants.get(h); - return !e || e->try_lock(current_thread_id); - } - - case handle_types::semaphore: { - auto* s = c.semaphores.get(h); - if (s) - { - return s->try_lock(); - } - - break; - } - - case handle_types::thread: { - const auto* t = c.threads.get(h); - if (t) - { - return t->is_terminated(); - } - - break; - } - } - - throw std::runtime_error("Bad object: " + std::to_string(h.value.type)); - } -} - -emulator_thread::emulator_thread(memory_manager& memory, const process_context& context, const uint64_t start_address, - const uint64_t argument, const uint64_t stack_size, const uint32_t id) - : memory_ptr(&memory), - stack_size(page_align_up(std::max(stack_size, static_cast(STACK_SIZE)))), - start_address(start_address), - argument(argument), - id(id), - last_registers(context.default_register_set) -{ - this->stack_base = memory.allocate_memory(this->stack_size, memory_permission::read_write); - - this->gs_segment = emulator_allocator{ - memory, - memory.allocate_memory(GS_SEGMENT_SIZE, memory_permission::read_write), - GS_SEGMENT_SIZE, - }; - - this->teb = this->gs_segment->reserve(); - - this->teb->access([&](TEB64& teb_obj) { - // Skips GetCurrentNlsCache - // This hack can be removed once this is fixed: - // https://github.com/momo5502/emulator/issues/128 - reinterpret_cast(&teb_obj)[0x179C] = 1; - - teb_obj.ClientId.UniqueProcess = 1ul; - teb_obj.ClientId.UniqueThread = static_cast(this->id); - teb_obj.NtTib.StackLimit = reinterpret_cast(this->stack_base); - teb_obj.NtTib.StackBase = reinterpret_cast(this->stack_base + this->stack_size); - teb_obj.NtTib.Self = &this->teb->ptr()->NtTib; - teb_obj.ProcessEnvironmentBlock = context.peb.ptr(); - }); -} - -void emulator_thread::mark_as_ready(const NTSTATUS status) -{ - this->pending_status = status; - this->await_time = {}; - this->await_objects = {}; - - // TODO: Find out if this is correct - if (this->waiting_for_alert) - { - this->alerted = false; - } - - this->waiting_for_alert = false; -} - -bool emulator_thread::is_terminated() const -{ - return this->exit_status.has_value(); -} - -bool emulator_thread::is_thread_ready(windows_emulator& win_emu) -{ - if (this->is_terminated()) - { - return false; - } - - if (this->waiting_for_alert) - { - if (this->alerted) - { - this->mark_as_ready(STATUS_ALERTED); - return true; - } - if (this->is_await_time_over()) - { - this->mark_as_ready(STATUS_TIMEOUT); - return true; - } - - return false; - } - - if (!this->await_objects.empty()) - { - bool all_signaled = true; - for (uint32_t i = 0; i < this->await_objects.size(); ++i) - { - const auto& obj = this->await_objects[i]; - - const auto signaled = is_object_signaled(win_emu.process(), obj, this->id); - all_signaled &= signaled; - - if (signaled && this->await_any) - { - this->mark_as_ready(STATUS_WAIT_0 + i); - return true; - } - } - - if (!this->await_any && all_signaled) - { - this->mark_as_ready(STATUS_SUCCESS); - return true; - } - - if (this->is_await_time_over()) - { - this->mark_as_ready(STATUS_TIMEOUT); - return true; - } - - return false; - } - - if (this->await_time.has_value()) - { - if (this->is_await_time_over()) - { - this->mark_as_ready(STATUS_SUCCESS); - return true; - } - - return false; - } - - return true; -} - -void emulator_thread::setup_registers(x64_emulator& emu, const process_context& context) const -{ - setup_stack(emu, this->stack_base, this->stack_size); - setup_gs_segment(emu, *this->gs_segment); - - CONTEXT64 ctx{}; - ctx.ContextFlags = CONTEXT64_ALL; - - unalign_stack(emu); - context_frame::save(emu, ctx); - - ctx.Rip = context.rtl_user_thread_start; - ctx.Rcx = this->start_address; - ctx.Rdx = this->argument; - - const auto ctx_obj = allocate_object_on_stack(emu); - ctx_obj.write(ctx); - - unalign_stack(emu); - - emu.reg(x64_register::rcx, ctx_obj.value()); - emu.reg(x64_register::rdx, context.ntdll->image_base); - emu.reg(x64_register::rip, context.ldr_initialize_thunk); } std::unique_ptr create_default_x64_emulator() @@ -1146,7 +718,7 @@ void windows_emulator::start(std::chrono::nanoseconds timeout, size_t count) while (true) { - if (this->switch_thread_ || !this->current_thread().is_thread_ready(*this)) + if (this->switch_thread_ || !this->current_thread().is_thread_ready(this->process())) { this->perform_thread_switch(); } diff --git a/src/windows-emulator/windows_objects.hpp b/src/windows-emulator/windows_objects.hpp new file mode 100644 index 00000000..57ea509f --- /dev/null +++ b/src/windows-emulator/windows_objects.hpp @@ -0,0 +1,252 @@ +#pragma once + +#include "handles.hpp" + +#include +#include +#include + +struct event : ref_counted_object +{ + bool signaled{}; + EVENT_TYPE type{}; + std::u16string name{}; + + bool is_signaled() + { + const auto res = this->signaled; + + if (this->type == SynchronizationEvent) + { + this->signaled = false; + } + + return res; + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + buffer.write(this->signaled); + buffer.write(this->type); + buffer.write(this->name); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->signaled); + buffer.read(this->type); + buffer.read(this->name); + } +}; + +struct mutant : ref_counted_object +{ + uint32_t locked_count{0}; + uint32_t owning_thread_id{}; + std::u16string name{}; + + bool try_lock(const uint32_t thread_id) + { + if (this->locked_count == 0) + { + ++this->locked_count; + this->owning_thread_id = thread_id; + return true; + } + + if (this->owning_thread_id != thread_id) + { + return false; + } + + ++this->locked_count; + return true; + } + + std::pair release(const uint32_t thread_id) + { + const auto old_count = this->locked_count; + + if (this->locked_count <= 0 || this->owning_thread_id != thread_id) + { + return {old_count, false}; + } + + --this->locked_count; + return {old_count, true}; + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + buffer.write(this->locked_count); + buffer.write(this->owning_thread_id); + buffer.write(this->name); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->locked_count); + buffer.read(this->owning_thread_id); + buffer.read(this->name); + } +}; + +struct file_entry +{ + std::filesystem::path file_path{}; + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->file_path); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->file_path); + } +}; + +struct file_enumeration_state +{ + size_t current_index{0}; + std::vector files{}; + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->current_index); + buffer.write_vector(this->files); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->current_index); + buffer.read_vector(this->files); + } +}; + +struct file : ref_counted_object +{ + utils::file_handle handle{}; + std::u16string name{}; + std::optional enumeration_state{}; + + bool is_file() const + { + return this->handle; + } + + bool is_directory() const + { + return !this->is_file(); + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + // TODO: Serialize handle + buffer.write(this->name); + buffer.write_optional(this->enumeration_state); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->name); + buffer.read_optional(this->enumeration_state); + this->handle = {}; + } +}; + +struct section : ref_counted_object +{ + std::u16string name{}; + std::u16string file_name{}; + uint64_t maximum_size{}; + uint32_t section_page_protection{}; + uint32_t allocation_attributes{}; + + bool is_image() const + { + return this->allocation_attributes & SEC_IMAGE; + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + buffer.write(this->name); + buffer.write(this->file_name); + buffer.write(this->maximum_size); + buffer.write(this->section_page_protection); + buffer.write(this->allocation_attributes); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->name); + buffer.read(this->file_name); + buffer.read(this->maximum_size); + buffer.read(this->section_page_protection); + buffer.read(this->allocation_attributes); + } +}; + +struct semaphore : ref_counted_object +{ + std::u16string name{}; + uint32_t current_count{}; + uint32_t max_count{}; + + bool try_lock() + { + if (this->current_count > 0) + { + --this->current_count; + return true; + } + + return false; + } + + std::pair release(const uint32_t release_count) + { + const auto old_count = this->current_count; + + if (this->current_count + release_count > this->max_count) + { + return {old_count, false}; + } + + this->current_count += release_count; + + return {old_count, true}; + } + + void serialize_object(utils::buffer_serializer& buffer) const override + { + buffer.write(this->name); + buffer.write(this->current_count); + buffer.write(this->max_count); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->name); + buffer.read(this->current_count); + buffer.read(this->max_count); + } +}; + +struct port : ref_counted_object +{ + std::u16string name{}; + uint64_t view_base{}; + + void serialize_object(utils::buffer_serializer& buffer) const override + { + buffer.write(this->name); + buffer.write(this->view_base); + } + + void deserialize_object(utils::buffer_deserializer& buffer) override + { + buffer.read(this->name); + buffer.read(this->view_base); + } +};