mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-19 03:33:56 +00:00
Cleanup windows-emulator and extract classes (#139)
This commit is contained in:
46
src/common/utils/moved_marker.hpp
Normal file
46
src/common/utils/moved_marker.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <type_traits>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
243
src/windows-emulator/emulator_thread.cpp
Normal file
243
src/windows-emulator/emulator_thread.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "emulator_thread.hpp"
|
||||
|
||||
#include "context_frame.hpp"
|
||||
#include "process_context.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
emulator_object<T> 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<uint64_t>(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<TEB64>();
|
||||
|
||||
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<uint8_t*>(&teb_obj)[0x179C] = 1;
|
||||
|
||||
teb_obj.ClientId.UniqueProcess = 1ul;
|
||||
teb_obj.ClientId.UniqueThread = static_cast<uint64_t>(this->id);
|
||||
teb_obj.NtTib.StackLimit = reinterpret_cast<std::uint64_t*>(this->stack_base);
|
||||
teb_obj.NtTib.StackBase = reinterpret_cast<std::uint64_t*>(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<CONTEXT64>(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);
|
||||
}
|
||||
199
src/windows-emulator/emulator_thread.hpp
Normal file
199
src/windows-emulator/emulator_thread.hpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
|
||||
#include "handles.hpp"
|
||||
#include "emulator_utils.hpp"
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
#include <utils/moved_marker.hpp>
|
||||
|
||||
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<memory_manager_wrapper>().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<NTSTATUS> exit_status{};
|
||||
std::vector<handle> await_objects{};
|
||||
bool await_any{false};
|
||||
bool waiting_for_alert{false};
|
||||
bool alerted{false};
|
||||
std::optional<std::chrono::steady_clock::time_point> await_time{};
|
||||
|
||||
std::optional<NTSTATUS> pending_status{};
|
||||
|
||||
std::optional<emulator_allocator> gs_segment;
|
||||
std::optional<emulator_object<TEB64>> teb;
|
||||
|
||||
std::vector<std::byte> 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<uint64_t>(x64_register::rax, static_cast<uint64_t>(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<TEB64>(*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 = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
199
src/windows-emulator/exception_dispatch.cpp
Normal file
199
src/windows-emulator/exception_dispatch.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "exception_dispatch.hpp"
|
||||
#include "process_context.hpp"
|
||||
#include "context_frame.hpp"
|
||||
|
||||
#include <platform/status.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
using exception_record = EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>;
|
||||
using exception_record_map = std::unordered_map<const exception_record*, emulator_object<exception_record>>;
|
||||
|
||||
emulator_object<exception_record> save_exception_record(emulator_allocator& allocator,
|
||||
const exception_record& record,
|
||||
exception_record_map& record_mapping)
|
||||
{
|
||||
const auto record_obj = allocator.reserve<exception_record>();
|
||||
record_obj.write(record);
|
||||
|
||||
if (record.ExceptionRecord)
|
||||
{
|
||||
record_mapping.emplace(&record, record_obj);
|
||||
|
||||
emulator_object<exception_record> nested_record_obj{allocator.get_memory()};
|
||||
const auto nested_record = record_mapping.find(reinterpret_cast<exception_record*>(record.ExceptionRecord));
|
||||
|
||||
if (nested_record != record_mapping.end())
|
||||
{
|
||||
nested_record_obj = nested_record->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
nested_record_obj = save_exception_record(
|
||||
allocator, *reinterpret_cast<exception_record*>(record.ExceptionRecord), record_mapping);
|
||||
}
|
||||
|
||||
record_obj.access([&](exception_record& r) {
|
||||
r.ExceptionRecord = reinterpret_cast<EmulatorTraits<Emu64>::PVOID>(nested_record_obj.ptr());
|
||||
});
|
||||
}
|
||||
|
||||
return record_obj;
|
||||
}
|
||||
|
||||
emulator_object<exception_record> 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<const exception_record*> 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<exception_record*>(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<EmulatorTraits<Emu64>> pointers)
|
||||
{
|
||||
constexpr auto mach_frame_size = 0x40;
|
||||
constexpr auto context_record_size = 0x4F0;
|
||||
const auto exception_record_size =
|
||||
calculate_exception_record_size(*reinterpret_cast<exception_record*>(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<uint8_t> 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<CONTEXT64> context_record_obj{emu, new_sp};
|
||||
context_record_obj.write(*reinterpret_cast<CONTEXT64*>(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<exception_record*>(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> machine_frame_obj{emu, new_sp + combined_size};
|
||||
machine_frame_obj.access([&](machine_frame& frame) {
|
||||
const auto& record = *reinterpret_cast<CONTEXT64*>(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<EmulatorTraits<Emu64>::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<DWORD>(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<EmulatorTraits<Emu64>> pointers{};
|
||||
pointers.ContextRecord = reinterpret_cast<EmulatorTraits<Emu64>::PVOID>(&ctx);
|
||||
pointers.ExceptionRecord = reinterpret_cast<EmulatorTraits<Emu64>::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, {});
|
||||
}
|
||||
24
src/windows-emulator/exception_dispatch.hpp
Normal file
24
src/windows-emulator/exception_dispatch.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <x64_emulator.hpp>
|
||||
|
||||
#include <platform/traits.hpp>
|
||||
#include <platform/primitives.hpp>
|
||||
|
||||
struct process_context;
|
||||
|
||||
void dispatch_exception(x64_emulator& emu, const process_context& proc, DWORD status,
|
||||
const std::vector<EmulatorTraits<Emu64>::ULONG_PTR>& parameters);
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T> && !std::is_same_v<T, DWORD>)
|
||||
void dispatch_exception(x64_emulator& emu, const process_context& proc, const T status,
|
||||
const std::vector<EmulatorTraits<Emu64>::ULONG_PTR>& parameters)
|
||||
{
|
||||
dispatch_exception(emu, proc, static_cast<DWORD>(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);
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <serialization.hpp>
|
||||
|
||||
struct handle_types
|
||||
{
|
||||
enum type : uint16_t
|
||||
|
||||
@@ -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<uint64_t>& split_points)
|
||||
{
|
||||
for (auto i = regions.begin(); i != regions.end(); ++i)
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include <memory_interface.hpp>
|
||||
|
||||
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{};
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
#include "module/module_manager.hpp"
|
||||
#include <utils/nt_handle.hpp>
|
||||
#include <utils/file_handle.hpp>
|
||||
|
||||
#include <x64_emulator.hpp>
|
||||
#include <serialization_helper.hpp>
|
||||
|
||||
#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<uint32_t, bool> 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<file_entry> 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<file_enumeration_state> 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<uint32_t, bool> 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<memory_manager_wrapper>().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<NTSTATUS> exit_status{};
|
||||
std::vector<handle> await_objects{};
|
||||
bool await_any{false};
|
||||
bool waiting_for_alert{false};
|
||||
bool alerted{false};
|
||||
std::optional<std::chrono::steady_clock::time_point> await_time{};
|
||||
|
||||
std::optional<NTSTATUS> pending_status{};
|
||||
|
||||
std::optional<emulator_allocator> gs_segment;
|
||||
std::optional<emulator_object<TEB64>> teb;
|
||||
|
||||
std::vector<std::byte> 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<uint64_t>(x64_register::rax, static_cast<uint64_t>(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<TEB64>(*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)
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -12,46 +12,12 @@
|
||||
#include <utils/lazy_object.hpp>
|
||||
|
||||
#include "apiset.hpp"
|
||||
#include "exception_dispatch.hpp"
|
||||
|
||||
constexpr auto MAX_INSTRUCTIONS_PER_TIME_SLICE = 100000;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
emulator_object<T> 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<EmulatorTraits<Emu64>>;
|
||||
using exception_record_map = std::unordered_map<const exception_record*, emulator_object<exception_record>>;
|
||||
|
||||
emulator_object<exception_record> save_exception_record(emulator_allocator& allocator,
|
||||
const exception_record& record,
|
||||
exception_record_map& record_mapping)
|
||||
{
|
||||
const auto record_obj = allocator.reserve<exception_record>();
|
||||
record_obj.write(record);
|
||||
|
||||
if (record.ExceptionRecord)
|
||||
{
|
||||
record_mapping.emplace(&record, record_obj);
|
||||
|
||||
emulator_object<exception_record> nested_record_obj{allocator.get_memory()};
|
||||
const auto nested_record = record_mapping.find(reinterpret_cast<exception_record*>(record.ExceptionRecord));
|
||||
|
||||
if (nested_record != record_mapping.end())
|
||||
{
|
||||
nested_record_obj = nested_record->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
nested_record_obj = save_exception_record(
|
||||
allocator, *reinterpret_cast<exception_record*>(record.ExceptionRecord), record_mapping);
|
||||
}
|
||||
|
||||
record_obj.access([&](exception_record& r) {
|
||||
r.ExceptionRecord = reinterpret_cast<EmulatorTraits<Emu64>::PVOID>(nested_record_obj.ptr());
|
||||
});
|
||||
}
|
||||
|
||||
return record_obj;
|
||||
}
|
||||
|
||||
emulator_object<exception_record> 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<const exception_record*> 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<exception_record*>(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<EmulatorTraits<Emu64>> pointers)
|
||||
{
|
||||
constexpr auto mach_frame_size = 0x40;
|
||||
constexpr auto context_record_size = 0x4F0;
|
||||
const auto exception_record_size =
|
||||
calculate_exception_record_size(*reinterpret_cast<exception_record*>(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<uint8_t> 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<CONTEXT64> context_record_obj{emu, new_sp};
|
||||
context_record_obj.write(*reinterpret_cast<CONTEXT64*>(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<exception_record*>(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> machine_frame_obj{emu, new_sp + combined_size};
|
||||
machine_frame_obj.access([&](machine_frame& frame) {
|
||||
const auto& record = *reinterpret_cast<CONTEXT64*>(pointers.ContextRecord);
|
||||
frame.rip = record.Rip;
|
||||
frame.rsp = record.Rsp;
|
||||
frame.ss = record.SegSs;
|
||||
frame.cs = record.SegCs;
|
||||
frame.eflags = record.EFlags;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T>)
|
||||
void dispatch_exception(x64_emulator& emu, const process_context& proc, const T status,
|
||||
const std::vector<EmulatorTraits<Emu64>::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<DWORD>(status);
|
||||
record.ExceptionFlags = 0;
|
||||
record.ExceptionRecord = 0;
|
||||
record.ExceptionAddress = emu.read_instruction_pointer();
|
||||
record.NumberParameters = static_cast<DWORD>(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<EmulatorTraits<Emu64>> pointers{};
|
||||
pointers.ContextRecord = reinterpret_cast<EmulatorTraits<Emu64>::PVOID>(&ctx);
|
||||
pointers.ExceptionRecord = reinterpret_cast<EmulatorTraits<Emu64>::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<uint64_t>(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<TEB64>();
|
||||
|
||||
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<uint8_t*>(&teb_obj)[0x179C] = 1;
|
||||
|
||||
teb_obj.ClientId.UniqueProcess = 1ul;
|
||||
teb_obj.ClientId.UniqueThread = static_cast<uint64_t>(this->id);
|
||||
teb_obj.NtTib.StackLimit = reinterpret_cast<std::uint64_t*>(this->stack_base);
|
||||
teb_obj.NtTib.StackBase = reinterpret_cast<std::uint64_t*>(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<CONTEXT64>(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<x64_emulator> 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();
|
||||
}
|
||||
|
||||
252
src/windows-emulator/windows_objects.hpp
Normal file
252
src/windows-emulator/windows_objects.hpp
Normal file
@@ -0,0 +1,252 @@
|
||||
#pragma once
|
||||
|
||||
#include "handles.hpp"
|
||||
|
||||
#include <serialization_helper.hpp>
|
||||
#include <utils/file_handle.hpp>
|
||||
#include <platform/synchronisation.hpp>
|
||||
|
||||
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<uint32_t, bool> 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<file_entry> 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<file_enumeration_state> 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<uint32_t, bool> 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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user