Use clock interfaces to precisely control time

This commit is contained in:
Maurice Heumann
2025-03-18 11:48:44 +01:00
parent 0a28b13d07
commit 450e3c2a9c
15 changed files with 214 additions and 97 deletions

View File

@@ -3,7 +3,9 @@
namespace utils
{
std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(const LARGE_INTEGER delay_interval)
std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(steady_clock& steady_time,
system_clock& system_time,
const LARGE_INTEGER delay_interval)
{
if (delay_interval.QuadPart <= 0)
{
@@ -13,7 +15,7 @@ namespace utils
const auto relative_duration =
std::chrono::microseconds(relative_ticks_in_ms) + std::chrono::nanoseconds(relative_fraction_ns);
return std::chrono::steady_clock::now() + relative_duration;
return steady_time.now() + relative_duration;
}
const auto delay_seconds_since_1601 = delay_interval.QuadPart / HUNDRED_NANOSECONDS_IN_ONE_SECOND;
@@ -24,12 +26,12 @@ namespace utils
const auto target_time = std::chrono::system_clock::from_time_t(delay_seconds_since_1970) +
std::chrono::nanoseconds(delay_fraction_ns);
const auto now_system = std::chrono::system_clock::now();
const auto now_system = system_time.now();
const auto duration_until_target =
std::chrono::duration_cast<std::chrono::microseconds>(target_time - now_system);
return std::chrono::steady_clock::now() + duration_until_target;
return steady_time.now() + duration_until_target;
}
KSYSTEM_TIME convert_to_ksystem_time(const std::chrono::system_clock::time_point& tp)

View File

@@ -10,7 +10,62 @@ constexpr auto WINDOWS_EPOCH_DIFFERENCE = EPOCH_DIFFERENCE_1601_TO_1970_SECONDS
namespace utils
{
std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(const LARGE_INTEGER delay_interval);
template <typename Clock>
struct clock
{
using base_clock = Clock;
using time_point = typename base_clock::time_point;
using duration = typename base_clock::duration;
virtual ~clock() = default;
virtual time_point now()
{
return base_clock::now();
}
};
template <typename Clock>
class tick_clock : public clock<Clock>
{
public:
tick_clock(const typename tick_clock::time_point start, const uint64_t frequency)
: frequency_(frequency),
start_(start)
{
if (this->frequency_ == 0)
{
throw std::invalid_argument("Frequency can not be 0");
}
}
typename tick_clock::time_point now() override
{
const auto passed_ticks = this->ticks();
const auto passed_time =
tick_clock::duration(passed_ticks * tick_clock::duration::period::den / this->frequency_);
return this->start_ + passed_time;
}
virtual uint64_t ticks() = 0;
uint64_t get_frequency() const
{
return this->frequency_;
}
private:
uint64_t frequency_{1};
typename tick_clock::time_point start_{};
};
using system_clock = clock<std::chrono::system_clock>;
using steady_clock = clock<std::chrono::steady_clock>;
std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(steady_clock& steady_time,
system_clock& system_time,
LARGE_INTEGER delay_interval);
KSYSTEM_TIME convert_to_ksystem_time(const std::chrono::system_clock::time_point& tp);
void convert_to_ksystem_time(volatile KSYSTEM_TIME* dest, const std::chrono::system_clock::time_point& tp);
std::chrono::system_clock::time_point convert_from_ksystem_time(const KSYSTEM_TIME& time);
@@ -18,5 +73,5 @@ namespace utils
#ifndef OS_WINDOWS
using __time64_t = int64_t;
#endif
LARGE_INTEGER convert_unix_to_windows_time(const __time64_t unix_time);
LARGE_INTEGER convert_unix_to_windows_time(__time64_t unix_time);
}

View File

@@ -423,6 +423,8 @@ int main(const int argc, const char* argv[])
RUN_TEST(test_native_exceptions, "Native Exceptions")
RUN_TEST(test_tls, "TLS")
Sleep(1);
if (!reproducible)
{
RUN_TEST(test_socket, "Socket")

View File

@@ -49,7 +49,7 @@ namespace test
utils::buffer_deserializer deserializer{serializer1.get_buffer()};
windows_emulator new_emu{{.emulation_root = get_emulator_root()}};
windows_emulator new_emu{{.emulation_root = get_emulator_root(), .use_relative_time = true}};
new_emu.deserialize(deserializer);
utils::buffer_serializer serializer2{};
@@ -92,7 +92,7 @@ namespace test
utils::buffer_deserializer deserializer{serializer.get_buffer()};
windows_emulator new_emu{{.emulation_root = get_emulator_root()}};
windows_emulator new_emu{{.emulation_root = get_emulator_root(), .use_relative_time = true}};
new_emu.log.disable_output(true);
new_emu.deserialize(deserializer);

View File

@@ -457,7 +457,7 @@ namespace
const auto status = this->execute_ioctl(win_emu, *this->delayed_ioctl_);
if (status == STATUS_PENDING)
{
if (!this->timeout_ || this->timeout_ > std::chrono::steady_clock::now())
if (!this->timeout_ || this->timeout_ > win_emu.steady_clock().now())
{
return;
}
@@ -593,7 +593,8 @@ namespace
std::optional<std::chrono::steady_clock::time_point> timeout{};
if (info.Timeout.QuadPart != std::numeric_limits<int64_t>::max())
{
timeout = utils::convert_delay_interval_to_time_point(info.Timeout);
timeout = utils::convert_delay_interval_to_time_point(win_emu.steady_clock(),
win_emu.system_clock(), info.Timeout);
}
this->delay_ioctrl(c, timeout);

View File

@@ -148,7 +148,7 @@ bool emulator_thread::is_terminated() const
return this->exit_status.has_value();
}
bool emulator_thread::is_thread_ready(process_context& process)
bool emulator_thread::is_thread_ready(process_context& process, utils::steady_clock& steady_clock)
{
if (this->is_terminated())
{
@@ -162,7 +162,7 @@ bool emulator_thread::is_thread_ready(process_context& process)
this->mark_as_ready(STATUS_ALERTED);
return true;
}
if (this->is_await_time_over())
if (this->is_await_time_over(steady_clock))
{
this->mark_as_ready(STATUS_TIMEOUT);
return true;
@@ -194,7 +194,7 @@ bool emulator_thread::is_thread_ready(process_context& process)
return true;
}
if (this->is_await_time_over())
if (this->is_await_time_over(steady_clock))
{
this->mark_as_ready(STATUS_TIMEOUT);
return true;
@@ -205,7 +205,7 @@ bool emulator_thread::is_thread_ready(process_context& process)
if (this->await_time.has_value())
{
if (this->is_await_time_over())
if (this->is_await_time_over(steady_clock))
{
this->mark_as_ready(STATUS_SUCCESS);
return true;

View File

@@ -65,14 +65,14 @@ class emulator_thread : public ref_counted_object
void mark_as_ready(NTSTATUS status);
bool is_await_time_over() const
bool is_await_time_over(utils::steady_clock& steady_clock) const
{
return this->await_time.has_value() && this->await_time.value() < std::chrono::steady_clock::now();
return this->await_time.has_value() && this->await_time.value() < steady_clock.now();
}
bool is_terminated() const;
bool is_thread_ready(process_context& process);
bool is_thread_ready(process_context& process, utils::steady_clock& steady_clock);
void save(x64_emulator& emu)
{

View File

@@ -6,6 +6,8 @@
#include "memory_utils.hpp"
#include "address_utils.hpp"
#include <utils/time.hpp>
// TODO: Replace with pointer handling structure for future 32 bit support
using emulator_pointer = uint64_t;
@@ -47,6 +49,8 @@ using x64_emulator_wrapper = object_wrapper<x64_emulator>;
using memory_manager_wrapper = object_wrapper<memory_manager>;
using module_manager_wrapper = object_wrapper<module_manager>;
using process_context_wrapper = object_wrapper<process_context>;
using system_clock_wrapper = object_wrapper<utils::system_clock>;
using steady_clock_wrapper = object_wrapper<utils::steady_clock>;
using windows_emulator_wrapper = object_wrapper<windows_emulator>;
template <typename T>

View File

@@ -10,7 +10,7 @@ constexpr auto KUSD_BUFFER_SIZE = page_align_up(KUSD_SIZE);
namespace
{
void setup_kusd(KUSER_SHARED_DATA64& kusd, const bool use_relative_time)
void setup_kusd(KUSER_SHARED_DATA64& kusd)
{
memset(reinterpret_cast<void*>(&kusd), 0, sizeof(kusd));
@@ -60,25 +60,17 @@ namespace
kusd.TelemetryCoverageRound = 0x00000001;
kusd.LangGenerationCount = 0x00000003;
kusd.InterruptTimeBias = 0x00000015a5d56406;
kusd.QpcBias = 0x000000159530c4af;
kusd.ActiveProcessorCount = 0x0000000c;
kusd.ActiveGroupCount = 0x01;
kusd.QpcData.QpcData = 0x0083;
kusd.QpcData.QpcBypassEnabled = 0x83;
kusd.TimeZoneBiasEffectiveStart.QuadPart = 0x01db276e654cb2ff;
kusd.TimeZoneBiasEffectiveEnd.QuadPart = 0x01db280b8c3b2800;
kusd.XState.EnabledFeatures = 0x000000000000001f;
kusd.XState.EnabledVolatileFeatures = 0x000000000000000f;
kusd.XState.Size = 0x000003c0;
if (use_relative_time)
{
kusd.QpcFrequency = 1000;
}
else
{
kusd.QpcFrequency = std::chrono::steady_clock::period::den;
}
kusd.QpcData.QpcData = 0x0083;
kusd.QpcData.QpcBypassEnabled = 0x83;
kusd.QpcBias = 0x000000159530c4af;
kusd.QpcFrequency = utils::steady_clock::duration::period::den;
constexpr std::u16string_view root_dir{u"C:\\WINDOWS"};
memcpy(&kusd.NtSystemRoot.arr[0], root_dir.data(), root_dir.size() * 2);
@@ -102,9 +94,9 @@ namespace utils
}
}
kusd_mmio::kusd_mmio(memory_manager& memory, process_context& process)
kusd_mmio::kusd_mmio(memory_manager& memory, utils::system_clock& clock)
: memory_(&memory),
process_(&process)
system_clock_(&clock)
{
}
@@ -114,32 +106,24 @@ kusd_mmio::~kusd_mmio()
}
kusd_mmio::kusd_mmio(utils::buffer_deserializer& buffer)
: kusd_mmio(buffer.read<memory_manager_wrapper>(), buffer.read<process_context_wrapper>())
: kusd_mmio(buffer.read<memory_manager_wrapper>(), buffer.read<system_clock_wrapper>())
{
}
void kusd_mmio::setup(const bool use_relative_time)
void kusd_mmio::setup()
{
this->use_relative_time_ = use_relative_time;
setup_kusd(this->kusd_, use_relative_time);
this->start_time_ = utils::convert_from_ksystem_time(this->kusd_.SystemTime);
setup_kusd(this->kusd_);
this->register_mmio();
}
void kusd_mmio::serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->use_relative_time_);
buffer.write(this->kusd_);
buffer.write(this->start_time_);
}
void kusd_mmio::deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->use_relative_time_);
buffer.read(this->kusd_);
buffer.read(this->start_time_);
this->deregister_mmio();
this->register_mmio();
@@ -178,21 +162,7 @@ uint64_t kusd_mmio::address()
void kusd_mmio::update()
{
auto time = this->start_time_;
if (this->use_relative_time_)
{
const auto passed_time = this->process_->executed_instructions;
const auto clock_frequency = static_cast<uint64_t>(this->kusd_.QpcFrequency);
using duration = std::chrono::system_clock::duration;
time += duration(passed_time * duration::period::den / clock_frequency);
}
else
{
time = std::chrono::system_clock::now();
}
const auto time = this->system_clock_->now();
utils::convert_to_ksystem_time(&this->kusd_.SystemTime, time);
}

View File

@@ -5,13 +5,15 @@
#include "x64_emulator.hpp"
#include <utils/time.hpp>
struct process_context;
class windows_emulator;
class kusd_mmio
{
public:
kusd_mmio(memory_manager& memory, process_context& process);
kusd_mmio(memory_manager& memory, utils::system_clock& clock);
~kusd_mmio();
kusd_mmio(utils::buffer_deserializer& buffer);
@@ -36,17 +38,15 @@ class kusd_mmio
static uint64_t address();
void setup(bool use_relative_time);
void setup();
private:
memory_manager* memory_{};
process_context* process_{};
utils::system_clock* system_clock_{};
bool registered_{};
bool use_relative_time_{};
KUSER_SHARED_DATA64 kusd_{};
std::chrono::system_clock::time_point start_time_{};
uint64_t read(uint64_t addr, size_t size);

View File

@@ -28,12 +28,12 @@ namespace
}
void process_context::setup(x64_emulator& emu, memory_manager& memory, const application_settings& app_settings,
const emulator_settings& emu_settings, const mapped_module& executable,
const mapped_module& ntdll, const apiset::container& apiset_container)
const mapped_module& executable, const mapped_module& ntdll,
const apiset::container& apiset_container)
{
setup_gdt(emu, memory);
this->kusd.setup(emu_settings.use_relative_time);
this->kusd.setup();
this->base_allocator = create_allocator(memory, PEB_SEGMENT_SIZE);
auto& allocator = this->base_allocator;

View File

@@ -38,18 +38,17 @@ struct process_context
utils::optional_function<void(handle h, emulator_thread& thr)> on_thread_terminated{};
};
process_context(x64_emulator& emu, memory_manager& memory, callbacks& cb)
process_context(x64_emulator& emu, memory_manager& memory, utils::system_clock& clock, callbacks& cb)
: callbacks_(&cb),
base_allocator(emu),
peb(emu),
process_params(emu),
kusd(memory, *this)
kusd(memory, clock)
{
}
void setup(x64_emulator& emu, memory_manager& memory, const application_settings& app_settings,
const emulator_settings& emu_settings, const mapped_module& executable, const mapped_module& ntdll,
const apiset::container& apiset_container);
const mapped_module& executable, const mapped_module& ntdll, const apiset::container& apiset_container);
handle create_thread(memory_manager& memory, const uint64_t start_address, const uint64_t argument,
const uint64_t stack_size);

View File

@@ -27,21 +27,15 @@ namespace
if (performance_counter)
{
performance_counter.access([&](LARGE_INTEGER& value) {
if (c.win_emu.time_is_relative())
{
value.QuadPart = static_cast<LONGLONG>(c.proc.executed_instructions);
}
else
{
value.QuadPart = std::chrono::steady_clock::now().time_since_epoch().count();
}
value.QuadPart = c.win_emu.steady_clock().now().time_since_epoch().count();
});
}
if (performance_frequency)
{
performance_frequency.access(
[&](LARGE_INTEGER& value) { value.QuadPart = c.proc.kusd.get().QpcFrequency; });
performance_frequency.access([&](LARGE_INTEGER& value) {
value.QuadPart = c.proc.kusd.get().QpcFrequency; //
});
}
return STATUS_SUCCESS;
@@ -3639,7 +3633,8 @@ namespace
if (timeout.value() && !t.await_time.has_value())
{
t.await_time = utils::convert_delay_interval_to_time_point(timeout.read());
t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.steady_clock(),
c.win_emu.system_clock(), timeout.read());
}
c.win_emu.yield_thread();
@@ -3666,7 +3661,8 @@ namespace
if (timeout.value() && !t.await_time.has_value())
{
t.await_time = utils::convert_delay_interval_to_time_point(timeout.read());
t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.steady_clock(),
c.win_emu.system_clock(), timeout.read());
}
c.win_emu.yield_thread();
@@ -3703,7 +3699,8 @@ namespace
}
auto& t = c.win_emu.current_thread();
t.await_time = utils::convert_delay_interval_to_time_point(delay_interval.read());
t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.steady_clock(), c.win_emu.system_clock(),
delay_interval.read());
c.win_emu.yield_thread();
@@ -3745,7 +3742,8 @@ namespace
if (timeout.value() && !t.await_time.has_value())
{
t.await_time = utils::convert_delay_interval_to_time_point(timeout.read());
t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.steady_clock(),
c.win_emu.system_clock(), timeout.read());
}
c.win_emu.yield_thread();

View File

@@ -86,7 +86,7 @@ namespace
auto& emu = win_emu.emu();
auto& context = win_emu.process;
const auto is_ready = thread.is_thread_ready(context);
const auto is_ready = thread.is_thread_ready(context, win_emu.steady_clock());
if (!is_ready && !force)
{
@@ -161,6 +161,48 @@ namespace
return false;
}
template <typename Clock>
struct instruction_tick_clock : utils::tick_clock<Clock>
{
windows_emulator* win_emu_{};
instruction_tick_clock(windows_emulator& win_emu, const typename instruction_tick_clock::time_point start = {})
: utils::tick_clock<Clock>(start, 1000),
win_emu_(&win_emu)
{
}
uint64_t ticks() override
{
if (!this->win_emu_->base_constructed_) // TODO: Remove that
{
throw std::runtime_error("Requesting ticks before construction!");
}
return this->win_emu_->process.executed_instructions;
}
};
std::unique_ptr<utils::steady_clock> get_steady_clock(windows_emulator& win_emu, const bool use_relative_time)
{
if (use_relative_time)
{
return std::make_unique<instruction_tick_clock<utils::steady_clock::base_clock>>(win_emu);
}
return std::make_unique<utils::steady_clock>();
}
std::unique_ptr<utils::system_clock> get_system_clock(windows_emulator& win_emu, const bool use_relative_time)
{
if (use_relative_time)
{
return std::make_unique<instruction_tick_clock<utils::system_clock::base_clock>>(win_emu);
}
return std::make_unique<utils::system_clock>();
}
}
std::unique_ptr<x64_emulator> create_default_x64_emulator()
@@ -175,18 +217,22 @@ windows_emulator::windows_emulator(application_settings app_settings, const emul
this->callbacks = std::move(callbacks);
fixup_application_settings(app_settings);
this->setup_process(app_settings, settings);
this->setup_process(app_settings);
}
windows_emulator::windows_emulator(const emulator_settings& settings, std::unique_ptr<x64_emulator> emu)
: emu_(std::move(emu)),
system_clock_(get_system_clock(*this, settings.use_relative_time)),
steady_clock_(get_steady_clock(*this, settings.use_relative_time)),
emulation_root{settings.emulation_root.empty() ? settings.emulation_root : absolute(settings.emulation_root)},
file_sys(emulation_root.empty() ? emulation_root : emulation_root / "filesys"),
memory(*this->emu_),
registry(emulation_root.empty() ? settings.registry_directory : emulation_root / "registry"),
mod_manager(memory, file_sys, callbacks),
process(*this->emu_, memory, callbacks)
process(*this->emu_, memory, *this->system_clock_, callbacks)
{
this->base_constructed_ = true;
#ifndef OS_WINDOWS
if (this->emulation_root.empty())
{
@@ -215,7 +261,7 @@ windows_emulator::windows_emulator(const emulator_settings& settings, std::uniqu
windows_emulator::~windows_emulator() = default;
void windows_emulator::setup_process(const application_settings& app_settings, const emulator_settings& emu_settings)
void windows_emulator::setup_process(const application_settings& app_settings)
{
const auto& emu = this->emu();
auto& context = this->process;
@@ -229,7 +275,7 @@ void windows_emulator::setup_process(const application_settings& app_settings, c
const auto apiset_data = apiset::obtain(this->emulation_root);
this->process.setup(this->emu(), this->memory, app_settings, emu_settings, *executable, *ntdll, apiset_data);
this->process.setup(this->emu(), this->memory, app_settings, *executable, *ntdll, apiset_data);
const auto ntdll_data = emu.read_memory(ntdll->image_base, ntdll->size_of_image);
const auto win32u_data = emu.read_memory(win32u->image_base, win32u->size_of_image);
@@ -251,8 +297,14 @@ void windows_emulator::perform_thread_switch()
this->switch_thread_ = false;
while (!switch_to_next_thread(*this))
{
// TODO: Optimize that
std::this_thread::sleep_for(1ms);
if (this->use_relative_time_)
{
this->process.executed_instructions += MAX_INSTRUCTIONS_PER_TIME_SLICE;
}
else
{
std::this_thread::sleep_for(1ms);
}
}
}
@@ -450,7 +502,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->process))
if (this->switch_thread_ || !this->current_thread().is_thread_ready(this->process, this->steady_clock()))
{
this->perform_thread_switch();
}
@@ -517,9 +569,24 @@ void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
return windows_emulator_wrapper{*this}; //
});
buffer.register_factory<system_clock_wrapper>([this] {
return system_clock_wrapper{this->system_clock()}; //
});
buffer.register_factory<steady_clock_wrapper>([this] {
return steady_clock_wrapper{this->steady_clock()}; //
});
buffer.read(this->switch_thread_);
const auto old_relative_time = this->use_relative_time_;
buffer.read(this->use_relative_time_);
if (old_relative_time != this->use_relative_time_)
{
throw std::runtime_error("Can not deserialize emulator with different time dimensions");
}
this->memory.unmap_all_memory();
this->emu().deserialize_state(buffer, false);

View File

@@ -48,6 +48,8 @@ struct emulator_settings
class windows_emulator
{
std::unique_ptr<x64_emulator> emu_{};
std::unique_ptr<utils::system_clock> system_clock_{};
std::unique_ptr<utils::steady_clock> steady_clock_{};
public:
std::filesystem::path emulation_root{};
@@ -83,6 +85,26 @@ class windows_emulator
return *this->emu_;
}
utils::system_clock& system_clock()
{
return *this->system_clock_;
}
const utils::system_clock& system_clock() const
{
return *this->system_clock_;
}
utils::steady_clock& steady_clock()
{
return *this->steady_clock_;
}
const utils::steady_clock& steady_clock() const
{
return *this->steady_clock_;
}
emulator_thread& current_thread() const
{
if (!this->process.active_thread)
@@ -149,10 +171,7 @@ class windows_emulator
void perform_thread_switch();
bool activate_thread(uint32_t id);
bool time_is_relative() const
{
return this->use_relative_time_;
}
bool base_constructed_{false};
private:
bool switch_thread_{false};
@@ -166,6 +185,6 @@ class windows_emulator
// std::optional<process_context> process_snapshot_{};
void setup_hooks();
void setup_process(const application_settings& app_settings, const emulator_settings& emu_settings);
void setup_process(const application_settings& app_settings);
void on_instruction_execution(uint64_t address);
};