mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-24 14:11:02 +00:00
Merge remote-tracking branch 'origin/main' into unicorn-upgrade-2
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use icicle_cpu::ExceptionCode;
|
||||
use icicle_cpu::ValueSource;
|
||||
use std::collections::HashSet;
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use crate::registers;
|
||||
@@ -73,31 +74,96 @@ fn qualify_hook_id(hook_id: u32, hook_type: HookType) -> u32 {
|
||||
|
||||
pub struct HookContainer<Func: ?Sized> {
|
||||
hook_id: u32,
|
||||
is_iterating: bool,
|
||||
hooks: HashMap<u32, Box<Func>>,
|
||||
hooks_to_add: HashMap<u32, Box<Func>>,
|
||||
hooks_to_remove: HashSet<u32>,
|
||||
}
|
||||
|
||||
impl<Func: ?Sized> HookContainer<Func> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
hook_id: 0,
|
||||
is_iterating: false,
|
||||
hooks: HashMap::new(),
|
||||
hooks_to_add: HashMap::new(),
|
||||
hooks_to_remove: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_hook(&mut self, callback: Box<Func>) -> u32 {
|
||||
self.hook_id += 1;
|
||||
let id = self.hook_id;
|
||||
self.hooks.insert(id, callback);
|
||||
|
||||
if self.is_iterating {
|
||||
self.hooks_to_add.insert(id, callback);
|
||||
} else {
|
||||
self.hooks.insert(id, callback);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn get_hooks(&self) -> &HashMap<u32, Box<Func>> {
|
||||
return &self.hooks;
|
||||
pub fn for_each_hook<F>(&mut self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&Func),
|
||||
{
|
||||
let was_iterating = self.do_pre_access_work();
|
||||
|
||||
for (_, func) in &self.hooks {
|
||||
callback(func.as_ref());
|
||||
}
|
||||
|
||||
self.do_post_access_work(was_iterating);
|
||||
}
|
||||
|
||||
pub fn access_hook<F>(&mut self, id: u32, mut callback: F)
|
||||
where
|
||||
F: FnMut(&Func),
|
||||
{
|
||||
let was_iterating = self.do_pre_access_work();
|
||||
|
||||
let hook = self.hooks.get(&id);
|
||||
if hook.is_some() {
|
||||
callback(hook.unwrap().as_ref());
|
||||
}
|
||||
|
||||
self.do_post_access_work(was_iterating);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
return self.hooks.is_empty();
|
||||
}
|
||||
|
||||
pub fn remove_hook(&mut self, id: u32) {
|
||||
self.hooks.remove(&id);
|
||||
if self.is_iterating {
|
||||
self.hooks_to_remove.insert(id);
|
||||
} else {
|
||||
self.hooks.remove(&id);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_pre_access_work(&mut self) -> bool {
|
||||
let was_iterating = self.is_iterating;
|
||||
self.is_iterating = true;
|
||||
return was_iterating;
|
||||
}
|
||||
|
||||
fn do_post_access_work(&mut self, was_iterating: bool) {
|
||||
self.is_iterating = was_iterating;
|
||||
if self.is_iterating {
|
||||
return;
|
||||
}
|
||||
|
||||
let to_remove = std::mem::take(&mut self.hooks_to_remove);
|
||||
for id in &to_remove {
|
||||
self.hooks.remove(&id);
|
||||
}
|
||||
|
||||
let to_add = std::mem::take(&mut self.hooks_to_add);
|
||||
for (id, func) in to_add {
|
||||
self.hooks.insert(id, func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +223,7 @@ struct ExecutionHooks {
|
||||
specific_hooks: HookContainer<dyn Fn(u64)>,
|
||||
block_hooks: HookContainer<dyn Fn(u64, u64)>,
|
||||
address_mapping: HashMap<u64, Vec<u32>>,
|
||||
one_time_callbacks: Vec<Box<dyn Fn()>>,
|
||||
}
|
||||
|
||||
impl ExecutionHooks {
|
||||
@@ -167,31 +234,38 @@ impl ExecutionHooks {
|
||||
specific_hooks: HookContainer::new(),
|
||||
block_hooks: HookContainer::new(),
|
||||
address_mapping: HashMap::new(),
|
||||
one_time_callbacks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_hooks(&self, address: u64) {
|
||||
for (_key, func) in self.generic_hooks.get_hooks() {
|
||||
func(address);
|
||||
fn run_hooks(&mut self, address: u64) {
|
||||
if !self.one_time_callbacks.is_empty() {
|
||||
let callbacks = std::mem::take(&mut self.one_time_callbacks);
|
||||
for cb in callbacks {
|
||||
cb.as_ref()();
|
||||
}
|
||||
}
|
||||
|
||||
self.generic_hooks.for_each_hook(|func| {
|
||||
func(address);
|
||||
});
|
||||
|
||||
let mapping = self.address_mapping.get(&address);
|
||||
if mapping.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
for id in mapping.unwrap() {
|
||||
let func = self.specific_hooks.get_hooks().get(&id);
|
||||
if func.is_some() {
|
||||
func.unwrap()(address);
|
||||
}
|
||||
self.specific_hooks.access_hook(*id, |func| {
|
||||
func(address);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_block(&mut self, address: u64, instructions: u64) {
|
||||
for (_key, func) in self.block_hooks.get_hooks() {
|
||||
self.block_hooks.for_each_hook(|func| {
|
||||
func(address, instructions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, cpu: &mut icicle_cpu::Cpu, address: u64) {
|
||||
@@ -224,6 +298,10 @@ impl ExecutionHooks {
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn schedule(&mut self, callback: Box<dyn Fn()>) {
|
||||
self.one_time_callbacks.push(callback);
|
||||
}
|
||||
|
||||
pub fn remove_generic_hook(&mut self, id: u32) {
|
||||
self.generic_hooks.remove_hook(id);
|
||||
}
|
||||
@@ -298,6 +376,15 @@ impl icicle_cpu::mem::IoMemory for MmioHandler {
|
||||
impl IcicleEmulator {
|
||||
pub fn new() -> Self {
|
||||
let mut virtual_machine = create_x64_vm();
|
||||
let capacity_400mb = 50_000;
|
||||
|
||||
let mut capacity = 8 * 2 * capacity_400mb; // ~8gb
|
||||
if cfg!(target_pointer_width = "32") {
|
||||
capacity = 2 * capacity_400mb; // ~1gb
|
||||
}
|
||||
|
||||
virtual_machine.cpu.mem.set_capacity(capacity);
|
||||
|
||||
let stop_value = Rc::new(RefCell::new(false));
|
||||
let exec_hooks = Rc::new(RefCell::new(ExecutionHooks::new(stop_value.clone())));
|
||||
|
||||
@@ -368,10 +455,10 @@ impl IcicleEmulator {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_interrupt(&self, code: i32) -> bool {
|
||||
for (_key, func) in self.interrupt_hooks.get_hooks() {
|
||||
fn handle_interrupt(&mut self, code: i32) -> bool {
|
||||
self.interrupt_hooks.for_each_hook(|func| {
|
||||
func(code);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -393,16 +480,15 @@ impl IcicleEmulator {
|
||||
}
|
||||
|
||||
fn handle_violation(&mut self, address: u64, permission: u8, unmapped: bool) -> bool {
|
||||
let hooks = &self.violation_hooks.get_hooks();
|
||||
if hooks.is_empty() {
|
||||
if self.violation_hooks.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut continue_execution = true;
|
||||
|
||||
for (_key, func) in self.violation_hooks.get_hooks() {
|
||||
self.violation_hooks.for_each_hook(|func| {
|
||||
continue_execution &= func(address, permission, unmapped);
|
||||
}
|
||||
});
|
||||
|
||||
return continue_execution;
|
||||
}
|
||||
@@ -412,9 +498,9 @@ impl IcicleEmulator {
|
||||
return self.handle_interrupt(value as i32);
|
||||
}
|
||||
|
||||
for (_key, func) in self.syscall_hooks.get_hooks() {
|
||||
self.syscall_hooks.for_each_hook(|func| {
|
||||
func();
|
||||
}
|
||||
});
|
||||
|
||||
self.vm.cpu.write_pc(self.vm.cpu.read_pc() + 2);
|
||||
return true;
|
||||
@@ -521,6 +607,10 @@ impl IcicleEmulator {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_on_next_instruction(&mut self, callback: Box<dyn Fn()>) {
|
||||
self.execution_hooks.borrow_mut().schedule(callback);
|
||||
}
|
||||
|
||||
pub fn map_memory(&mut self, address: u64, length: u64, permissions: u8) -> bool {
|
||||
const MAPPING_PERMISSIONS: u8 = icicle_vm::cpu::mem::perm::MAP
|
||||
| icicle_vm::cpu::mem::perm::INIT
|
||||
@@ -533,14 +623,7 @@ impl IcicleEmulator {
|
||||
value: 0x0,
|
||||
};
|
||||
|
||||
let layout = icicle_vm::cpu::mem::AllocLayout {
|
||||
addr: Some(address),
|
||||
size: length,
|
||||
align: 0x1000,
|
||||
};
|
||||
|
||||
let res = self.get_mem().alloc_memory(layout, mapping);
|
||||
return res.is_ok();
|
||||
return self.get_mem().map_memory_len(address, length, mapping);
|
||||
}
|
||||
|
||||
pub fn map_mmio(
|
||||
@@ -555,14 +638,7 @@ impl IcicleEmulator {
|
||||
let handler = MmioHandler::new(read_function, write_function);
|
||||
let handler_id = mem.register_io_handler(handler);
|
||||
|
||||
let layout = icicle_vm::cpu::mem::AllocLayout {
|
||||
addr: Some(address),
|
||||
size: length,
|
||||
align: 0x1000,
|
||||
};
|
||||
|
||||
let res = mem.alloc_memory(layout, handler_id);
|
||||
return res.is_ok();
|
||||
return self.get_mem().map_memory_len(address, length, handler_id);
|
||||
}
|
||||
|
||||
pub fn unmap_memory(&mut self, address: u64, length: u64) -> bool {
|
||||
|
||||
@@ -307,6 +307,14 @@ pub fn icicle_remove_hook(ptr: *mut c_void, id: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub fn icicle_run_on_next_instruction(ptr: *mut c_void, callback: RawFunction, data: *mut c_void) {
|
||||
unsafe {
|
||||
let emulator = &mut *(ptr as *mut IcicleEmulator);
|
||||
emulator.run_on_next_instruction(Box::new(move || callback(data)));
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub fn icicle_read_register(
|
||||
ptr: *mut c_void,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#define ICICLE_EMULATOR_IMPL
|
||||
#include "icicle_x86_64_emulator.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <utils/object.hpp>
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
using icicle_emulator = struct icicle_emulator_;
|
||||
|
||||
@@ -21,8 +23,8 @@ extern "C"
|
||||
icicle_emulator* icicle_create_emulator();
|
||||
int32_t icicle_protect_memory(icicle_emulator*, uint64_t address, uint64_t length, uint8_t permissions);
|
||||
int32_t icicle_map_memory(icicle_emulator*, uint64_t address, uint64_t length, uint8_t permissions);
|
||||
int32_t icicle_map_mmio(icicle_emulator*, uint64_t address, uint64_t length, icicle_mmio_read_func* read_callback,
|
||||
void* read_data, icicle_mmio_write_func* write_callback, void* write_data);
|
||||
int32_t icicle_map_mmio(icicle_emulator*, uint64_t address, uint64_t length, icicle_mmio_read_func* read_callback, void* read_data,
|
||||
icicle_mmio_write_func* write_callback, void* write_data);
|
||||
int32_t icicle_unmap_memory(icicle_emulator*, uint64_t address, uint64_t length);
|
||||
int32_t icicle_read_memory(icicle_emulator*, uint64_t address, void* data, size_t length);
|
||||
int32_t icicle_write_memory(icicle_emulator*, uint64_t address, const void* data, size_t length);
|
||||
@@ -44,6 +46,7 @@ extern "C"
|
||||
void icicle_start(icicle_emulator*, size_t count);
|
||||
void icicle_stop(icicle_emulator*);
|
||||
void icicle_destroy_emulator(icicle_emulator*);
|
||||
void icicle_run_on_next_instruction(icicle_emulator*, raw_func* callback, void* data);
|
||||
}
|
||||
|
||||
namespace icicle
|
||||
@@ -58,24 +61,35 @@ namespace icicle
|
||||
}
|
||||
}
|
||||
|
||||
emulator_hook* wrap_hook(const uint32_t id)
|
||||
{
|
||||
return reinterpret_cast<emulator_hook*>(static_cast<size_t>(id));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct function_object : utils::object
|
||||
{
|
||||
bool* hook_state{};
|
||||
std::function<T> func{};
|
||||
|
||||
function_object(std::function<T> f = {})
|
||||
: func(std::move(f))
|
||||
function_object(std::function<T> f = {}, bool* state = nullptr)
|
||||
: hook_state(state),
|
||||
func(std::move(f))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto operator()(Args&&... args) const
|
||||
{
|
||||
bool old_state{};
|
||||
if (this->hook_state)
|
||||
{
|
||||
old_state = *this->hook_state;
|
||||
*this->hook_state = true;
|
||||
}
|
||||
|
||||
const auto _ = utils::finally([&] {
|
||||
if (this->hook_state)
|
||||
{
|
||||
*this->hook_state = old_state;
|
||||
}
|
||||
});
|
||||
|
||||
return this->func.operator()(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
@@ -83,10 +97,18 @@ namespace icicle
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::unique_ptr<function_object<T>> make_function_object(std::function<T> func)
|
||||
std::unique_ptr<function_object<T>> make_function_object(std::function<T> func, bool& hook_state)
|
||||
{
|
||||
return std::make_unique<function_object<T>>(std::move(func));
|
||||
return std::make_unique<function_object<T>>(std::move(func), &hook_state);
|
||||
}
|
||||
|
||||
struct memory_access_hook
|
||||
{
|
||||
uint64_t address{};
|
||||
uint64_t size{};
|
||||
memory_access_hook_callback callback{};
|
||||
bool is_read{};
|
||||
};
|
||||
}
|
||||
|
||||
class icicle_x86_64_emulator : public x86_64_emulator
|
||||
@@ -103,6 +125,10 @@ namespace icicle
|
||||
|
||||
~icicle_x86_64_emulator() override
|
||||
{
|
||||
reset_object_with_delayed_destruction(this->hooks_);
|
||||
reset_object_with_delayed_destruction(this->storage_);
|
||||
utils::reset_object_with_delayed_destruction(this->hooks_to_install_);
|
||||
|
||||
if (this->emu_)
|
||||
{
|
||||
icicle_destroy_emulator(this->emu_);
|
||||
@@ -162,8 +188,7 @@ namespace icicle
|
||||
return icicle_read_register(this->emu_, reg, value, size);
|
||||
}
|
||||
|
||||
void map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb,
|
||||
mmio_write_callback write_cb) override
|
||||
void map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) override
|
||||
{
|
||||
struct mmio_wrapper : utils::object
|
||||
{
|
||||
@@ -236,12 +261,12 @@ namespace icicle
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto obj = make_function_object(std::move(callback));
|
||||
auto obj = make_function_object(std::move(callback), this->is_in_hook_);
|
||||
auto* ptr = obj.get();
|
||||
|
||||
const auto invoker = +[](void* cb) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(cb);
|
||||
(void)func(); //
|
||||
(void)func(0); //
|
||||
};
|
||||
|
||||
const auto id = icicle_add_syscall_hook(this->emu_, invoker, ptr);
|
||||
@@ -252,7 +277,7 @@ namespace icicle
|
||||
|
||||
emulator_hook* hook_basic_block(basic_block_hook_callback callback) override
|
||||
{
|
||||
auto object = make_function_object(std::move(callback));
|
||||
auto object = make_function_object(std::move(callback), this->is_in_hook_);
|
||||
auto* ptr = object.get();
|
||||
auto* wrapper = +[](void* user, const uint64_t addr, const uint64_t instructions) {
|
||||
basic_block block{};
|
||||
@@ -271,7 +296,7 @@ namespace icicle
|
||||
|
||||
emulator_hook* hook_interrupt(interrupt_hook_callback callback) override
|
||||
{
|
||||
auto obj = make_function_object(std::move(callback));
|
||||
auto obj = make_function_object(std::move(callback), this->is_in_hook_);
|
||||
auto* ptr = obj.get();
|
||||
auto* wrapper = +[](void* user, const int32_t code) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(user);
|
||||
@@ -286,10 +311,9 @@ namespace icicle
|
||||
|
||||
emulator_hook* hook_memory_violation(memory_violation_hook_callback callback) override
|
||||
{
|
||||
auto obj = make_function_object(std::move(callback));
|
||||
auto obj = make_function_object(std::move(callback), this->is_in_hook_);
|
||||
auto* ptr = obj.get();
|
||||
auto* wrapper =
|
||||
+[](void* user, const uint64_t address, const uint8_t operation, const int32_t unmapped) -> int32_t {
|
||||
auto* wrapper = +[](void* user, const uint64_t address, const uint8_t operation, const int32_t unmapped) -> int32_t {
|
||||
const auto violation_type = unmapped //
|
||||
? memory_violation_type::unmapped
|
||||
: memory_violation_type::protection;
|
||||
@@ -307,7 +331,7 @@ namespace icicle
|
||||
|
||||
emulator_hook* hook_memory_execution(const uint64_t address, memory_execution_hook_callback callback) override
|
||||
{
|
||||
auto object = make_function_object(std::move(callback));
|
||||
auto object = make_function_object(std::move(callback), this->is_in_hook_);
|
||||
auto* ptr = object.get();
|
||||
auto* wrapper = +[](void* user, const uint64_t addr) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(user);
|
||||
@@ -322,7 +346,7 @@ namespace icicle
|
||||
|
||||
emulator_hook* hook_memory_execution(memory_execution_hook_callback callback) override
|
||||
{
|
||||
auto object = make_function_object(std::move(callback));
|
||||
auto object = make_function_object(std::move(callback), this->is_in_hook_);
|
||||
auto* ptr = object.get();
|
||||
auto* wrapper = +[](void* user, const uint64_t addr) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(user);
|
||||
@@ -335,49 +359,37 @@ namespace icicle
|
||||
return wrap_hook(id);
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size,
|
||||
memory_access_hook_callback callback) override
|
||||
emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size, memory_access_hook_callback callback) override
|
||||
{
|
||||
auto obj = make_function_object(std::move(callback));
|
||||
auto* ptr = obj.get();
|
||||
auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(user);
|
||||
func(address, data, length);
|
||||
};
|
||||
|
||||
const auto id = icicle_add_read_hook(this->emu_, address, address + size, wrapper, ptr);
|
||||
this->hooks_[id] = std::move(obj);
|
||||
|
||||
return wrap_hook(id);
|
||||
return this->try_install_memory_access_hook(memory_access_hook{
|
||||
.address = address,
|
||||
.size = size,
|
||||
.callback = std::move(callback),
|
||||
.is_read = true,
|
||||
});
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size,
|
||||
memory_access_hook_callback callback) override
|
||||
emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size, memory_access_hook_callback callback) override
|
||||
{
|
||||
auto obj = make_function_object(std::move(callback));
|
||||
auto* ptr = obj.get();
|
||||
auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(user);
|
||||
func(address, data, length);
|
||||
};
|
||||
|
||||
const auto id = icicle_add_write_hook(this->emu_, address, address + size, wrapper, ptr);
|
||||
this->hooks_[id] = std::move(obj);
|
||||
|
||||
return wrap_hook(id);
|
||||
return this->try_install_memory_access_hook(memory_access_hook{
|
||||
.address = address,
|
||||
.size = size,
|
||||
.callback = std::move(callback),
|
||||
.is_read = false,
|
||||
});
|
||||
}
|
||||
|
||||
void delete_hook(emulator_hook* hook) override
|
||||
{
|
||||
const auto id = static_cast<uint32_t>(reinterpret_cast<size_t>(hook));
|
||||
const auto entry = this->hooks_.find(id);
|
||||
if (entry == this->hooks_.end())
|
||||
if (this->is_in_hook_)
|
||||
{
|
||||
return;
|
||||
this->hooks_to_delete_.insert(hook);
|
||||
this->schedule_action_execution();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->delete_hook_internal(hook);
|
||||
}
|
||||
|
||||
icicle_remove_hook(this->emu_, id);
|
||||
this->hooks_.erase(entry);
|
||||
}
|
||||
|
||||
void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override
|
||||
@@ -437,9 +449,138 @@ namespace icicle
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_in_hook_{false};
|
||||
std::list<std::unique_ptr<utils::object>> storage_{};
|
||||
std::unordered_map<uint32_t, std::unique_ptr<utils::object>> hooks_{};
|
||||
std::unordered_map<emulator_hook*, std::optional<uint32_t>> id_mapping_{};
|
||||
icicle_emulator* emu_{};
|
||||
uint32_t index_{0};
|
||||
|
||||
std::unordered_set<emulator_hook*> hooks_to_delete_{};
|
||||
std::unordered_map<emulator_hook*, memory_access_hook> hooks_to_install_{};
|
||||
|
||||
emulator_hook* wrap_hook(const std::optional<uint32_t> icicle_id)
|
||||
{
|
||||
const auto id = ++this->index_;
|
||||
auto* hook = reinterpret_cast<emulator_hook*>(static_cast<size_t>(id));
|
||||
|
||||
this->id_mapping_[hook] = icicle_id;
|
||||
|
||||
return hook;
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_access(memory_access_hook hook, emulator_hook* hook_id)
|
||||
{
|
||||
auto obj = make_function_object(std::move(hook.callback), this->is_in_hook_);
|
||||
auto* ptr = obj.get();
|
||||
auto* wrapper = +[](void* user, const uint64_t address, const void* data, size_t length) {
|
||||
const auto& func = *static_cast<decltype(ptr)>(user);
|
||||
func(address, data, length);
|
||||
};
|
||||
|
||||
auto* installer = hook.is_read ? &icicle_add_read_hook : &icicle_add_write_hook;
|
||||
const auto id = installer(this->emu_, hook.address, hook.address + hook.size, wrapper, ptr);
|
||||
this->hooks_[id] = std::move(obj);
|
||||
|
||||
if (hook_id)
|
||||
{
|
||||
this->id_mapping_[hook_id] = id;
|
||||
return hook_id;
|
||||
}
|
||||
|
||||
return wrap_hook(id);
|
||||
}
|
||||
|
||||
void delete_hook_internal(emulator_hook* hook)
|
||||
{
|
||||
auto hook_id = this->id_mapping_.find(hook);
|
||||
if (hook_id == this->id_mapping_.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hook_id->second.has_value())
|
||||
{
|
||||
this->hooks_to_delete_.insert(hook);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = *hook_id->second;
|
||||
this->id_mapping_.erase(hook_id);
|
||||
|
||||
const auto entry = this->hooks_.find(id);
|
||||
if (entry == this->hooks_.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
icicle_remove_hook(this->emu_, id);
|
||||
const auto obj = std::move(entry->second);
|
||||
this->hooks_.erase(entry);
|
||||
(void)obj;
|
||||
}
|
||||
|
||||
void perform_pending_actions()
|
||||
{
|
||||
auto hooks_to_install = std::move(this->hooks_to_install_);
|
||||
const auto hooks_to_delete = std::move(this->hooks_to_delete_);
|
||||
|
||||
this->hooks_to_delete_ = {};
|
||||
this->hooks_to_install_ = {};
|
||||
|
||||
for (auto& hook : hooks_to_install)
|
||||
{
|
||||
this->hook_memory_access(std::move(hook.second), hook.first);
|
||||
}
|
||||
|
||||
for (auto* hook : hooks_to_delete)
|
||||
{
|
||||
this->delete_hook_internal(hook);
|
||||
}
|
||||
}
|
||||
|
||||
emulator_hook* try_install_memory_access_hook(memory_access_hook hook)
|
||||
{
|
||||
if (!this->is_in_hook_)
|
||||
{
|
||||
return this->hook_memory_access(std::move(hook), nullptr);
|
||||
}
|
||||
|
||||
auto* hook_id = wrap_hook(std::nullopt);
|
||||
this->hooks_to_install_[hook_id] = std::move(hook);
|
||||
|
||||
this->schedule_action_execution();
|
||||
|
||||
return hook_id;
|
||||
}
|
||||
|
||||
void schedule_action_execution()
|
||||
{
|
||||
this->run_on_next_instruction([this] {
|
||||
this->perform_pending_actions(); //
|
||||
});
|
||||
}
|
||||
|
||||
void run_on_next_instruction(std::function<void()> func) const
|
||||
{
|
||||
auto* heap_func = new std::function(std::move(func));
|
||||
auto* callback = +[](void* data) {
|
||||
auto* cb = static_cast<std::function<void()>*>(data);
|
||||
|
||||
try
|
||||
{
|
||||
(*cb)();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
delete cb;
|
||||
};
|
||||
|
||||
icicle_run_on_next_instruction(this->emu_, callback, heap_func);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<x86_64_emulator> create_x86_64_emulator()
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace unicorn
|
||||
return UC_X86_INS_RDTSC;
|
||||
case x86_hookable_instructions::rdtscp:
|
||||
return UC_X86_INS_RDTSCP;
|
||||
case x86_hookable_instructions::sgdt:
|
||||
return UC_X86_INS_SGDT;
|
||||
default:
|
||||
throw std::runtime_error("Bad instruction for mapping");
|
||||
}
|
||||
@@ -207,7 +209,7 @@ namespace unicorn
|
||||
|
||||
~unicorn_x86_64_emulator() override
|
||||
{
|
||||
this->hooks_.clear();
|
||||
reset_object_with_delayed_destruction(this->hooks_);
|
||||
uc_close(this->uc_);
|
||||
}
|
||||
|
||||
@@ -244,8 +246,11 @@ namespace unicorn
|
||||
|
||||
void load_gdt(const pointer_type address, const uint32_t limit) override
|
||||
{
|
||||
const std::array<uint64_t, 4> gdtr = {0, address, limit, 0};
|
||||
this->write_register(x86_register::gdtr, gdtr.data(), gdtr.size() * sizeof(uint64_t));
|
||||
uc_x86_mmr gdt{};
|
||||
gdt.base = address;
|
||||
gdt.limit = limit;
|
||||
|
||||
this->write_register(x86_register::gdtr, &gdt, sizeof(gdt));
|
||||
}
|
||||
|
||||
void set_segment_base(const x86_register base, const pointer_type value) override
|
||||
@@ -288,8 +293,7 @@ namespace unicorn
|
||||
|
||||
if (size < result_size)
|
||||
{
|
||||
throw std::runtime_error("Register size mismatch: " + std::to_string(size) +
|
||||
" != " + std::to_string(result_size));
|
||||
throw std::runtime_error("Register size mismatch: " + std::to_string(size) + " != " + std::to_string(result_size));
|
||||
}
|
||||
|
||||
return result_size;
|
||||
@@ -303,15 +307,13 @@ namespace unicorn
|
||||
|
||||
if (size < result_size)
|
||||
{
|
||||
throw std::runtime_error("Register size mismatch: " + std::to_string(size) +
|
||||
" != " + std::to_string(result_size));
|
||||
throw std::runtime_error("Register size mismatch: " + std::to_string(size) + " != " + std::to_string(result_size));
|
||||
}
|
||||
|
||||
return result_size;
|
||||
}
|
||||
|
||||
void map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb,
|
||||
mmio_write_callback write_cb) override
|
||||
void map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) override
|
||||
{
|
||||
auto read_wrapper = [c = std::move(read_cb)](uc_engine*, const uint64_t addr, const uint32_t s) {
|
||||
assert_64bit_limit(s);
|
||||
@@ -320,8 +322,7 @@ namespace unicorn
|
||||
return value;
|
||||
};
|
||||
|
||||
auto write_wrapper = [c = std::move(write_cb)](uc_engine*, const uint64_t addr, const uint32_t s,
|
||||
const uint64_t value) {
|
||||
auto write_wrapper = [c = std::move(write_cb)](uc_engine*, const uint64_t addr, const uint32_t s, const uint64_t value) {
|
||||
assert_64bit_limit(s);
|
||||
c(addr, &value, s);
|
||||
};
|
||||
@@ -331,8 +332,8 @@ namespace unicorn
|
||||
.write = mmio_callbacks::write_wrapper(std::move(write_wrapper)),
|
||||
};
|
||||
|
||||
uce(uc_mmio_map(*this, address, size, cb.read.get_c_function(), cb.read.get_user_data(),
|
||||
cb.write.get_c_function(), cb.write.get_user_data()));
|
||||
uce(uc_mmio_map(*this, address, size, cb.read.get_c_function(), cb.read.get_user_data(), cb.write.get_c_function(),
|
||||
cb.write.get_user_data()));
|
||||
|
||||
this->mmio_[address] = std::move(cb);
|
||||
}
|
||||
@@ -368,8 +369,7 @@ namespace unicorn
|
||||
uce(uc_mem_write(*this, address, data, size));
|
||||
}
|
||||
|
||||
void apply_memory_protection(const uint64_t address, const size_t size,
|
||||
memory_permission permissions) override
|
||||
void apply_memory_protection(const uint64_t address, const size_t size, memory_permission permissions) override
|
||||
{
|
||||
uce(uc_mem_protect(*this, address, size, static_cast<uint32_t>(permissions)));
|
||||
}
|
||||
@@ -384,34 +384,44 @@ namespace unicorn
|
||||
if (inst_type == x86_hookable_instructions::invalid)
|
||||
{
|
||||
function_wrapper<int, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) {
|
||||
return (c() == instruction_hook_continuation::skip_instruction) ? 1 : 0;
|
||||
return (c(0) == instruction_hook_continuation::skip_instruction) ? 1 : 0;
|
||||
});
|
||||
|
||||
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()));
|
||||
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()));
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
}
|
||||
else if (inst_type == x86_hookable_instructions::syscall)
|
||||
{
|
||||
function_wrapper<void, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) { c(); });
|
||||
function_wrapper<void, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) { (void)c(0); });
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
else if (inst_type == x86_hookable_instructions::sgdt)
|
||||
{
|
||||
function_wrapper<int, uc_engine*, uint64_t> wrapper([c = std::move(callback)](uc_engine*, const uint64_t data) {
|
||||
return (c(data) == instruction_hook_continuation::skip_instruction) ? 1 : 0;
|
||||
});
|
||||
|
||||
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));
|
||||
}
|
||||
else
|
||||
{
|
||||
function_wrapper<int, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) {
|
||||
return (c() == instruction_hook_continuation::skip_instruction) ? 1 : 0;
|
||||
return (c(0) == instruction_hook_continuation::skip_instruction) ? 1 : 0;
|
||||
});
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
@@ -437,8 +447,8 @@ namespace unicorn
|
||||
unicorn_hook hook{*this};
|
||||
auto container = std::make_unique<hook_container>();
|
||||
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_BLOCK, wrapper.get_function(),
|
||||
wrapper.get_user_data(), 0, std::numeric_limits<pointer_type>::max()));
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_BLOCK, wrapper.get_function(), wrapper.get_user_data(), 0,
|
||||
std::numeric_limits<pointer_type>::max()));
|
||||
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
|
||||
@@ -455,8 +465,8 @@ namespace unicorn
|
||||
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()));
|
||||
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));
|
||||
|
||||
@@ -468,16 +478,16 @@ namespace unicorn
|
||||
emulator_hook* hook_memory_violation(memory_violation_hook_callback callback) override
|
||||
{
|
||||
function_wrapper<bool, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(
|
||||
[c = std::move(callback), this](uc_engine*, const uc_mem_type type, const uint64_t address,
|
||||
const int size, const int64_t) {
|
||||
[c = std::move(callback), this](uc_engine*, const uc_mem_type type, const uint64_t address, const int size,
|
||||
const int64_t) {
|
||||
const auto ip = this->read_instruction_pointer();
|
||||
|
||||
assert(size >= 0);
|
||||
const auto operation = map_memory_operation(type);
|
||||
const auto violation = map_memory_violation_type(type);
|
||||
|
||||
const auto resume = c(address, static_cast<uint64_t>(size), operation, violation) ==
|
||||
memory_violation_continuation::resume;
|
||||
const auto resume =
|
||||
c(address, static_cast<uint64_t>(size), operation, violation) == memory_violation_continuation::resume;
|
||||
|
||||
const auto new_ip = this->read_instruction_pointer();
|
||||
const auto has_ip_changed = ip != new_ip;
|
||||
@@ -507,8 +517,8 @@ namespace unicorn
|
||||
unicorn_hook hook{*this};
|
||||
auto container = std::make_unique<hook_container>();
|
||||
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_INVALID, wrapper.get_function(),
|
||||
wrapper.get_user_data(), 0, std::numeric_limits<uint64_t>::max()));
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_INVALID, wrapper.get_function(), wrapper.get_user_data(), 0,
|
||||
std::numeric_limits<uint64_t>::max()));
|
||||
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
|
||||
@@ -517,11 +527,9 @@ namespace unicorn
|
||||
return result;
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_execution(const uint64_t address, const uint64_t size,
|
||||
memory_execution_hook_callback callback)
|
||||
emulator_hook* hook_memory_execution(const uint64_t address, const uint64_t size, memory_execution_hook_callback callback)
|
||||
{
|
||||
auto exec_wrapper = [c = std::move(callback)](uc_engine*, const uint64_t address,
|
||||
const uint32_t /*size*/) {
|
||||
auto exec_wrapper = [c = std::move(callback)](uc_engine*, const uint64_t address, const uint32_t /*size*/) {
|
||||
c(address); //
|
||||
};
|
||||
|
||||
@@ -529,8 +537,8 @@ namespace unicorn
|
||||
|
||||
unicorn_hook hook{*this};
|
||||
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_CODE, wrapper.get_function(),
|
||||
wrapper.get_user_data(), address, address + size));
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_CODE, wrapper.get_function(), wrapper.get_user_data(), address,
|
||||
address + size));
|
||||
|
||||
auto* container = this->create_hook_container();
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
@@ -542,17 +550,14 @@ namespace unicorn
|
||||
return this->hook_memory_execution(0, std::numeric_limits<uint64_t>::max(), std::move(callback));
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_execution(const uint64_t address,
|
||||
memory_execution_hook_callback callback) override
|
||||
emulator_hook* hook_memory_execution(const uint64_t address, memory_execution_hook_callback callback) override
|
||||
{
|
||||
return this->hook_memory_execution(address, 1, std::move(callback));
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size,
|
||||
memory_access_hook_callback callback) override
|
||||
emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size, memory_access_hook_callback callback) override
|
||||
{
|
||||
auto read_wrapper = [c = std::move(callback)](uc_engine*, const uc_mem_type type,
|
||||
const uint64_t address, const int length,
|
||||
auto read_wrapper = [c = std::move(callback)](uc_engine*, const uc_mem_type type, const uint64_t address, const int length,
|
||||
const uint64_t value) {
|
||||
const auto operation = map_memory_operation(type);
|
||||
if (operation == memory_operation::read && length > 0)
|
||||
@@ -561,23 +566,21 @@ namespace unicorn
|
||||
}
|
||||
};
|
||||
|
||||
function_wrapper<void, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(
|
||||
std::move(read_wrapper));
|
||||
function_wrapper<void, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(std::move(read_wrapper));
|
||||
|
||||
unicorn_hook hook{*this};
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_READ_AFTER, wrapper.get_function(),
|
||||
wrapper.get_user_data(), address, address + size));
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_READ_AFTER, wrapper.get_function(), wrapper.get_user_data(),
|
||||
address, address + size));
|
||||
|
||||
auto* container = this->create_hook_container();
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
return container->as_opaque_hook();
|
||||
}
|
||||
|
||||
emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size,
|
||||
memory_access_hook_callback callback) override
|
||||
emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size, memory_access_hook_callback callback) override
|
||||
{
|
||||
auto write_wrapper = [c = std::move(callback)](uc_engine*, const uc_mem_type type, const uint64_t addr,
|
||||
const int length, const uint64_t value) {
|
||||
auto write_wrapper = [c = std::move(callback)](uc_engine*, const uc_mem_type type, const uint64_t addr, const int length,
|
||||
const uint64_t value) {
|
||||
const auto operation = map_memory_operation(type);
|
||||
if (operation == memory_operation::write && length > 0)
|
||||
{
|
||||
@@ -585,13 +588,12 @@ namespace unicorn
|
||||
}
|
||||
};
|
||||
|
||||
function_wrapper<void, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(
|
||||
std::move(write_wrapper));
|
||||
function_wrapper<void, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(std::move(write_wrapper));
|
||||
|
||||
unicorn_hook hook{*this};
|
||||
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_WRITE, wrapper.get_function(),
|
||||
wrapper.get_user_data(), address, address + size));
|
||||
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_WRITE, wrapper.get_function(), wrapper.get_user_data(), address,
|
||||
address + size));
|
||||
|
||||
auto* container = this->create_hook_container();
|
||||
container->add(std::move(wrapper), std::move(hook));
|
||||
@@ -608,14 +610,14 @@ namespace unicorn
|
||||
|
||||
void delete_hook(emulator_hook* hook) override
|
||||
{
|
||||
const auto entry =
|
||||
std::ranges::find_if(this->hooks_, [&](const std::unique_ptr<hook_object>& hook_ptr) {
|
||||
return hook_ptr->as_opaque_hook() == hook;
|
||||
});
|
||||
const auto entry = std::ranges::find_if(
|
||||
this->hooks_, [&](const std::unique_ptr<hook_object>& hook_ptr) { return hook_ptr->as_opaque_hook() == hook; });
|
||||
|
||||
if (entry != this->hooks_.end())
|
||||
{
|
||||
const auto obj = std::move(*entry);
|
||||
this->hooks_.erase(entry);
|
||||
(void)obj;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user