mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-23 05:31:03 +00:00
Cleanup windows-emulator and extract classes
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user