From 5471844c5d53659dd720d8d8a66cc147ef9bd4ec Mon Sep 17 00:00:00 2001 From: screamm Date: Sun, 11 Oct 2026 17:56:45 +0200 Subject: [PATCH 1/2] feat(libkernel): add setitimer on the alarm timer PS5MC (PPSA99420) imports setitimer (hPWDGx8ioXQ, aerolib) at registry index 472, after tcsetpgrp and before getitimer. Predicted stop: "FAIL: unresolved ELF import hPWDGx8ioXQ from libkernel.prx". setitimer follows FreeBSD 11.4 kern_time.c (8c639945) sys_setitimer and kern_setitimer: EINVAL for an unknown timer and for an itimerfix failure or more than INT32_MAX / 2 seconds; a zero value clears the interval and disarms; a null value reads the timer, EFAULT when the output is null too. ITIMER_REAL shares the alarm state, now with an interval that re-arms it as realitexpire does; alarm clears it, as FreeBSD's alarm.c does. Guest SIGALRM handlers are not run, so an expiry with a handler, or with SIGALRM blocked (signals are never left pending), throws naming the interval; SIG_IGN drops it and the default disposition exits with 142, as alarm already did. Intervals below 1 ms throw (itimerfix rounds them to tick / 16 and the console's hz is unknown). ITIMER_VIRTUAL and ITIMER_PROF throw when armed and read and disarm as zero. Co-Authored-By: Claude Fable 5.1 --- core/libs/prx/libkernel/CMakeLists.txt | 9 + .../libs/prx/libkernel/System/src/Signals.cpp | 110 ++++++++++-- core/libs/tests/GuestItimer.cpp | 159 ++++++++++++++++++ docs/dev/debt/libkernel.md | 1 + 4 files changed, 265 insertions(+), 14 deletions(-) create mode 100644 core/libs/tests/GuestItimer.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 62cc6ef66..d9d1de27a 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -619,6 +619,15 @@ if(BUILD_TESTING) set_tests_properties(guest_sigtimedwait PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_itimer_tests ${LIBS_INCLUDE_DIR}/tests/GuestItimer.cpp) + target_include_directories(guest_itimer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_itimer_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_itimer_tests) + add_test(NAME guest_itimer COMMAND guest_itimer_tests) + if(WIN32) + set_tests_properties(guest_itimer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_parent_process_tests ${LIBS_INCLUDE_DIR}/tests/GuestParentProcess.cpp) target_include_directories(guest_parent_process_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_parent_process_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 0e06f1538..817c3ed63 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -90,6 +90,7 @@ struct AlarmState { std::mutex lock; std::condition_variable changed; std::optional deadline; + std::chrono::microseconds interval{0}; bool threadStarted = false; }; @@ -98,19 +99,28 @@ AlarmState& Alarms() { return *state; } -void ExpireAlarm() { +std::string DescribeInterval(std::chrono::microseconds interval) { + char text[64]; + std::snprintf(text, sizeof(text), "%lld.%06lld s", static_cast(interval.count() / 1000000), + static_cast(interval.count() % 1000000)); + return text; +} + +void ExpireAlarm(std::chrono::microseconds interval) { std::uintptr_t handler = 0; { std::lock_guard lock(registration); handler = dispositions[GuestSigalrm].handler; } if (handler == 1) return; + if ((blockedMask.load() & (1u << (GuestSigalrm - 1))) != 0) + throw std::runtime_error("ITIMER_REAL (it_interval " + DescribeInterval(interval) + ") expired while SIGALRM is blocked: pending signals are not implemented"); if (handler == 0) { std::fprintf(stderr, "SIGALRM: the alarm expired with the default disposition; the process terminates\n"); std::fflush(stderr); std::_Exit(128 + GuestSigalrm); } - throw std::runtime_error("alarm: delivering SIGALRM to a guest handler is not implemented"); + throw std::runtime_error("ITIMER_REAL (it_interval " + DescribeInterval(interval) + ") expired: delivering SIGALRM to a guest handler is not implemented"); } void RunAlarms() { @@ -123,30 +133,43 @@ void RunAlarms() { } const auto deadline = *alarms.deadline; if (alarms.changed.wait_until(lock, deadline) != std::cv_status::timeout || alarms.deadline != deadline) continue; - alarms.deadline.reset(); + const auto interval = alarms.interval; + if (interval.count() == 0) alarms.deadline.reset(); + else { + const auto now = std::chrono::steady_clock::now(); + const auto periods = now < deadline ? 1 : (now - deadline) / interval + 1; + alarms.deadline = deadline + periods * interval; + } lock.unlock(); - ExpireAlarm(); + ExpireAlarm(interval); lock.lock(); } } + +std::chrono::microseconds RemainingAlarm(const AlarmState& alarms, std::chrono::steady_clock::time_point now) { + if (!alarms.deadline || *alarms.deadline <= now) return std::chrono::microseconds(0); + return std::chrono::duration_cast(*alarms.deadline - now); +} + +void NotifyAlarms(AlarmState& alarms) { + if (!alarms.threadStarted) { + std::thread(RunAlarms).detach(); + alarms.threadStarted = true; + } + alarms.changed.notify_all(); +} } extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) { auto& alarms = Alarms(); std::lock_guard lock(alarms.lock); const auto now = std::chrono::steady_clock::now(); - unsigned int remaining = 0; - if (alarms.deadline && *alarms.deadline > now) { - const auto micros = std::chrono::duration_cast(*alarms.deadline - now).count(); - remaining = static_cast((micros + 999999) / 1000000); - } + const auto micros = RemainingAlarm(alarms, now).count(); + const auto remaining = static_cast((micros + 999999) / 1000000); + alarms.interval = std::chrono::microseconds(0); if (seconds == 0) alarms.deadline.reset(); else alarms.deadline = now + std::chrono::seconds(seconds); - if (!alarms.threadStarted) { - std::thread(RunAlarms).detach(); - alarms.threadStarted = true; - } - alarms.changed.notify_all(); + NotifyAlarms(alarms); return remaining; } @@ -181,6 +204,65 @@ extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output return 0; } +namespace { +constexpr int ItimerReal = 0; +constexpr int ItimerProf = 2; +constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2; +constexpr std::int64_t MinExactIntervalMicros = 1000; + +bool ValidItimer(const KernelTimeval& time) { + return time.tv_sec >= 0 && time.tv_usec >= 0 && time.tv_usec < 1000000 && time.tv_sec <= MaxItimerSeconds; +} + +bool ItimerSet(const KernelTimeval& time) { + return time.tv_sec != 0 || time.tv_usec != 0; +} + +std::chrono::microseconds ItimerDuration(const KernelTimeval& time) { + return std::chrono::seconds(time.tv_sec) + std::chrono::microseconds(time.tv_usec); +} + +KernelTimeval ItimerTimeval(std::chrono::microseconds duration) { + return {duration.count() / 1000000, duration.count() % 1000000}; +} +} + +extern "C" int APS5_VABI setitimer_nid_postfix(int which, const GuestItimerval* value, GuestItimerval* previous) { + if (which < ItimerReal || which > ItimerProf) { *__error_nid_postfix() = 22; return -1; } + if (value == nullptr && previous == nullptr) { *__error_nid_postfix() = 14; return -1; } + GuestItimerval requested{}; + if (value != nullptr) { + requested = *value; + if (!ValidItimer(requested.value)) { *__error_nid_postfix() = 22; return -1; } + if (!ItimerSet(requested.value)) requested.interval = {}; + else if (!ValidItimer(requested.interval)) { *__error_nid_postfix() = 22; return -1; } + } + if (which != ItimerReal) { + if (ItimerSet(requested.value)) + throw std::runtime_error(std::string("setitimer: ") + (which == ItimerProf ? "ITIMER_PROF" : "ITIMER_VIRTUAL") + " needs process CPU time accounting, which is not implemented"); + if (previous != nullptr) *previous = {}; + return 0; + } + if (requested.interval.tv_sec == 0 && requested.interval.tv_usec != 0 && requested.interval.tv_usec < MinExactIntervalMicros) + throw std::runtime_error("setitimer: it_interval " + DescribeInterval(ItimerDuration(requested.interval)) + " is below 1 ms, where the kernel rounds it up to a clock tick that is not known"); + GuestItimerval old{}; + { + auto& alarms = Alarms(); + std::lock_guard lock(alarms.lock); + const auto now = std::chrono::steady_clock::now(); + old.interval = ItimerTimeval(alarms.interval); + old.value = ItimerTimeval(RemainingAlarm(alarms, now)); + if (value != nullptr) { + alarms.interval = ItimerDuration(requested.interval); + if (ItimerSet(requested.value)) alarms.deadline = now + ItimerDuration(requested.value); + else alarms.deadline.reset(); + NotifyAlarms(alarms); + } + } + if (previous != nullptr) *previous = old; + return 0; +} + struct GuestStack { void* sp; std::size_t size; diff --git a/core/libs/tests/GuestItimer.cpp b/core/libs/tests/GuestItimer.cpp new file mode 100644 index 000000000..1dcee1709 --- /dev/null +++ b/core/libs/tests/GuestItimer.cpp @@ -0,0 +1,159 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct GuestItimerval { + KernelTimeval interval; + KernelTimeval value; +}; +static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); + +using Handler = void (APS5_VABI *)(int); +using TerminateHandler = void (*)(); +extern "C" { +int APS5_VABI setitimer_nid_postfix(int, const GuestItimerval*, GuestItimerval*); +unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int); +Handler APS5_VABI signal_nid_postfix(int, Handler); +int* APS5_VABI __error_nid_postfix(); +void* APS5_VABI __cxa_current_primary_exception_nid_postfix(); +std::type_info* APS5_VABI __cxa_current_exception_type_nid_postfix(); +TerminateHandler APS5_VABI _ZSt13set_terminatePFvvE_nid_postfix(TerminateHandler); +} + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static std::int64_t Micros(const KernelTimeval& time) { + Require(time.tv_sec >= 0 && time.tv_usec >= 0 && time.tv_usec < 1000000, "timeval is not normalized"); + return time.tv_sec * 1000000 + time.tv_usec; +} + +static GuestItimerval Read(int which) { + GuestItimerval current{{11, 22}, {33, 44}}; + *__error_nid_postfix() = 12345; + Require(setitimer_nid_postfix(which, nullptr, ¤t) == 0, "reading the timer failed"); + Require(*__error_nid_postfix() == 12345, "reading the timer changed errno"); + return current; +} + +static void RequireFailure(int which, const GuestItimerval* value, int error, const char* message) { + GuestItimerval previous{{11, 22}, {33, 44}}; + const auto saved = previous; + *__error_nid_postfix() = 0; + Require(setitimer_nid_postfix(which, value, &previous) == -1 && *__error_nid_postfix() == error, message); + Require(std::memcmp(&previous, &saved, sizeof(previous)) == 0, "a failed setitimer wrote the previous value"); +} + +volatile std::sig_atomic_t delivered = 0; +static void APS5_VABI OnAlarm(int signal) { + delivered = signal; +} + +static void OnTerminate() { + const auto* type = __cxa_current_exception_type_nid_postfix(); + const auto* error = static_cast(__cxa_current_primary_exception_nid_postfix()); + if (type != nullptr && error != nullptr && std::strcmp(type->name(), typeid(std::runtime_error).name()) == 0 + && std::strstr(error->what(), "it_interval 0.250000 s") != nullptr && delivered == 0) { + std::printf("expiry threw: %s\n", error->what()); + std::fflush(stdout); + std::_Exit(0); + } + std::fprintf(stderr, "unexpected termination at expiry\n"); + std::_Exit(1); +} + +int main() { + const auto ignore = reinterpret_cast(std::uintptr_t{1}); + Require(signal_nid_postfix(14, ignore) == nullptr, "SIGALRM was not initially defaulted"); + const GuestItimerval valid{{0, 0}, {30, 0}}; + for (int invalid : {-1, 3, INT_MIN, INT_MAX}) { + RequireFailure(invalid, &valid, 22, "an unknown timer did not report EINVAL"); + RequireFailure(invalid, nullptr, 22, "an unknown timer read did not report EINVAL"); + *__error_nid_postfix() = 0; + Require(setitimer_nid_postfix(invalid, nullptr, nullptr) == -1 && *__error_nid_postfix() == 22, "EINVAL is not checked before EFAULT"); + } + for (int which : {0, 1, 2}) { + *__error_nid_postfix() = 0; + Require(setitimer_nid_postfix(which, nullptr, nullptr) == -1 && *__error_nid_postfix() == 14, "a read into null did not report EFAULT"); + for (const GuestItimerval bad : {GuestItimerval{{0, 0}, {-1, 0}}, GuestItimerval{{0, 0}, {0, -1}}, + GuestItimerval{{0, 0}, {0, 1000000}}, GuestItimerval{{0, 0}, {INT_MAX / 2 + 1, 0}}, + GuestItimerval{{-1, 0}, {1, 0}}, GuestItimerval{{0, 1000000}, {1, 0}}, GuestItimerval{{INT_MAX / 2 + 1, 0}, {1, 0}}}) + RequireFailure(which, &bad, 22, "an invalid timeval did not report EINVAL"); + const GuestItimerval disarm{{-5, 7}, {0, 0}}; + GuestItimerval previous{{11, 22}, {33, 44}}; + Require(setitimer_nid_postfix(which, &disarm, &previous) == 0, "disarming with an invalid interval failed"); + Require(Micros(previous.interval) == 0 && Micros(previous.value) == 0, "an idle timer reported a value"); + Require(Micros(Read(which).interval) == 0, "disarming kept the interval"); + } + for (int cpu : {1, 2}) { + GuestItimerval previous{{11, 22}, {33, 44}}; + const auto saved = previous; + bool threw = false; + try { + setitimer_nid_postfix(cpu, &valid, &previous); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw && std::memcmp(&previous, &saved, sizeof(previous)) == 0, "a CPU timer was armed"); + Require(Micros(Read(cpu).value) == 0, "a CPU timer kept a value"); + } + bool threw = false; + const GuestItimerval tiny{{0, 999}, {1, 0}}; + try { + setitimer_nid_postfix(0, &tiny, nullptr); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw && Micros(Read(0).value) == 0, "an interval below the clock tick was armed"); + + GuestItimerval previous{}; + Require(setitimer_nid_postfix(0, &valid, &previous) == 0 && Micros(previous.value) == 0, "arming ITIMER_REAL failed"); + const auto first = Micros(Read(0).value); + Require(first > 29000000 && first <= 30000000, "the armed value is not visible"); + std::this_thread::sleep_for(std::chrono::milliseconds(25)); + const auto second = Micros(Read(0).value); + Require(second > 0 && second < first, "the remaining time does not count down"); + const GuestItimerval repeating{{2, 500000}, {20, 0}}; + Require(setitimer_nid_postfix(0, &repeating, &previous) == 0, "re-arming ITIMER_REAL failed"); + Require(Micros(previous.interval) == 0 && Micros(previous.value) > 0 && Micros(previous.value) <= second, "re-arming did not return the previous value"); + const GuestItimerval zero{{0, 0}, {0, 0}}; + Require(setitimer_nid_postfix(0, &zero, &previous) == 0, "disarming ITIMER_REAL failed"); + Require(Micros(previous.interval) == 2500000 && Micros(previous.value) > 19000000 && Micros(previous.value) <= 20000000, + "disarming did not return the previous value"); + const auto idle = Read(0); + Require(Micros(idle.interval) == 0 && Micros(idle.value) == 0, "the disarmed timer is still set"); + + const GuestItimerval periodic{{0, 300000}, {0, 100000}}; + Require(setitimer_nid_postfix(0, &periodic, nullptr) == 0, "arming a periodic timer failed"); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + const auto rearmed = Read(0); + Require(Micros(rearmed.interval) == 300000 && Micros(rearmed.value) > 0 && Micros(rearmed.value) <= 300000, + "the periodic timer was not re-armed by its interval"); + Require(GuestAlarm_nid_no_patch(5) == 1, "alarm did not report the periodic timer"); + const auto alarmed = Read(0); + Require(Micros(alarmed.interval) == 0 && Micros(alarmed.value) > 4000000 && Micros(alarmed.value) <= 5000000, "alarm did not replace the timer"); + Require(setitimer_nid_postfix(0, &zero, &previous) == 0 && Micros(previous.value) > 0, "disarming the alarm failed"); + Require(GuestAlarm_nid_no_patch(0) == 0, "setitimer did not cancel the alarm"); + + Require(signal_nid_postfix(14, OnAlarm) == ignore, "SIGALRM handler was not installed"); + _ZSt13set_terminatePFvvE_nid_postfix(OnTerminate); + const GuestItimerval handled{{0, 250000}, {0, 50000}}; + Require(setitimer_nid_postfix(0, &handled, nullptr) == 0, "arming the handled timer failed"); + std::this_thread::sleep_for(std::chrono::seconds(2)); + std::fprintf(stderr, "the expiry with a handler neither delivered nor threw\n"); + return 1; +} diff --git a/docs/dev/debt/libkernel.md b/docs/dev/debt/libkernel.md index 132b6b9b4..c91168e0a 100644 --- a/docs/dev/debt/libkernel.md +++ b/docs/dev/debt/libkernel.md @@ -94,6 +94,7 @@ - [sem_timedwait](../../../core/libs/prx/libkernel/Pthread/Posix/Sem.cpp) (libkernel) consumes an available count before checking the timeout and waits indefinitely for a null timeout, following FreeBSD 11's `sem_timedwait`. Console behavior for null and invalid timeouts when a count is available is unverified. - [Windows file timestamps](../../../core/libs/prx/libkernel/File/include/WindowsFileTime.hpp) use the host filesystem's timestamp range and resolution. `utimes` and `futimes` reject timestamps outside nonnegative signed 64-bit Windows file-time ticks with `EINVAL`, including exactly 1601-01-01 00:00:00 UTC, whose zero value means "leave unchanged" to `SetFileTime`. Guest directory descriptors still resolve through their recorded path. Filesystems with coarser timestamp resolution cannot retain every guest microsecond - [Windows file identity](../../../core/libs/prx/libkernel/File/src/NativeStat.cpp) (libkernel) - `stat` and `fstat` report the volume serial number as `st_dev` and the 64-bit file index folded into the guest's 32-bit `st_ino` (never 0), so two different files can in principle share an `st_ino`; the fold is not taken from a reference. `st_nlink` is clamped to 65535, the width of the guest field. The call keeps the `_wstat64` values when `GetFileInformationByHandle` fails +- [setitimer](../../../core/libs/prx/libkernel/System/src/Signals.cpp) (libkernel) follows FreeBSD's `sys_setitimer` and `kern_setitimer`: `EINVAL` for timers other than `ITIMER_REAL`, `ITIMER_VIRTUAL` and `ITIMER_PROF`, and for a value, or the interval of a non-zero value, with a negative field, `tv_usec` of 1000000 or more, or more than `INT32_MAX / 2` seconds; a null value reads the timer as `getitimer` does, with `EFAULT` when the output is null too. `ITIMER_REAL` shares the timer thread of `alarm`, which clears its interval: a zero value disarms it, the previous value is returned, and each expiry re-arms it by `it_interval`. An expiry delivers nothing: with `SIG_IGN` it is dropped, with the default disposition the process exits with status 142, and with a handler, or with `SIGALRM` blocked, it throws, since guest `SIGALRM` handlers are never run and signals are never left pending. The value is not raised to `tick / 16` as `itimerfix` does, and an interval below 1 ms throws, since the console's `hz`, and so that rounding, is unknown (1 ms covers any `hz` of 63 or more). `ITIMER_VIRTUAL` and `ITIMER_PROF` throw when armed, as process CPU time is not accounted, and read and disarm as zero. PPSA99420 imports it and fails to load without it - On Linux, [direct memory](../../../core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp) lives in one sparse memfd at file offset = physical address, so the [GPU imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) can share one udmabuf buffer object per 64 MiB of it. Released direct memory is not punched out of the file, because a live import of its chunk pins the pages and the GPU would keep reading the old ones: its pages stay committed (the process keeps the high-water mark of the direct memory it used) and are zeroed when the range is allocated again. - [system](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) runs no command. It follows FreeBSD 11's `lib/libc/stdlib/system.c` for a sandbox without `/bin/sh`: a null command returns 1 unconditionally, as that version does, and any command returns the wait status of a child that could not exec the shell and called `_exit(127)` (`0x7f00`). Whether the console's application sandbox has no shell, and whether `vfork` succeeds there (FreeBSD returns -1 when it fails), is unverified - [sceKernelWriteThrottlingStatus](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) reports no write throttling: the host SSD has no write budget, so the first qword of the 32-byte status is set to `UINT32_MAX` and the rest is zeroed. PPSA17221 reads only that qword: it multiplies it by 64 KiB and compares the product with byte thresholds up to 0x4AF00000 to pick one of five tiers. When the call fails the title assumes 0x4B000001 bytes, which selects the same top tier as `UINT32_MAX`. What the value measures on hardware (a remaining budget or a rate) and the meaning of the other three qwords are unknown From 5e1ffc29ae24e9140389128293d679f11e13c7d2 Mon Sep 17 00:00:00 2001 From: Yossi Yadgar Date: Mon, 12 Oct 2026 01:17:15 +0300 Subject: [PATCH 2/2] fix(libkernel): report the setitimer interval from getitimer getitimer on main predates setitimer and always reported a zero it_interval, so a periodic ITIMER_REAL armed by setitimer read back as one-shot. --- core/libs/prx/libkernel/System/src/Signals.cpp | 3 +++ core/libs/tests/GuestItimer.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 817c3ed63..23899ae3c 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -199,6 +199,9 @@ extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output result.value.tv_sec = micros / 1000000; result.value.tv_usec = micros % 1000000; } + const auto interval = alarms.interval.count(); + result.interval.tv_sec = interval / 1000000; + result.interval.tv_usec = interval % 1000000; } *output = result; return 0; diff --git a/core/libs/tests/GuestItimer.cpp b/core/libs/tests/GuestItimer.cpp index 1dcee1709..895844197 100644 --- a/core/libs/tests/GuestItimer.cpp +++ b/core/libs/tests/GuestItimer.cpp @@ -22,6 +22,7 @@ using Handler = void (APS5_VABI *)(int); using TerminateHandler = void (*)(); extern "C" { int APS5_VABI setitimer_nid_postfix(int, const GuestItimerval*, GuestItimerval*); +int APS5_VABI getitimer_nid_postfix(int, GuestItimerval*); unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int); Handler APS5_VABI signal_nid_postfix(int, Handler); int* APS5_VABI __error_nid_postfix(); @@ -130,6 +131,10 @@ int main() { const GuestItimerval repeating{{2, 500000}, {20, 0}}; Require(setitimer_nid_postfix(0, &repeating, &previous) == 0, "re-arming ITIMER_REAL failed"); Require(Micros(previous.interval) == 0 && Micros(previous.value) > 0 && Micros(previous.value) <= second, "re-arming did not return the previous value"); + GuestItimerval queried{{11, 22}, {33, 44}}; + Require(getitimer_nid_postfix(0, &queried) == 0, "getitimer failed"); + Require(Micros(queried.interval) == 2500000 && Micros(queried.value) > 19000000 && Micros(queried.value) <= 20000000, + "getitimer did not report the setitimer interval"); const GuestItimerval zero{{0, 0}, {0, 0}}; Require(setitimer_nid_postfix(0, &zero, &previous) == 0, "disarming ITIMER_REAL failed"); Require(Micros(previous.interval) == 2500000 && Micros(previous.value) > 19000000 && Micros(previous.value) <= 20000000,