Cleanup windows-emulator and extract classes

This commit is contained in:
Maurice Heumann
2025-02-10 13:42:49 +01:00
parent 1b544376ce
commit 2686251fde
12 changed files with 980 additions and 923 deletions

View File

@@ -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();
}