Skip to main content

jit/
lib.rs

1//! The runtime owns guest process state and calls generated native code through a stable C ABI.
2//!
3//! The guest is an RV64 Linux process. The host is the machine that runs this crate.
4//! The runtime maps the guest ELF file, builds its first user stack, and handles a small
5//! set of Linux system calls. Generated code uses raw pointers to enter and leave this
6//! runtime. Thus, the data layout in this file is also part of the JIT interface.
7
8mod mmu;
9mod remi;
10
11use liil::nnil::{Nnil, NnilInstruction};
12
13use std::collections::{BTreeMap, BTreeSet, VecDeque};
14use std::error::Error;
15use std::fs::{self, File, OpenOptions};
16use std::io::{self, IsTerminal as _, Read as _, Seek as _, SeekFrom, Write as _};
17use std::os::unix::fs::{FileExt as _, MetadataExt as _};
18use std::panic::{AssertUnwindSafe, catch_unwind, set_hook, take_hook};
19use std::path::{Component, Path, PathBuf};
20use std::sync::{
21    Arc,
22    atomic::{AtomicBool, Ordering},
23};
24use std::time::{Duration, Instant};
25
26pub use mmu::{GuestMemory, GuestMemoryAbi, GuestPageAbi, GuestTranslationAbi, PagePerms};
27use mmu::{align_down, align_up};
28use remi::compile_jit_multi;
29pub use remi::{CompiledJit, compile_jit};
30
31/*
32
33    todo v7
34     - performance
35     - timen
36     - alle nnils impl.: riscv-arch testsuite
37        - Z* instructions
38     - syscalls ? exit & recovery
39     - reset
40     - privilged
41     - threads?
42    - mmu? / memory richtig
43
44*/
45
46// todo - barebones
47// These fixed sizes keep the host and generated copies of `GuestState` ABI-compatible.
48// Slots 0..31 hold integer registers, slots 32..63 hold floating-point registers,
49// and slots 64..95 are spare project capacity.
50// The emitter checks each index before it writes generated Rust code.
51const GUEST_REG_COUNT: usize = 96;
52// NNIL uses numbered temporary values. The fixed ABI array has a project maximum of 256.
53// The emitter rejects a temporary index that reaches this limit.
54const GUEST_TMP_COUNT: usize = 256;
55// A small fixed array keeps debugger data inside the C-compatible control structure.
56// The value 64 is a project limit, not a RISC-V or Linux ABI value.
57const DEBUG_SLOTS: usize = 64;
58
59// RISC-V register x0 always reads as zero. Generated code must also ignore writes to it.
60const INTEGER_ZERO_REG: usize = 0;
61// Slots 0 through 31 hold x0 through x31. Thus, slot 32 can hold floating-point f0
62// without a second register array.
63const FP_REG_BASE: usize = 32;
64
65// This emulator selects 4 KiB guest pages. Linux and RISC-V do not require this one size.
66// One project value must be used in masks, mappings, `mmap` offsets, and AT_PAGESZ.
67const PAGE_SIZE: usize = 4096;
68
69// The project gives each guest a deterministic 128 KiB stack. This is an emulator
70// policy value. It is not a limit from the RISC-V ABI.
71const DEFAULT_STACK_SIZE: u64 = 0x20_000;
72// Keep the stack at a stable high guest address so low ELF mappings have much free space.
73// Page alignment avoids a partial top page. The fixed address makes mappings repeatable.
74const DEFAULT_STACK_TOP: u64 = 0x0000_7fff_ffff_0000;
75// Leave 256 bytes of mapped headroom below the stack top before startup data is added.
76// This project reserve has no documented ABI purpose. It is not an unmapped guard page.
77const DEFAULT_STACK_POINTER_OFFSET: u64 = 0x100;
78// Load the ELF interpreter at a fixed 1 GiB guest bias for deterministic separation
79// from normal low-address executable segments.
80const DYNAMIC_LOADER_BIAS: u64 = 0x4000_0000;
81// Start the automatic guest `mmap` cursor at 1.25 GiB, above the fixed loader bias.
82// Successful mappings move this cursor upward.
83const MMAP_BASE: u64 = 0x5000_0000;
84
85// These values are from the architecture-neutral Linux file-control UAPI.
86// Source: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h
87// Bits 0 and 1 encode the access mode. The mask 3 selects only these two bits.
88const O_ACCMODE: u64 = 3;
89// Bit 0 requests write-only access. A clear access field means read-only access.
90const O_WRONLY: u64 = 1;
91// Bit 1 requests read and write access.
92const O_RDWR: u64 = 2;
93// Bits 6, 7, 9, 10, and 19 are independent options. Bitwise AND tests one option,
94// and bitwise OR can test a group of options without changing the other flag bits.
95// O_CREAT creates a missing file. O_EXCL makes creation fail if the file exists.
96// O_TRUNC clears an existing file. O_APPEND puts each write at the file end.
97// O_CLOEXEC asks a future `exec` operation to close this descriptor.
98const O_CREAT: u64 = 0x40;
99const O_EXCL: u64 = 0x80;
100const O_TRUNC: u64 = 0x200;
101const O_APPEND: u64 = 0x400;
102const O_CLOEXEC: u64 = 0x80000;
103
104// Commands 1 and 2 read and write the per-descriptor close-on-exec flag.
105const F_GETFD: u64 = 1;
106const F_SETFD: u64 = 2;
107// Only bit 0 has a defined meaning for these commands. Mask all other argument bits.
108const FD_CLOEXEC: u64 = 1;
109
110// todo -- wird das überhaupt gebraucht
111// siehe https://docs.rs/auxv/latest/auxv/
112// Linux puts these type numbers in the auxiliary vector on the first process stack.
113// The dynamic loader and C library use the values to find loader information without
114// parsing the executable again.
115// Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/auxvec.h
116// Consumer reference: https://man7.org/linux/man-pages/man3/getauxval.3.html
117// Type 0 ends the vector. Its value field has no meaning.
118const AT_NULL: u64 = 0;
119// Types 3 through 5 give the address, entry size, and count of program headers.
120const AT_PHDR: u64 = 3;
121const AT_PHENT: u64 = 4;
122const AT_PHNUM: u64 = 5;
123// Type 6 tells libc which page size the emulated MMU uses.
124const AT_PAGESZ: u64 = 6;
125// Type 7 is the load base of the ELF interpreter. It is zero for a static process.
126const AT_BASE: u64 = 7;
127// Type 8 carries process-entry flags. This runtime supplies zero.
128const AT_FLAGS: u64 = 8;
129// Type 9 is the executable entry. It is not the interpreter entry.
130const AT_ENTRY: u64 = 9;
131// Types 11 through 14 supply real and effective user and group IDs.
132const AT_UID: u64 = 11;
133const AT_EUID: u64 = 12;
134const AT_GID: u64 = 13;
135const AT_EGID: u64 = 14;
136// Type 16 reports the RISC-V ISA letters that this runtime chooses to advertise.
137const AT_HWCAP: u64 = 16;
138// Type 17 gives the number of `times()` clock ticks in one second.
139const AT_CLKTCK: u64 = 17;
140// Type 23 tells libc if secure execution rules must apply. Zero means normal execution.
141const AT_SECURE: u64 = 23;
142// Type 25 points to exactly 16 seed bytes on the initial stack.
143const AT_RANDOM: u64 = 25;
144// Type 26 is a second architecture capability word. This runtime has no values for it.
145const AT_HWCAP2: u64 = 26;
146// Type 31 points to the pathname used to execute the file.
147// This runtime approximates that pathname with the first argument string.
148const AT_EXECFN: u64 = 31;
149
150// Linux assigns one HWCAP bit to each ISA letter at position `letter - 'A'`.
151// These shifts set A=0, C=2, D=3, F=5, I=8, and M=12. OR combines the six bits.
152// This is a fixed advertised IMAFDC word. It is not derived from decoder coverage;
153// atomics and parts of floating-point support are still incomplete.
154// Source: https://github.com/torvalds/linux/blob/master/arch/riscv/include/uapi/asm/hwcap.h
155const RISCV_HWCAP: u64 = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5) | (1 << 8) | (1 << 12);
156
157// Linux gives AT_RANDOM a pointer to 16 bytes. Fixed bytes make guest startup and
158// snapshots repeatable. They are not suitable for security or cryptography.
159// Source: https://github.com/torvalds/linux/blob/master/fs/binfmt_elf.c
160const ELF_AUX_RANDOM: [u8; 16] = *b"moin-liebe-leute";
161
162// https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/boxing_errors.html
163// ELF, file, memory, and compiler operations return different error types. A boxed
164// error lets one public result type carry all of them without a large local error enum.
165type JitResult<T> = Result<T, Box<dyn Error>>;
166// Generated libraries expose one C-ABI function. It is unsafe because Rust cannot
167// prove that the raw pointer is valid or that both libraries use the same layout.
168pub(crate) type ExecuteFn = unsafe extern "C" fn(*mut GuestState) -> ExitReason;
169
170// Each auxiliary-vector record contains one 64-bit type and one 64-bit value on RV64.
171type AuxEntry = (u64, u64);
172
173#[repr(u8)]
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
175/// Distinguishes fault types so the runtime can report the failed guest operation.
176pub enum AccessKind {
177    // Values 0 through 3 are enum tags shared with generated code. They are not permission bits.
178    // Zero means that no memory fault is pending.
179    /// Reports that no memory access fault is pending.
180    None = 0,
181    // Nonzero values keep read, write, and instruction-fetch failures separate.
182    /// Reports a failed guest data read.
183    Read = 1,
184    /// Reports a failed guest data write.
185    Write = 2,
186    /// Reports a failed guest instruction fetch.
187    Execute = 3,
188}
189
190// C ABI kompatibiliät todo muss das?
191// ExitReason von gamozo!
192#[repr(C)]
193#[derive(Clone, Copy, Debug, PartialEq, Eq)]
194/// Transfers control from generated code to the runtime without unwinding across the ABI.
195pub enum ExitReason {
196    // The order is part of the C ABI that generated libraries copy. Do not reorder it
197    // unless the generated definition changes at the same time.
198    // Implicit discriminants start at zero and increase by one.
199    /// Reports that generated code has not selected a stop reason.
200    None,
201    // The generated block could not find the next guest PC in its dispatch table.
202    /// Returns an uncached computed target to the runtime for compilation or dispatch.
203    IndirectBranch,
204    // These values return detailed MMU failures without a host signal or Rust panic.
205    /// Reports that a guest load did not have a readable mapping.
206    ReadFault,
207    /// Reports that a guest store did not have a writable mapping.
208    WriteFault,
209    /// Reports that instruction fetch did not have an executable mapping.
210    ExecFault,
211    // A host call has no runtime implementation, so the caller can decide what to do.
212    /// Reports an NNIL host call that the runtime does not implement.
213    UnhandledHostCall,
214    // GuestExit is separate from Syscall so normal process exit is easy to identify.
215    /// Reports normal termination through the guest `exit` system call.
216    GuestExit,
217    /// Returns a guest environment call to the runtime system-call handler.
218    Syscall,
219    /// Returns a guest breakpoint instruction to the runtime.
220    Sysbreak,
221    // These stops support the interactive debugger and bounded execution.
222    /// Stops before a translated block at a configured guest address.
223    Breakpoint,
224    /// Stops after another thread sets the shared pause flag.
225    Paused,
226    /// Stops after a bounded run uses its translated-block budget.
227    BlockLimit,
228}
229
230// todo - fuzzer api klären
231#[repr(C)]
232/// Keeps debugger checks optional so normal generated blocks do not call back into the runtime.
233pub struct GuestControl {
234    // A raw pointer avoids an `Arc` in the generated ABI. The runtime owns the `Arc`.
235    pause: *const AtomicBool,
236    // Fixed arrays make the layout stable across the host and generated library.
237    breakpoints: [u64; DEBUG_SLOTS],
238    // Only this sorted prefix contains active breakpoints.
239    breakpoint_count: usize,
240    // `u64::MAX` is the no-skip sentinel. A breakpoint at that address conflicts with it.
241    skip_breakpoint: u64,
242    // Generated blocks reduce this value when bounded execution is active.
243    blocks_left: u64,
244    // Use one byte as an ABI flag. Zero is false and any nonzero value is true.
245    limited: u8,
246    // This array is a ring. `trace_pos` can increase after the array becomes full.
247    trace: [u64; DEBUG_SLOTS],
248    trace_pos: usize,
249}
250
251#[repr(C)]
252#[derive(Clone, Copy)]
253/// Pairs a guest target with native code for indirect dispatch from generated code.
254pub struct JitEntryAbi {
255    // The vector is sorted by this key so generated code can use binary search.
256    /// Gives the guest block-entry address used as the sorted lookup key.
257    pub guest_pc: u64,
258    // This pointer enters the compiled block that starts at `guest_pc`.
259    /// Calls native code that starts at `guest_pc`.
260    pub execute: ExecuteFn,
261}
262
263#[repr(C)]
264#[derive(Clone)]
265/// Uses one shared layout because both the runtime and generated libraries access guest state.
266pub struct GuestState {
267    // Generated code writes the reason before it gives control back to the runtime.
268    /// Tells the runtime why generated code returned.
269    pub exit_reason: ExitReason,
270    // The PC is a guest virtual byte address, not a host function address.
271    /// Gives the current guest virtual instruction address.
272    pub pc: u64,
273    // Registers store raw bits. Slots 0 through 31 are integer x0 through x31.
274    // Floating-point register f0 starts at slot 32.
275    /// Stores raw integer and floating-point register bits in the shared ABI layout.
276    pub regs: [u64; GUEST_REG_COUNT],
277    // NNIL temporaries live only for translated calculations but share block exits.
278    /// Stores NNIL temporary values that must remain available across generated functions.
279    pub tmps: [u64; GUEST_TMP_COUNT],
280    // Generated code uses this MMU view because it cannot borrow `GuestMemory`.
281    /// Points to the MMU view that generated code can use without Rust containers.
282    pub memory: *mut GuestMemoryAbi, // todo siehe txt
283    // These fields identify the guest operation that failed. The runtime can then
284    // report an emulated fault instead of a host segmentation fault.
285    /// Gives the guest address of the last failed memory access.
286    pub fault_addr: u64,
287    /// Identifies the operation that failed at `fault_addr`.
288    pub fault_access: AccessKind,
289    // A null pointer selects fast execution without debugger checks.
290    /// Points to optional debugger controls, or is null for unrestricted execution.
291    pub control: *mut GuestControl,
292    // This borrowed sorted array lets compiled blocks call other compiled blocks.
293    /// Points to sorted cross-artifact dispatch entries.
294    pub jit_entries: *const JitEntryAbi,
295    /// Gives the valid length of `jit_entries`.
296    pub jit_entry_count: usize,
297}
298
299impl Default for GuestState {
300    /// Starts with no active guest operation and no borrowed ABI pointers.
301    /// The runtime installs each pointer only after it owns the target allocation.
302    fn default() -> Self {
303        Self {
304            exit_reason: ExitReason::None,
305            pc: 0,
306            regs: [0; GUEST_REG_COUNT],
307            tmps: [0; GUEST_TMP_COUNT],
308            memory: std::ptr::null_mut(),
309            fault_addr: 0,
310            fault_access: AccessKind::None,
311            control: std::ptr::null_mut(),
312            jit_entries: std::ptr::null(),
313            jit_entry_count: 0,
314        }
315    }
316}
317
318// todo -- refactor nach remu über cli flags setzen lassen
319#[derive(Debug, Clone)]
320/// Keeps process and compiler inputs together so snapshots and cache behavior are reproducible.
321pub struct RemuConfig {
322    // These strings become guest-owned NUL-terminated data on the initial stack.
323    /// Supplies guest argument strings for the initial process stack.
324    pub argv: Vec<String>,
325    /// Supplies guest environment strings for the initial process stack.
326    pub envp: Vec<String>,
327    // Absolute guest paths resolve below `sysroot`. Syscall policy normally treats this tree
328    // as read-only. The openat subset documents one flag-combination limit.
329    /// Selects the read-only host root for absolute guest paths.
330    pub sysroot: Option<PathBuf>,
331    // Relative guest paths resolve below `workdir`. Guest file changes stay here.
332    /// Selects the writable host root for relative guest paths.
333    pub workdir: Option<PathBuf>,
334    // This value goes to rustc as `opt-level`. The runtime accepts only 0, 1, or 2
335    // to limit compile time during lazy translation.
336    /// Selects Rust compiler optimization level 0, 1, or 2 for generated code.
337    pub jit_opt_level: u8,
338    // A disk cache can reuse generated source and machine code across runtime instances.
339    /// Selects an optional directory for reusable generated artifacts.
340    pub jit_cache_dir: Option<PathBuf>,
341    // Raw JIT output uses the custom ELF mapper and the Linux x86-64 memory-map ABI.
342    /// Requests the Linux x86-64 raw loader instead of a shared library.
343    pub raw_jit: bool, // todo - so wie gamozolabs - nur linux64
344}
345
346impl Default for RemuConfig {
347    /// Selects repeatable compiler and cache settings for callers that need no guest I/O roots.
348    fn default() -> Self {
349        Self {
350            argv: Vec::new(),
351            envp: Vec::new(),
352            sysroot: None,
353            workdir: None,
354            jit_opt_level: 2,
355            jit_cache_dir: Some(PathBuf::from("jit-tmp/cache")),
356            raw_jit: false,
357        }
358    }
359}
360
361// todo - nm, readelf
362#[derive(Debug, Clone)]
363/// Keeps file and memory sizes separate because ELF load segments can contain zero-filled BSS.
364pub struct LoadSegment {
365    // ELF virtual addresses are guest addresses. They never point into host memory.
366    /// Gives the first guest virtual address of the segment.
367    pub vaddr: u64,
368    // `mem_size` includes file bytes and the zero-filled tail, such as `.bss`.
369    /// Gives the total mapped size, including the zero-filled tail.
370    pub mem_size: u64,
371    // `file_size` limits the bytes that the loader copies from the ELF file.
372    /// Gives the number of initialized bytes copied from the file.
373    pub file_size: u64,
374    // The MMU enforces the R, W, and X flags after it maps the segment.
375    /// Gives the guest access permissions from the ELF program header.
376    pub perms: PagePerms,
377    // Keep an owned copy because the input byte slice can go out of scope.
378    /// Owns the initialized file bytes after the source ELF buffer is gone.
379    pub bytes: Vec<u8>,
380}
381
382#[derive(Debug, Clone)]
383/// Retains symbol metadata for entry selection and debugger address display.
384pub struct ProgramSymbol {
385    // A copied name makes symbol lookup independent from the ELF string-table lifetime.
386    /// Owns the symbol name from its ELF string table.
387    pub name: String,
388    // This is the guest symbol address for an executable image.
389    /// Gives the symbol guest address.
390    pub value: u64,
391    // A zero size is valid. Some assembler and linker symbols have no object extent.
392    /// Gives the symbol extent, or zero when ELF does not supply one.
393    pub size: u64,
394    // Index zero is ELF `SHN_UNDEF`. Dynamic imports commonly use that index.
395    /// Gives the defining ELF section index; zero means undefined.
396    pub section_index: u16,
397}
398
399impl ProgramSymbol {
400    /// Rejects undefined table entries when the debugger resolves an address.
401    pub fn is_defined(&self) -> bool {
402        self.section_index != 0
403    }
404}
405
406#[derive(Debug, Clone)]
407/// Retains jump-slot locations so dynamic imports can be connected later.
408pub struct ImportedSymbol {
409    // The name lets a later shim select the host service.
410    /// Owns the imported symbol name used to select a future shim.
411    pub name: String,
412    // A jump-slot relocation writes the resolved function address at this GOT address.
413    /// Gives the guest GOT slot that receives the resolved address.
414    pub got_addr: u64,
415}
416
417#[derive(Debug, Clone)]
418/// Stores parsed ELF data in a form that the MMU can map without parsing again.
419pub struct ProgramImage {
420    // ELF type 2 is an executable. Type 3 is a shared object or a PIE executable.
421    /// Gives the ELF object type, such as `ET_EXEC` or `ET_DYN`.
422    pub elf_type: u16,
423    // Start execution here unless PT_INTERP makes the runtime start an interpreter first.
424    /// Gives the main executable entry address before interpreter selection.
425    pub entry_pc: u64,
426    // Program headers, not sections, define the data that Linux maps into a process.
427    /// Owns the loadable segments that define the initial process image.
428    pub segments: Vec<LoadSegment>,
429    // A tree map gives stable lookup and iteration order for debugger output.
430    /// Maps each retained ELF symbol name to its metadata.
431    pub symbols: BTreeMap<String, ProgramSymbol>,
432    /// Lists RISC-V jump-slot relocations for future import connection.
433    pub imports: Vec<ImportedSymbol>,
434    // `PT_INTERP` names the guest dynamic loader, such as `/lib/ld-linux-riscv64-lp64d.so.1`.
435    /// Names the guest dynamic loader from `PT_INTERP`, when present.
436    pub interp: Option<String>,
437    // The initial break follows the highest load segment so the heap does not cover ELF data.
438    /// Gives the page-aligned initial heap end after all load segments.
439    pub brk_base: u64,              // initialer heap start
440    // Linux passes these three values in AT_PHDR, AT_PHENT, and AT_PHNUM.
441    /// Gives the mapped program-header address used for `AT_PHDR`.
442    pub elf_phdr_addr: Option<u64>, // todo
443    /// Gives one program-header entry size used for `AT_PHENT`.
444    pub elf_phent_size: u16,        // todo
445    /// Gives the program-header count used for `AT_PHNUM`.
446    pub elf_phnum: u16,             // todo
447}
448
449impl ProgramImage {
450    /// Builds a synthetic image from caller-supplied segments.
451    pub fn from_segments(entry_pc: u64, mut segments: Vec<LoadSegment>) -> Self {
452        // Sorted segments make memory mapping and free-range behavior deterministic.
453        segments.sort_by_key(|segment| segment.vaddr);
454
455        // todo -- heap
456        // Round every segment end up to 4 KiB because `brk` maps and unmaps complete pages.
457        // Saturating addition limits the unrounded end. `align_up` still assumes that the
458        // rounded result fits in u64.
459        let brk_base = segments
460            .iter()
461            .map(|segment| {
462                align_up(
463                    segment.vaddr.saturating_add(segment.mem_size),
464                    PAGE_SIZE as u64,
465                )
466            })
467            .max()
468            .unwrap_or(0);
469
470        Self {
471            // Value 2 is ELF `ET_EXEC`. Synthetic images have fixed guest addresses.
472            elf_type: 2,
473            entry_pc,
474            segments,
475            symbols: BTreeMap::new(),
476            imports: Vec::new(),
477            interp: None,
478            brk_base,
479            elf_phdr_addr: None,
480            elf_phent_size: 0,
481            elf_phnum: 0,
482        }
483    }
484
485    // todo !!!
486    /// Accepts only RISC-V ELF64 little-endian input because the current process ABI is RV64.
487    pub fn from_elf_bytes(bytes: &[u8]) -> JitResult<Self> {
488        // The generic ELF ABI assigns machine number 243 to RISC-V.
489        // Source: https://gabi.xinuos.com/elf/a-emachine.html
490        const EM_RISCV: u16 = 243; // unnötig (?)
491
492        // Program-header types tell a process loader what it must map or inspect.
493        // `PT_LOAD` maps bytes. `PT_DYNAMIC` describes dynamic linking. `PT_INTERP`
494        // names that linker. `PT_PHDR` gives the in-memory header-table address.
495        // Source: https://gabi.xinuos.com/elf/07-pheader.html
496        const PT_LOAD: u32 = 1;
497        const PT_INTERP: u32 = 3;
498        const PT_DYNAMIC: u32 = 2;
499        const PT_PHDR: u32 = 6;
500
501        // ELF program-header permissions use bit 0 for execute, bit 1 for write,
502        // and bit 2 for read. This differs from Linux `PROT_*`, which uses R=1,
503        // W=2, and X=4. Each AND test isolates one permission bit and ignores other flags.
504        const PF_X: u32 = 1;
505        const PF_W: u32 = 2;
506        const PF_R: u32 = 4;
507
508        // These section types are the only ones needed for names and jump-slot imports.
509        // `SHT_SYMTAB` is the full link symbol table. `SHT_DYNSYM` is the smaller table
510        // used for dynamic linking. `SHT_RELA` stores relocations with explicit addends.
511        // Source: https://gabi.xinuos.com/elf/03-sheader.html
512        const SHT_RELA: u32 = 4;
513        const SHT_SYMTAB: u32 = 2;
514        const SHT_DYNSYM: u32 = 11;
515
516        // RISC-V relocation type 5 connects one PLT entry to its GOT function slot.
517        // Source: https://riscv-non-isa.github.io/riscv-elf-psabi-doc/#relocations
518        const R_RISCV_JUMP_SLOT: u32 = 5;
519
520        //  const ELF_MAGIC: &[u8; 4] = b"\x7fELF";
521        // const ELFCLASS64: u8 = 2;
522        // const ELFDATA2LSB: u8 = 1;
523        /* if bytes.len() < 64 || &bytes[..4] != ELF_MAGIC {
524            return Err(io::Error::other("falsch").into());
525        }
526        if bytes[4] != ELFCLASS64 {
527            return Err(io::Error::other("falsch").into());
528        }
529        if bytes[5] != ELFDATA2LSB {
530            return Err(io::Error::other("falsch").into());
531        } */
532
533        // Check the magic, class, and byte order before fixed-offset reads interpret the input.
534        // This parser does not validate all ELF identity and header-version fields.
535        // Bytes 0..4 are 0x7f and `ELF`. Byte 4 is class 2 for ELF64. Byte 5 is data
536        // encoding 1 for little endian. All later integer reads depend on these values.
537        // Source: https://gabi.xinuos.com/elf/02-eheader.html
538        if bytes.get(..6) != Some(b"\x7fELF\x02\x01") {
539            return Err(io::Error::other("expected a little-endian ELF64 file").into());
540        }
541
542        // The first 16 bytes are `e_ident`. Thus, `e_type` starts at byte 16 and
543        // `e_machine` starts at byte 18 in an ELF64 header.
544        let elf_type = read_u16(bytes, 16)?;
545        let march = read_u16(bytes, 18)?;
546
547        if march != EM_RISCV {
548            return Err(io::Error::other(format!("nicht riscv???? -- sondern: {}", march)).into());
549        }
550
551        // ELF64 aligns the next 64-bit fields to byte 24. `e_entry` is the guest entry.
552        let entry_pc = read_u64(bytes, 24)?;
553
554        // Bytes 32 and 40 hold the program-header and section-header table offsets.
555        // Bytes 54 through 60 give table strides and counts. Fixed reads still require
556        // program entries of at least 56 bytes and section entries of at least 64 bytes,
557        // but this parser does not validate those minima. It also lacks extended counts.
558        let phoff_u64 = read_u64(bytes, 32)?;
559        let phoff = phoff_u64 as usize;
560        let phentsize_u16 = read_u16(bytes, 54)?;
561        let phentsize = phentsize_u16 as usize;
562        let phnum_u16 = read_u16(bytes, 56)?;
563        let phnum = phnum_u16 as usize;
564
565        let shoff = read_u64(bytes, 40)? as usize;
566        let shentsize = read_u16(bytes, 58)? as usize;
567        let shnum = read_u16(bytes, 60)? as usize;
568
569        let mut segments = Vec::new();
570        let mut interp = None;
571        let mut elf_phdr_addr = None;
572
573        // Some ELF files have no `PT_PHDR`. Keep a derived address as a fallback for AT_PHDR.
574        let mut dephdra = None;
575
576        for idx in 0..phnum {
577            // Each index selects one file-defined entry. Checked addition rejects address
578            // wrap. Saturating multiplication cannot wrap before the checked addition.
579            let header = phoff
580                .checked_add(idx.saturating_mul(phentsize))
581                .ok_or_else(|| io::Error::other("program header overflow"))?;
582
583            // An ELF64 program header is 56 bytes. The field offsets are 0 for type,
584            // 4 for flags, 8 for file offset, 16 for virtual address, 32 for file size,
585            // and 40 for memory size. Physical address and alignment are not needed here.
586            let p_type = read_u32(bytes, header)?;
587            let p_flags = read_u32(bytes, header + 4)?;
588            let p_offset_u64 = read_u64(bytes, header + 8)?;
589            let p_offset = p_offset_u64 as usize;
590            let p_vaddr = read_u64(bytes, header + 16)?;
591            let p_filesz = read_u64(bytes, header + 32)?;
592            let p_memsz = read_u64(bytes, header + 40)?;
593
594            // dbg!(header);
595            // dbg!(p_type);
596
597            // todo --
598            match p_type {
599                PT_LOAD => {
600                    if dephdra.is_none() {
601                        let Some(segment_end) = p_offset_u64.checked_add(p_filesz) else {
602                            return Err(io::Error::other("overflow").into());
603                        };
604
605                        // Derive AT_PHDR when ELF omits PT_PHDR and maps the table start.
606                        // This check does not prove that the complete header table is mapped.
607                        // Convert a file offset to a guest address with the containing
608                        // segment relation `p_vaddr + (e_phoff - p_offset)`.
609                        if phoff_u64 >= p_offset_u64 && phoff_u64 < segment_end {
610                            let delta = phoff_u64 - p_offset_u64;
611
612                            dephdra = Some(
613                                p_vaddr
614                                    .checked_add(delta)
615                                    .ok_or_else(|| io::Error::other("overflow"))?,
616                            );
617                        }
618                    }
619
620                    if p_filesz > p_memsz {
621                        // A load segment can add a zero-filled memory tail, but it cannot
622                        // place more file bytes in less memory.
623                        return Err(io::Error::other("overflow").into());
624                    }
625
626                    let end = p_offset
627                        .checked_add(p_filesz as usize)
628                        .ok_or_else(|| io::Error::other("overflow"))?;
629
630                    let file_bytes = bytes
631                        .get(p_offset..end)
632                        .ok_or_else(|| io::Error::other("overflow"))?;
633
634                    // Preserve ELF permissions so guest fetches and data accesses fault correctly.
635                    // Bitwise AND asks if one ELF flag is set. Bitwise OR adds the matching
636                    // internal permission without removing permissions that were added before it.
637                    let mut perms = PagePerms::empty();
638
639                    if (p_flags & PF_R) != 0 {
640                        perms |= PagePerms::READ;
641                    }
642                    if (p_flags & PF_W) != 0 {
643                        perms |= PagePerms::WRITE;
644                    }
645                    if (p_flags & PF_X) != 0 {
646                        perms |= PagePerms::EXEC;
647                    }
648
649                    segments.push(LoadSegment {
650                        vaddr: p_vaddr,
651                        mem_size: p_memsz,
652                        file_size: p_filesz,
653                        perms,
654                        bytes: file_bytes.to_vec(),
655                    });
656                }
657
658                PT_INTERP => {
659                    // ELF requires a trailing NUL byte in this file range.
660                    let end = p_offset
661                        .checked_add(p_filesz as usize)
662                        .ok_or_else(|| io::Error::other("interp overflow"))?;
663
664                    let raw = bytes
665                        .get(p_offset..end)
666                        .ok_or_else(|| io::Error::other("interp overflow"))?;
667
668                    // Use bytes before the first NUL. A missing terminator is not rejected here.
669                    let path = raw.split(|byte| *byte == 0).next().unwrap_or_default();
670                    interp = Some(String::from_utf8_lossy(path).into_owned());
671                }
672
673                // The guest interpreter processes dynamic metadata after it starts.
674                // This parser does no separate work for PT_DYNAMIC and relies on overlapping
675                // PT_LOAD bytes when the ELF file supplies them.
676                PT_DYNAMIC => {}
677                PT_PHDR => {
678                    // `p_vaddr` is already the in-memory address that AT_PHDR requires.
679                    elf_phdr_addr = Some(p_vaddr);
680                }
681
682                _ => {
683                    // dbg!("?");
684                }
685            }
686        }
687
688        let elf_phdr_addr = elf_phdr_addr
689            .or(dephdra)
690            .ok_or_else(|| io::Error::other("elf headers nicht da"))?;
691
692        // Section headers are not required for process mapping. They are parsed only to
693        // support symbol names, entry selection, debugger output, and import discovery.
694        let sections = parse_section_headers(bytes, shoff, shentsize, shnum)?;
695        let mut symbols = BTreeMap::<String, ProgramSymbol>::new();
696        let mut symbol_sections = BTreeMap::<usize, Vec<ProgramSymbol>>::new();
697
698        for (section_idx, section) in sections.iter().enumerate() {
699            if matches!(section.sh_type, SHT_SYMTAB | SHT_DYNSYM) {
700                let parsed = parse_symbol_section(bytes, &sections, section)?;
701
702                // passt noch nicht todo
703                for symbol in &parsed {
704                    if symbol.name.is_empty() {
705                        continue;
706                    }
707
708                    // Prefer defined, nonzero symbols because debug and entry lookup need addresses.
709                    let replace = match symbols.get(&symbol.name) {
710                        Some(existing) => {
711                            (symbol.is_defined() && !existing.is_defined())
712                                || (symbol.is_defined() && existing.value == 0 && symbol.value != 0)
713                                || (!existing.is_defined()
714                                    && symbol.value != 0
715                                    && existing.value == 0)
716                        }
717                        None => true,
718                    };
719
720                    if replace {
721                        symbols.insert(symbol.name.clone(), symbol.clone());
722                    }
723                }
724
725                symbol_sections.insert(section_idx, parsed);
726            }
727        }
728
729        let mut imports = Vec::new();
730
731        for section in &sections {
732            if section.sh_type != SHT_RELA || section.sh_entsize == 0 {
733                // A zero entry size cannot be the divisor for a relocation count.
734                continue;
735            }
736
737            let symbol_table_index = section.sh_link as usize;
738            let Some(section_symbols) = symbol_sections.get(&symbol_table_index) else {
739                continue;
740            };
741
742            // ELF64 Rela records are 24 bytes, but this parser checks only for a nonzero
743            // entry size and reads the first 16 bytes. A smaller declared size is not rejected.
744            // Integer division also ignores incomplete trailing bytes. These are parser limits.
745            let count = (section.sh_size / section.sh_entsize) as usize;
746            //let count = 0;
747
748            for idx in 0..count {
749                let rela_offset = section
750                    .sh_offset
751                    .checked_add((idx as u64).saturating_mul(section.sh_entsize))
752                    .ok_or_else(|| io::Error::other("overflow"))?
753                    as usize;
754
755                // In an ELF64 `Rela` record, `r_offset` starts at byte 0 and `r_info`
756                // starts at byte 8. The explicit addend at byte 16 is not needed to find imports.
757                let r_offset = read_u64(bytes, rela_offset)?;
758                let r_info = read_u64(bytes, rela_offset + 8)?;
759                // ELF64 packs the relocation type in the low 32 bits. A cast to `u32`
760                // discards the high symbol-index bits and keeps this type field.
761                let r_type = r_info as u32;
762
763                // Only jump slots identify external function calls that the runtime can shim.
764                if r_type != R_RISCV_JUMP_SLOT {
765                    continue;
766                }
767
768                // Shift right by 32 to remove the type field and move the symbol-table
769                // index into the low bits. This follows the ELF64 `ELF64_R_SYM` rule.
770                let symbol_idx = (r_info >> 32) as usize;
771
772                let Some(symbol) = section_symbols.get(symbol_idx) else {
773                    return Err(io::Error::other("missing symbol ").into());
774                };
775
776                if symbol.name.is_empty() {
777                    continue;
778                }
779
780                imports.push(ImportedSymbol {
781                    name: symbol.name.clone(),
782                    got_addr: r_offset,
783                });
784            }
785        }
786
787        // Sort by GOT address for deterministic grouping. `dedup_by` removes only adjacent
788        // exact duplicates; different names at one address can keep equal pairs apart.
789        imports.sort_by_key(|symbol| symbol.got_addr);
790        imports.dedup_by(|lhs, rhs| lhs.got_addr == rhs.got_addr && lhs.name == rhs.name);
791
792        //dbg!(imports);
793
794        Ok(Self {
795            elf_type,
796            // The first page after the highest memory segment is the initial heap break.
797            // Saturating addition limits the unrounded end. `align_up` assumes that its
798            // rounded result fits in u64.
799            brk_base: segments
800                .iter()
801                .map(|segment| {
802                    align_up(
803                        segment.vaddr.saturating_add(segment.mem_size),
804                        PAGE_SIZE as u64,
805                    )
806                })
807                .max()
808                .unwrap_or(0),
809            entry_pc,
810            segments,
811            symbols,
812            imports,
813            interp,
814            elf_phdr_addr: Some(elf_phdr_addr),
815            elf_phent_size: phentsize_u16,
816            elf_phnum: phnum_u16,
817        })
818    }
819
820    // siehe trait
821    /// Limits lifting to file-backed executable bytes so data and BSS are not decoded as code.
822    pub fn exec_region_containing(&self, pc: u64) -> Option<(u64, &[u8])> {
823        self.segments.iter().find_map(|segment| {
824            // Checked addition rejects a malformed segment whose file range wraps.
825            let end = segment.vaddr.checked_add(segment.file_size)?;
826
827            if segment.perms.contains(PagePerms::EXEC) && pc >= segment.vaddr && pc < end {
828                Some((segment.vaddr, segment.bytes.as_slice()))
829            } else {
830                None
831            }
832        })
833    }
834
835    /// Finds one symbol by its exact ELF name for entry and register setup.
836    pub fn find_symbol(&self, name: &str) -> Option<&ProgramSymbol> {
837        self.symbols.get(name)
838    }
839
840    /// Finds the closest defined symbol at or below a mapped guest address.
841    pub fn symbol_at(&self, address: u64) -> Option<&ProgramSymbol> {
842        // Reject unmapped addresses before selecting the nearest preceding symbol.
843        if !self.segments.iter().any(|segment| {
844            address >= segment.vaddr && address < segment.vaddr.saturating_add(segment.mem_size)
845        }) {
846            return None;
847        }
848        // A symbol size can be zero or missing. Use the nearest preceding value so the
849        // debugger can still label addresses inside assembler and linker symbols.
850        self.symbols
851            .values()
852            .filter(|symbol| symbol.is_defined() && symbol.value <= address)
853            .max_by_key(|symbol| symbol.value)
854    }
855
856    /// Borrows sorted jump-slot imports without exposing the owned vector for changes.
857    pub fn imports(&self) -> &[ImportedSymbol] {
858        &self.imports
859    }
860}
861
862struct GuestFile {
863    // The host file supplies storage, but the descriptor number and flags belong to the guest.
864    file: File,
865    // Keep `FD_CLOEXEC` outside the host file because only a guest `exec` would use it.
866    fd_flags: u64,
867}
868
869// todo - fuzz api später - erstmal provisorisch
870struct SnapshotFile {
871    // A cloned handle keeps the open file alive when the active guest closes its descriptor.
872    file: File,
873    // File data is external to guest memory, so save the stream position explicitly.
874    offset: u64,
875    fd_flags: u64,
876}
877
878/// Saves mutable process data while leaving compiled code available for reuse after restore.
879pub struct RuntimeSnapshot {
880    // The baseline includes mappings, bytes, permissions, stack bounds, and heap state.
881    memory: GuestMemory,
882    // Raw ABI pointers are cleared before this value is stored.
883    state: GuestState,
884    // Guest descriptor numbers must remain stable after restore.
885    files: BTreeMap<i32, SnapshotFile>,
886    next_fd: i32,
887    // A restored allocation sequence must start its next search at the same address.
888    mmap_base: u64,
889}
890
891/// Uses a lifting callback so this crate does not depend on a specific guest architecture crate.
892pub struct JitRuntime<L>
893where
894    L: Fn(&GuestMemory, u64) -> JitResult<Nnil>,
895{
896    // Keep parsed executable metadata for symbols, startup data, and debugger queries.
897    program: ProgramImage,
898    // Keep normalized path roots and compiler settings for every later guest operation.
899    process: RemuConfig,
900    // A box gives the MMU owner a stable address while generated code has raw ABI pointers.
901    memory: Box<GuestMemory>,
902
903    // The key is a guest block-entry PC. A tree map keeps keys sorted for ABI dispatch.
904    cache: BTreeMap<u64, CompiledJit>,
905    // This dense mirror has only the C-compatible data that generated code needs.
906    jit_entries: Vec<JitEntryAbi>,
907
908    // Dynamic programs start at the loader entry. `program.entry_pc` stays the main entry.
909    initial_pc: u64,
910    // Zero means a static process. A dynamic process uses the fixed interpreter bias.
911    loader_base: u64,
912    // This is the register and stop-state object shared with native blocks.
913    state: GuestState,
914    // Guest descriptors 0, 1, and 2 map directly to host standard streams. This map starts at 3.
915    files: BTreeMap<i32, GuestFile>,
916    // Monotonic allocation makes the next descriptor stable after snapshot restore.
917    next_fd: i32,
918    // This cursor separates automatic mappings from the growing heap.
919    mmap_base: u64,
920    // Generated source and compiler products go here for inspection and loading.
921    jit_output_dir: PathBuf,
922    // Keep diagnostic work out of the fast path unless the caller requests it.
923    verbose: bool,
924
925    // todo - mehr verbose stats später
926    // todo - fuzz logs?
927    // These counters measure JIT cost but do not affect guest-visible state.
928    compile_count: usize,
929    compile_time: Duration,
930    execute_time: Duration,
931    // Boxes keep raw control pointers stable even when the runtime value moves.
932    control: Box<GuestControl>,
933    // The `Arc` keeps the atomic flag alive while another thread requests a pause.
934    pause: Arc<AtomicBool>,
935    // `None` is used only during construction and while `reset` moves the value out.
936    initial_snapshot: Option<RuntimeSnapshot>,
937
938    // Eight words hold syscall number, PC, and argument registers a0 through a5.
939    last_syscall: Option<[u64; 8]>,
940    // The callback changes guest bytes at one PC into one NNIL region.
941    lift_region: L,
942}
943
944impl<L> JitRuntime<L>
945where
946    L: Fn(&GuestMemory, u64) -> JitResult<Nnil>,
947{
948    /// Creates a runtime with default guest-process settings and the local output directory.
949    pub fn new(program: ProgramImage, lift_region: L) -> JitResult<Self> {
950        // todo - default path
951        Self::init_runtime(
952            program,
953            RemuConfig::default(),
954            Path::new("output").to_path_buf(),
955            lift_region,
956        )
957    }
958
959    /// Maps the program and optional interpreter, then builds Linux process startup state.
960    pub fn init_runtime(
961        program: ProgramImage,
962        mut process: RemuConfig,
963        jit_output_dir: PathBuf,
964        lift_region: L,
965    ) -> JitResult<Self> {
966        // todo - doku schreiben
967        // Reject unsupported settings before files, mappings, or snapshots are created.
968        if process.jit_opt_level > 2 {
969            return Err(io::Error::other("JIT optimization level ist entweder 0, 1 oder 2").into());
970        }
971        // `cfg!` is a compile-time host check. Raw artifacts contain x86-64 Linux details,
972        // so another host cannot safely load them.
973        if process.raw_jit && !cfg!(all(target_os = "linux", target_arch = "x86_64")) {
974            return Err(io::Error::other("--raw-jit läuft nur auf Linux x86-64!!").into());
975        }
976        if let Some(cache_dir) = &process.jit_cache_dir {
977            fs::create_dir_all(cache_dir)?;
978        }
979
980        // Map the main executable before the interpreter so overlap checks include it.
981        let mut memory = Box::new(GuestMemory::from_program(&program)?);
982
983        // Canonical roots make later path checks reliable.
984        if let Some(sysroot) = &process.sysroot {
985            process.sysroot = Some(fs::canonicalize(sysroot).map_err(|err| {
986                io::Error::other(format!(
987                    "cannot open sysroot in {}: {err}",
988                    sysroot.display()
989                ))
990            })?);
991        }
992
993        // todo - workdir wirklich gebraucht?
994        // Use the host current directory only when the caller gives no writable guest root.
995        let workdir = process.workdir.clone().unwrap_or(std::env::current_dir()?);
996        let workdir = fs::canonicalize(&workdir).map_err(|err| {
997            io::Error::other(format!(
998                "cannot open workdir in {}: {err}",
999                workdir.display()
1000            ))
1001        })?;
1002        if !workdir.is_dir() {
1003            return Err(io::Error::other("workdir is not a directory").into());
1004        }
1005        process.workdir = Some(workdir);
1006
1007        // dbg!(process);
1008
1009        let mut loader_base = 0;
1010        let mut initial_pc = program.entry_pc;
1011
1012        if let Some(interp) = &program.interp {
1013            // `PT_INTERP` makes this a dynamic process. The named file is a guest absolute
1014            // path, so it must come from the configured sysroot and not the host root.
1015            let sysroot = process.sysroot.as_ref().ok_or_else(|| {
1016                io::Error::other(format!(
1017                    "WICHTIG - dynamic ELF requires --sysroot containing {interp}"
1018                ))
1019            })?;
1020
1021            let loader_path = resolve_guest_path(sysroot, interp)?;
1022            let loader_bytes = fs::read(&loader_path).map_err(|err| {
1023                io::Error::other(format!(
1024                    "cannot read guest interpreter {}: {err}",
1025                    loader_path.display()
1026                ))
1027            })?;
1028            // Apply the same ELF checks to the loader because it will execute as guest code.
1029            let loader = ProgramImage::from_elf_bytes(&loader_bytes)?;
1030
1031            // if loader.elf_type != 3 {
1032            //     return Err(io::Error::other("ist nich ET_DYN").into());
1033            // }
1034
1035            // A fixed bias keeps loader addresses stable across repeat fuzzing runs.
1036            loader_base = DYNAMIC_LOADER_BIAS;
1037
1038            // Check the loader pages first to avoid a partially mapped interpreter.
1039            // This arithmetic assumes that each biased segment end and rounded end fit in u64.
1040            for segment in &loader.segments {
1041                if segment.mem_size == 0 {
1042                    continue;
1043                }
1044
1045                // Segment addresses and sizes need not be page-aligned. Check every host-style
1046                // mapping page from the rounded-down start to the rounded-up end.
1047                let start = align_down(loader_base + segment.vaddr, PAGE_SIZE as u64);
1048                let end = align_up(
1049                    loader_base + segment.vaddr + segment.mem_size,
1050                    PAGE_SIZE as u64,
1051                );
1052
1053                if !memory.range_is_free(start, end - start) {
1054                    return Err(io::Error::other("??").into());
1055                }
1056            }
1057
1058            // Add the bias to every loader segment and to its entry PC. The main executable
1059            // retains its original addresses for AT_ENTRY and AT_PHDR.
1060            memory.map_program_at(&loader, loader_base)?;
1061            initial_pc = loader_base
1062                .checked_add(loader.entry_pc)
1063                .ok_or_else(|| io::Error::other("interpreter entry error"))?;
1064        }
1065
1066        // `Arc` uses shared ownership. The debugger can retain a clone without borrowing
1067        // the complete runtime.
1068        let mut state = GuestState::default();
1069        let pause = Arc::new(AtomicBool::new(false));
1070
1071        // dbg!(state);
1072
1073        // todo -- hier meckert clippy
1074        // Initialize raw pointers only after their boxed owners have stable addresses.
1075        state.memory = memory.as_abi_mut();
1076        // `regs[2]` is RISC-V x2 (`sp`). Use the same 256-byte top reserve as startup setup.
1077        state.regs[2] = memory.stack_top - DEFAULT_STACK_POINTER_OFFSET;
1078
1079        let mut runtime = Self {
1080            program,
1081            process,
1082            memory,
1083            cache: BTreeMap::new(),
1084            jit_entries: Vec::new(),
1085            initial_pc,
1086            loader_base,
1087            state,
1088            files: BTreeMap::new(),
1089            // Linux processes conventionally start with 0, 1, and 2 as standard streams.
1090            // This runtime keeps those descriptors fixed instead of permitting close and reuse.
1091            next_fd: 3,
1092            mmap_base: MMAP_BASE,
1093            jit_output_dir,
1094            verbose: false,
1095
1096            compile_count: 0,
1097            compile_time: Duration::ZERO,
1098            execute_time: Duration::ZERO,
1099
1100            // todo -- anders machen
1101            control: Box::new(GuestControl {
1102                pause: Arc::as_ptr(&pause),
1103                breakpoints: [0; DEBUG_SLOTS],
1104                breakpoint_count: 0,
1105                // Use the no-skip sentinel until execution stops at a breakpoint.
1106                skip_breakpoint: u64::MAX,
1107                blocks_left: 0,
1108                // Zero is false in the generated C-compatible ABI.
1109                limited: 0,
1110                trace: [0; DEBUG_SLOTS],
1111                trace_pos: 0,
1112            }),
1113            pause,
1114            initial_snapshot: None,
1115            last_syscall: None,
1116            lift_region,
1117        };
1118
1119        //runtime.initialize_shim_data()?;
1120        //runtime.install_shims()?;
1121
1122        runtime.initialize_process_state()?;
1123
1124        runtime.state.pc = runtime.initial_pc;
1125        runtime.state.memory = runtime.memory.as_abi_mut();
1126        // The initial snapshot restores guest memory and file-handle state during reset.
1127        // It does not restore external file contents that the guest or another process changes.
1128        runtime.initial_snapshot = Some(runtime.snapshot()?);
1129
1130        Ok(runtime)
1131    }
1132
1133    /// Returns the number of guest entry PCs that have native code.
1134    pub fn cache_len(&self) -> usize {
1135        self.cache.len()
1136    }
1137
1138    /// Borrows parsed program data without giving permission to change runtime mappings.
1139    pub fn program(&self) -> &ProgramImage {
1140        &self.program
1141    }
1142
1143    /// Borrows guest registers and stop data for inspection.
1144    pub fn state(&self) -> &GuestState {
1145        &self.state
1146    }
1147
1148    /// Gives controlled callers direct access to guest registers and the PC.
1149    pub fn state_mut(&mut self) -> &mut GuestState {
1150        &mut self.state
1151    }
1152
1153    /// Borrows guest memory for reads and mapping inspection.
1154    pub fn memory(&self) -> &GuestMemory {
1155        &self.memory
1156    }
1157
1158    /// Borrows guest memory for changes and first refreshes its generated-code view.
1159    pub fn memory_mut(&mut self) -> &mut GuestMemory {
1160        // Refresh the ABI first because a previous mapping change can make its pointers stale.
1161        self.state.memory = self.memory.as_abi_mut();
1162        &mut self.memory
1163    }
1164
1165    /// Changes both reset entry and current execution entry to keep them consistent.
1166    pub fn set_entry_pc(&mut self, pc: u64) {
1167        self.initial_pc = pc;
1168        self.state.pc = pc;
1169    }
1170
1171    /// Enables compiler, dispatch, and syscall diagnostics for manual investigation.
1172    pub fn set_verbose(&mut self, verbose: bool) {
1173        self.verbose = verbose;
1174    }
1175
1176    /// Exposes the normalized roots and JIT options that this runtime uses.
1177    pub fn process_config(&self) -> &RemuConfig {
1178        &self.process
1179    }
1180
1181    /// Clones the shared pause handle so another thread can stop generated code safely.
1182    pub fn pause_flag(&self) -> Arc<AtomicBool> {
1183        Arc::clone(&self.pause)
1184    }
1185
1186    /// Adds one guest block-entry address to the generated-code breakpoint table.
1187    pub fn add_breakpoint(&mut self, address: u64) -> Result<(), &'static str> {
1188        // The fixed generated-code ABI has DEBUG_SLOTS entries. This method does not report
1189        // a full table, so callers must not add more than DEBUG_SLOTS different addresses.
1190        let mut values = self.control.breakpoints[..self.control.breakpoint_count].to_vec();
1191
1192        if values.contains(&address) {
1193            return Ok(());
1194        }
1195
1196        // Generated code uses binary search, so the shared breakpoint array must stay sorted.
1197        values.push(address);
1198        values.sort_unstable();
1199
1200        self.control.breakpoint_count = values.len();
1201        self.control.breakpoints[..values.len()].copy_from_slice(&values);
1202        Ok(())
1203    }
1204
1205    /// Removes all copies of an address and keeps the active prefix compact.
1206    pub fn remove_breakpoint(&mut self, address: u64) {
1207        let mut values = self.control.breakpoints[..self.control.breakpoint_count].to_vec();
1208        values.retain(|value| *value != address);
1209        self.control.breakpoint_count = values.len();
1210        self.control.breakpoints[..values.len()].copy_from_slice(&values);
1211    }
1212
1213    /// Hides all saved array values by setting the active count to zero.
1214    pub fn clear_breakpoints(&mut self) {
1215        self.control.breakpoint_count = 0;
1216    }
1217
1218    /// Returns only the active sorted prefix, not unused fixed-array slots.
1219    pub fn breakpoints(&self) -> &[u64] {
1220        &self.control.breakpoints[..self.control.breakpoint_count]
1221    }
1222
1223    /// Returns the retained block-entry trace in execution order for debugger output.
1224    pub fn trace(&self) -> Vec<u64> {
1225        // Reorder the ring buffer so callers always receive entries from oldest to newest.
1226        // `min(DEBUG_SLOTS)` limits output to the array capacity. Subtraction finds the oldest
1227        // retained sequence number. Modulo DEBUG_SLOTS maps each sequence number to its slot.
1228        let count = self.control.trace_pos.min(DEBUG_SLOTS);
1229        let start = self.control.trace_pos.saturating_sub(count);
1230        (start..self.control.trace_pos)
1231            .map(|index| self.control.trace[index % DEBUG_SLOTS])
1232            .collect()
1233    }
1234
1235    /// Returns the last syscall register record for debugger and fuzzer diagnostics.
1236    pub fn last_syscall(&self) -> Option<[u64; 8]> {
1237        self.last_syscall
1238    }
1239
1240    /// Runs the architecture callback without compiling or changing the JIT cache.
1241    pub fn lift_at(&self, pc: u64) -> JitResult<Nnil> {
1242        (self.lift_region)(&self.memory, pc)
1243    }
1244
1245    /// Captures guest-visible mutable state so one runtime can replay from this point.
1246    pub fn snapshot(&mut self) -> JitResult<RuntimeSnapshot> {
1247        let mut files = BTreeMap::new();
1248        for (&fd, file) in &mut self.files {
1249            // `try_clone` keeps the same underlying open file available in the snapshot.
1250            // Save the current offset because the guest can change it with read or seek.
1251            files.insert(
1252                fd,
1253                SnapshotFile {
1254                    file: file.file.try_clone()?,
1255                    offset: file.file.stream_position()?,
1256                    fd_flags: file.fd_flags,
1257                },
1258            );
1259        }
1260
1261        // Raw ABI pointers belong to this runtime and cannot be copied into a saved snapshot.
1262        let mut state = self.state.clone();
1263
1264        state.memory = std::ptr::null_mut();
1265        state.control = std::ptr::null_mut();
1266        state.jit_entries = std::ptr::null();
1267        state.jit_entry_count = 0;
1268
1269        // Start dirty tracking after the full baseline copy for fast later restores.
1270        let memory = (*self.memory).clone();
1271        self.memory.clear_dirty();
1272
1273        // dbg!(state);
1274
1275        Ok(RuntimeSnapshot {
1276            memory,
1277            state,
1278            files,
1279            next_fd: self.next_fd,
1280            mmap_base: self.mmap_base,
1281        })
1282    }
1283
1284    // todo - optimize
1285    // todo - dirty bit so wie gamozo
1286    /// Restores process data but keeps compiled native blocks for fast replay.
1287    pub fn restore(&mut self, snapshot: &RuntimeSnapshot) -> JitResult<()> {
1288        let mut files = BTreeMap::new();
1289
1290        // Clone file descriptions and restore offsets so guest I/O resumes at the same position.
1291        for (&fd, saved) in &snapshot.files {
1292            let mut file = saved.file.try_clone()?;
1293            file.seek(SeekFrom::Start(saved.offset))?;
1294            files.insert(
1295                fd,
1296                GuestFile {
1297                    file,
1298                    fd_flags: saved.fd_flags,
1299                },
1300            );
1301        }
1302
1303        // Use dirty pages when layouts match; otherwise rebuild all memory and ABI pointers.
1304        if !self.memory.restore_dirty_from(&snapshot.memory) {
1305            *self.memory = snapshot.memory.clone();
1306        }
1307
1308        self.state = snapshot.state.clone();
1309        self.state.memory = self.memory.as_abi_mut();
1310
1311        // Do not resume old debugger limits or pointers after a restore.
1312        self.state.control = std::ptr::null_mut();
1313        self.sync_jit_entries();
1314        self.files = files;
1315        self.next_fd = snapshot.next_fd;
1316
1317        // self.sync_jit_entries();
1318
1319        self.mmap_base = snapshot.mmap_base;
1320        self.last_syscall = None;
1321        Ok(())
1322    }
1323
1324    // todo -- siehe restore 
1325    /// Restores the snapshot that was made after initial process setup.
1326    pub fn reset(&mut self) -> JitResult<()> {
1327        // Rust cannot mutably borrow `self` for `restore` while it also borrows a field
1328        // from `self`. `take` moves the snapshot out, then the code puts it back.
1329        let snapshot = self
1330            .initial_snapshot
1331            .take()
1332            .ok_or_else(|| io::Error::other(" snapshot missing"))?;
1333
1334        // Put the snapshot back even when restore reports an error, so a later reset can retry.
1335        let result = self.restore(&snapshot);
1336        self.initial_snapshot = Some(snapshot);
1337        result
1338    }
1339
1340    /*
1341    // todo - brauchen wir die alle hier wirklich?
1342
1343            let nnil = (self.lift_region)(&self.program, self.state.pc)?;
1344        let compiled = compile_jit(&nnil, &self.jit_output_dir)?;
1345
1346        if self.verbose {
1347            println!(
1348                "jit-cache-miss: neu compiled für pc=0x{:x}: {} {}",
1349                self.state.pc,
1350                compiled.source_path().display(),
1351                compiled.library_path().display(),
1352            );
1353        }
1354
1355        self.cache.insert(self.state.pc, compiled);
1356    } else {
1357        if self.verbose {
1358            println!(
1359                "jit-cache-hit für pc=0x{:x}",
1360                self.state.pc,
1361            );
1362        } */
1363
1364    /// Compiles a linear walk from the entry to reduce lazy JIT stops during startup.
1365    pub fn precompile(&mut self) -> JitResult<usize> {
1366        let start_pc = self.initial_pc;
1367
1368        let segment_end = self.memory.executable_end(start_pc).ok_or_else(|| {
1369            io::Error::other(format!("entry pc 0x{start_pc:x} is not executable"))
1370        })?;
1371
1372        // https://ratatui.rs/recipes/apps/panic-hooks/
1373
1374        // Hide expected decoder panics while the best-effort walk probes unknown code.
1375        // Rust panic hooks are process-global. A concurrent panic can also be hidden, and
1376        // restoring this saved hook can replace a hook that another thread installed.
1377        let old_panic_hook = take_hook();
1378        set_hook(Box::new(|_| {}));
1379
1380        let mut pc = start_pc;
1381        let mut regions = Vec::new();
1382
1383        // Walk linearly to get broad startup coverage without requiring a complete control-flow graph.
1384        while pc < segment_end {
1385            // The decoder can panic on unsupported bytes. Catch that panic because this
1386            // optional walk must not terminate the emulator. The callback only borrows memory.
1387            let attempt = catch_unwind(AssertUnwindSafe(|| (self.lift_region)(&self.memory, pc)));
1388
1389            match attempt {
1390                Ok(Ok(nnil)) => {
1391                    let next_pc = nnil.pc();
1392
1393                    // dbg!(nnil);
1394
1395                    // Every lifted region must advance. This check prevents an infinite walk.
1396                    if next_pc <= pc {
1397                        if self.verbose {
1398                            println!(
1399                                "aot-stop: entry walk kein progress bei pc=0x{:x} next=0x{:x}",
1400                                pc, next_pc
1401                            );
1402                        }
1403                        break;
1404                    }
1405
1406                    regions.push(nnil);
1407
1408                    if next_pc >= segment_end {
1409                        break;
1410                    }
1411
1412                    pc = next_pc;
1413                }
1414
1415                Ok(Err(err)) => {
1416                    if self.verbose {
1417                        println!("aot-skip: entry walk stopped at pc=0x{:x}: {}", pc, err);
1418                    }
1419                    break;
1420                }
1421
1422                Err(_) => {
1423                    if self.verbose {
1424                        println!("aot-skip: entry walk panic pc=0x{:x}", pc);
1425                    }
1426                    break;
1427                }
1428            }
1429        }
1430
1431        // Restore the process-wide hook so later real panics remain visible.
1432        set_hook(old_panic_hook);
1433
1434        if regions.is_empty() {
1435            return Ok(0);
1436        }
1437
1438        // One bundle reduces rustc start cost for all regions found by the walk.
1439        let compiled_regions = match compile_jit_multi(
1440            &regions,
1441            &self.jit_output_dir,
1442            "generated-aot",
1443            self.process.jit_opt_level,
1444            self.process.jit_cache_dir.as_deref(),
1445            self.process.raw_jit,
1446        ) {
1447
1448            Ok(compiled_regions) => compiled_regions,
1449            Err(err) => {
1450                if self.verbose {
1451                    println!("aot-bundle-stop: failed to compile start batch: {}", err);
1452                }
1453                return Ok(0);
1454            }
1455        };
1456
1457        // dbg!(compiled_regions);
1458
1459        if self.verbose
1460            && let Some((_, first)) = compiled_regions.first()
1461        {
1462            println!(
1463                "aot-bundle: compiled {} regions into {} {}",
1464                compiled_regions.len(),
1465                first.source_path().display(),
1466                first.library_path().display(),
1467            );
1468        }
1469
1470        let compiled_count = compiled_regions.len();
1471
1472        // Each returned handle shares the loaded bundle but has its own guest entry function.
1473        for (entry_pc, compiled) in compiled_regions {
1474            self.cache.insert(entry_pc, compiled);
1475        }
1476        self.sync_jit_entries();
1477
1478        Ok(compiled_count)
1479    }
1480
1481    // todo -- schnitstelle nach "außen"
1482    // todo -- hashing
1483    // todo -- resetting
1484    /// Runs until the guest exits, faults, makes an unsupported call, or requests a stop.
1485    pub fn run(&mut self) -> JitResult<ExitReason> {
1486        // A null control pointer removes debugger checks from an unrestricted run.
1487        self.state.control = std::ptr::null_mut();
1488        self.run_inner()
1489    }
1490
1491    /// Runs at most `limit` translated blocks so a debugger can step or stay responsive.
1492    pub fn run_for_blocks(&mut self, limit: u64) -> JitResult<ExitReason> {
1493        // One block is one translated basic block, not one RISC-V instruction.
1494        self.control.blocks_left = limit;
1495
1496        // One is true in this byte-sized ABI flag, so generated blocks use the limit.
1497        self.control.limited = 1;
1498
1499        // Skip the current breakpoint once so continue and step can make progress.
1500        self.control.skip_breakpoint = if self.state.exit_reason == ExitReason::Breakpoint {
1501            self.state.pc
1502        } else {
1503            u64::MAX
1504        };
1505
1506        self.state.control = &mut *self.control;
1507        let result = self.run_inner();
1508
1509        // dbg!(self.state);
1510
1511        self.state.control = std::ptr::null_mut();
1512        self.control.limited = 0;
1513        // The pause flag is only a stop request. It does not protect other memory, so relaxed
1514        // atomic ordering is sufficient and avoids an unnecessary synchronization barrier.
1515        self.pause.store(false, Ordering::Relaxed);
1516        result
1517    }
1518
1519    fn run_inner(&mut self) -> JitResult<ExitReason> {
1520        // PC zero is the runtime's not-started sentinel. Replace it with the selected entry.
1521        // This convention prevents this path from starting or resuming a real block at address zero.
1522        if self.state.pc == 0 {
1523            self.state.pc = self.initial_pc;
1524        }
1525
1526        // Each native exit returns here for lazy compilation, syscalls, or user control.
1527        loop {
1528            self.state.memory = self.memory.as_abi_mut();
1529            self.state.exit_reason = ExitReason::None;
1530
1531            // Remove details from the previous exit. A caller must not mistake an old
1532            // fault address for the cause of a later syscall or debugger stop.
1533            self.state.fault_addr = 0;
1534            self.state.fault_access = AccessKind::None;
1535
1536            if self.verbose {
1537                println!("neuer pc=0x{:x}", self.state.pc)
1538            }
1539
1540            // todo - fault memory läuft noch nicht ganz
1541            if !self.memory.can_execute(self.state.pc) {
1542                self.state.exit_reason = ExitReason::ExecFault;
1543                self.state.fault_addr = self.state.pc;
1544                self.state.fault_access = AccessKind::Execute;
1545
1546
1547                return Ok(self.finished(ExitReason::ExecFault));
1548            }
1549
1550            // Compile only on first entry so unused guest regions do not pay JIT cost.
1551            if !self.cache.contains_key(&self.state.pc) {
1552                let _ = self.compile_region(self.state.pc)?;
1553            } else if self.verbose {
1554                println!("jit-cache-hit für pc=0x{:x}", self.state.pc);
1555            }
1556
1557            // todo -- raw_jit siehe oben
1558            let compiled = self
1559                .cache
1560                .get(&self.state.pc)
1561                .ok_or_else(|| io::Error::other("cache fehler"))?;
1562
1563            // Calling native code is unsafe because the library receives a raw pointer.
1564            // `CompiledJit` keeps the library loaded for the full call.
1565            let exit_reason = if self.verbose {
1566                let started = Instant::now();
1567                let reason = unsafe { compiled.run(&mut self.state) };
1568                self.execute_time += started.elapsed();
1569                reason
1570            } else {
1571                unsafe { compiled.run(&mut self.state) }
1572            };
1573
1574            // Internal dispatch exits resume automatically. Caller-visible stops return to the caller.
1575            match exit_reason {
1576                ExitReason::IndirectBranch => continue,
1577                ExitReason::Syscall => {
1578                    if self.verbose {
1579                        self.log_syscall();
1580                    }
1581                    if self.handle_syscall() {
1582                        continue;
1583                    }
1584                    if self.state.exit_reason == ExitReason::GuestExit {
1585                        return Ok(self.finished(ExitReason::GuestExit));
1586                    }
1587                    return Ok(self.finished(ExitReason::Syscall));
1588                }
1589                other => return Ok(self.finished(other)),
1590            }
1591        }
1592    }
1593
1594    // todo - schöne logs für benchmarks
1595    fn finished(&self, reason: ExitReason) -> ExitReason {
1596        if self.verbose {
1597            println!(
1598                "jit stats: {} rustc calls, {:.2?} compiling, {:.2?} executing",
1599                self.compile_count, self.compile_time, self.execute_time
1600            );
1601        }
1602        reason
1603    }
1604
1605    // todo -- refactor vereinfachen
1606    // Return `(compiled, next_pc)` so callers can distinguish a cache hit and continue a walk.
1607    fn compile_region(&mut self, pc: u64) -> JitResult<(bool, u64)> {
1608        if self.cache.contains_key(&pc) {
1609            /*  if self.verbose {
1610                println!(
1611                    "jit-cache-hit: für pc=0x{:x}",
1612                    pc,
1613                );
1614            } */
1615            return Ok((false, pc));
1616        }
1617
1618        // A bounded batch improves cross-region branches without making one source file unlimited.
1619        // The limit of 128 is a project tradeoff. It gives rustc more cross-block context
1620        // and caps region count and source size for one cache miss. It cannot bound rustc time.
1621        let regions = self.collect_lazy_batch(pc, 128)?;
1622        // Collection reports an error if it cannot lift the required first region.
1623        // Thus, index zero is the requested block and is safe to use as the fallback.
1624        let next_pc = regions[0].pc();
1625
1626        // Use the simpler single-entry artifact path when no useful neighbor was found.
1627        if regions.len() == 1 {
1628            let started = Instant::now();
1629
1630            let compiled = compile_jit(
1631                &regions[0],
1632                &self.jit_output_dir,
1633                self.process.jit_opt_level,
1634                self.process.jit_cache_dir.as_deref(),
1635                self.process.raw_jit,
1636            )?;
1637
1638            if compiled.compiled_now() {
1639                self.compile_count += 1;
1640                self.compile_time += started.elapsed();
1641            }
1642
1643            if self.verbose {
1644                println!(
1645                    "jit-cache-{} für pc=0x{:x}: {} {}",
1646                    if compiled.compiled_now() {
1647                        "miss: compiled"
1648                    } else {
1649                        "disk-hit!"
1650                    },
1651                    pc,
1652                    compiled.source_path().display(),
1653                    compiled.library_path().display(),
1654                );
1655            }
1656
1657            self.cache.insert(pc, compiled);
1658            self.sync_jit_entries();
1659
1660            return Ok((true, next_pc));
1661        }
1662
1663        let started = Instant::now();
1664
1665        // The result keeps compiled handles with one shared-artifact cache flag because
1666        // statistics and diagnostics need both parts.
1667        let (compiled_regions, compiled_now) = match compile_jit_multi(
1668            &regions,
1669            &self.jit_output_dir,
1670            "generated-batch",
1671            self.process.jit_opt_level,
1672            self.process.jit_cache_dir.as_deref(),
1673            self.process.raw_jit,
1674        ) {
1675            Ok(compiled_regions) => {
1676                // All entries come from one artifact. Its first handle therefore reports
1677                // whether rustc built that common artifact now.
1678                let compiled_now = compiled_regions
1679                    .first()
1680                    .is_some_and(|(_, compiled)| compiled.compiled_now());
1681
1682                if compiled_now {
1683                    self.compile_count += 1;
1684                    self.compile_time += started.elapsed();
1685                }
1686                (compiled_regions, compiled_now)
1687            }
1688
1689            Err(err) => {
1690                // Keep execution available when one neighbor makes the batch invalid.
1691                self.compile_count += 1;
1692                self.compile_time += started.elapsed();
1693                if self.verbose {
1694                    println!(
1695                        "jit-batch-stop: fallback to single region bei pc=0x{:x}: {}",
1696                        pc, err
1697                    );
1698                }
1699
1700                let started = Instant::now();
1701                
1702                /*
1703                 * self.sync_jit_entries();
1704                 */
1705
1706                let compiled = compile_jit(
1707                    &regions[0],
1708                    &self.jit_output_dir,
1709                    self.process.jit_opt_level,
1710                    self.process.jit_cache_dir.as_deref(),
1711                    self.process.raw_jit,
1712                )?;
1713
1714                if compiled.compiled_now() {
1715                    self.compile_count += 1;
1716                    self.compile_time += started.elapsed();
1717                }
1718
1719                if self.verbose {
1720                    println!(
1721                        "jit-cache-{} für pc=0x{:x}: {} {}",
1722                        if compiled.compiled_now() {
1723                            "miss: compiled"
1724                        } else {
1725                            "disk-hit !"
1726                        },
1727                        pc,
1728                        compiled.source_path().display(),
1729                        compiled.library_path().display(),
1730                    );
1731                }
1732
1733                // dbg!(self.cache);
1734
1735                self.cache.insert(pc, compiled);
1736                self.sync_jit_entries();
1737                return Ok((true, next_pc));
1738            }
1739        };
1740
1741        // dbg!(compiled_regions);
1742
1743        if self.verbose
1744            && let Some((_, first)) = compiled_regions.first()
1745        {
1746            println!(
1747                "jit-cache-batch-{}: {} regions ab pc=0x{:x}: {} {}",
1748                if compiled_now { "compiled" } else { "disk-hit !" },
1749                compiled_regions.len(),
1750                pc,
1751                first.source_path().display(),
1752                first.library_path().display(),
1753            );
1754        }
1755
1756        for (entry_pc, compiled) in compiled_regions {
1757            self.cache.insert(entry_pc, compiled);
1758        }
1759        self.sync_jit_entries();
1760
1761        Ok((true, next_pc))
1762    }
1763
1764    // todo -- code dupliction
1765    // Collect direct control-flow neighbors so one rustc call can link their tail calls.
1766    fn collect_lazy_batch(&self, start_pc: u64, max_regions: usize) -> JitResult<Vec<Nnil>> {
1767        self.memory.executable_end(start_pc).ok_or_else(|| {
1768            io::Error::other(format!("entry pc 0x{start_pc:x} is not executable! ende"))
1769        })?;
1770
1771        // Breadth-first order favors nearby direct successors and gives stable bundle content.
1772        let mut queue = VecDeque::from([start_pc]);
1773        let mut seen = BTreeSet::new();
1774        let mut regions = Vec::new();
1775
1776        // Stop when the queue is empty or the project batch limit is reached. Thus, one
1777        // cache miss cannot grow the compilation bundle without a limit.
1778        while let Some(pc) = queue.pop_front()
1779            && regions.len() < max_regions
1780        {
1781            // `insert` returns false for a duplicate. Short-circuit OR then skips that PC
1782            // without a second tree lookup. Non-executable targets are also unsafe to lift.
1783            if !seen.insert(pc) || !self.memory.can_execute(pc) {
1784                continue;
1785            }
1786            
1787            // todo -- refactor vereinfachen
1788
1789            // A bad optional successor must not prevent compilation of the required start region.
1790            // The hook is process-global. Concurrent panics can be hidden, and restoring the
1791            // saved value can replace a hook that another thread installed.
1792            let old_panic_hook = take_hook();
1793            set_hook(Box::new(|_| {}));
1794            let lifted = catch_unwind(AssertUnwindSafe(|| (self.lift_region)(&self.memory, pc)));
1795            set_hook(old_panic_hook);
1796
1797            // Failure at an optional neighbor is harmless. Failure at the requested start
1798            // address must be reported because execution cannot make progress without it.
1799            let nnil = match lifted {
1800                Ok(Ok(nnil)) => nnil,
1801                Ok(Err(_)) if pc != start_pc || !regions.is_empty() => continue,
1802                Ok(Err(err)) => return Err(err),
1803                Err(payload) => {
1804                    if pc != start_pc || !regions.is_empty() {
1805                        continue;
1806                    }
1807
1808                    // Rust panic payloads are usually `&str` or `String`. Downcast without
1809                    // taking ownership so the diagnostic can cover both common forms.
1810                    let message = payload
1811                        .downcast_ref::<&str>()
1812                        .copied()
1813                        .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
1814                        .unwrap_or("unknown panic");
1815
1816                    // Show four executable bytes for context. This can include a second compressed
1817                    // instruction. If four bytes are unavailable, show the first 16-bit parcel.
1818                    let raw = self
1819                        .memory
1820                        .fetch_u32(pc)
1821                        .map(|value| format!("0x{value:08x}"))
1822                        .or_else(|| {
1823                            self.memory
1824                                .fetch_u16(pc)
1825                                .map(|value| format!("0x{value:04x}"))
1826                        })
1827                        .unwrap_or_else(|| "unreadable".to_string());
1828
1829                    return Err(io::Error::other(format!(
1830                        "lifter panic bei guest pc 0x{pc:x}, instruction {raw}: {message}"
1831                    ))
1832                    .into());
1833                }
1834            };
1835
1836            let next_pc = nnil.pc();
1837
1838            if next_pc <= pc {
1839                return Err(io::Error::other(format!(
1840                    "compiling pc 0x{pc:x} kein progress (next pc 0x{next_pc:x})"
1841                ))
1842                .into());
1843            }
1844
1845            // Queue only static targets because indirect targets are unknown until runtime.
1846            for successor in direct_successors(&nnil) {
1847                if !seen.contains(&successor) {
1848                    queue.push_back(successor);
1849                }
1850            }
1851
1852            if !self.cache.contains_key(&pc) {
1853                regions.push(nnil);
1854            }
1855        }
1856
1857        Ok(regions)
1858    }
1859
1860    fn log_syscall(&self) {
1861        // The RV64 Linux ABI puts the syscall number in x17 (`a7`). Arguments use
1862        // x10 through x15 (`a0` through `a5`), and x2 is the stack pointer.
1863        // Source: https://man7.org/linux/man-pages/man2/syscall.2.html
1864        let nr = self.state.regs[17];
1865
1866        println!(
1867            "achtung syscall {} - pc=0x{:x} a0=0x{:x} a1=0x{:x} a2=0x{:x} a3=0x{:x} a4=0x{:x} a5=0x{:x} sp=0x{:x}",
1868            nr,
1869            self.state.pc,
1870            self.state.regs[10],
1871            self.state.regs[11],
1872            self.state.regs[12],
1873            self.state.regs[13],
1874            self.state.regs[14],
1875            self.state.regs[15],
1876            self.state.regs[2],
1877        );
1878    }
1879
1880    // todo - fuzz api
1881    // todo -- refactor, eigene datei
1882    fn handle_syscall(&mut self) -> bool {
1883        // Keep the entry registers before a handler changes a0 into the return value.
1884        let nr = self.state.regs[17];
1885        self.last_syscall = Some([
1886            nr,
1887            self.state.pc,
1888            self.state.regs[10],
1889            self.state.regs[11],
1890            self.state.regs[12],
1891            self.state.regs[13],
1892            self.state.regs[14],
1893            self.state.regs[15],
1894        ]);
1895
1896        // dbg!(self.state);
1897
1898        // Handlers use positive Linux errno numbers internally. The values used below are:
1899        // 2=ENOENT, 5=EIO, 9=EBADF, 12=ENOMEM, 13=EACCES, 14=EFAULT,
1900        // 17=EEXIST, 22=EINVAL, 25=ENOTTY, 30=EROFS, and 38=ENOSYS.
1901        // Sources:
1902        // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
1903        // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
1904
1905        // https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html
1906        // Emulate only the process services needed by current user-mode guests.
1907        // RV64 uses the architecture-neutral Linux syscall table for these numbers.
1908        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
1909        // 17=getcwd, 25=fcntl, 29=ioctl, 35=unlinkat, 48=faccessat,
1910        // 56=openat, 57=close, 62=lseek, 63=read, 64=write, 66=writev,
1911        // 67=pread64, 78=readlinkat, 79=newfstatat, and 80=fstat.
1912        let result = match nr {
1913            17 => self.sys_getcwd(),
1914            25 => self.sys_fcntl(),
1915            // No guest ioctl is available. Error 25 is `ENOTTY`, which tells libc that
1916            // this descriptor does not support the requested device operation.
1917            29 => Err(25),
1918            35 => self.sys_unlinkat(),
1919            // Number 439 is `faccessat2`. Routing it to the old path check ignores its fourth
1920            // flags argument. This is an ABI limitation of the current subset.
1921            48 | 439 => self.sys_faccessat(),
1922            56 => self.sys_openat(),
1923            57 => self.sys_close(),
1924            62 => self.sys_lseek(),
1925            63 => self.sys_read(),
1926            64 => self.sys_write(),
1927            66 => self.sys_writev(),
1928            67 => self.sys_pread64(),
1929            78 => self.sys_readlinkat(),
1930            79 => self.sys_newfstatat(),
1931            80 => self.sys_fstat(),
1932            // 93 is `exit` and 94 is `exit_group`. This runtime has one guest thread,
1933            // so both calls stop the complete guest process.
1934            93 | 94 => {
1935                self.state.exit_reason = ExitReason::GuestExit;
1936                return false;
1937            }
1938            // 96 is `set_tid_address`. Return virtual thread ID 1 because there is one thread.
1939            // The runtime does not record the x10 clear-child-TID pointer or clear it at exit.
1940            96 => Ok(1),
1941            // Calls 99 and 233 are set_robust_list and madvise; this model ignores both.
1942            // Call 259 is riscv_flush_icache. Returning success without invalidating translated
1943            // blocks is a limitation and can leave stale code after a guest modifies instructions.
1944            99 | 233 | 259 => Ok(0),
1945            // 113=clock_gettime, 123=sched_getaffinity, 160=uname, and 163=getrlimit.
1946            113 => self.sys_clock_gettime(),
1947            123 => self.sys_sched_getaffinity(),
1948            160 => self.sys_uname(),
1949            163 => self.sys_getrlimit(),
1950            // 172=getpid and 178=gettid. Both identify the only virtual task as 1.
1951            172 | 178 => Ok(1),
1952            // 174..177 are getuid, geteuid, getgid, and getegid. A stable unprivileged
1953            // identity matches the IDs in auxiliary-vector and synthetic stat data.
1954            174..=177 => Ok(1000),
1955            // 214=brk, 215=munmap, 222=mmap, and 226=mprotect.
1956            214 => self.sys_brk(),
1957            215 => self.sys_munmap(),
1958            222 => self.sys_mmap(),
1959            226 => self.sys_mprotect(),
1960            // 258 is `riscv_hwprobe`. Error 38 is `ENOSYS`, so libc can use an older
1961            // capability path when detailed CPU data is not available.
1962            // Source: https://github.com/torvalds/linux/blob/master/arch/riscv/kernel/syscalls/syscall.tbl
1963            258 => Err(38),
1964            // 261=prlimit64 and 278=getrandom.
1965            261 => self.sys_prlimit64(),
1966            278 => self.sys_getrandom(),
1967            // 293 is `rseq`. Return `ENOSYS` because this runtime has no guest thread scheduler.
1968            293 => Err(38),
1969            // Return control to the caller when host policy does not define this syscall.
1970            _ => return false,
1971        };
1972
1973        // Linux returns success values or negative errno values in x10 (`a0`).
1974        self.state.regs[10] = result.unwrap_or_else(guest_errno);
1975        self.state.exit_reason = ExitReason::None;
1976        true
1977    }
1978
1979    fn sys_openat(&mut self) -> Result<u64, i32> {
1980        // `openat` receives dirfd in x10, path in x11, flags in x12, and mode in x13.
1981        // This subset uses host creation defaults, so it does not read the mode argument.
1982        // x10 is signed because Linux uses -100 for `AT_FDCWD`. Casting the saved u64
1983        // preserves its two's-complement bit pattern as an i64.
1984        let dirfd = self.state.regs[10] as i64;
1985        // One 4 KiB page is a project limit for path scanning. It bounds work when a guest
1986        // gives an address with no NUL terminator.
1987        let path = self.read_guest_string(self.state.regs[11], 4096)?;
1988        let flags = self.state.regs[12];
1989
1990        // Supporting only AT_FDCWD avoids implementing guest directory-FD state and keeps
1991        // every lookup relative to the configured process roots.
1992        // Source for -100 (`AT_FDCWD`):
1993        // https://github.com/torvalds/linux/blob/master/include/uapi/linux/fcntl.h
1994        if dirfd != -100 {
1995            // Error 22 is `EINVAL`: this emulator does not accept another dirfd form.
1996            return Err(22);
1997        }
1998
1999        // AND with 3 removes all option bits and keeps the two access-mode bits.
2000        let access = flags & O_ACCMODE;
2001
2002        if access > O_RDWR {
2003            // Access value 3 is reserved, so it is not a valid read or write mode.
2004            return Err(22);
2005        }
2006        // Detect normal write access and the listed file-changing options.
2007        let writable = access != 0 || flags & (O_CREAT | O_TRUNC | O_APPEND) != 0;
2008        // This test omits O_EXCL. O_EXCL without O_CREAT can still reach Rust `create_new`
2009        // below, so this subset does not fully enforce the read-only sysroot for that form.
2010        // Absolute paths are intended to stay in the read-only sysroot.
2011        if path.starts_with('/') && writable {
2012            // Error 30 is `EROFS`. This presents the sysroot as a read-only guest file system.
2013            return Err(30);
2014        }
2015
2016        let path = self.resolve_process_path(&path, flags & O_CREAT != 0)?;
2017        let mut options = OpenOptions::new();
2018
2019
2020        // Translate only the supported Linux flags to host `OpenOptions`. Read-only is
2021        // encoded as zero, so `access != O_WRONLY` enables reads for modes 0 and 2.
2022        // Unsupported option bits are ignored instead of rejected. This is an ABI limitation.
2023        options
2024            .read(access != O_WRONLY)
2025            .write(access != 0)
2026            .create(flags & O_CREAT != 0)
2027            .create_new(flags & O_EXCL != 0)
2028            .truncate(flags & O_TRUNC != 0)
2029            .append(flags & O_APPEND != 0);
2030
2031        let file = options.open(path).map_err(host_errno)?;
2032        // Allocate monotonically instead of reusing a closed number. This keeps descriptor
2033        // assignment deterministic across snapshots and avoids a free-list data structure.
2034        let fd = self.next_fd;
2035        self.next_fd += 1;
2036
2037        self.files.insert(
2038            fd,
2039            GuestFile {
2040                file,
2041                // AND tests only Linux bit 19. Store the guest flag even though this runtime
2042                // does not currently replace its process image with guest `exec`.
2043                fd_flags: if flags & O_CLOEXEC != 0 {
2044                    FD_CLOEXEC
2045                } else {
2046                    0
2047                },
2048            },
2049        );
2050
2051        // dbg!(self.files);
2052
2053        Ok(fd as u64)
2054    }
2055
2056    fn sys_unlinkat(&mut self) -> Result<u64, i32> {
2057        // `unlinkat` receives dirfd in x10, path in x11, and option flags in x12.
2058        // Support only `AT_FDCWD` with flags zero. In particular, flag 0x200
2059        // (`AT_REMOVEDIR`) is not implemented because this function removes files only.
2060        if self.state.regs[10] as i64 != -100 || self.state.regs[12] != 0 {
2061            return Err(22);
2062        }
2063        let path = self.read_guest_string(self.state.regs[11], 4096)?;
2064        // Never remove files from the shared sysroot.
2065        if path.starts_with('/') {
2066            return Err(30);
2067        }
2068        let path = self.resolve_process_path(&path, false)?;
2069        fs::remove_file(path).map_err(host_errno)?;
2070        Ok(0)
2071    }
2072
2073    fn sys_fcntl(&mut self) -> Result<u64, i32> {
2074        // `fcntl` receives the descriptor in x10, command in x11, and command value in x12.
2075        let fd = self.state.regs[10] as i32;
2076        let command = self.state.regs[11];
2077        let argument = self.state.regs[12];
2078        // Error 9 is `EBADF`. Standard streams have no stored guest flag record here.
2079        let file = self.files.get_mut(&fd).ok_or(9)?;
2080
2081        match command {
2082            F_GETFD => Ok(file.fd_flags),
2083            F_SETFD => {
2084                // Keep only bit 0 because Linux defines no other descriptor flag here.
2085                file.fd_flags = argument & FD_CLOEXEC;
2086                Ok(0)
2087            }
2088            _ => Err(22),
2089        }
2090    }
2091
2092    fn sys_close(&mut self) -> Result<u64, i32> {
2093        // `close` receives its descriptor in x10.
2094        let fd = self.state.regs[10] as i32;
2095        // Treat standard streams as always present because the runtime does not own their handles.
2096        if (0..=2).contains(&fd) || self.files.remove(&fd).is_some() {
2097            Ok(0)
2098        } else {
2099            // Error 9 is `EBADF`: the guest number is not open.
2100            Err(9)
2101        }
2102    }
2103
2104    fn sys_read(&mut self) -> Result<u64, i32> {
2105        // `read` receives the descriptor in x10, guest buffer in x11, and byte count in x12.
2106        let fd = self.state.regs[10] as i32;
2107        let addr = self.state.regs[11];
2108        let size = usize::try_from(self.state.regs[12]).map_err(|_| 22)?;
2109        // Read into host-owned memory first. This prevents the host I/O API from receiving
2110        // a raw guest pointer and lets the MMU validate the later copy.
2111        let mut bytes = vec![0; size];
2112
2113        // let size = 256;
2114
2115        // Descriptor 0 is the conventional standard input number.
2116        let count = if fd == 0 {
2117            io::stdin().read(&mut bytes).map_err(host_errno)?
2118        } else {
2119            let file = self.files.get_mut(&fd).ok_or(9)?;
2120            file.file.read(&mut bytes).map_err(host_errno)?
2121        };
2122
2123        // Copy only bytes returned by the host so unread buffer space stays unchanged.
2124        if !self.memory.store_bytes(addr, &bytes[..count]) {
2125            // Error 14 is `EFAULT`: the output guest range is not writable.
2126            return Err(14);
2127        }
2128        Ok(count as u64)
2129    }
2130
2131    fn sys_write(&mut self) -> Result<u64, i32> {
2132        // `write` receives the descriptor in x10, guest buffer in x11, and byte count in x12.
2133        let fd = self.state.regs[10] as i32;
2134        let size = usize::try_from(self.state.regs[12]).map_err(|_| 22)?;
2135        // Copy from guest memory before host I/O so invalid guest pages return `EFAULT`
2136        // instead of causing a host memory fault.
2137        let bytes = self
2138            .memory
2139            .load_bytes(self.state.regs[11], size)
2140            .ok_or(14)?;
2141
2142        let count = self.write_fd(fd, &bytes)?;
2143        Ok(count as u64)
2144    }
2145
2146    fn sys_writev(&mut self) -> Result<u64, i32> {
2147        // `writev` receives the descriptor in x10, iovec address in x11, and count in x12.
2148        let fd = self.state.regs[10] as i32;
2149        let iov = self.state.regs[11];
2150        let count = usize::try_from(self.state.regs[12]).map_err(|_| 22)?;
2151        // Bound allocation and guest-memory work for invalid vector counts.
2152        // Linux defines `UIO_MAXIOV` as 1024, so larger arrays are invalid.
2153        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/uio.h
2154        if count > 1024 {
2155            return Err(22);
2156        }
2157
2158        let mut bytes = Vec::new();
2159        for index in 0..count {
2160            // RV64 `struct iovec` contains an 8-byte pointer followed by an 8-byte size.
2161            // Thus, each record is 16 bytes and record `index` starts at `iov + index * 16`.
2162            let entry = self
2163                .memory
2164                .load_bytes(iov + (index * 16) as u64, 16)
2165                .ok_or(14)?;
2166            // The first byte ranges have known length because `load_bytes` returned 16 bytes.
2167            // Convert each field from guest little-endian order to a host integer.
2168            let addr = u64::from_le_bytes(entry[..8].try_into().unwrap());
2169            let size = u64::from_le_bytes(entry[8..].try_into().unwrap());
2170            let size = usize::try_from(size).map_err(|_| 22)?;
2171            bytes.extend(self.memory.load_bytes(addr, size).ok_or(14)?);
2172        }
2173
2174        Ok(self.write_fd(fd, &bytes)? as u64)
2175    }
2176
2177    // todo
2178    fn write_fd(&mut self, fd: i32, bytes: &[u8]) -> Result<usize, i32> {
2179        // Mark terminal output so emulator diagnostics stay distinct from guest text.
2180        match fd {
2181            // Descriptors 1 and 2 conventionally start as standard output and error.
2182            // This runtime keeps those meanings fixed instead of permitting close and reuse.
2183            1 => write_marked_output(
2184                io::stdout().lock(),
2185                bytes,
2186                "stdout",
2187                // ANSI sequence 1;36 selects bright cyan on a compatible terminal.
2188                "\x1b[1;36m", // juckt
2189                io::stdout().is_terminal(),
2190            ),
2191            2 => write_marked_output(
2192                io::stderr().lock(),
2193                bytes,
2194                "stderr",
2195                // ANSI sequence 1;31 selects bright red for error output.
2196                "\x1b[1;31m",
2197                io::stderr().is_terminal(),
2198            ),
2199            _ => self
2200                .files
2201                .get_mut(&fd)
2202                .ok_or(9)?
2203                .file
2204                .write(bytes)
2205                .map_err(host_errno),
2206        }
2207    }
2208
2209    fn sys_pread64(&mut self) -> Result<u64, i32> {
2210        // `pread64` receives fd, buffer, count, and file offset in x10 through x13.
2211        let fd = self.state.regs[10] as i32;
2212        let addr = self.state.regs[11];
2213        let size = usize::try_from(self.state.regs[12]).map_err(|_| 22)?;
2214        let offset = self.state.regs[13];
2215        let file = self.files.get(&fd).ok_or(9)?;
2216
2217        let mut bytes = vec![0; size];
2218
2219        // Positional reads must not change the file offset saved in runtime snapshots.
2220        let count = file.file.read_at(&mut bytes, offset).map_err(host_errno)?;
2221
2222        if !self.memory.store_bytes(addr, &bytes[..count]) {
2223            return Err(14);
2224        }
2225        Ok(count as u64)
2226    }
2227
2228    fn sys_lseek(&mut self) -> Result<u64, i32> {
2229        // `lseek` receives fd in x10, signed offset in x11, and origin in x12.
2230        let fd = self.state.regs[10] as i32;
2231        let offset = self.state.regs[11] as i64;
2232        let whence = self.state.regs[12];
2233        let file = self.files.get_mut(&fd).ok_or(9)?;
2234        // Linux values 0, 1, and 2 are `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`.
2235        // A start-relative position cannot be negative, but current and end offsets can be.
2236        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/fs.h
2237        let position = match whence {
2238            0 if offset >= 0 => SeekFrom::Start(offset as u64),
2239            0 => return Err(22),
2240            1 => SeekFrom::Current(offset),
2241            2 => SeekFrom::End(offset),
2242            _ => return Err(22),
2243        };
2244
2245        file.file.seek(position).map_err(host_errno)
2246    }
2247
2248    fn sys_faccessat(&self) -> Result<u64, i32> {
2249        // `faccessat` receives dirfd in x10, path in x11, and access mode in x12.
2250        // This subset checks path existence below the selected root. It does not evaluate
2251        // the requested access-mode argument against guest credentials.
2252        if self.state.regs[10] as i64 != -100 {
2253            return Err(22);
2254        }
2255        let path = self.read_guest_string(self.state.regs[11], 4096)?;
2256        self.resolve_process_path(&path, false).map(|_| 0)
2257    }
2258
2259    fn sys_fstat(&mut self) -> Result<u64, i32> {
2260        // `fstat` receives the descriptor in x10 and the guest `stat` address in x11.
2261        let fd = self.state.regs[10] as i32;
2262        // Standard streams have no owned `File`, so write a synthetic character-device record.
2263        let metadata = if (0..=2).contains(&fd) {
2264            None
2265        } else {
2266            Some(
2267                self.files
2268                    .get(&fd)
2269                    .ok_or(9)?
2270                    .file
2271                    .metadata()
2272                    .map_err(host_errno)?,
2273            )
2274        };
2275        self.write_guest_stat(self.state.regs[11], metadata.as_ref())
2276    }
2277
2278    fn sys_newfstatat(&mut self) -> Result<u64, i32> {
2279        // `newfstatat` receives dirfd, path, output address, and flags in x10 through x13.
2280        // Allow only bit 8, Linux `AT_SYMLINK_NOFOLLOW` (0x100). `flags & !0x100`
2281        // clears that supported bit and detects if any unsupported bit remains.
2282        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/fcntl.h
2283        if self.state.regs[10] as i64 != -100 || self.state.regs[13] & !0x100 != 0 {
2284            return Err(22);
2285        }
2286        let path = self.read_guest_string(self.state.regs[11], 4096)?;
2287        let path = self.resolve_process_path(&path, false)?;
2288        // `symlink_metadata` normally describes the link itself, but path resolution above
2289        // already canonicalizes and follows the final link. NOFOLLOW is not fully implemented.
2290        let metadata = if self.state.regs[13] & 0x100 != 0 {
2291            fs::symlink_metadata(path)
2292        } else {
2293            fs::metadata(path)
2294        }
2295        .map_err(host_errno)?;
2296
2297        self.write_guest_stat(self.state.regs[12], Some(&metadata))
2298
2299        // dbg!(self.state);
2300    }
2301
2302    fn sys_readlinkat(&mut self) -> Result<u64, i32> {
2303        // `readlinkat` receives dirfd, path, output address, and size in x10 through x13.
2304        // This current shortcut ignores dirfd and path. It returns the configured argv[0].
2305        // let path = self.read_guest_string(self.state.regs[11], 4096)?;
2306
2307        // if path != "/proc/self/exe" {
2308        //     return Err(2);
2309        // }
2310
2311        let size = usize::try_from(self.state.regs[13]).map_err(|_| 22)?;
2312        // Report argv[0] so `/proc/self/exe` style guest queries see their guest executable name.
2313        let bytes = self
2314            .process
2315            .argv
2316            .first()
2317            .map(String::as_bytes)
2318            .unwrap_or_default();
2319
2320        // `readlinkat` does not append a NUL byte. Copy at most the supplied buffer size
2321        // and return the exact number of bytes that were copied.
2322        let count = size.min(bytes.len());
2323
2324        if !self
2325            .memory
2326            .store_bytes(self.state.regs[12], &bytes[..count])
2327        {
2328            return Err(14);
2329        }
2330        Ok(count as u64)
2331    }
2332
2333    fn sys_getcwd(&mut self) -> Result<u64, i32> {
2334        // `getcwd` receives the output address in x10 and buffer size in x11.
2335        let addr = self.state.regs[10];
2336        // The virtual process always starts at guest root. Two bytes are needed for `/`
2337        // and its C-string NUL terminator.
2338        if self.state.regs[11] < 2 || !self.memory.store_bytes(addr, b"/\0") {
2339            return Err(14);
2340        }
2341        // The raw Linux syscall should return length 2, including NUL. This implementation
2342        // returns the buffer address and also uses EFAULT for an undersized buffer.
2343        Ok(addr)
2344    }
2345
2346    fn sys_brk(&mut self) -> Result<u64, i32> {
2347        // `brk` receives the requested first address after the heap in x10.
2348        let requested = self.state.regs[10];
2349
2350        if requested == 0 {
2351            // Linux permits a zero request to query the current program break.
2352            return Ok(self.memory.brk);
2353        }
2354
2355        // Keep the heap below the current automatic mmap cursor. Successful mmap calls can
2356        // move this boundary upward, so it is not a fixed heap limit.
2357        if requested >= self.mmap_base {
2358            // Linux `brk` reports the unchanged break when it cannot grant the request.
2359            return Ok(self.memory.brk);
2360        }
2361
2362        let old = self.memory.brk;
2363
2364        if requested > old {
2365            // The break can end within a page. Map only complete new pages between the
2366            // old and requested page-rounded ends, with normal heap read/write permission.
2367            let start = align_up(old, PAGE_SIZE as u64);
2368            let end = align_up(requested, PAGE_SIZE as u64);
2369            if end > start {
2370                self.memory
2371                    .map_zeroed(start, end - start, PagePerms::READ | PagePerms::WRITE)
2372                    .map_err(|_| 12)?;
2373            }
2374        } else {
2375            // Keep the page that contains the new break. Only pages wholly above it can go.
2376            let start = align_up(requested, PAGE_SIZE as u64);
2377            let end = align_up(old, PAGE_SIZE as u64);
2378            if end > start {
2379                self.memory.unmap(start, end - start).map_err(|_| 22)?;
2380                // Removed heap pages can contain code, so their compiled entries are unsafe to keep.
2381                self.invalidate_cache(start, end - start);
2382            }
2383        }
2384
2385        self.memory.brk = requested;
2386        Ok(requested)
2387    }
2388
2389    fn sys_mmap(&mut self) -> Result<u64, i32> {
2390        // `mmap` receives address, length, protection, flags, fd, and offset in x10 through x15.
2391        let hint = self.state.regs[10];
2392        // Linux mappings cover complete pages, so round a nonzero byte length up to 4 KiB.
2393        // `align_up` assumes that the rounded guest length fits in u64.
2394        let size = align_up(self.state.regs[11], PAGE_SIZE as u64);
2395        let prot = self.state.regs[12];
2396        let flags = self.state.regs[13];
2397        // Linux flag 0x10 is `MAP_FIXED`: the address is a requirement, not a hint.
2398        let fixed = flags & 0x10 != 0;
2399
2400
2401        // Linux flag 0x20 is `MAP_ANONYMOUS`: no file supplies the initial bytes.
2402        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h
2403        let anonymous = flags & 0x20 != 0;
2404        // Only MAP_FIXED and MAP_ANONYMOUS affect this implementation. It does not implement
2405        // or validate MAP_PRIVATE, MAP_SHARED, or other Linux mmap flags.
2406
2407        // Protection bits 0, 1, and 2 mean read, write, and execute. Decimal 7 is the
2408        // mask `0b111`. AND with its complement detects all unsupported protection bits.
2409        if size == 0 || prot & !7 != 0 {
2410            return Err(22);
2411        }
2412
2413        // Honor a free hint, but use the deterministic mmap cursor when it overlaps a mapping.
2414        let base = if fixed {
2415            // A fixed mapping must start at a page boundary because it can replace pages.
2416            if !hint.is_multiple_of(PAGE_SIZE as u64) {
2417                return Err(22);
2418            }
2419            hint
2420        } else if hint != 0
2421            && self
2422                .memory
2423                .range_is_free(align_down(hint, PAGE_SIZE as u64), size)
2424        {
2425            align_down(hint, PAGE_SIZE as u64)
2426        } else {
2427            self.memory
2428                .find_free_range(self.mmap_base, size)
2429                .ok_or(12)?
2430        };
2431
2432        let bytes = if anonymous {
2433            // An empty source tells the MMU to zero-fill the complete new mapping.
2434            Vec::new()
2435        } else {
2436            let fd = self.state.regs[14] as i32;
2437            let offset = self.state.regs[15];
2438            // Linux file mappings require the byte offset to be a multiple of page size.
2439            if !offset.is_multiple_of(PAGE_SIZE as u64) {
2440                return Err(22);
2441            }
2442            let file = self.files.get(&fd).ok_or(9)?;
2443            let mut bytes = vec![0; size as usize];
2444            let count = file.file.read_at(&mut bytes, offset).map_err(host_errno)?;
2445            // Keep only bytes that the host file supplied. The MMU zero-fills the tail.
2446            bytes.truncate(count);
2447            bytes
2448        };
2449
2450        self.memory
2451            .replace_mapping(base, size, page_perms(prot), &bytes)
2452            .map_err(|_| 12)?;
2453        // Replacement can change executable bytes or permissions at an existing guest PC.
2454        self.invalidate_cache(base, size);
2455        // A successful replacement validated that the mapping end fits in u64.
2456        self.mmap_base = self.mmap_base.max(base + size);
2457
2458        // dbg!(self.memory);
2459
2460        Ok(base)
2461    }
2462
2463    fn sys_mprotect(&mut self) -> Result<u64, i32> {
2464        // `mprotect` receives address, length, and protection in x10 through x12.
2465        let base = self.state.regs[10];
2466        let size = self.state.regs[11];
2467        let prot = self.state.regs[12];
2468        // As in `mmap`, reject every protection bit outside the low R/W/X mask.
2469        if prot & !7 != 0 || !self.memory.protect(base, size, page_perms(prot)) {
2470            return Err(22);
2471        }
2472        Ok(0)
2473    }
2474
2475    fn sys_munmap(&mut self) -> Result<u64, i32> {
2476        // `munmap` receives address in x10 and byte length in x11.
2477        let base = self.state.regs[10];
2478        let size = self.state.regs[11];
2479        self.memory.unmap(base, size).map_err(|_| 22)?;
2480        self.invalidate_cache(base, size);
2481        Ok(0)
2482    }
2483
2484    fn sys_getrandom(&mut self) -> Result<u64, i32> {
2485        // `getrandom` receives output address in x10, byte count in x11, and flags in x12.
2486        // This deterministic subset ignores all flag values, including invalid Linux flags.
2487        let size = usize::try_from(self.state.regs[11]).map_err(|_| 22)?;
2488        // Deterministic bytes make replay and fuzzing results reproducible.
2489        // They are not random and are not safe for cryptographic or security use.
2490        // The odd multiplier 31 visits each byte value once for every 256 indices.
2491        // The offset 17 avoids an all-zero prefix. Wrapping keeps arithmetic modulo 256.
2492        let bytes = (0..size)
2493            .map(|idx| (idx as u8).wrapping_mul(31).wrapping_add(17))
2494            .collect::<Vec<_>>();
2495        if !self.memory.store_bytes(self.state.regs[10], &bytes) {
2496            return Err(14);
2497        }
2498
2499        Ok(size as u64)
2500    }
2501
2502    fn sys_clock_gettime(&mut self) -> Result<u64, i32> {
2503        // `clock_gettime` receives the clock ID in x10 and output address in x11.
2504        // This deterministic clock accepts and ignores every clock ID. Linux does not do this;
2505        // it is an ABI limitation of the emulator.
2506        // A fixed time removes host timing from deterministic guest runs.
2507        // RV64 `timespec` has one 8-byte seconds field and one 8-byte nanoseconds field.
2508        // Sixteen zero bytes therefore represent time zero without host-layout casts.
2509        if !self.memory.store_bytes(self.state.regs[11], &[0; 16]) {
2510            return Err(14);
2511        }
2512        Ok(0)
2513    }
2514
2515    fn sys_sched_getaffinity(&mut self) -> Result<u64, i32> {
2516        // `sched_getaffinity` receives pid, mask size, and mask address in x10 through x12.
2517        // This single-task model does not need the pid to select another guest task.
2518        let size = usize::try_from(self.state.regs[11]).map_err(|_| 22)?;
2519        // Expose one virtual CPU so guests do not create host-dependent worker counts.
2520        // Bit 0 in the byte selects virtual CPU 0. Return 1 because one mask byte was written.
2521        // Raw Linux returns its kernel affinity-mask byte size, which need not be one.
2522        // Returning one is an emulator policy and ABI limitation.
2523        if size == 0 || !self.memory.store_bytes(self.state.regs[12], &[1]) {
2524            return Err(14);
2525        }
2526        Ok(1)
2527    }
2528
2529    fn sys_uname(&mut self) -> Result<u64, i32> {
2530        // `uname` receives the guest `utsname` output address in x10.
2531        // Fixed identity data prevents host details from changing guest behavior.
2532        // Linux `new_utsname` has six arrays of 65 bytes. Each array permits 64 text
2533        // bytes plus one NUL. The arrays are system, node, release, version, machine,
2534        // and domain name.
2535        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/utsname.h
2536        let mut uts = [0u8; 65 * 6];
2537        for (index, value) in ["Linux", "remu", "6.0.0", "#1", "riscv64", ""]
2538            .iter()
2539            .enumerate()
2540        {
2541            // Multiply by one complete field width to keep text in its own NUL-filled array.
2542            let start = index * 65;
2543            uts[start..start + value.len()].copy_from_slice(value.as_bytes());
2544        }
2545        if !self.memory.store_bytes(self.state.regs[10], &uts) {
2546            return Err(14);
2547        }
2548        Ok(0)
2549    }
2550
2551    fn sys_getrlimit(&mut self) -> Result<u64, i32> {
2552        // `getrlimit` receives the resource in x10 and output address in x11.
2553        self.write_guest_rlimit(self.state.regs[11], self.state.regs[10])
2554    }
2555
2556    fn sys_prlimit64(&mut self) -> Result<u64, i32> {
2557        // `prlimit64` receives pid, resource, new-limit pointer, and old-limit pointer
2558        // in x10 through x13. This one-process model ignores pid.
2559        // A non-null x12 is a request to set a new limit. Reject it because emulator
2560        // policy limits must stay consistent with mapped memory and descriptor handling.
2561        if self.state.regs[12] != 0 {
2562            return Err(22);
2563        }
2564        // A non-null x13 requests the old limit. x11 contains the resource number.
2565        if self.state.regs[13] != 0 {
2566            self.write_guest_rlimit(self.state.regs[13], self.state.regs[11])?;
2567        }
2568        Ok(0)
2569    }
2570
2571    fn read_guest_string(&self, addr: u64, max: usize) -> Result<String, i32> {
2572        // Linux path arguments are NUL-terminated byte strings in guest memory. This runtime
2573        // chooses a Rust `String`, so it accepts only UTF-8 guest paths.
2574        // Path handlers pass 4096 as an emulator work limit because it is one selected guest
2575        // page. This value is not a universal Linux pathname limit.
2576        let mut bytes = Vec::new();
2577        let mut current = addr;
2578
2579        // Read per page so a terminator before an unmapped next page still succeeds.
2580        while bytes.len() < max {
2581            // A 4 KiB page is a power of two. AND with 4095 (`PAGE_SIZE - 1`) keeps
2582            // the low 12 address bits, which are the offset inside the current page.
2583            // Subtract that offset to get the byte count up to the next page boundary.
2584            let page_left = PAGE_SIZE - (current as usize & (PAGE_SIZE - 1));
2585            let count = page_left.min(max - bytes.len());
2586            let chunk = self.memory.load_bytes(current, count).ok_or(14)?;
2587            if let Some(end) = chunk.iter().position(|byte| *byte == 0) {
2588                // Exclude the NUL marker. Error 22 (`EINVAL`) reports a non-UTF-8 path.
2589                bytes.extend_from_slice(&chunk[..end]);
2590                return String::from_utf8(bytes).map_err(|_| 22);
2591            }
2592            bytes.extend_from_slice(&chunk);
2593            current = current.checked_add(count as u64).ok_or(14)?;
2594        }
2595
2596        // No NUL occurred within the policy limit, so the path argument is invalid.
2597        Err(22)
2598    }
2599
2600    // todo - egal
2601    fn write_guest_stat(&mut self, addr: u64, metadata: Option<&fs::Metadata>) -> Result<u64, i32> {
2602        // Build the RV64 Linux byte layout explicitly because the host Rust type has another ABI.
2603        // The generic 64-bit Linux `struct stat` is 128 bytes. It has explicit padding at
2604        // bytes 40..48 and 60..64, and unused bytes at 120..128. Starting with zero fills
2605        // these ABI gaps and prevents host data from leaking to the guest.
2606        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/stat.h
2607        let mut stat = [0u8; 128];
2608        if let Some(metadata) = metadata {
2609            // Bytes 0 and 8 hold 64-bit device and inode numbers.
2610            put_u64(&mut stat, 0, metadata.dev());
2611            put_u64(&mut stat, 8, metadata.ino());
2612            // Bytes 16 through 28 hold four 32-bit values: mode, link count, user ID,
2613            // and group ID. These widths are part of the guest ABI, not host Rust types.
2614            put_u32(&mut stat, 16, metadata.mode());
2615            put_u32(&mut stat, 20, metadata.nlink() as u32);
2616            put_u32(&mut stat, 24, metadata.uid());
2617            put_u32(&mut stat, 28, metadata.gid());
2618            // Byte 32 holds the special-file device. Byte 40 is the first padding slot.
2619            put_u64(&mut stat, 32, metadata.rdev());
2620            // File size starts at byte 48. Preferred I/O block size is a 32-bit value at 56.
2621            put_u64(&mut stat, 48, metadata.size());
2622            put_u32(&mut stat, 56, metadata.blksize() as u32);
2623            // Byte 60 is padding. The 64-bit count of allocated 512-byte blocks starts at 64.
2624            put_u64(&mut stat, 64, metadata.blocks());
2625            // Three timestamp pairs follow. Each pair has 8-byte seconds and 8-byte
2626            // nanoseconds fields, at offsets 72, 88, and 104.
2627            put_u64(&mut stat, 72, metadata.atime() as u64);
2628            put_u64(&mut stat, 80, metadata.atime_nsec() as u64);
2629            put_u64(&mut stat, 88, metadata.mtime() as u64);
2630            put_u64(&mut stat, 96, metadata.mtime_nsec() as u64);
2631            put_u64(&mut stat, 104, metadata.ctime() as u64);
2632            put_u64(&mut stat, 112, metadata.ctime_nsec() as u64);
2633        } else {
2634            // Octal 020000 is `S_IFCHR`, and 0666 gives read/write permission to all users.
2635            // This makes a standard stream look like a character device instead of a file.
2636            put_u32(&mut stat, 16, 0o020666);
2637            // A synthetic stream has one link and the same unprivileged ID 1000 as auxv.
2638            put_u32(&mut stat, 20, 1);
2639            put_u32(&mut stat, 24, 1000);
2640            put_u32(&mut stat, 28, 1000);
2641            // Report 4 KiB as the preferred I/O block size to match guest memory pages.
2642            put_u32(&mut stat, 56, PAGE_SIZE as u32);
2643        }
2644        if !self.memory.store_bytes(addr, &stat) {
2645            return Err(14);
2646        }
2647        Ok(0)
2648    }
2649
2650    fn resolve_process_path(&self, guest_path: &str, create: bool) -> Result<PathBuf, i32> {
2651        let path = Path::new(guest_path);
2652        // Absolute guest paths use the sysroot; relative paths use the writable work directory.
2653        let root = if path.is_absolute() {
2654            // Error 2 is `ENOENT`. Without a sysroot, no absolute guest tree exists.
2655            self.process.sysroot.as_ref().ok_or(2)?
2656        } else {
2657            self.process.workdir.as_ref().ok_or(2)?
2658        };
2659
2660        resolve_beneath(root, path, create).map_err(host_errno)
2661    }
2662
2663    fn write_guest_rlimit(&mut self, addr: u64, resource: u64) -> Result<u64, i32> {
2664        // Linux resource 3 is `RLIMIT_STACK`, and 7 is `RLIMIT_NOFILE`. The project
2665        // exposes its 128 KiB mapped stack and reports 1024 descriptors. It does not enforce
2666        // that descriptor limit. It also reports infinity for unknown resources, although
2667        // Linux normally returns EINVAL. Infinity has an all-ones unsigned representation.
2668        // Source: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/resource.h
2669        let value = match resource {
2670            3 => DEFAULT_STACK_SIZE,
2671            7 => 1024,
2672            _ => u64::MAX,
2673        };
2674        // RV64 `struct rlimit` has two 8-byte words. Write the same value as the soft
2675        // current limit and the hard maximum limit.
2676        if !self.memory.store_le(addr, 8, value) || !self.memory.store_le(addr + 8, 8, value) {
2677            return Err(14);
2678        }
2679        Ok(0)
2680    }
2681
2682    fn invalidate_cache(&mut self, base: u64, size: u64) {
2683        // Native code is tied to guest addresses whose mappings can be replaced or removed.
2684        // Saturating addition treats an overflowing range as one that reaches the highest
2685        // guest address. It cannot wrap and retain low-address entries by mistake.
2686        let end = base.saturating_add(size);
2687        self.cache.retain(|pc, _| *pc < base || *pc >= end);
2688        self.sync_jit_entries();
2689    }
2690
2691    fn sync_jit_entries(&mut self) {
2692        // Rebuild after each cache change because vector growth can move the raw ABI array.
2693        self.jit_entries = self
2694            .cache
2695            .iter()
2696            .map(|(&guest_pc, compiled)| JitEntryAbi {
2697                guest_pc,
2698                execute: compiled.entry(),
2699            })
2700            .collect();
2701        // `as_ptr` borrows vector storage without moving entries. Generated code must use
2702        // the paired count and must not keep the pointer after the cache changes.
2703        self.state.jit_entries = self.jit_entries.as_ptr();
2704        self.state.jit_entry_count = self.jit_entries.len();
2705
2706        // dbg!(self.state.jit_entries);
2707    }
2708
2709    fn initialize_process_state(&mut self) -> JitResult<()> {
2710        // RISC-V x2 is `sp`. Begin below the exact mapping top so startup data can grow down.
2711        self.state.regs[2] = self.memory.stack_top - DEFAULT_STACK_POINTER_OFFSET;
2712
2713        // Use the linker-defined global pointer because RISC-V code can address data through it.
2714        self.state.regs[3] = self
2715            .program
2716            .find_symbol("__global_pointer$")
2717            .map(|symbol| symbol.value)
2718            .unwrap_or(0);
2719
2720        // RISC-V x4 is `tp`, the thread pointer. Zero is valid until a guest dynamic loader
2721        // sets up thread-local storage. This runtime does not create a guest thread itself.
2722        self.state.regs[4] = 0;
2723
2724        self.build_elf_entry_state()?;
2725        self.state.pc = self.initial_pc;
2726        Ok(())
2727    }
2728
2729    // todo - quatsch
2730    fn build_elf_entry_state(&mut self) -> JitResult<()> {
2731        // Linux starts a process with strings at high stack addresses and an entry table
2732        // below them. The table contains argc, argv, envp, and auxiliary-vector records.
2733        // Source: https://github.com/torvalds/linux/blob/master/fs/binfmt_elf.c
2734        let mut sp = self.memory.stack_top - DEFAULT_STACK_POINTER_OFFSET;
2735
2736        // Clone these vectors to avoid a Rust borrow conflict: the stack-writing methods
2737        // need mutable access to the runtime while configuration is stored in the same value.
2738        let argv_values = self.process.argv.clone();
2739        let envp_values = self.process.envp.clone();
2740
2741        // Store strings first so the final entry table can contain stable guest pointers.
2742        let argv = self.push_guest_strings(&mut sp, &argv_values)?;
2743        let envp = self.push_guest_strings(&mut sp, &envp_values)?;
2744
2745        // Keep the 16-byte AT_RANDOM object aligned to 16 bytes. Linux defines the object
2746        // size as 16, and aligned placement also preserves the final RISC-V stack alignment.
2747        let random_addr = self.push_guest_stack_bytes(&mut sp, &ELF_AUX_RANDOM, 16)?;
2748
2749        let argc = argv.len() as u64;
2750        // Linux AT_EXECFN points at the executed pathname. This runtime uses argv[0] as an approximation.
2751        // Use null when no argument exists.
2752        let execfn_ptr = argv.first().copied().unwrap_or(0);
2753
2754        // todo -- siehe oben struct auxv
2755        let auxv = self.build_elf_auxv(random_addr, execfn_ptr)?;
2756
2757        // RV64 uses 8-byte words. The table has one argc word, one word per pointer,
2758        // one null word after each pointer list, and two words per auxiliary entry.
2759        let table_size = 8
2760            + ((argv.len() + 1) as u64 * 8)
2761            + ((envp.len() + 1) as u64 * 8)
2762            + (auxv.len() as u64 * 16);
2763
2764        // Linux requires an aligned initial stack for the process entry ABI.
2765        // The RISC-V psABI requires x2 to be aligned to 128 bits, which is 16 bytes.
2766        // Source: https://riscv-non-isa.github.io/riscv-elf-psabi-doc/#integer-calling-convention
2767        let table_addr = self.reserve_guest_stack(&mut sp, table_size, 16)?;
2768        self.write_elf_entry_table(table_addr, argc, &argv, &envp, &auxv)?;
2769
2770        // x1 is the return-address register. Process entry has no RISC-V caller, so zero is
2771        // the no-caller sentinel. A guest can still execute address zero if it is mapped.
2772        self.state.regs[1] = 0;
2773        self.state.regs[2] = table_addr;
2774        // `_start` reads argc and pointer lists from x2. Clear a0 through a2 so startup
2775        // cannot depend on old values from a reused runtime state.
2776        self.state.regs[10] = 0;
2777        self.state.regs[11] = 0;
2778        self.state.regs[12] = 0;
2779
2780        Ok(())
2781    }
2782
2783    // todo ??
2784    fn build_elf_auxv(&self, random_addr: u64, execfn_ptr: u64) -> JitResult<Vec<AuxEntry>> {
2785        // The main executable must expose its program headers because the dynamic loader
2786        // uses them before normal application code starts.
2787        let phdr_addr = self
2788            .program
2789            .elf_phdr_addr
2790            .ok_or_else(|| io::Error::other("missing ELF program header address"))?;
2791
2792        // Keep the records explicit. This makes guest-visible process policy independent
2793        // from the host process and gives the dynamic loader a stable environment.
2794        Ok(vec![
2795            (AT_PHDR, phdr_addr),
2796            (AT_PHENT, self.program.elf_phent_size as u64),
2797            (AT_PHNUM, self.program.elf_phnum as u64),
2798            (AT_PAGESZ, PAGE_SIZE as u64),
2799            // Zero means no interpreter. Otherwise this is the fixed loader bias.
2800            (AT_BASE, self.loader_base),
2801            (AT_FLAGS, 0),
2802            (AT_ENTRY, self.program.entry_pc),
2803            // ID 1000 models one unprivileged user and agrees with identity syscalls.
2804            (AT_UID, 1000),
2805            (AT_EUID, 1000),
2806            (AT_GID, 1000),
2807            (AT_EGID, 1000),
2808            (AT_HWCAP, RISCV_HWCAP),
2809            // Advertise the fixed policy value of 100 clock ticks per second.
2810            (AT_CLKTCK, 100),
2811            (AT_SECURE, 0),
2812            (AT_RANDOM, random_addr),
2813            (AT_HWCAP2, 0),
2814            (AT_EXECFN, execfn_ptr),
2815            // The final zero type is mandatory because consumers scan until AT_NULL.
2816            (AT_NULL, 0),
2817        ])
2818    }
2819
2820    fn write_elf_entry_table(
2821        &mut self,
2822        table_addr: u64,
2823        argc: u64,
2824        argv: &[u64],
2825        envp: &[u64],
2826        auxv: &[AuxEntry],
2827    ) -> JitResult<()> {
2828        // `c` is a guest byte address. Each write advances it by one RV64 word.
2829        let mut c = table_addr;
2830
2831        self.write_guest_u64(c, argc, "argc")?;
2832        c += 8;
2833
2834        // The table order is argc, argv pointers, null, envp pointers, null, then auxv.
2835        self.write_u64_s(&mut c, argv, "argv entry")?;
2836        self.write_guest_u64(c, 0, "argv terminator")?;
2837        c += 8;
2838
2839        self.write_u64_s(&mut c, envp, "envp entry")?;
2840        self.write_guest_u64(c, 0, "envp terminator")?;
2841        c += 8;
2842
2843        // Write each auxiliary entry as its type word followed by its value word, as the ABI requires.
2844        for &(kind, value) in auxv {
2845            self.write_guest_u64(c, kind, "auxv type")?;
2846            c += 8;
2847            self.write_guest_u64(c, value, "auxv value")?;
2848            c += 8;
2849        }
2850
2851        Ok(())
2852    }
2853
2854    fn write_u64_s(&mut self, cursor: &mut u64, values: &[u64], what: &str) -> JitResult<()> {
2855        for &value in values {
2856            self.write_guest_u64(*cursor, value, what)?;
2857            // Advance by 8 because every pointer and integer word is 64 bits on RV64.
2858            *cursor += 8;
2859        }
2860
2861        Ok(())
2862    }
2863
2864    fn write_guest_u64(&mut self, addr: u64, value: u64, what: &str) -> JitResult<()> {
2865        // Store exactly 8 bytes in guest little-endian order. Do not copy the host memory
2866        // representation because host byte order is not part of the guest ABI.
2867        if !self.memory.store_le(addr, 8, value) {
2868            return Err(io::Error::other(format!("failed to write guest {what}")).into());
2869        }
2870
2871        Ok(())
2872    }
2873
2874    // fn invalidate_cache(&mut self, base: u64, size: u64) {
2875    //     let end = base.saturating_add(size);
2876    //     self.cache.retain(|pc, _| *pc < base || *pc >= end);
2877    //     self.sync_jit_entries();
2878    // }
2879
2880    fn push_guest_strings(&mut self, sp: &mut u64, values: &[String]) -> JitResult<Vec<u64>> {
2881        let mut addresses = Vec::with_capacity(values.len());
2882
2883        // Push in reverse because the stack grows down, then restore logical argument order.
2884        for value in values.iter().rev() {
2885            // Linux C strings need one zero byte after their text.
2886            let mut bytes = value.as_bytes().to_vec();
2887            bytes.push(0);
2888            let addr = self.push_guest_stack_bytes(sp, &bytes, 1)?;
2889            addresses.push(addr);
2890        }
2891
2892        addresses.reverse();
2893        Ok(addresses)
2894    }
2895
2896    fn push_guest_stack_bytes(&mut self, sp: &mut u64, bytes: &[u8], align: u64) -> JitResult<u64> {
2897        // Reserve before writing because a downward-growing stack places new data below `sp`.
2898        let addr = self.reserve_guest_stack(sp, bytes.len() as u64, align)?;
2899
2900        if !self.memory.store_bytes(addr, bytes) {
2901            return Err(io::Error::other("failed to write guest stack bytes").into());
2902        }
2903        Ok(addr)
2904    }
2905
2906    fn reserve_guest_stack(&mut self, sp: &mut u64, size: u64, align: u64) -> JitResult<u64> {
2907        // Checked subtraction rejects a request that would wrap below guest address zero.
2908        let next = sp
2909            .checked_sub(size)
2910            .ok_or_else(|| io::Error::other("guest stack underflow"))?;
2911
2912        // Alignment can add padding below the object. `max(1)` prevents a zero alignment.
2913        // `align_down` requires a power of two. Current callers supply only 1 or 16.
2914        let aligned = align_down(next, align.max(1));
2915
2916        // The stack mapping starts 128 KiB below its fixed top. Reject data outside it.
2917        if aligned < DEFAULT_STACK_TOP - DEFAULT_STACK_SIZE {
2918            return Err(io::Error::other("overflow!").into());
2919        }
2920
2921        *sp = aligned;
2922        Ok(aligned)
2923    }
2924}
2925
2926// todo - quatsch
2927// Finds known block-entry PCs so lazy compilation can include direct neighbors.
2928fn direct_successors(nnil: &Nnil) -> Vec<u64> {
2929    let mut successors = Vec::new();
2930
2931    // Use only explicit targets so lazy batching never guesses an indirect destination.
2932    // NNIL regions end with their control-flow operation. Earlier instructions cannot
2933    // select the next region, so only the final instruction is relevant here.
2934    match nnil.insns().last() {
2935        Some(
2936            NnilInstruction::Beq { ttgt, ftgt, .. }
2937            | NnilInstruction::Bne { ttgt, ftgt, .. }
2938            | NnilInstruction::Blt { ttgt, ftgt, .. }
2939            | NnilInstruction::Bgt { ttgt, ftgt, .. },
2940        ) => {
2941            // Conditional branches have one false label and one true label. Both can be
2942            // the next block, so resolve both labels to guest addresses.
2943            if let Some(address) = nnil.label_address(*ftgt) {
2944                successors.push(address.0);
2945            }
2946            if let Some(address) = nnil.label_address(*ttgt) {
2947                successors.push(address.0);
2948            }
2949        }
2950
2951        Some(NnilInstruction::Jr { dst } | NnilInstruction::Jmp { dst }) => {
2952            // `dst` is an NNIL instruction handle. It is a static successor only when that
2953            // instruction is a nonnegative immediate guest address.
2954            if let Some(NnilInstruction::Imm { imm }) = nnil.insns().get(dst.0 as usize)
2955                && *imm >= 0
2956            {
2957                successors.push(*imm as u64);
2958            }
2959        }
2960
2961
2962        Some(NnilInstruction::Syscall {} | NnilInstruction::Sysbreak {}) => {
2963            // Generated code records the post-region PC before either stop. A later resume
2964            // starts there; a syscall can also resume inside the runtime.
2965            successors.push(nnil.pc());
2966        }
2967        _ => {}
2968    }
2969
2970    successors
2971}
2972
2973fn resolve_guest_path(sysroot: &Path, guest_path: &str) -> io::Result<PathBuf> {
2974    // Check ELF interpreter paths against the configured sysroot.
2975    if !guest_path.starts_with('/') {
2976        return Err(io::Error::other("guest path is not absolute"));
2977    }
2978
2979    resolve_beneath(sysroot, Path::new(guest_path), false)
2980}
2981
2982// todo - macht fuzzer schon?
2983// Converts a guest path to a host path and checks its current canonical target against `root`.
2984fn resolve_beneath(root: &Path, path: &Path, create: bool) -> io::Result<PathBuf> {
2985    let mut relative = PathBuf::new();
2986
2987    // Reject parent components before canonicalization to make escape attempts explicit.
2988    for component in path.components() {
2989        match component {
2990            // Ignore the guest root marker so `PathBuf::join` cannot replace the host root.
2991            // A current-directory marker does not change the location.
2992            Component::RootDir | Component::CurDir => {}
2993            // Normal components are the only guest-controlled text added below the root.
2994            Component::Normal(value) => relative.push(value),
2995            // `..` can leave the root. A platform prefix can select another host volume.
2996            Component::ParentDir | Component::Prefix(_) => {
2997                return Err(io::Error::new(
2998                    io::ErrorKind::PermissionDenied,
2999                    "guest path escapes its root",
3000                ));
3001            }
3002        }
3003    }
3004
3005    let candidate = root.join(relative);
3006
3007    // Canonicalization detects symbolic links that leave the allowed root at check time.
3008    // A later host-side link change can race the file operation, so this is not a strong sandbox boundary.
3009    match fs::canonicalize(&candidate) {
3010        // `Path::starts_with` compares path components, not an unsafe text prefix.
3011        Ok(path) if path.starts_with(root) => Ok(path),
3012        Ok(_) => Err(io::Error::new(
3013            io::ErrorKind::PermissionDenied,
3014            "guest path escapes its root",
3015        )),
3016
3017        Err(err) if create && err.kind() == io::ErrorKind::NotFound => {
3018            // A new file has no canonical path, so validate its existing parent instead.
3019            let parent = candidate
3020                .parent()
3021                .ok_or_else(|| io::Error::other("guest path has no parent"))?;
3022            let parent = fs::canonicalize(parent)?;
3023            if !parent.starts_with(root) {
3024                return Err(io::Error::new(
3025                    io::ErrorKind::PermissionDenied,
3026                    "guest path escapes its root",
3027                ));
3028            }
3029            let name = candidate
3030                .file_name()
3031                .ok_or_else(|| io::Error::other("guest path has no file name"))?;
3032            Ok(parent.join(name))
3033        }
3034        Err(err) => Err(err),
3035    }
3036}
3037
3038fn host_errno(err: io::Error) -> i32 {
3039    // Preserve native Unix error numbers when the host supplied one. These are guest Linux
3040    // values only on a Linux host. Another Unix host needs an explicit errno translation.
3041    if let Some(errno) = err.raw_os_error() {
3042        return errno;
3043    }
3044
3045    // Use Linux generic errno values when Rust has only a portable error category:
3046    // 2=ENOENT, 13=EACCES, 17=EEXIST, and 22=EINVAL. Error 5, EIO, is the project fallback.
3047    // Source: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
3048    match err.kind() {
3049        io::ErrorKind::NotFound => 2,
3050        io::ErrorKind::PermissionDenied => 13,
3051        io::ErrorKind::AlreadyExists => 17,
3052        io::ErrorKind::InvalidInput | io::ErrorKind::InvalidData => 22,
3053        _ => 5,
3054    }
3055}
3056
3057fn guest_errno(errno: i32) -> u64 {
3058    // Linux reports syscall errors as negative values in the unsigned guest register.
3059    // The final cast keeps the two's-complement bit pattern that RV64 sees in x10.
3060    (-(errno as i64)) as u64
3061}
3062
3063// todo - code dup
3064fn write_marked_output(
3065    mut output: impl io::Write,
3066    bytes: &[u8],
3067    name: &str,
3068    color: &str,
3069    colored: bool,
3070) -> Result<usize, i32> {
3071
3072    // Add color only for terminals so redirected guest output has no escape sequences.
3073    let (color, reset) = if colored {
3074        // ANSI sequence 0 clears color and intensity after the marker.
3075        (color, "\x1b[0m")
3076    } else {
3077        ("", "")
3078    };
3079
3080    writeln!(output, "\n{color}----- guest {name} -----{reset}").map_err(host_errno)?;
3081    // `write` can complete only a prefix. Return that actual count as the guest result.
3082    let count = output.write(bytes).map_err(host_errno)?;
3083    // Add a display newline only when the written prefix has none. This newline is part
3084    // of the host marker and is not included in the guest byte count.
3085    if bytes
3086        .get(..count)
3087        .is_none_or(|written| !written.ends_with(b"\n"))
3088    {
3089        writeln!(output).map_err(host_errno)?;
3090    }
3091
3092    writeln!(output, "{color}----- end guest {name} -----{reset}").map_err(host_errno)?;
3093    // Flush now so guest output appears before later emulator diagnostics.
3094    output.flush().map_err(host_errno)?;
3095
3096    Ok(count)
3097}
3098
3099fn page_perms(prot: u64) -> PagePerms {
3100    // Linux `PROT_READ`, `PROT_WRITE`, and `PROT_EXEC` use the same low three bits as
3101    // `PagePerms`. Callers reject higher bits before this narrowing cast.
3102    PagePerms::from_bits(prot as u8)
3103}
3104
3105fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
3106    // A 32-bit field occupies four bytes. Explicit little-endian order builds RV64 data
3107    // correctly even if the host layout or alignment is different.
3108    // The caller must supply an in-bounds fixed ABI offset, or slicing will panic.
3109    bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
3110}
3111
3112fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
3113    // A 64-bit field occupies eight bytes. The caller must supply an in-bounds fixed ABI
3114    // offset, or slicing will panic.
3115    bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
3116}
3117
3118// todo
3119#[derive(Debug, Clone, Copy)]
3120// Store only section fields that later symbol and relocation code reads.
3121struct ElfSectionHeader {
3122    // The type selects symbol, string, relocation, or other section semantics.
3123    sh_type: u32,
3124    // Offset and size select this section's byte range in the ELF file.
3125    sh_offset: u64,
3126    sh_size: u64,
3127    // Its meaning depends on the section type. For SYMTAB or DYNSYM it selects the name
3128    // string table. For RELA it selects the related symbol table.
3129    sh_link: u32,
3130    // A nonzero entry size converts the section byte size to a record count.
3131    sh_entsize: u64,
3132}
3133
3134// todo -- gamozo macht readelf, nm
3135// Reads the ELF64 section table for optional symbol and import metadata.
3136fn parse_section_headers(
3137    bytes: &[u8],
3138    shoff: usize,
3139    shentsize: usize,
3140    shnum: usize,
3141) -> JitResult<Vec<ElfSectionHeader>> {
3142    if shnum == 0 {
3143        // An ELF can execute without section headers because program headers define mapping.
3144        // ELF also uses zero for extended numbering. This parser treats zero as no sections
3145        // and does not support that extended form.
3146        return Ok(Vec::new());
3147    }
3148
3149    let mut sections = Vec::with_capacity(shnum);
3150
3151    // Retain only fields needed for symbol and relocation parsing.
3152    for idx in 0..shnum {
3153        // Multiply the file-defined entry size by the index. Saturation prevents a low wrap
3154        // in that product, and checked addition protects the table base addition.
3155        let header = shoff
3156            .checked_add(idx.saturating_mul(shentsize))
3157            .ok_or_else(|| io::Error::other("section header overflow"))?;
3158
3159        // Check the declared entry range. The final `header + shentsize` is unchecked, and
3160        // this parser does not require the 64 bytes that its fixed ELF64 field reads need.
3161        let _ = bytes
3162            .get(header..header + shentsize)
3163            .ok_or_else(|| io::Error::other(""))?;
3164
3165        // ELF64 section fields start at byte offsets 4, 24, 32, 40, and 56.
3166        // Source: https://gabi.xinuos.com/elf/03-sheader.html
3167        sections.push(ElfSectionHeader {
3168            sh_type: read_u32(bytes, header + 4)?,
3169            sh_offset: read_u64(bytes, header + 24)?,
3170            sh_size: read_u64(bytes, header + 32)?,
3171            sh_link: read_u32(bytes, header + 40)?,
3172            sh_entsize: read_u64(bytes, header + 56)?,
3173        });
3174    }
3175
3176    Ok(sections)
3177}
3178
3179fn parse_symbol_section(
3180    bytes: &[u8],
3181    sections: &[ElfSectionHeader],
3182    section: &ElfSectionHeader,
3183) -> JitResult<Vec<ProgramSymbol>> {
3184    if section.sh_entsize == 0 {
3185        // A zero divisor cannot define records, so treat the malformed table as empty.
3186        return Ok(Vec::new());
3187    }
3188
3189    // ELF links each symbol table to the string table that owns its names.
3190    let strtab = sections
3191        .get(section.sh_link as usize)
3192        .ok_or_else(|| io::Error::other(""))?;
3193
3194    let strtab_bytes = read_elf_range(bytes, strtab.sh_offset, strtab.sh_size, "string table")?;
3195    // Integer division silently ignores an incomplete trailing record. This is a parser limit.
3196    // ELF64 symbols need 24 bytes, but this parser does not reject a smaller nonzero entry size.
3197    let count = (section.sh_size / section.sh_entsize) as usize;
3198
3199    let mut symbols = Vec::with_capacity(count);
3200
3201    for idx in 0..count {
3202        // Select entry `idx` inside the symbol-table section without unchecked addition.
3203        let sym_offset = section
3204            .sh_offset
3205            .checked_add((idx as u64).saturating_mul(section.sh_entsize))
3206            .ok_or_else(|| io::Error::other("symbol table overflow"))?
3207            as usize;
3208
3209        // An ELF64 symbol record is 24 bytes. `st_name` is at 0, `st_shndx` at 6,
3210        // `st_value` at 8, and `st_size` at 16. Bytes 4 and 5 hold binding/type and
3211        // visibility, which current debugger lookup does not need.
3212        // Source: https://gabi.xinuos.com/elf/05-symtab.html
3213        let name_offset = read_u32(bytes, sym_offset)? as usize;
3214        let section_index = read_u16(bytes, sym_offset + 6)?;
3215
3216        let value = read_u64(bytes, sym_offset + 8)?;
3217        let size = read_u64(bytes, sym_offset + 16)?;
3218        let name = read_elf_string(strtab_bytes, name_offset);
3219
3220        symbols.push(ProgramSymbol {
3221            name,
3222            value,
3223            size,
3224            section_index,
3225        });
3226    }
3227
3228    // dbg!(symbols);
3229    Ok(symbols)
3230}
3231
3232// todo - code dup
3233// Reads one 16-bit ELF integer from an in-bounds slice.
3234fn read_u16(bytes: &[u8], offset: usize) -> io::Result<u16> {
3235    // The caller must ensure that `offset + 2` does not overflow usize.
3236    let raw = bytes
3237        .get(offset..offset + 2)
3238        .ok_or_else(|| io::Error::other("short ELF read (u16)"))?;
3239    // The slice check guarantees exactly two bytes, so this conversion cannot fail.
3240    // The ELF identity check already established little-endian byte order.
3241    Ok(u16::from_le_bytes(raw.try_into().unwrap()))
3242}
3243
3244// Reads one 32-bit ELF integer from an in-bounds slice.
3245fn read_u32(bytes: &[u8], offset: usize) -> io::Result<u32> {
3246    // The caller must ensure that `offset + 4` does not overflow usize.
3247    let raw = bytes
3248        .get(offset..offset + 4)
3249        .ok_or_else(|| io::Error::other("short ELF read (u32)"))?;
3250
3251    // The four-byte slice makes `try_into` safe to unwrap.
3252    Ok(u32::from_le_bytes(raw.try_into().unwrap()))
3253}
3254
3255// Reads one 64-bit ELF integer from an in-bounds slice.
3256fn read_u64(bytes: &[u8], offset: usize) -> io::Result<u64> {
3257    // The caller must ensure that `offset + 8` does not overflow usize.
3258    let raw = bytes
3259        .get(offset..offset + 8)
3260        .ok_or_else(|| io::Error::other("short ELF read (u64)"))?;
3261
3262    // The eight-byte slice makes `try_into` safe to unwrap.
3263    Ok(u64::from_le_bytes(raw.try_into().unwrap()))
3264}
3265
3266fn read_elf_range<'a>(bytes: &'a [u8], offset: u64, size: u64, what: &str) -> io::Result<&'a [u8]> {
3267    // Check every conversion and addition because ELF offsets are untrusted input.
3268    let start =
3269        usize::try_from(offset).map_err(|_| io::Error::other(format!("{what} offset overflow")))?;
3270
3271    let len =
3272        usize::try_from(size).map_err(|_| io::Error::other(format!("{what} size overflow")))?;
3273
3274    let end = start
3275        .checked_add(len)
3276        .ok_or_else(|| io::Error::other(format!("{what} range overflow")))?;
3277
3278    bytes
3279        .get(start..end)
3280        .ok_or_else(|| io::Error::other(format!("{what} range overflow")))
3281}
3282
3283fn read_elf_string(bytes: &[u8], offset: usize) -> String {
3284    // An invalid string-table offset is not allowed to index the host slice.
3285    let Some(rest) = bytes.get(offset..) else {
3286        return String::new();
3287    };
3288    // Stop at NUL because following bytes belong to other strings in the table.
3289    // If no NUL remains, this parser accepts all remaining bytes. That is a malformed-ELF limit.
3290    let end = rest
3291        .iter()
3292        .position(|byte| *byte == 0)
3293        .unwrap_or(rest.len());
3294    // ELF names are bytes. Lossy conversion preserves usable diagnostics for an invalid
3295    // UTF-8 name and returns owned text that does not borrow the ELF input.
3296    String::from_utf8_lossy(&rest[..end]).into_owned()
3297}
3298
3299/*
3300
3301fn read_elf_parse(bytes: &[u8], offset: usize) -> String {
3302    let Some(rest) = bytes.get(offset..) else {
3303        return String::new();
3304
3305}
3306*/