diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-21 21:17:44 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-21 21:17:44 +0200 |
| commit | 71b6445b048456bf198d8b622570db95f4665cb9 (patch) | |
| tree | 808e73fa95709ed9c2d409aecaf275fd70365293 /bins | |
| parent | input: total keyboard routing + per-terminal discipline (ADR 0006 ph1) (diff) | |
| download | rs-dos-71b6445b048456bf198d8b622570db95f4665cb9.tar.gz rs-dos-71b6445b048456bf198d8b622570db95f4665cb9.zip | |
exec: PE32+ loader, versioned ABI, first external binaries (v0.6.0)
rsapi: the kernel<->binary ABI crate (no_std, no deps). Versioned
Exports table (magic + version header), EntryPoint = extern "C" on
x86_64-unknown-uefi (Win64: RCX/RDX/R8), Line/Arg/StreamResult types.
Binaries link only this crate.
rsnix::load: PE32+ parser — DOS/COFF/optional headers, section copy,
BSS zero-fill, .reloc base relocations (DIR64) via rva_to_file mapping
(the reloc directory is an RVA, not a file offset). Images are
allocated at LOAD_BASE=192MiB inside the page allocator's conventional
span and identity-mapped user+writable+nx.
rsnix::exec: resolution name -> /disk/<UID>/bin/<kind>/<name>.efi per
mounted volume; spawn reserves a sched slot, stores a per-task ExecCtx
(exports + argv block + image) before launch_at installs the frame —
no adopt race. Trampoline calls entry(&Exports, argc, argv), posts
TaskMsg::User(exit_code) to the shell. abort_current notifies the
shell with u64::MAX when an exec'd binary dies, so a faulting command
cannot leave the shell blocked forever.
sched: reserve_slot/launch_at split out of spawn; task_exit made pub.
shell: exec_external runs before non-frozen built-ins (ADR 0006
resolution rule); frozen core (help/clear/echo/halt/poweroff/exit/
rescan) is exempt. Exports v1 wires put_line to the terminal and
get_line to EOF; stream injection lands in Phase 3. ^C while waiting
kills the command task.
bins/{hello,boom}: gate binaries. hello prints through the exports
table; boom faults on 0x400000000000 to prove containment. The image
entry point IS the ABI entry (efi_main carries the Exports signature).
Version 0.6.0: exec exists end-to-end.
Gates verified in QEMU: 'hello' loads from \\bin\\cli\\hello.efi, calls
put_line across the ABI, exits 0, shell unblocks; 'boom' faults, task
dies gracefully with full report, shell survives, jobs shows the corpse;
built-in fallback works when the file is absent; test-disk PASS.
Diffstat (limited to 'bins')
| -rw-r--r-- | bins/boom/Cargo.toml | 10 | ||||
| -rw-r--r-- | bins/boom/src/main.rs | 31 | ||||
| -rw-r--r-- | bins/hello/Cargo.toml | 10 | ||||
| -rw-r--r-- | bins/hello/src/main.rs | 33 |
4 files changed, 84 insertions, 0 deletions
diff --git a/bins/boom/Cargo.toml b/bins/boom/Cargo.toml new file mode 100644 index 0000000..2d31886 --- /dev/null +++ b/bins/boom/Cargo.toml | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | [package] | ||
| 2 | name = "boom" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | |||
| 6 | [lints] | ||
| 7 | workspace = true | ||
| 8 | |||
| 9 | [dependencies] | ||
| 10 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/boom/src/main.rs b/bins/boom/src/main.rs new file mode 100644 index 0000000..0c3201b --- /dev/null +++ b/bins/boom/src/main.rs | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | //! boom — crash-containment probe (ADR 0006 Phase 2 gate). | ||
| 2 | //! | ||
| 3 | //! Deliberately faults on an unmapped address. The kernel must kill only | ||
| 4 | //! this task; the shell survives and `jobs` shows the corpse. | ||
| 5 | |||
| 6 | #![no_std] | ||
| 7 | #![no_main] | ||
| 8 | |||
| 9 | #[panic_handler] | ||
| 10 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 11 | loop { | ||
| 12 | core::hint::spin_loop(); | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | /// The image entry point — the loader jumps here with the ABI arguments. | ||
| 17 | #[unsafe(no_mangle)] | ||
| 18 | pub extern "C" fn efi_main( | ||
| 19 | _exports: &'static rsapi::Exports, | ||
| 20 | _argc: usize, | ||
| 21 | _argv: *const rsapi::Arg, | ||
| 22 | ) -> i32 { | ||
| 23 | // Canonical-but-unmapped address (below 2^47, above any RAM/MMIO): | ||
| 24 | // the page walk terminates at a not-present entry → #PF with | ||
| 25 | // CR2 = address. Same trick as the built-in `fault` command. | ||
| 26 | unsafe { | ||
| 27 | let p = 0x4000_0000_0000usize as *mut u64; | ||
| 28 | p.write_volatile(0xDEAD_BEEF); | ||
| 29 | } | ||
| 30 | 0 | ||
| 31 | } | ||
diff --git a/bins/hello/Cargo.toml b/bins/hello/Cargo.toml new file mode 100644 index 0000000..ac71ded --- /dev/null +++ b/bins/hello/Cargo.toml | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | [package] | ||
| 2 | name = "hello" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | |||
| 6 | [lints] | ||
| 7 | workspace = true | ||
| 8 | |||
| 9 | [dependencies] | ||
| 10 | rsapi = { path = "../../rsapi" } | ||
diff --git a/bins/hello/src/main.rs b/bins/hello/src/main.rs new file mode 100644 index 0000000..d1d58df --- /dev/null +++ b/bins/hello/src/main.rs | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | //! hello — the first rs-dos external binary (ADR 0006 Phase 2 gate). | ||
| 2 | //! | ||
| 3 | //! Prints one line through the injected Exports table and exits 0. | ||
| 4 | //! Exercises the whole chain: PE32+ load, relocation, ABI call, task exit. | ||
| 5 | |||
| 6 | #![no_std] | ||
| 7 | #![no_main] | ||
| 8 | |||
| 9 | use rsapi::Exports; | ||
| 10 | |||
| 11 | #[panic_handler] | ||
| 12 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 13 | loop { | ||
| 14 | core::hint::spin_loop(); | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | /// The image entry point — the loader jumps here with the ABI arguments | ||
| 19 | /// (exports table, argc, argv). The conventional UEFI name is kept so | ||
| 20 | /// the PE looks normal; the signature is the rs-dos ABI. | ||
| 21 | #[unsafe(no_mangle)] | ||
| 22 | pub extern "C" fn efi_main(exports: &'static Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 23 | let msg = b"hello, world - from an external binary"; | ||
| 24 | let line = rsapi::Line { | ||
| 25 | ptr: msg.as_ptr(), | ||
| 26 | len: msg.len(), | ||
| 27 | }; | ||
| 28 | if (exports.put_line)(line) { | ||
| 29 | 0 | ||
| 30 | } else { | ||
| 31 | 1 | ||
| 32 | } | ||
| 33 | } | ||
