mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-19 19:53:56 +00:00
Support exception hooks
This commit is contained in:
191
src/emulator/byte_buffer.hpp
Normal file
191
src/emulator/byte_buffer.hpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class buffer_deserializer
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
buffer_deserializer(const std::basic_string_view<T>& buffer)
|
||||
: buffer_(reinterpret_cast<const std::byte*>(buffer.data()), buffer.size() * sizeof(T))
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
buffer_deserializer(const std::basic_string<T>& buffer)
|
||||
: buffer_deserializer(std::basic_string_view<T>(buffer.data(), buffer.size()))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
buffer_deserializer(const std::vector<T>& buffer)
|
||||
: buffer_deserializer(std::basic_string_view<T>(buffer.data(), buffer.size()))
|
||||
{
|
||||
}
|
||||
|
||||
void read(void* data, const size_t length)
|
||||
{
|
||||
if (this->offset_ + length > this->buffer_.size())
|
||||
{
|
||||
throw std::runtime_error("Out of bounds read from byte buffer");
|
||||
}
|
||||
|
||||
memcpy(data, this->buffer_.data() + this->offset_, length);
|
||||
this->offset_ += length;
|
||||
}
|
||||
|
||||
std::string read_data(const size_t length)
|
||||
{
|
||||
std::string result{};
|
||||
result.resize(length);
|
||||
|
||||
this->read(result.data(), result.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T read()
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
|
||||
T object{};
|
||||
this->read(&object, sizeof(object));
|
||||
return object;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> read_vector()
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
|
||||
std::vector<T> result{};
|
||||
const auto size = this->read<uint32_t>();
|
||||
const auto totalSize = size * sizeof(T);
|
||||
|
||||
if (this->offset_ + totalSize > this->buffer_.size())
|
||||
{
|
||||
throw std::runtime_error("Out of bounds read from byte buffer");
|
||||
}
|
||||
|
||||
result.resize(size);
|
||||
this->read(result.data(), totalSize);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string read_string()
|
||||
{
|
||||
std::string result{};
|
||||
const auto size = this->read<uint32_t>();
|
||||
|
||||
if (this->offset_ + size > this->buffer_.size())
|
||||
{
|
||||
throw std::runtime_error("Out of bounds read from byte buffer");
|
||||
}
|
||||
|
||||
result.resize(size);
|
||||
this->read(result.data(), size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t get_remaining_size() const
|
||||
{
|
||||
return this->buffer_.size() - offset_;
|
||||
}
|
||||
|
||||
std::string get_remaining_data()
|
||||
{
|
||||
return this->read_data(this->get_remaining_size());
|
||||
}
|
||||
|
||||
size_t get_offset() const
|
||||
{
|
||||
return this->offset_;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t offset_{0};
|
||||
std::basic_string_view<std::byte> buffer_{};
|
||||
};
|
||||
|
||||
class buffer_serializer
|
||||
{
|
||||
public:
|
||||
buffer_serializer() = default;
|
||||
|
||||
void write(const void* buffer, const size_t length)
|
||||
{
|
||||
this->buffer_.append(static_cast<const char*>(buffer), length);
|
||||
}
|
||||
|
||||
void write(const char* text)
|
||||
{
|
||||
this->write(text, strlen(text));
|
||||
}
|
||||
|
||||
void write_string(const char* str, const size_t length)
|
||||
{
|
||||
this->write<uint32_t>(static_cast<uint32_t>(length));
|
||||
this->write(str, length);
|
||||
}
|
||||
|
||||
void write_string(const std::string& str)
|
||||
{
|
||||
this->write_string(str.data(), str.size());
|
||||
}
|
||||
|
||||
void write_string(const char* str)
|
||||
{
|
||||
this->write_string(str, strlen(str));
|
||||
}
|
||||
|
||||
void write(const buffer_serializer& object)
|
||||
{
|
||||
const auto& buffer = object.get_buffer();
|
||||
this->write(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(const T& object)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
this->write(&object, sizeof(object));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write(const std::vector<T>& vec)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
this->write(vec.data(), vec.size() * sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write_vector(const std::vector<T>& vec)
|
||||
{
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
|
||||
this->write(static_cast<uint32_t>(vec.size()));
|
||||
this->write(vec);
|
||||
}
|
||||
|
||||
const std::string& get_buffer() const
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
|
||||
std::string move_buffer()
|
||||
{
|
||||
return std::move(this->buffer_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string buffer_{};
|
||||
};
|
||||
}
|
||||
@@ -17,6 +17,7 @@ enum class hook_continuation : bool
|
||||
|
||||
using hook_callback = std::function<hook_continuation()>;
|
||||
|
||||
using interrupt_hook_callback = std::function<void(int interrupt)>;
|
||||
using simple_memory_hook_callback = std::function<void(uint64_t address, size_t size)>;
|
||||
using complex_memory_hook_callback = std::function<void(uint64_t address, size_t size, memory_operation operation)>;
|
||||
|
||||
@@ -41,6 +42,8 @@ public:
|
||||
complex_memory_hook_callback callback) = 0;
|
||||
virtual emulator_hook* hook_instruction(int instruction_type, hook_callback callback) = 0;
|
||||
|
||||
virtual emulator_hook* hook_interrupt(interrupt_hook_callback callback) = 0;
|
||||
|
||||
virtual void delete_hook(emulator_hook* hook) = 0;
|
||||
|
||||
emulator_hook* hook_memory_read(const uint64_t address, const size_t size, simple_memory_hook_callback callback)
|
||||
|
||||
@@ -30,6 +30,20 @@ public:
|
||||
|
||||
virtual ~memory_manager() = default;
|
||||
|
||||
template <typename T>
|
||||
T read_memory(const uint64_t address)
|
||||
{
|
||||
T value{};
|
||||
this->read_memory(address, &value, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write_memory(const uint64_t address, const T& value)
|
||||
{
|
||||
this->write_memory(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
virtual void read_memory(uint64_t address, void* data, size_t size) = 0;
|
||||
virtual bool try_read_memory(uint64_t address, void* data, size_t size) = 0;
|
||||
virtual void write_memory(uint64_t address, const void* data, size_t size) = 0;
|
||||
|
||||
9
src/emulator/serializable.hpp
Normal file
9
src/emulator/serializable.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include "byte_buffer.hpp"
|
||||
|
||||
struct serializable
|
||||
{
|
||||
virtual ~serializable() = default;
|
||||
virtual void serialize(utils::buffer_serializer& buffer) = 0;
|
||||
virtual void deserialize(utils::buffer_deserializer& buffer) = 0;
|
||||
};
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
enum class x64_hookable_instructions
|
||||
{
|
||||
invalid,
|
||||
syscall,
|
||||
cpuid,
|
||||
rdtsc,
|
||||
|
||||
@@ -252,9 +252,6 @@ namespace unicorn
|
||||
emulator_hook* hook_instruction(int instruction_type,
|
||||
hook_callback callback)
|
||||
{
|
||||
const auto uc_instruction = map_hookable_instruction(
|
||||
static_cast<x64_hookable_instructions>(instruction_type));
|
||||
|
||||
function_wrapper<int, uc_engine*> wrapper([c = std::move(callback)](uc_engine*)
|
||||
{
|
||||
return (c() == hook_continuation::skip_instruction)
|
||||
@@ -263,11 +260,23 @@ namespace unicorn
|
||||
});
|
||||
|
||||
unicorn_hook hook{*this};
|
||||
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INSN, wrapper.get_function(),
|
||||
wrapper.get_user_data(), 0, std::numeric_limits<pointer_type>::max(), uc_instruction));
|
||||
|
||||
auto container = std::make_unique<hook_container>();
|
||||
|
||||
const auto inst_type = static_cast<x64_hookable_instructions>(instruction_type);
|
||||
|
||||
if (inst_type == x64_hookable_instructions::invalid)
|
||||
{
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INSN_INVALID, wrapper.get_function(),
|
||||
wrapper.get_user_data(), 0, std::numeric_limits<pointer_type>::max()));
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto uc_instruction = map_hookable_instruction(inst_type);
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INSN, wrapper.get_function(),
|
||||
wrapper.get_user_data(), 0, std::numeric_limits<pointer_type>::max(),
|
||||
uc_instruction));
|
||||
}
|
||||
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
|
||||
auto* result = container->as_opaque_hook();
|
||||
@@ -277,6 +286,26 @@ namespace unicorn
|
||||
return result;
|
||||
}
|
||||
|
||||
emulator_hook* hook_interrupt(interrupt_hook_callback callback) override
|
||||
{
|
||||
function_wrapper<void, uc_engine*, int> wrapper([c = std::move(callback)](uc_engine*, const int interrupt_type)
|
||||
{
|
||||
c(interrupt_type);
|
||||
});
|
||||
|
||||
unicorn_hook hook{*this};
|
||||
auto container = std::make_unique<hook_container>();
|
||||
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INTR, wrapper.get_function(),
|
||||
wrapper.get_user_data(), 0, std::numeric_limits<pointer_type>::max()));
|
||||
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
|
||||
auto* result = container->as_opaque_hook();
|
||||
this->hooks_.push_back(std::move(container));
|
||||
return result;
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_access(const uint64_t address, const size_t size, const memory_operation filter,
|
||||
complex_memory_hook_callback callback) override
|
||||
{
|
||||
|
||||
@@ -329,50 +329,25 @@ namespace
|
||||
return context;
|
||||
}
|
||||
|
||||
enum class gdb_registers
|
||||
{
|
||||
rax = 0,
|
||||
rbx,
|
||||
rcx,
|
||||
rdx,
|
||||
rsi,
|
||||
rdi,
|
||||
rbp,
|
||||
rsp,
|
||||
r8,
|
||||
r9,
|
||||
r10,
|
||||
r11,
|
||||
r12,
|
||||
r13,
|
||||
r14,
|
||||
r15,
|
||||
|
||||
rip,
|
||||
eflags,
|
||||
|
||||
end,
|
||||
};
|
||||
|
||||
std::unordered_map<gdb_registers, x64_register> register_map{
|
||||
{gdb_registers::rax, x64_register::rax},
|
||||
{gdb_registers::rbx, x64_register::rbx},
|
||||
{gdb_registers::rcx, x64_register::rcx},
|
||||
{gdb_registers::rdx, x64_register::rdx},
|
||||
{gdb_registers::rsi, x64_register::rsi},
|
||||
{gdb_registers::rdi, x64_register::rdi},
|
||||
{gdb_registers::rbp, x64_register::rbp},
|
||||
{gdb_registers::rsp, x64_register::rsp},
|
||||
{gdb_registers::r8, x64_register::r8},
|
||||
{gdb_registers::r9, x64_register::r9},
|
||||
{gdb_registers::r10, x64_register::r10},
|
||||
{gdb_registers::r11, x64_register::r11},
|
||||
{gdb_registers::r12, x64_register::r12},
|
||||
{gdb_registers::r13, x64_register::r13},
|
||||
{gdb_registers::r14, x64_register::r14},
|
||||
{gdb_registers::r15, x64_register::r15},
|
||||
{gdb_registers::rip, x64_register::rip},
|
||||
{gdb_registers::eflags, x64_register::rflags},
|
||||
std::vector gdb_registers{
|
||||
x64_register::rax,
|
||||
x64_register::rbx,
|
||||
x64_register::rcx,
|
||||
x64_register::rdx,
|
||||
x64_register::rsi,
|
||||
x64_register::rdi,
|
||||
x64_register::rbp,
|
||||
x64_register::rsp,
|
||||
x64_register::r8,
|
||||
x64_register::r9,
|
||||
x64_register::r10,
|
||||
x64_register::r11,
|
||||
x64_register::r12,
|
||||
x64_register::r13,
|
||||
x64_register::r14,
|
||||
x64_register::r15,
|
||||
x64_register::rip,
|
||||
x64_register::rflags,
|
||||
};
|
||||
|
||||
memory_operation map_breakpoint_type(const breakpoint_type type)
|
||||
@@ -488,13 +463,12 @@ namespace
|
||||
|
||||
try
|
||||
{
|
||||
const auto entry = register_map.find(static_cast<gdb_registers>(regno));
|
||||
if (entry == register_map.end())
|
||||
if (regno < 0 || regno >= gdb_registers.size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
this->emu_->read_register(entry->second, value, sizeof(*value));
|
||||
this->emu_->read_register(gdb_registers[regno], value, sizeof(*value));
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
@@ -507,13 +481,12 @@ namespace
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto entry = register_map.find(static_cast<gdb_registers>(regno));
|
||||
if (entry == register_map.end())
|
||||
if (regno < 0 || regno >= gdb_registers.size())
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
this->emu_->write_register(entry->second, &value, sizeof(value));
|
||||
this->emu_->write_register(gdb_registers[regno], &value, sizeof(value));
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
@@ -634,6 +607,26 @@ namespace
|
||||
return hook_continuation::skip_instruction;
|
||||
});
|
||||
|
||||
emu->hook_instruction(x64_hookable_instructions::invalid, [&]
|
||||
{
|
||||
const auto ip = emu->read_instruction_pointer();
|
||||
printf("Invalid instruction at: %llX\n", ip);
|
||||
return hook_continuation::skip_instruction;
|
||||
});
|
||||
|
||||
emu->hook_interrupt([&](int interrupt)
|
||||
{
|
||||
printf("Interrupt: %i\n", interrupt);
|
||||
if (interrupt == 13)
|
||||
{
|
||||
const auto sp = emu->reg(x64_register::rsp);
|
||||
const auto new_ip = emu->read_memory<uint64_t>(sp);
|
||||
|
||||
emu->reg(x64_register::rsp, sp + 8);
|
||||
emu->reg(x64_register::rip, new_ip);
|
||||
}
|
||||
});
|
||||
|
||||
watch_object(*emu, context.teb);
|
||||
watch_object(*emu, context.peb);
|
||||
watch_object(*emu, context.process_params);
|
||||
@@ -673,7 +666,7 @@ namespace
|
||||
puts("Launching gdb stub...");
|
||||
|
||||
x64_gdb_stub_handler handler{*emu};
|
||||
run_gdb_stub(handler, "i386:x86-64", static_cast<size_t>(gdb_registers::end), "0.0.0.0:28960");
|
||||
run_gdb_stub(handler, "i386:x86-64", gdb_registers.size(), "0.0.0.0:28960");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user