Languages: English | 简体中文 | 繁體中文 | 日本語 | 한국어 | Français | Deutsch | Español | Italiano | Русский | العربية
neverd emulate executes an image under an explicit guest OS profile. CPU
transport, image parsing, process entry and OS services have separate owners.
Build with NEVERD_ENABLE_CPU_EMULATION=ON; driver emulation also includes it.
The first profile, linux-elf64-v1, runs bounded little-endian x64 and AArch64
ELF ET_EXEC and self-relocating static ET_DYN programs at CPL3 or EL0.
It loads real ELF segments, constructs
the initial stack, resumes instruction quanta and handles explicit Linux
system-call requests. It is a freestanding process model, not a full Linux
distribution or a promise to run arbitrary libc binaries. Dynamic linking,
unsupported signal delivery, Linux process threads, general file systems and unsupported services fail
explicitly. The Android native profile,
android-aarch64-api28-v1, separately supports bounded API 28 ARM64 shared-library
function calls and Bionic models. The Windows PE64 process profile is described
below. The Darwin profiles add bounded macOS, iOS device
and iOS Simulator Mach-O processes. Android managed runtimes and guest kernel
workloads remain separate work.
neverd emulate guest.elf --profile=linux-elf64-v1 \
--options='{"backend":"auto","arguments":["guest","argument"],"environment":["MODE=test"],"instruction_limit":100000}'Matching Linux hosts select KVM, matching Windows hosts select WHP, and matching macOS hosts select HVF. Other host/guest ISA combinations use Unicorn. An unavailable selected backend is an error, with no silent fallback. An ELF guest still uses the Linux process model when executed on Windows. See CPU execution for the checked instruction inventory, native availability and cancellation limitations. Instruction support follows the selected checked CPU contract; vector register storage does not imply unrestricted SIMD execution.
The CLI emits one JSON report. Its exit code is 0 for a guest exit status of
zero, 2 for another guest exit status, 3 for incomplete execution (including
faults and limits), and 1 for invalid setup/API failure. The actual guest status
is in exit_status; it is not substituted for the CLI exit code.
The additive C entry point is
neverd_emulate_process_json.
Pass a session, nonempty input path, explicit profile and optional options JSON.
Free its result with neverd_free_string. A null result is a setup failure;
read neverd_last_error. A guest fault or resource stop returns a report. The
session's loaded analysis image is neither required nor changed.
Python exposes the same native validation:
report = session.emulate_process(
"guest.elf", "linux-elf64-v1",
'{"backend":"unicorn","arguments":["guest"],"environment":[]}',
)
output = bytes.fromhex(report["stdout_hex"])Options are a JSON object with at most 64 KiB of request text, excluding two
hexadecimal characters per validated android.dex_images byte. That explicit
DEX allowance raises the total wire ceiling to 2112 KiB; it does not enlarge
other field budgets. JSON escape overhead still counts as request text.
Unknown fields, null field values,
invalid types, embedded NULs in strings and nonpositive limits are rejected.
| Option | Default | Contract |
|---|---|---|
backend |
auto |
auto, unicorn, kvm, whp or hvf |
arguments |
Input filename | Complete argv, including argv[0]; empty selects the default |
environment |
[] |
Explicit guest strings; never inherits the host environment |
instruction_limit |
100000 | Shared admitted instruction attempts |
event_limit |
10000 | System-call events; charged before OS service handling |
timeout_microseconds |
5000000 | One monotonic deadline starting after process setup |
memory_limit |
67108864 | Physical/mapped memory budget |
stack_size |
1048576 | Page-aligned stack within the memory budget |
output_limit |
1048576 | Combined captured stdout/stderr bytes |
instruction_quantum |
1024 | Admission interval before yielding to the runtime |
linux_network |
Absent | Explicit offline TCP attempts and byte streams; requires linux_files; see contracts |
linux_time |
Absent | Explicit clock observations and optional idle advancement for Linux ELF64 and Android native workloads |
linux_files |
Absent | Explicit guest file catalogue, bounded write grants and optional live FD links |
linux_signals |
Absent | Explicit dispositions, initial thread mask and disabled alternate-stack state; bounded AArch64 handlers |
linux_priority |
Absent | Explicit per-task nice values and caller authority for raw Linux priority services |
linux_kernel |
Absent | Explicit released GKI branch, fixed guest task catalogue or observed absent guest kernel interfaces |
schema_version is 1. Results include profile, architecture, selected backend
and its selection reason, stop_reason, nullable exit_status, diagnostic,
entry/current PC, instruction/event counters, service records and the last
typed CPU exit. Addresses, syscall numbers, argument registers and raw return
bits are hexadecimal strings without a 0x prefix; they never lose bits
through a JSON floating-point consumer. stdout_hex and stderr_hex are
lowercase byte encodings, preserving NUL and invalid UTF-8. A null syscall
result denotes no modeled return, including process exit or an unsupported
request; it is distinct from a successful zero return.
writev shares those sinks on x64/ARM64 and through Android Bionic. It imports up to 1024 guest iovec entries before output, rejects negative lengths with EINVAL, validates user ranges, and applies Linux’s page-aligned transfer cap. An invalid descriptor returns EBADF before vector access; inaccessible metadata returns EFAULT without output. A later payload fault preserves the copied prefix. The output budget covers the whole vector before publication, across both streams. write and writev use the low 32 descriptor bits; vector count also follows Linux’s 32-bit import. Bionic alone converts raw negative errors to -1 and errno. LinuxOutputNativeTests runs ten original cases on host Linux with regular-file redirects; modeled x64/ARM64 cases also check budgets. Explicit GKI selection retains its released version's metadata and transfer-cap order; see released GKI contracts. See the Linux vector import contract.
The existing ELF loader supplies decoded program headers. OS policy validates ABI tags, segment alignment, mapped program-header tables and user-address bounds before execution. A generic mapping plan checks extents, permissions, overlap and budget before allocation; the model publishes only a fully prepared private address space. Linux mappings preserve file-page prefix/tail bytes, zero BSS, honor segment permissions and reserve stack guard gaps. Page-overlap layouts and contradictory headers are rejected rather than guessed.
Static PIE uses a deterministic load bias of at least 0x40000000, increased
to honor larger PT_LOAD alignment. Every mapped segment, entry PC and
AT_PHDR/AT_ENTRY uses that same bias; the file's program-header values stay
unmodified. AT_BASE remains zero because no interpreter is loaded. This is a
reproducible placement policy, not Linux ASLR. Startup follows the direct-entry
path of the Linux ELF loader.
The mapping source is explicitly the original file, so analysis-time pointer
fixups cannot leak into guest execution. Guest startup must perform its own
relocations and initialization. The loader decodes PT_DYNAMIC from bounded
original file records, independently of optional sections; the process model
requires a readable, terminated table of at most 4096 entries when present. PT_INTERP
and external dependency/filter/audit tags are rejected. The model does not
silently provide a dynamic linker, symbol resolver or constructor runner.
The initial stack contains aligned argc/argv/envp/auxv, mapped PHDR/PHENT/PHNUM,
entry/page-size values and identity entries. Model PID/TID/UID/GID are 1000.
AT_RANDOM contains the first 16 bytes of the input's SHA-256 for reproducible
execution; this is explicitly a deterministic model policy, not cryptographic
entropy. HWCAP/HWCAP2 are zero; there is no vDSO. Startup conventions follow the
Linux ELF loader.
Implemented calls are write, writev, exit, exit_group, getpid, gettid,
getuid, geteuid, getgid, getegid,
mmap, mprotect, munmap, madvise, brk, gettimeofday, clock_gettime and
bounded rt_sigaction, with
separate x64
and asm-generic ARM64
numbers. Returning x64 SYSCALL applies its RCX/R11 clobbers as well as RAX and
the next PC. ARM64 uses x8 for the number and x0 for the result. Unknown calls
stop as unsupported_service; they never execute host syscalls.
Real/effective identity queries agree with the corresponding auxv entries.
They use the deterministic model identity above; credential-changing services
such as setuid remain unsupported.
The optional linux_kernel input records explicitly observed absent kernel
interfaces. For example, a fixture with no pidfd_open implementation uses:
{"linux_kernel":{"unavailable_syscalls":["pidfd_open"]}}The selected raw call returns -ENOSYS before argument validation, as a missing
kernel entry does, and creates no descriptor or guest-memory effect. Bionic's
syscall wrapper retains its normal -1/errno translation. Missing input and an
empty list retain the existing unsupported boundary for this interface;
other unknown calls are not converted to ENOSYS. Currently only pidfd_open
is admitted; unknown names, duplicates and wrong types are rejected. This
input does not infer a kernel version, host availability or a working pidfd
implementation. See the kernel missing-call implementation.
An explicit linux_kernel.gki selects a released Android common kernel branch
from 5.10 through 6.18. Its current implemented subset includes pidfd_open
for the live model process and explicitly catalogued guest tasks, versioned
vector import, observed process CPU clocks and extended-attribute name/target
errors, with the same descriptor table used by
linux_files; Bionic and raw traps share ownership and error ordering.
Selecting GKI together with an absent pidfd_open observation is rejected.
An optional linux_kernel.tasks array supplies a closed, fixed catalogue of
additional live tasks as { "id": 2000, "group_leader": true } entries.
An omitted array leaves foreign target lookup unsupported; an empty array
observes no foreign tasks. A PID outside both the declared catalogue and the
local task owner returns ESRCH.
Entries and priority observations must be consistent. With cooperative Android
threads the fixed catalogue describes foreign tasks; it must not occupy any
identity reserved for child creation. Live local children come from the shared
thread owner and are rejected as nonleaders according to the selected GKI.
Exited children are absent from an explicit closed catalogue. Thread-bound
pidfds for local cooperative threads remain unsupported, including their exit
readiness; static foreign observations do not establish that lifetime.
Android API levels do not select a kernel. See the
released GKI contracts for all eight source pins,
descriptor behavior, tests and the remaining kernel coverage.
poll/ppoll and the supported level-triggered epoll subset use the shared
linux_files descriptor owner without requiring GKI selection. They preserve
ordered revents output, duplicate identities and explicitly scheduled TCP/UDP
readiness; finite waits use virtual idle time. Observed live pidfds remain
unready, with no invented exit notification, and a closed descriptor reports
POLLNVAL. See poll/epoll
for timeout, signal-mask, one-shot and unsupported-mode boundaries.
The optional linux_priority input declares nice state for fixture-owned tasks
with the caller's UID. Raw setpriority and getpriority share this state across
Linux ELF64 and Android native workloads; they never change host priorities.
{"linux_priority":{"tasks":[{"id":1000,"nice":0}],
"cap_sys_nice":false,"rlimit_nice":0}}Task IDs are distinct positive signed 32-bit values, initial nice values are
-20 through 19, and rlimit_nice is 0 through 40. cap_sys_nice and
rlimit_nice default to false and zero; task state is always explicit. Missing
input, unlisted tasks, and PRIO_PGRP/PRIO_USER selection stop with an unsupported
service. This includes newly created threads whose nice state was not declared;
the model does not guess inheritance or another task's ownership. PRIO_PROCESS
with who zero selects the current guest task, otherwise the named task.
Invalid selectors return raw -EINVAL. Set requests clamp their signed 32-bit
nice argument to -20..19. Lowering nice requires CAP_SYS_NICE or a sufficient
RLIMIT_NICE limit; denial returns raw -EACCES without changing state.
Raw get requests return 20 - nice, preserving the kernel's 40..1 encoding;
this is not libc's translated getpriority result. See the
Linux priority interface.
linux_signals supplies process-wide initial kernel actions. A missing entry
is unknown; it does not imply SIG_DFL. An explicit empty action list enables
installing actions without observing an unknown predecessor. For example:
{"linux_signals":{"actions":[
{"signal":11,"handler":0,"flags":0,"restorer":0,"mask":0}
]}}Signal numbers are 1–64; each entry requires all five fields. The four action words are unsigned 64-bit values, with decimal strings for values beyond the exact JSON integer range. Duplicate entries, unknown fields, blocked SIGKILL/SIGSTOP bits, and nonzero actions for those two signals are rejected. Inputs describe the kernel layout: handler, flags, restorer and eight-byte mask. They are not read from the host or inferred from a selected native function.
rt_sigaction requires sigsetsize == 8, copies the new 32-byte LP64 record
before validating the signal, then observes/replaces the shared disposition.
It removes SIGKILL/SIGSTOP from new masks. Common Linux action flags are retained,
including Bionic's sign-extended 32-bit flags; unknown flag extensions stop.
An invalid old-output pointer returns EFAULT after a successful installation.
Mixed-access kernel copies stop before unmodeled partial bytes are written.
These ordered effects follow the Linux signal implementation.
kill shares signed 32-bit PID/signal decoding across x64 and AArch64 raw
syscalls, Android imports and Bionic syscall. Invalid signal numbers return
EINVAL before target lookup. kill(getpid(), 0) succeeds without a disposition
snapshot; kill(getpid(), SIGKILL) terminates the whole guest workload with
stop_reason: "signaled" and termination_signal: 9. exit_status remains null
and the terminating call has no result. Bionic alone converts raw errno to
-1/thread-local errno; successful queries preserve errno. No host process is
looked up or signaled. Other PIDs and process-group selectors stop as unsupported.
Unknown targets are not reported as ESRCH. See the
Linux kill contract.
Standard self-process signals use one coalesced pending set and per-thread
blocked sets. Supply initial_mask explicitly (an unsigned 64-bit integer or
decimal string); an omitted mask remains unknown. New Android threads inherit
their creator's mask. Runnable threads receive eligible signals at guest
execution boundaries, with lower signal numbers selected first. rt_sigprocmask
uses an eight-byte set, captures input before output, filters SIGKILL/SIGSTOP,
and commits a valid mask change before a failed old-mask copy. rt_sigpending
copies the blocked pending subset, accepting output lengths from zero to eight.
Installing SIG_IGN, or SIG_DFL for SIGCHLD/SIGURG/SIGWINCH, discards pending
instances. Default terminating actions produce the same typed signaled
outcome as SIGKILL; core-dumping actions remain unsupported.
AArch64 handlers additionally require alternate_stack_disabled: true, meaning
the alternate stack is explicitly cleared and disabled. They execute original
guest instructions with a Linux rt_sigframe containing mutable siginfo,
ucontext, integer registers, NZCV and FPSIMD state. SA_SIGINFO, SA_NODEFER,
SA_RESETHAND and a supplied SA_RESTORER are supported. Otherwise a workload-local
RX mapping executes the real rt_sigreturn syscall. Return restores guest
frame bytes, including supported handler changes to PC, registers and mask;
there is no host callback that bypasses the handler. Frame padding is preserved.
Malformed frames and unsupported context extensions stop explicitly.
Nesting is bounded to 32 live frames.
Android API 28 adapters include sigprocmask[64], sigpending[64], signal,
and sigemptyset, sigfillset, sigaddset, sigdelset, sigismember (also their
64 variants). They share the Linux state while preserving Bionic's reserved
signals, wrapper copy ordering and errno. signal installs SA_RESTART.
An eligible pending signal also makes an Android cooperative waiter schedulable.
Ignored signals leave its operation pending; default termination ends the
process without completing that operation. A caught signal interrupts a
relative nanosleep or usleep before its deadline, even with SA_RESTART.
The shared sleep owner copies remaining time before entering the handler and
supplies raw -EINTR (or -EFAULT for an inaccessible remainder). An already
expired sleep completes normally. Partially accessible output stops explicitly.
Bionic wrappers defer errno conversion until rt_sigreturn, so a handler sees
the previous errno. Nested interrupted wrappers retain separate continuations.
The resumed x0 is authoritative: handler edits affect the eventual wrapper
result. Redirecting ucontext PC past the synthetic import's post-trap return
boundary bypasses its adapter; the model preserves that redirect and leaves
the native-call event incomplete instead of inventing a normal return.
Caught signals also interrupt cooperative poll/ppoll, epoll_wait/epoll_pwait
and raw pselect6; SA_RESTART does not retry these operations. Poll copies zero
revents before returning EINTR, while pselect6 leaves the input sets unchanged.
Raw ppoll/pselect6 update their remaining timeout; Bionic ppoll preserves the
caller's const timespec. Epoll leaves event storage, rotation and one-shot state
unchanged. A wholly inaccessible poll output returns EFAULT before EINTR;
inaccessible timeout output is ignored, and partial-field access stops explicitly.
These rules follow Linux poll/select,
epoll and
API 28 wrappers.
Direct socket read/recv/recvfrom waits have made no transfer before suspension.
Without SA_RESTART they return EINTR without consuming input or touching the
buffer/peer output. With SA_RESTART and no receive timeout captured on entry,
the wait is released and the original trap is restored in the signal frame.
After the handler, the guest re-executes that trap using its restored registers,
so changed arguments, descriptor reuse and PC redirection remain observable.
The interrupted attempt has no completed result; re-entry records a new attempt.
Modeled imports restart at their synthetic trap and expose their import argument
ABI, including the leading number for syscall. They do not manufacture the
internal registers/stack of a real libc implementation.
An entry-time SO_RCVTIMEO disables restart even if changed while suspended.
An already ready transfer completes before handler delivery, including short
reads. See socket interruption
and ARM64 restart.
An indefinite cooperative descriptor wait can be woken by another guest thread;
without a runnable owner, eligible signal or scheduled event it still stops
explicitly instead of consulting a host clock or transport.
Real-time queues, thread-directed sends, alternate stacks, job control, synchronous fault delivery, core dumps, x64 handler frames and interruption of synchronization/stdio waits remain unsupported. Temporary wait masks and TCP WAITALL partial-progress restart are also outside this contract. A caught signal directed to such a waiter stops explicitly. No host signals, devices or network services participate. ABI references: Linux signal semantics, AArch64 signal frames, AArch64 context layout, Bionic masks and Bionic signal helpers.
mincore supports ordered validation, zero-length queries and a first queried
page that is unmapped. Alignment errors return EINVAL before address-range
checks; an invalid query range returns ENOMEM before vector-range validation.
The vector consumes one byte per rounded-up page. Numerical vector-range
errors return EFAULT, but an unmapped vector does not precede ENOMEM for an
unmapped first query page. These paths do not write the vector. A mapped first
page, including PROT_NONE, stops explicitly because mapping ownership alone
does not establish Linux residency. The model does not skip a mapped prefix to
report a later hole, fabricate resident bits or query the host. This bounded
ordering follows the Linux implementation.
Static ELF TLS templates (PT_TLS) are validated as loader-owned facts, with
one template, bounded file/memory extents, alignment congruence and readable
initialized bytes. Guest startup allocates and initializes each TLS block and
installs its thread pointer; the Linux model does not invent a libc-specific
TCB or DTV. This permits compiler-generated local-exec TLS in freestanding
programs. Dynamic TLS, a dynamic linker and OS thread creation remain separate
work.
On x64, arch_prctl supports ARCH_SET_FS, ARCH_GET_FS, ARCH_SET_GS and
ARCH_GET_GS. Set accepts an unmapped user-range base; a later dereference still
checks user permissions. Kernel-range bases return guest EPERM; invalid get
destinations return guest EFAULT without faulting the CPU. Other operations
stop as unsupported, never as a success stub. The behavior follows the
Linux arch_prctl implementation.
ARM64 startup installs TPIDR_EL0 with the architecturally unprivileged MSR
instruction. The corresponding MRS, x64 FS/GS memory accesses, and CPU context
restoration preserve each thread pointer across execution quanta and backend
entries. This does not itself implement a thread scheduler.
Descriptors 1 and 2 are virtual byte sinks. write validates readable user
pages, returns a readable prefix when a later page is inaccessible, and returns
guest EFAULT when no bytes are readable. A bad descriptor returns EBADF;
a zero-count write still validates the user address range, but does not
require a mapped page or read any payload.
These sinks do not model Linux pipe atomicity or file objects. Native fixture
comparison agrees on ordinary output and on partial writes to regular files;
Linux pipes can reject that same small cross-page write completely. Output
limits stop before publishing an over-budget write.
Anonymous memory services use the same process address space and physical
memory budget as the loaded image and stack. mmap accepts exactly
MAP_PRIVATE | MAP_ANONYMOUS, with ordinary PROT_NONE, PROT_READ,
PROT_READ | PROT_WRITE, PROT_READ | PROT_EXEC and readable RWX permissions.
Free, page-aligned hints are honored; otherwise placement searches gaps from
0x100000000, then from the minimum user address, reserving the stack guards.
This deterministic policy does not emulate Linux ASLR. New anonymous pages
are zero-filled and independently owned, so partial unmapping can reclaim
unpinned page allocations. A CPU projection or retained backing view may keep
a retired allocation alive until its own lifetime ends.
Lengths round to pages. munmap tolerates holes and repeated removal;
mprotect changes the mapped prefix before returning ENOMEM at a hole.
PROT_NONE preserves the allocation and bytes while denying guest access.
The raw brk call returns the requested byte boundary on success and the old
boundary on failure; it is not the libc wrapper's zero/minus-one convention.
The initial break is the page-aligned image end. Growth respects other mappings
and the memory budget; shrinking preserves bytes in its remaining partial page.
Rules and error precedence for the supported subset follow the Linux
mapping and
protection services.
madvise admits MADV_MERGEABLE and MADV_UNMERGEABLE. It records KSM
eligibility over guest RAM, including read-only and PROT_NONE pages. The
deterministic profile keeps the background scanner stopped: advice does not
deduplicate backing allocations or change bytes or access permissions. Partial
revocation splits the eligible range; munmap and heap shrink retire the
removed policy, and new allocations start ineligible. Alignment and rounding
overflow return EINVAL; an aligned empty range succeeds. Holes return
ENOMEM while still applying advice to mapped portions on either side. The
behavior argument uses its low 32 bits, matching the Linux int ABI. These
rules follow Linux's advice dispatch
and KSM policy. Other
advice, including MADV_DONTNEED and MADV_FREE, stops as an unsupported
service before changing state; discarding content is not modeled as a hint.
Explicit linux_files.devices[].read_only_mappings additionally admit
read-only shared device RAM snapshots, with shared backing across opens,
mapping lifetime independent of descriptor close and bounded permission
changes. See device contracts for the supplied
page format and supported operations.
Ordinary file, anonymous shared and fixed mappings, grow-down, huge pages, memory locking, protection keys, execute-only/write-only policy and other flags remain explicit unsupported service contracts. They stop before publishing effects or inventing a syscall return. Ordinary range/length/alignment errors within the admitted subset return guest errors and allow execution to continue. No memory service forwards a guest pointer or mapping request to the host OS.
The optional linux_time input supplies observations, fixed by default, shared by raw
Linux services and Android Bionic. It never reads the host clock or supplies a
default epoch. Instruction time is frozen unless explicitly configured. For example:
{"linux_time":{"clocks":[
{"id":0,"seconds":"4294967297","nanoseconds":987654321},
{"id":1,"seconds":123,"nanoseconds":456789}],
"timezone":{"minutes_west":-60,"dst_time":0}}}Clock IDs include Linux's static IDs 0–9 and 11: realtime, monotonic, process CPU,
thread CPU, monotonic raw, realtime coarse, monotonic coarse, boottime,
realtime alarm, boottime alarm and TAI. Each is independent; absent clocks are
unknown, including coarse variants. A selected released GKI contract or an
explicit linux_kernel.tasks catalogue also admits negative encoded process
CPU clock IDs with explicit live group observations;
see released GKI clocks.
PROF, VIRT and SCHED retain independent observations; aliases of the current
process share one observation. Duplicate identities,
unknown input IDs and more than 16384 clock observations are errors.
Seconds are signed 64-bit and must be nonnegative for CPU clocks;
nanoseconds must be in [0, 1000000000). Integer
JSON numbers are accepted within ±9007199254740991; decimal strings preserve
the full signed range. Timezone fields are signed 32-bit integers. C++ callers
use ProcessOptions::LinuxTime, with the same value validation. Other process
profiles reject this option.
"advance_on_idle": true explicitly enables relative nanosleep on x64 and
ARM64, including Android's named and variadic wrappers and scalar usleep.
Clock IDs 0, 1, 4 and 7 advance from their independent initial values by elapsed
virtual time. Monotonic raw (4) never borrows the epoch of monotonic (1).
Process CPU clock
observations may coexist with this policy and stay fixed during idle advancement.
Other clock inputs remain excluded from this policy.
A sleeping Android thread retains its original pending service;
other runnable threads execute first. When all threads are blocked, time advances
to the earliest sleep deadline. A single-threaded workload advances directly.
Without an instruction-time input, a busy runnable thread can leave a sleeper
pending until the workload budget expires.
"nanoseconds_per_instruction":100 optionally assigns a deterministic virtual
wall-time rate to admitted guest instructions. Zero (the default) disables it.
The unsigned 64-bit rate may also be a decimal string. A workload-wide cumulative
instruction count is accounted exactly once at successful service and quantum
boundaries, across all Android threads. Realtime, monotonic, monotonic raw and
boottime share that elapsed time with idle advancement; supplied process CPU samples stay fixed.
Unsupported clock kinds are rejected just as for idle advancement. Multiplication,
elapsed-time and epoch overflow stop before changing any clock.
This rate is a declared analysis input, not measured hardware timing or CPU-time
accounting. It lets busy guest polling loops observe scheduled device-page changes
and lets sleepers wake while another guest thread runs. Updates remain quantized
by actual execution boundaries and instruction_quantum; reports should retain
the rate, quantum and producer trace. No host clock or sample PC participates.
The request is copied before checking nonnegative seconds and normalized
nanoseconds. Bad input addresses return EFAULT; invalid values return EINVAL.
Normal completion leaves the remaining-time pointer and Bionic errno untouched.
Zero duration returns without advancing. Saturated or overflowing deadlines and
clock overflow stop explicitly; no partial clock advancement is committed.
Relative sleep interruption in cooperative Android threads follows the signal
contract above; no SA_RESTART retry is applied. This deterministic policy excludes
absolute sleeps,
timer slack, CPU-time accounting and host scheduling. Disabled policy retains
an explicit unsupported-service stop for sleep.
The copy/validation/completion boundary follows Linux's
relative nanosleep implementation;
Android's error conversion follows the API 28
AArch64 Bionic wrapper.
clock_gettime consumes the low signed 32 bits of its clock ID, returns
EINVAL for invalid positive IDs before accessing the destination. Admitted
process CPU clocks validate their target before the destination; other encoded
and dynamic clocks stop explicitly. A known clock without input stops as
unsupported_service. gettimeofday writes seconds and truncated microseconds
as two ordered 64-bit fields, followed by the optional pair of 32-bit timezone
fields. gettimeofday(NULL, NULL) needs no input. Missing timezone input stops
after any completed timeval writes. The x64 time syscall returns realtime
seconds and optionally stores one 64-bit value; ARM64 has no such syscall.
These follow the Linux time service
and clock dispatch
contracts for the modeled subset.
Fully writable outputs, including unaligned ones, are supported. A wholly
inaccessible output returns EFAULT; earlier completed gettimeofday fields
remain written. Mixed accessibility within a single 8-byte store or 16-byte
timespec copy stops before that operation because architecture-specific partial
fault writes are not modeled. Adjustment, resolution queries, scheduling,
sleep and real device clocks remain unsupported.
linux_files supplies initial file bytes to Linux ELF64 and Android native
workloads. C++ uses ProcessOptions::LinuxFiles; C, Python and CLI share this
strict JSON contract:
Optional devices entries add explicit descriptor-owned ioctl transactions and read-only mapping snapshots
in the same namespace. Device responses are declared inputs; regular file
bytes remain immutable unless granted a write_limit; device mappings are not implied.
Optional symbolic_links entries use the same canonical absolute namespace:
{"path":"/links/item","target_hex":"2e2e2f746172676574"}. Targets are
nonempty NUL-free bytes and share the catalogue's count and byte limits with
regular files. A link cannot also name a file or an ancestor directory.
Raw x64 readlink, x64/AArch64 readlinkat, Android imports and dlsym bindings
copy the observed target prefix without adding NUL. Absolute queries ignore
dirfd; the kernel's signed low-32-bit size check precedes pathname access.
Link queries preserve file cursors and do not resolve the target, including
relative or dangling targets. Traversal through links, link metadata,
relative queries and unknown descriptor-backed links stop explicitly; other
file operations cannot silently treat these links as missing regular files.
Mixed-permission output copies remain unsupported before any bytes are written.
The error ordering follows the Linux readlink contract.
{"linux_files":{"files":[
{"path":"/fixture/data","bytes_hex":"00ff410a805a"}],
"descriptor_limit":256}}files is required and may be empty. Every entry requires path and
bytes_hex, with optional metadata and write_limit; empty bytes describe an empty file. Paths must be distinct,
absolute and canonical, with no NUL, empty, . or .. components or trailing
slash. Paths have fewer than 4096 bytes and components at most 255 bytes.
A file cannot be another file's parent directory. The catalogue holds at most
256 files and 16 MiB total content and NUL-terminated path bytes. The existing
64 KiB JSON request ceiling also applies. descriptor_limit defaults to 256,
accepts 3–4096, and is the exclusive descriptor ceiling, including 0, 1 and 2.
Other process profiles reject these options before loading an image.
The catalogue is closed: absent paths return ENOENT. No host file, process
command line, environment or implicit /proc content is consulted. Without
linux_files, file services remain unsupported. Supplied bytes are explicit
workload facts; naming a proc path does not model procfs or its dynamic content.
Raw x64 open, x64/ARM64 openat, read, close and lseek share one
process-owned descriptor table. Android open, open64, openat, openat64,
read, close, lseek, lseek64, fstat, fstat64 and syscall use that same state, including
across guest threads. Bionic alone maps kernel errors to -1 and thread-local
errno; success preserves errno. Absolute paths ignore dirfd; mode is unused
without creation. Ordinary file opens admit O_RDONLY, optional O_CLOEXEC
and the architecture's O_LARGEFILE. O_DIRECTORY also admits known pathname
failures and requires the final object to be a directory. O_DIRECT admits
pathname and descriptor-exhaustion failures before reaching an opened file;
the catalogue does not infer its backing filesystem's direct-I/O support.
ARM64's directory/direct bits are 0x4000/0x10000, while x64 uses
0x10000/0x4000. These stable source-pinned rules do not require GKI selection.
Unlisted flags, cwd-relative paths, internal repeated separators, ..
components, creation, symlink traversal and descriptor-control operations
remain unsupported. openat additionally resolves canonical relative names
and . against a live directory FD. Exec is unmodeled, so close-on-exec flags have
no observable transition in this subset.
Guest queries accept canonical absolute names followed by one or more /
characters. Trailing separators require the final object to be a directory:
queries of a regular file with a trailing slash return ENOTDIR, and missing
names still return ENOENT. Paths consisting only of slashes name the root.
Directory opens are modeled; directory stat metadata remains unknown. This parsing does
not relax the canonical keys required by the catalogue configuration. The
directory requirement follows Linux
pathname lookup.
Existence queries use the same pathname import and closed catalogue. Raw x64
access (21), x64/ARM64 faccessat (269/48), and Android access/faccessat
support F_OK: files, their ancestor directories and / exist; missing paths
return ENOENT, and a file used as an ancestor returns ENOTDIR. The explicit
catalogue admits traversal of its directory prefixes; it supplies no general
directory permission model. Queries allocate no descriptor and preserve all
cursors, including when the descriptor table is full. Invalid low 32-bit mode
bits return EINVAL before pathname access. R_OK, W_OK and X_OK on an
existing entry remain unsupported: observed metadata does not establish
credentials, ACLs or mount policy. Raw faccessat consumes three arguments;
the API 28 Bionic wrapper
rejects nonzero flags with EINVAL before entering the kernel. The kernel
validation order follows Linux access.
Each open starts an independent cursor and chooses the lowest unused
nonnegative descriptor. Exhaustion returns EMFILE after pathname import;
close releases the descriptor. Slots 0–2 initially reserve unknown stdin and
the existing captured stdout/stderr streams. Closing those slots enables
ordinary file reuse; writes through a closed slot or read-only file return
EBADF. Reading stdin remains unsupported; it is never assumed to be empty.
Descriptors, open flags and seek modes consume their low 32 bits.
Directories and live FD links
Catalogue parents and / are enumerable directories. directories optionally
adds canonical empty directory paths. They share the 256-entry catalogue limit.
getdents64 (x64 217, AArch64 61) emits LP64 records with 8-byte alignment,
opaque continuation cookies, type, inode, name and NUL; padding remains
unchanged. . and .. precede byte-sorted catalogue children. Supplied file
inodes are retained; other inodes are deterministic workload-local identities,
not captured host values. At most 8,448 such identities are admitted. A device
whose char/block type is unknown has DT_UNKNOWN. dup shares the directory
cursor; independent opens do not. Small buffers, EOF, invalid descriptors,
non-directories and wholly inaccessible outputs have explicit errno behavior;
mixed-permission record writes stop as unsupported. Directory reads return
EISDIR. Directory SEEK_SET/SEEK_CUR accepts nonnegative cookies.
dynamic_proc_fds: true reserves /proc/self/fd for live links and enumeration.
Opening, duplicating, replacing, closing and reusing an FD immediately changes
that namespace; no stale static link overrides it. Known file, directory and
device paths come from their actual open description. Standard captured streams
have no inferred tty/pipe pathname, so their readlink query stops explicitly.
/proc/self reports the modeled process ID. Conflicting catalogue entries are
rejected. The virtual FD table preallocates descriptor_limit rounded up to
64 slots: proc cookies include closed slots and reach that table boundary at
EOF. Rewind is required to revisit reused slots below the cursor. These rules
follow the kernel record ABI
and FD iteration.
This option does not supply dynamic maps, task, status or fdinfo files, nor
PID aliases, proc mount metadata or general proc symlink traversal. The separate
dynamic_proc contract supplies maps, live tasks, supported
status fields and fdinfo, including process/thread-self aliases.
Bounded writes to existing files
An entry's write_limit grants mutable content with that maximum extent.
The full reservation counts against the 16 MiB file budget. The grant does
not assert real DAC/SELinux permissions or disk capacity. O_WRONLY, O_RDWR,
O_TRUNC and O_APPEND use one mutable object per catalogue path, shared by
all opens; cursor and flags remain per open description. dup shares both.
write overwrites, zero-fills holes, preserves readable-page prefixes on a
partial input fault, and chooses the current object end for append. Kernel
validation checks the original signed cursor and count before append selects
EOF, following the VFS write contract.
Reads and newly opened descriptors observe the resulting bytes. Missing write
grants and exhausted reservations stop explicitly; they are not fabricated
EACCES/ENOSPC results. O_CREAT can open an existing entry; O_EXCL returns
EEXIST before truncation. Creating absent objects remains unmodeled. O_CREAT | O_DIRECTORY also stops
explicitly because its behavior depends on the Linux release.
Without an explicit track_write_metadata provider, any write or truncation
invalidates captured stat observations. A later stat
stops rather than returning stale size, block counts or timestamps. The known
filesystem block size remains usable for stdio buffer selection. Vectored
mutable-file writes, regular file mappings, truncate syscalls, unlink/rename,
new objects and general permission changes remain unmodeled. An explicit
metadata provider can
maintain complete size, sparse allocation and virtual timestamps after writes.
Captured stdout/stderr keep their existing bounded output semantics.
Directory creation supports only failures already determined by pathname
lookup. Raw x64 mkdir (83), x64/ARM64 mkdirat (258/34), and Android
mkdir/mkdirat share the catalogue: a missing parent returns ENOENT, a file
ancestor returns ENOTDIR, and an existing file or directory returns EEXIST.
The final name's EEXIST result also applies to a regular file followed by
trailing slashes; directory creation checks the parent separately.
They preserve the catalogue, descriptors and cursors. An absent final name
under an existing directory remains unsupported; mode, umask, write permission
and successful creation have no invented defaults. The boundary follows
Linux parent and final-component lookup
and the API 28 Bionic wrapper.
Reads validate the original user range before the page-aligned Linux transfer
cap or EOF, copy only available bytes, and advance by exactly the copied
prefix. A later inaccessible page preserves that prefix; a fault before any
byte returns EFAULT. Empty reads and EOF do not probe payload mappings.
A zero-length user range may start exactly at the user limit. After validating
that range, a file position plus the original count above INT64_MAX returns
EINVAL, including at EOF; neither failure advances the cursor.
Ordinary SEEK_SET, SEEK_CUR and SEEK_END keep a signed 64-bit nonnegative
cursor, permit seeking beyond EOF, and reject negative/overflowing positions
without changing it. SEEK_DATA and SEEK_HOLE remain unsupported. These are
bounded memory-file semantics based on Linux's open lifecycle,
read/seek contracts
and AArch64 flags,
not general filesystem compatibility.
File status requires explicit observations. The optional metadata object has
exactly these fields; all are required when the object is present:
| Fields | Admitted values |
|---|---|
device |
Linux encoded unsigned 32-bit device number |
inode |
Unsigned 64-bit integer |
mode |
S_IFREG (32768) plus permission/special bits (0–4095) |
link_count, uid, gid |
Unsigned 32-bit integers |
size, blocks |
0–INT64_MAX; blocks are 512-byte units |
block_size |
0–INT32_MAX |
access_time, modification_time, change_time |
Objects with signed 64-bit seconds and nanoseconds from 0 to 999999999 |
All observation integers accept decimal strings for their full width. Numeric
JSON values must be integral and within ±(2^53−1), as for linux_time. Unknown
fields, missing members and invalid ranges fail before execution. C++ stores
these observations in LinuxFileOptions::Metadata, keyed by a path already in
Files; a key without file bytes is rejected. For example, this is an explicit
virtual-file observation with nonempty bytes and a reported size of zero:
{"linux_files":{"files":[{"path":"/fixture/virtual","bytes_hex":"616263",
"metadata":{"device":1,"inode":"18446744073709551615","mode":33060,
"link_count":1,"uid":1000,"gid":1000,"size":0,"block_size":4096,"blocks":0,
"access_time":{"seconds":0,"nanoseconds":0},
"modification_time":{"seconds":0,"nanoseconds":0},
"change_time":{"seconds":0,"nanoseconds":0}}}]}}These immutable observations do not change access policy or byte-vector read
and seek behavior, and are never inferred from host metadata. Raw fstat
(x64 5, AArch64 80), Bionic fstat/fstat64 and variadic syscall use the same
descriptor state without advancing its cursor. The ABI structures are 144 bytes
on x64 and 128 bytes on AArch64, with zero rdev and padding for regular files.
An invalid descriptor returns EBADF before accessing the output. A file
without metadata or a standard stream with unknown identity stops explicitly.
With known metadata, a wholly inaccessible or out-of-range output returns
EFAULT. Mixed writable/inaccessible output stops without changing bytes;
architecture-specific partial copy_to_user effects are not modeled. This is
the same fixed-copy policy used by Linux clock services. See the Linux
status conversion,
AArch64 layout
and x64 layout.
newfstatat (x64 262, AArch64 79), Bionic fstatat/fstatat64 and variadic
syscall query those same observations. Absolute paths in the supported form ignore dirfd;
missing entries return ENOENT, and traversal through a regular file returns
ENOTDIR. AT_SYMLINK_NOFOLLOW and AT_NO_AUTOMOUNT are accepted for supported
regular-file queries; link metadata, link traversal and automount behavior
remain unmodeled. A non-NULL empty pathname
with AT_EMPTY_PATH queries an existing descriptor without changing its cursor;
without that flag it returns ENOENT. Closed descriptors return EBADF.
The directory and flags arguments use their low 32 bits. Invalid flag bits
return EINVAL before path errors; the complete pathname is imported before
status output, so input and output may overlap.
Relative paths, directory metadata, the current working directory, NULL with
AT_EMPTY_PATH, and stat synchronization flags remain unsupported. The last two
have changed across kernel releases; the model does not infer a kernel version.
Path queries use the same fixed-copy policy and explicit metadata requirement
as descriptor queries. Contracts were checked against Linux
v4.9 and
v6.12, and the LP64
Bionic service aliases.
statfs (x64 137, AArch64 43) and fstatfs (x64 138, AArch64 44)
resolve names and descriptors in that same explicit catalogue. Path imports
preserve EFAULT and ENAMETOOLONG; missing names return ENOENT, and a regular
file used as a directory returns ENOTDIR. Closed or unknown descriptors return
EBADF, using the low 32 bits of the descriptor argument. These errors precede
output access and leave every output byte unchanged. Queries of existing files,
implicit directories, root or live standard streams remain unsupported because
file metadata does not provide filesystem observations. No successful filesystem
status or mount information is inferred. Bionic statfs/statfs64,
fstatfs/fstatfs64 and variadic syscall share these error paths and the usual
errno conversion. See Linux's filesystem query order
and the API 28 LP64 wrappers.
windows-pe64-v1 supports bounded Windows x64/ARM64 console processes with PEB/TEB, static and dynamic TLS, DllMain, named Win32 APIs and explicit acyclic DLL graphs. Guest modules support named/ordinal code and data imports, DIR64 rebasing, forwarded exports and actual loader-list identities. LoadLibraryA / LoadLibraryW, FreeLibrary and GetProcAddress use the configured module catalogue. CRT/GUI, ARM64 frame-based user SEH, threads and general Windows application compatibility remain unfinished; native ARM64 KVM/WHP evidence is still pending.
WindowsProcessTime.cpp owns host-backed GetSystemTimeAsFileTime, GetTickCount, QueryPerformanceCounter, QueryPerformanceFrequency and ZwDelayExecution. FILETIME uses 100 ns units from 1601; the performance counter uses a monotonic clock and its reported 10 MHz frequency, while tick count wraps at 32 bits in milliseconds. Nonalertable relative delays up to 500 ms and a zero interval complete; alertable, positive absolute and longer delays stop explicitly without shortened waits or a successful result. These services preserve LastError and use the same model across CPU backends. ZwDelayExecution reads only the low 8 bits of BOOLEAN; unused argument-register bits do not change the wait policy.
Windows x64 ABI, QueryPerformanceFrequency, FILETIME.
Windows virtual memory adds VirtualAlloc, VirtualFree, VirtualProtect, VirtualQuery and current-process FlushInstructionCache. The OS layer owns reservations; AddressSpace remains the authority for committed pages, permissions and backing. Tests cover dynamic code rewriting, access faults and memory-budget reuse.
WriteProcessMemory follows the observed x64/ARM64 committed-page contract for current-process writes up to 4 KiB. It preserves each region's protection, copied prefixes, byte counts and LastError, including ERROR_NOACCESS, ERROR_PARTIAL_COPY and RX-prefix success. WindowsMemoryWriteTests.cpp checks all 25 protection pairs; check_windows_memory_write.py verifies the same original executable on native Windows CI. Uncommitted destinations remain explicitly unsupported.
Private allocations support MEM_RESERVE, MEM_COMMIT, MEM_DECOMMIT, MEM_RELEASE and MEM_TOP_DOWN, with 64 KiB reservation alignment and 4 KiB pages. Reserve-only regions consume no guest RAM. Recommit preserves bytes and updates protection; decommit returns individual page backing. Whole-range validation and staged allocation prevent partial changes on ordinary allocation/protection failures. Query returns the 48-byte x64/ARM64 memory-information layout and coalesces forward within one allocation. The initial image, environment, heap arena, API gates and stack margins participate in placement. Stack allocation identity agrees with TEB. If a successful VirtualProtect makes its old-protection output read-only, the new protection remains installed, the output bytes stay unchanged, and the call still succeeds. An uncommitted-range protection failure returns ERROR_INVALID_ADDRESS, writes PAGE_NOACCESS to the old-protection output and leaves page permissions unchanged.
Supported protections are PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE_READ and PAGE_EXECUTE_READWRITE. Guard pages, execute-only and copy-on-write policies, cache modifiers, large pages, reset/write-watch/placeholders and modification of model-owned runtime mappings remain explicit unsupported operations. Only private virtual allocations can be decommitted or released.
VirtualAlloc, VirtualFree, VirtualProtect, VirtualQuery, MEMORY_BASIC_INFORMATION.
neverd emulate guest.exe --profile=windows-pe64-v1 \
--options='{"backend":"auto","arguments":["guest.exe","argument"],"environment":["MODE=test"]}'The single-thread profile keeps the PE32+ EXE at its preferred base and admits explicitly supplied DLLs with optional entry points and static TLS. WindowsProcessOptions::Modules, or JSON windows.modules, supplies up to 64 guest basenames and host input paths through name and path; there is no host DLL search or execution. ASCII names compare without case; duplicate names and overrides of modeled system providers fail. Only reachable files are read. Named/ordinal function and data imports bind to actual mapped exports; holes, missing symbols, cycles, bound/delay imports and unsupported load configuration/CFG fail explicitly. DIR64 records rebase colliding movable DLLs; fixed collisions and relocation writes into linker metadata fail before publishing the affected image.
readPEProgramExports owns original export identities and bounded metadata footprints. WindowsProcessModules owns the guest graph and one exact provider/name API gate per process. VirtualMemory reserves every image before mapping; AddressSpace owns pages and permissions. PEB/LDR lists contain real images, with loader registration order in the initialization list. Registration and dependency-based attach order are tracked separately. GetModuleHandleW accepts NULL or ASCII basenames, compares without case, and appends .dll when no extension is supplied; paths, non-ASCII lookup and trailing-dot rules remain unsupported. Missing names return error 126; success preserves LastError. API models are not installed system DLLs.
Input-file bytes and aggregate image extents each share memory_limit; runtime mappings also consume the image budget. Preparation shares a 65,536-record and 64 MiB metadata-read allowance, bounded names and the workload deadline. Blocking host I/O has no hard time guarantee. The original EXE→DLL→DLL fixture checks rebased pointers, ordinal calls, shared data, API pointer identity, MEM_IMAGE, loader lists and executable TLS attach/detach. NeverDWindowsProcessTests owns these checks and the direct native Windows oracle; NeverDPEProgramExportsTests checks malformed metadata and work accounting, and NeverDProcessPublicTests checks C ABI/CLI catalogue parity. Unavailable transports are explicit skips.
WindowsProcessLifetime runs dependency DLL TLS callbacks then DllMain, followed by EXE TLS and entry, on one CPU under the same execution budget. Each module gets an independent TLS index and aligned block copied from the relocated, linked image within a shared 64 KiB arena. TLS reserved arguments are zero; startup/process-detach DllMain receives an opaque non-null value. Explicit process exit detaches successfully initialized DLLs in reverse loader-list order, then EXE TLS, even if EXE initialization had not run. Startup DllMain(FALSE) exits with 0xc0000142 without detach notifications. Faults and exhausted budgets do not invent cleanup. Returning from the PE entry with guest DLLs requires unsupported thread termination and stops explicitly. Nonzero SizeOfZeroFill remains unsupported; zero-initialized bytes in the actual TLS template are supported. DLLs without entry points receive TLS attach but no process-detach notifications.
WindowsProcessExports resolves static imports and GetProcAddress through the same named/ordinal identities, including code, data, aliases and chained forwarders. Only demanded startup forwarders add catalogue modules and initialization dependencies; unused forwarders do not load files. Export names are case sensitive; a missing name returns NULL/error 127, a direct missing ordinal (including a hole) returns NULL/error 182, and a null query argument returns error 87, while success preserves LastError. Unknown module handles remain unsupported. Exact provider/name API gates are reserved once from the bounded registry. Resolution checks every queried image’s live PE headers and export metadata, rejects changes or unreadable bytes, limits chains to 64 entries, and shares remaining preparation metadata credits and the workload deadline. A forwarder targeting a hole returns the target image base and preserves LastError; forwarding to ordinal zero returns error 87. The returned base is a data address, not permission to execute image headers. Runtime forwarders can load configured catalogue modules and complete initialization before returning a lookup result. Live export-table rewriting remains unsupported.
WindowsProcessLoader loads ASCII DLL basenames from windows.modules and owns explicit references, shared dependencies and startup retention. Repeated forwarded queries do not acquire extra references. Catalogue slots carry a new resident generation on reload. TLS and DllMain execute on the same CPU below suspended API frames; restoring registers preserves guest writes and uses the live return slot. Dynamic attach/detach reserved pointers are zero. Failed attach during explicit loading returns error 1114 after cleanup, retaining successful independent nested loads. Unload releases image mappings and TLS; reload restores original image contents. Loader-list or TLS-pointer changes outside the model fail explicitly. File, image and metadata work credits remain cumulative across failures and reloads. System providers use their mapped PE bases as module handles. Filesystem search, non-ASCII paths, LoadLibraryEx flags, cyclic imports and reentrant transitions of an already initializing or unloading module remain unsupported.
A DLL supplied as the process input uses WindowsLibraryHost.cpp: a modeled PE EXE executes LoadLibraryA → FreeLibrary → ExitProcess on the same guest CPU. The input consumes one of the 64 catalogue slots, retains its basename, and shares preparation and execution budgets with the host and dependencies. GetModuleHandle(NULL) identifies the host; GetModuleFileNameA accepts resident module handles. Self-file opens still access only the explicit input file. Failed attach follows ordinary error 1114 and cleanup; no export signature is guessed.
GetEnvironmentVariableW, SetEnvironmentVariableW, GetEnvironmentStringsW, FreeEnvironmentStringsW, ExpandEnvironmentStringsW share the live guest environment block in PEB process parameters. Names are ASCII and case insensitive; values are UTF-16. Updates validate inputs, capacity and writable memory before publication. Snapshots remain independent after changes and release their guest backing on free. The block has a 64 KiB model limit; strings and expansions are bounded and check the workload deadline. Unknown pointer ownership, malformed blocks, ANSI code pages and overlapping expansion buffers remain unsupported. WindowsEnvironmentTests.cpp compares original x64/ARM64 fixtures across available backends and requires an independent native Windows oracle in CI.
WindowsProcessHeap now owns allocation, HeapReAlloc, free and size queries in one process heap. Resizing preserves the retained bytes; HEAP_ZERO_MEMORY clears newly exposed bytes, and HEAP_REALLOC_IN_PLACE_ONLY forbids movement. Failed resizing preserves the old block and returns NULL with ERROR_NOT_ENOUGH_MEMORY (8), matching the native observations. Separate page backing lets shrink/free return capacity, while staged growth and bounded copies observe the workload deadline. Custom heaps, exception-generating flags, unknown ownership and inaccessible copy/zero spans stop explicitly. WindowsHeapTests.cpp covers both ISAs, forced movement, budget reuse and failure atomicity; CI also runs the original EXE against native Windows.
windows.peb_version explicitly supplies the PEB major, minor, build and platform fields. All four unsigned integer fields are required; build is 16 bits and the other fields are 32 bits. Omission preserves the profile’s zero-filled version fields. WindowsProcessEnvironment owns their initialization on x64 and ARM64. This input does not choose native service numbers or certify compatibility with that Windows release. Capture and native replay must agree on any environment bytes on which retained code depends.
WindowsSystemModules builds bounded PE64 model images for ntdll.dll, kernelbase.dll and kernel32.dll on both ISAs. Their mapped bases are shared by ASCII GetModuleHandleA / GetModuleHandleW, LoadLibraryA / LoadLibraryW and GetProcAddress; PEB/LDR and MEM_IMAGE describe those same images. Static imports, named queries and guest forwarders use the same API gates and export resolver. Providers stay pinned, have no guest initialization callbacks and do not prevent entry return after ordinary guest DLLs unload. Changed headers or export metadata stop lookup. Unknown system export names and nonzero system ordinal queries stop explicitly; case-only mismatches of modeled names and empty names return error 127, while a null query returns 87. Generated bytes and addresses are model policy; Windows DLL version layouts, native ordinals and cross-provider aliases are not reconstructed. WindowsSystemTests.cpp compares original x64/ARM64 executables with native Windows, including eight independent initial-thread returns.
WindowsProcessExceptions implements AddVectoredExceptionHandler, RemoveVectoredExceptionHandler and RaiseException on one CPU with the process budget. Ordered handlers may register or remove handlers, raise nested exceptions, call modeled APIs, load DLLs and exit the process. x64/ARM64 data-access violations and x64 integer divide faults can resume after validated guest edits to CONTEXT; general registers, SIMD and supported FP state are preserved. Software exceptions resume through a real return instruction in the modeled provider. The model bounds registrations to 128 retained entries and nesting to 16 frames. Invalid dispositions, changed exception pointers, unsupported context fields and exhausted bounds fail explicitly. ARM64 frame-based SEH/unwinding, debugger delivery and execute/guard faults remain unsupported. WindowsExceptionTests.cpp compares original EXE/DLL scenarios against native Windows; native ARM64 KVM/WHP evidence remains pending. AddVectoredExceptionHandler, RemoveVectoredExceptionHandler, RaiseException, CONTEXT x64, ARM64_NT_CONTEXT. Software exception records carry EXCEPTION_SOFTWARE_ORIGINATE (0x80), independently of the caller’s noncontinuable flag; the original Windows executable checks the exact software and hardware flag values. EXCEPTION_RECORD.
WindowsProcessContext retains each dispatch frame’s origin. Admitted x64 data-access and divide faults expose RF (0x10000) in CONTEXT.EFlags; RaiseException, including software access-violation codes, retains the current context. The origin survives VEH/VCH and SEH search/unwind. Valid continuation restores logical CPU flags without RF; guest edits to RF are rejected before state publication. This bounded profile does not model instruction breakpoints or guest-controlled RF. WindowsExceptionTests.cpp checks saved records, restoration and unchanged CPU/RAM on rejection.
Windows ring3 maps checked x64 operand_alignment faults to STATUS_ACCESS_VIOLATION with parameters [read, UINT64_MAX], including stores, following independent native observations. The CPU layer supplies the cause; Windows does not guess it from vector 13 or decode the instruction again. WindowsAlignmentProcessTests.cpp runs original PE instructions across 72 fault scenarios and 9 address-repair retries (72 + 9), checking PC, RF, XMM state and RAM. Unclassified or inconsistent faults remain rejected. Process and driver fault reports preserve nullable cause and hexadecimal error_code; absence stays distinct from zero. This delivery applies to the checked x64 user profile.
AddVectoredContinueHandler and RemoveVectoredContinueHandler maintain a separate ordered list, sharing the 128 retained registration limit with exception handlers. Continue callbacks run after a vectored exception handler accepts continuation; they see the same mutable exception record and CONTEXT. Final context validation happens after these callbacks, including nested exceptions and DLL notifications. Handles cannot be removed through the other handler family. WindowsContinuationTests.cpp compares original executables for ordering, short-circuiting, mutation, context repair, nested dispatch, loader callbacks and process exit against native Windows. The tested Windows x64 vectored path permits continuation with EXCEPTION_NONCONTINUABLE set; this does not establish frame-based SEH behavior. Native ARM64 execution remains unverified. AddVectoredContinueHandler, RemoveVectoredContinueHandler.
RtlCaptureContext is available through kernel32.dll and ntdll.dll for x64 and ARM64. Shared WindowsProcessContext and IntegerABI record the caller’s PC/SP without changing CPU state or LastError. Native Windows observations establish x64 flags 0x10000f, preservation of untouched home/debug/vector storage, and the legacy 32-bit x87 address fields; ARM64 records PC from LR and clears the saved X0/LR. Register values, SIMD and FP controls come from the guest; x64 selectors and the MXCSR capability mask follow the configured guest CPU. Invalid, unaligned or partly inaccessible destination records fail before publication. WindowsContextTests.cpp covers direct imports, provider lookup, VEH callbacks, cross-page output and failure atomicity. scripts/check_windows_context.py runs the original executable on Windows x64 and ARM64, with a separate native nonempty-x87 oracle. These ARM64 API observations do not establish native KVM/WHP execution. Context restore, stack walking and dynamic function tables remain separate work. WindowsProcessServices.def declares exact module restrictions: the modeled kernelbase.dll lookup returns ERROR_PROC_NOT_FOUND (127), matching native observations rather than creating an extra export. RtlCaptureContext.
WindowsProcessSEH uses shared X64SEH in os/windows/exception/ (NeverDEmulationWindowsException, available without drivers) for x64 __C_specific_handler and UNWIND_INFO V1. After VEH search it supports filters, finally callbacks, nonlocal handler transfer, nested/collided dispatch and rebased EXE/DLL frames, preserving nonvolatile GPR/XMM state. Filter continuation runs VCH with the same CONTEXT. WindowsSEHTests.cpp compares 23 original scenarios with native Windows; KVM/WHP/Unicorn share these semantics. Dispatch rechecks image generations, headers, unwind/scope bytes, personality code regions and IAT bindings under the process budget. Changed metadata or unloaded retained images fail explicitly. ARM64 frame SEH, C++ EH, dynamic function tables, general RtlUnwind/NtContinue, and unwinding across loader/VEH/VCH callback boundaries remain unsupported.
For EXCEPTION_NONCONTINUABLE, an x64 filter returning EXCEPTION_CONTINUE_EXECUTION dispatches STATUS_NONCONTINUABLE_EXCEPTION (0xc0000025, flags 0x81, null linked record) with a fresh context. VEH runs again before search restarts on the retained logical stack, preserving finally order and EXE/DLL frame identity under the same depth and execution budgets. The 23 native scenarios include 21 successful executions and two terminations: accepting this secondary exception in VEH/VCH remains unhandled even after restoring the original CONTEXT. The model reports that outcome as a runtime failure. Software exception addresses equal their saved PC; internal dispatcher addresses and register layout are model policy. Windows x64 CI.
Before dynamic detach callbacks, the module leaves the initialization list while its mapping, name lookup and load/memory list membership remain available. Entry-return oracles observe the initial thread independently of system worker threads.
WindowsDynamicTests.cpp compares original x64/ARM64 DLLs and EXEs with independent native Windows observations: reference counts, shared dependencies, nested loading, failed attach cleanup, forwarded lookup, process exit, no-entry DLLs and fresh TLS on reload. Additional regressions reject modified loader metadata and stale code pointers, preserve cumulative preparation budgets and keep interrupted API results incomplete. Windows CI requires the native oracle and WHP cases; cross-compilation and Unicorn ARM64 do not establish native ARM64 execution.
A missing library anywhere in a GetProcAddress forwarder chain returns error 127; an explicit LoadLibrary of a missing catalogue module returns 126. The native oracle and each available backend assert all 41 declared loader scenarios; return after unloading every DLL is observed 16 times per DLL variant on Windows. Failed initialization in a GetProcAddress forwarder also returns 127 after cleanup. Process-detach callbacks preserve the exiting caller’s stack contents.
WindowsExportTests.cpp uses original x64/ARM64 DLLs and an EXE to check forwarded code/data/ordinal calls, aliases, initializer queries, rebasing, case-sensitive misses, LastError, cyclic and nonresident targets, invalid pointers and metadata changes after successful queries. The same EXE has an independent native Windows oracle; WHP cases are mandatory in native CI. C ABI/CLI tests compare complete reports. Native ARM64 hardware evidence remains pending. Variants with and without EXE exports cover both dependency graphs, PEB-list order, detach order and name/ordinal/null error codes.
WindowsLifetimeTests.cpp compares frozen traces with independent native Windows processes and KVM/WHP/Unicorn execution: normal exit, entry return, both DLL initialization failures, four early exits and DLLs without entry points. It separately checks callback faults, shared budgets, relocated TLS fields and aggregate TLS capacity. The native entry-return probe retains the initial thread handle and checks its exit code and exact thread/process notification sequence in 64 repetitions. Remaining child threads are terminated after observation; their process exit is not treated as the entry return value.
{"windows":{"modules":[{"name":"middle.dll","path":"inputs/middle.dll"},{"name":"leaf.dll","path":"inputs/leaf.dll"}]}}GS on x64 and x18 on ARM64 point to TEB. Supported state includes stack bounds, self pointer, PID/TID, PEB, process parameters, LastError and TLS. Inputs are strict UTF-8 converted to UTF-16; argv is quoted for Microsoft CRT parsing. Environment names are ASCII, case-insensitive duplicates are rejected, values may be Unicode, and the sorted environment is double-NUL terminated. No host environment or filesystem is inherited. Static TLS copies its template, zeroes BSS and writes a 32-bit index; dynamic TLS uses separate TEB slots. Attach and detach read live callback arrays in order, with all instructions and named calls sharing one deadline and resource account. Normal process exit runs detach callbacks. Entry return is supported only with no resident guest DLLs; a second ExitProcess during process-exit cleanup remains unsupported.
The exact API inventory is WindowsProcessServices.def: ExitProcess, RtlExitUserProcess, standard-output handles and synchronous WriteFile, LastError, process/thread identifiers and pseudo-handles, GetCommandLineW / HeapReAlloc, process heap allocation/free/size, dynamic TLS and LoadLibraryA / LoadLibraryW / FreeLibrary / GetModuleHandleA / GetModuleHandleW / GetProcAddress. Provider names are restricted to kernel32.dll, kernelbase.dll and ntdll.dll with exact export identity. Direct syscalls and forged callback gates cannot select API models. Heap backing is owned by the process and reclaimed on free. Writes capture binary bytes; Win32 argument errors remain distinct from unsupported asynchronous I/O or user exception dispatch. Pointer aliasing observes the initial completion-count write and the live call-return slot.
The windows.native_calls report preserves module/function names, declared scalar arguments and nullable result bits. It does not invent native NT syscall numbers. NeverDWindowsProcessTests covers real x64/ARM64 PE startup, compiler TLS, live callback changes, heap/LastError, aliasing, invalid metadata, privilege faults and limits across available backends. NeverDProcessPublicTests checks the same PE through CLI/C ABI. Native Windows CI runs the original EXE as an independent behavioral oracle and requires WHP cases; native ARM64 runtime evidence still requires a suitable machine.
WriteFile with a nonempty unreadable input buffer returns ERROR_INVALID_USER_BUFFER (1784), zeros the completion count and publishes no bytes.
windows.defer_unmodeled loads an image whose loader facts the model does not implement and stops only if execution depends on one. Exports outside the API inventory and modules outside the catalogue bind to opaque entries: each identity resolves to one address, and executing it stops as unsupported_service naming module!export. Directories the model does not interpret stay uninterpreted, metadata the file does not back is not read at load, and frame-based exception dispatch through such an image stops. observeProcess adds a ProcessObserver that reads the stopped process at its start and at each execution watch; it cannot change guest state, and ending the run reports observer. Unpacking is built on both.
PE/COFF, ARM64 ABI, WriteFile, TLS, Wine 10.0 loader. GetProcAddress.
cmake --build build-cpu --target NeverDLinuxProcessTests NeverDExecutionSessionTests NeverDX64MemoryUpdateTests NeverDThreadPointerTests --parallel 4
ctest --test-dir build-cpu -L '^NeverD(LinuxProcess|ExecutionSession|X64MemoryUpdate|ThreadPointer)Tests$' --output-on-failure
# In a shared-library/CLI build:
cmake --build build-cpu --target NeverDProcessPublicTests --parallel 4
ctest --test-dir build-cpu -L '^NeverDProcessPublicTests$' --output-on-failureTests compile original freestanding ELF entry assembly and C for both ISAs.
They exercise data/BSS, actual startup metadata, syscall errors, binary output,
permission faults, partial writes, unsupported services and budget preservation
across quanta. Local-exec TLS fixtures initialize independent aligned blocks,
zero TLS BSS, install thread pointers and check preserved values across switches.
On x64 they also verify arch_prctl errors without losing the previous base.
Backend cells report unavailable transports as skips. The public
suite traverses the shared C ABI and CLI and checks report/exit-code agreement.
The static PIE fixtures check relocated auxiliary values and raw zero RELA
slots before performing their own data/function-pointer relocations. Mapping
tests separately retain analysis fixups when that byte source is selected;
dynamic-table tests cover sections being absent and malformed/dependent input.
Anonymous-memory fixtures execute allocation, protection, holes, remapping,
heap growth/shrink and handled syscall errors on both ISAs. Separate actual
guest stores fault after ordinary and partial protection changes. The x64
fixture also rewrites code between RW and RX transitions and calls both
versions at the same address. The same x64 ELF runs natively on Linux as an
independent result/fault oracle. Pure memory tests cover budget exhaustion,
reclamation and authoritative mapping snapshots without retaining RAM.
Cross-compilation and Unicorn ARM64 results are not native ARM64 KVM/WHP evidence.
Android atoi, atol and atoll accept bounded C-locale decimal prefixes,
including leading whitespace and one sign. The first nondigit ends the scan;
representable 32-bit/64-bit results preserve errno. Out-of-range results stop
explicitly without choosing libc-specific wrapping or saturation. This subset
follows C11 section 7.22.1.2;
it does not add general strtol bases, end pointers or locale mutation.