-
Notifications
You must be signed in to change notification settings - Fork 19
Refactor elfuse launch flag parsing code #285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jserv
merged 5 commits into
sysprog21:main
from
henrybear327:oci/refactor_launch_flag_code
Aug 13, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
4a76d74
Rename tests/lib/rosetta-test.sh to report.sh
henrybear327 d38efb2
Derive every usage synopsis from one flag list
henrybear327 4e5454a
Extract elfuse_launch into src/core/launch.c
henrybear327 e99c3bd
Route main() error paths through one cleanup label
henrybear327 35d2cfd
Rename test-rosetta-statics.sh's run_check helper
henrybear327 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,212 @@ | ||
| /* elfuse VM launch: bring-up + GDB + run loop + teardown | ||
| * | ||
| * Copyright 2026 elfuse contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * | ||
| * Implementation of elfuse_launch (contract and caller/callee ownership in | ||
| * launch.h). It lives apart from src/main.c so bring-up is one path behind a | ||
| * struct, leaving CLI concerns (option parsing, sysroot provisioning, the | ||
| * shebang loop) in main(). | ||
| * | ||
| * shim_blob.h is included here, not in src/main.c, so the static | ||
| * shim_bin / shim_bin_len blob has a single object definition site. | ||
| */ | ||
|
|
||
| #include "launch.h" | ||
|
|
||
| #include <Hypervisor/Hypervisor.h> | ||
| #include <Hypervisor/hv_vcpu.h> | ||
| #include <stdbool.h> | ||
| #include <stdint.h> | ||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include "core/bootstrap.h" | ||
| #include "core/guest.h" | ||
| #include "core/shim-globals.h" | ||
| #include "core/sysroot.h" | ||
|
|
||
| #include "runtime/futex.h" /* futex_interrupt_request */ | ||
| #include "runtime/procemu.h" /* proc_pty_release_process_slaves */ | ||
| #include "runtime/thread.h" | ||
| #include "syscall/poll.h" /* wakeup_pipe_signal */ | ||
| #include "syscall/proc.h" | ||
|
|
||
| #include "debug/gdbstub.h" | ||
| #include "debug/log.h" | ||
| #include "debug/syscall-hist.h" | ||
|
|
||
| /* Embedded shim binary (generated by xxd -i from shim.bin). */ | ||
| #include "shim_blob.h" | ||
|
|
||
| /* The shim code slot in the infra reserve is sized tight (INFRA_SHIM_SLOT, a | ||
| * few x the current blob) so the rest of the reserve goes to the page-table | ||
| * pool. If the shim ever outgrows the slot it would overlap the shim-data | ||
| * block; fail the build loudly rather than corrupt memory at boot. Enlarge | ||
| * INFRA_SHIM_SLOT (and shrink the pool to match) if this fires. | ||
| */ | ||
| _Static_assert(sizeof(shim_bin) <= INFRA_SHIM_SLOT, | ||
| "shim blob exceeds its infra slot; bump INFRA_SHIM_SLOT"); | ||
|
|
||
| int elfuse_launch(const launch_args_t *args) | ||
| { | ||
| extern char **environ; | ||
| char **envp_use = environ; | ||
|
|
||
| guest_t g; | ||
| bool guest_initialized = false; | ||
| guest_bootstrap_t boot; | ||
| /* Local copy of the temp flag (ownership contract in launch.h): the | ||
| * caller's launch_args_t is const, and the flag must drop once the | ||
| * unlink happens. | ||
| */ | ||
| bool elf_host_temp = args->elf_host_temp; | ||
| /* The guest-visible entrypoint path is argv[0]; elf_path is the | ||
| * resolved host path to that binary. They differ when path | ||
| * translation or a FUSE-materialized temp is involved. | ||
| */ | ||
| const char *elf_guest_path = (args->guest_argc > 0 && args->guest_argv) | ||
| ? args->guest_argv[0] | ||
| : args->elf_path; | ||
|
|
||
| if (guest_bootstrap_prepare( | ||
| &g, args->elf_path, elf_host_temp, elf_guest_path, args->sysroot, | ||
| args->guest_argc, args->guest_argv, envp_use, shim_bin, | ||
| shim_bin_len, args->verbose, &guest_initialized, &boot) < 0) | ||
| goto fail; | ||
|
|
||
| /* A FUSE-materialized temp has been loaded; drop it once the guest | ||
| * has its own mapping, unless Rosetta still needs the reopenable | ||
| * host path. | ||
| */ | ||
| if (elf_host_temp && !g.is_rosetta) { | ||
| unlink(args->elf_path); | ||
| elf_host_temp = false; | ||
| } | ||
|
|
||
| /* Reject GDB for a Rosetta (x86_64) guest here, not just in main(): the | ||
| * stub exposes the aarch64 shim's register/memory view, which is the wrong | ||
| * architecture for a Rosetta-translated x86_64 guest. main() rejects it up | ||
| * front via a static ELF probe, but enforcing it in elfuse_launch (once | ||
| * bring-up has set g.is_rosetta) makes every caller inherit the constraint, | ||
| * including the planned OCI run helper. | ||
| */ | ||
| if (args->gdb_port > 0 && g.is_rosetta) { | ||
| log_error(LAUNCH_GDB_X86_64_MSG); | ||
| goto fail; | ||
| } | ||
|
|
||
| if (args->sysroot) { | ||
| bool case_sensitive = true; | ||
| bool case_preserving = true; | ||
| if (sysroot_probe_case_sensitivity(args->sysroot, &case_sensitive, | ||
| &case_preserving) == 0) | ||
| proc_set_sysroot_casefold(case_preserving && !case_sensitive); | ||
| else | ||
| proc_set_sysroot_casefold(false); | ||
| } else { | ||
| proc_set_sysroot_casefold(false); | ||
| } | ||
|
|
||
| hv_vcpu_t vcpu; | ||
| hv_vcpu_exit_t *vexit; | ||
| if (guest_bootstrap_create_vcpu(&g, &boot, args->verbose, &vcpu, &vexit) < | ||
| 0) | ||
| goto fail; | ||
|
|
||
| /* GDB setup must happen before the first run so entry-stop and | ||
| * hardware breakpoints can affect the initial vCPU. | ||
| */ | ||
| if (args->gdb_port > 0) { | ||
| if (gdb_stub_init(args->gdb_port, &g) < 0) { | ||
| log_error("failed to initialize GDB stub"); | ||
| goto fail; | ||
| } | ||
| gdb_stub_sync_debug_regs(vcpu); | ||
| if (args->gdb_stop_on_entry) | ||
| gdb_stub_wait_for_attach(); | ||
| } | ||
|
|
||
| /* vcpu_run_loop owns guest execution until exit, fatal signal, or timeout. | ||
| */ | ||
| int exit_code = | ||
| vcpu_run_loop(vcpu, vexit, &g, args->verbose, args->timeout_sec, NULL); | ||
|
|
||
| /* Tear down debugger state before joining workers: a worker parked in | ||
| * gdb_stub_handle_stop() stays active (not deactivated) until this | ||
| * broadcasts resume_cond, so joining first would just time out and | ||
| * detach it while it is still paused. | ||
| */ | ||
| gdb_stub_shutdown(); | ||
|
|
||
| /* Join worker vCPU threads before guest_destroy unmaps the guest slab: a | ||
| * sibling still mid-iteration in its own run loop would fault on freed | ||
| * guest memory and crash the host with SIGSEGV, masking the real exit | ||
| * code. The join is a no-op once workers have wound down (the common | ||
| * single-threaded case). | ||
| * | ||
| * vcpu_run_loop can also return via a bare break (alarm timeout 124, a | ||
| * fatal default-disposition signal, or ELR_EL1==0) with no one having | ||
| * requested exit_group or kicked the siblings out of hv_vcpu_run. Mirror | ||
| * guest_destroy's request-interrupt prefix here first; otherwise this join | ||
| * burns its full poll cap and detaches every worker, and guest_destroy's | ||
| * own interrupt-join skips them (it honors join_abandoned), leaving live | ||
| * pthreads to fault on the imminent unmap. | ||
| */ | ||
| if (!proc_exit_group_requested()) | ||
| proc_request_exit_group(0); | ||
| futex_interrupt_request(); | ||
| wakeup_pipe_signal(); | ||
| thread_interrupt_all(); | ||
| /* Workers parked on internal condvars (fork barrier, ptrace stop/wait) | ||
| * see neither the pipe nor the vCPU kick; broadcast so they re-check the | ||
| * exit-group flag and terminate before the join below gives up on them. | ||
| */ | ||
| thread_wake_exit_waiters(); | ||
| thread_join_workers(); | ||
|
|
||
| /* Diagnostic counter dump runs before guest_destroy so the | ||
| * shim_data mapping is still valid. ELFUSE_SHIM_STATS is the gate; | ||
| * an unset variable produces no output. | ||
| */ | ||
| if (shim_globals_stats_enabled()) | ||
| shim_globals_counters_dump(&g); | ||
|
|
||
| /* Dump the startup histogram before guest_destroy so any | ||
| * cleanup-path syscalls (closing host fds, unmapping the slab) do | ||
| * not appear in the captured set. The dump is a no-op when | ||
| * ELFUSE_STARTUP_TRACE=syscalls was not requested. | ||
| */ | ||
| syscall_hist_dump(); | ||
|
|
||
| /* Give back any pty slaves this process still holds before the guest | ||
| * teardown below. The guest's stdio slaves are closed by the kernel, | ||
| * not by the guest, so they never pass through the per-fd close hook; a | ||
| * master in another process would otherwise wait forever for a hangup | ||
| * this exit should have produced. Bring-up failures skip this: the fail | ||
| * path is only reachable before the run loop, so no slave exists yet. | ||
| */ | ||
| proc_pty_release_process_slaves(); | ||
|
|
||
| if (guest_initialized) | ||
| guest_destroy(&g); | ||
|
|
||
| /* Rosetta guests keep the FUSE-materialized temp alive for the whole run | ||
| * (the translator reopens the host path); drop it now that the guest is | ||
| * gone so repeated Rosetta launches do not accumulate temp files. | ||
| */ | ||
| if (elf_host_temp) | ||
| unlink(args->elf_path); | ||
|
|
||
| return exit_code; | ||
|
|
||
| fail: | ||
| /* Bring-up failed: unwind whatever exists so far, including the temp | ||
| * unlink this side owns past the prepare call (contract in launch.h). | ||
| */ | ||
| if (guest_initialized) | ||
| guest_destroy(&g); | ||
| if (elf_host_temp) | ||
| unlink(args->elf_path); | ||
| return 1; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* elfuse VM launch entry: post-CLI bring-up + run loop + teardown | ||
| * | ||
| * Copyright 2026 elfuse contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * | ||
| * elfuse_launch is the single entry point for "run a guest binary in a | ||
| * fresh HVF VM until it exits". main() is its only in-tree caller; keeping | ||
| * bring-up behind one struct is what lets another front end (the planned | ||
| * OCI run helper) reuse this path instead of growing a second bring-up. | ||
| * | ||
| * The function owns the guest_t, the vCPU, the GDB stub, the run loop, the | ||
| * diagnostic dumps, and guest teardown; it does NOT own the elf_path / | ||
| * sysroot / guest_argv heap copies or the sysroot_mount the host CLI may | ||
| * have provisioned. Those stay with the caller so behaviors that need the | ||
| * original CLI argv (proctitle rewriting, --create-sysroot detach on exit, | ||
| * host cwd save+restore) stay coherent however the launch was kicked off. | ||
| * | ||
| * The caller owns every pointer in launch_args_t for the duration of the | ||
| * call; elfuse_launch reads but never frees them. Per-field lifetime and | ||
| * ownership notes live on the struct members below. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <stdbool.h> | ||
| #include <stdint.h> | ||
|
|
||
| typedef struct { | ||
| /* Host path to the guest ELF; may be a FUSE-materialized temp when | ||
| * elf_host_temp is set. | ||
| */ | ||
| const char *elf_path; | ||
|
|
||
| /* elf_path is a FUSE-materialized temp to unlink once | ||
| * guest_bootstrap_prepare has loaded it (kept for Rosetta guests, which | ||
| * reopen the path). The caller owns the unlink on any pre-prepare | ||
| * failure; elfuse_launch owns it from the prepare call onward, | ||
| * including a prepare that fails. | ||
| */ | ||
| bool elf_host_temp; | ||
|
|
||
| /* Host filesystem path to the sysroot the guest sees as / (absolute), | ||
| * or NULL when the guest runs without a sysroot. | ||
| */ | ||
| const char *sysroot; | ||
|
|
||
| /* Argv the guest sees. guest_argv[0] is the guest-visible entrypoint | ||
| * path (what the guest reads back via /proc/self/exe and argv[0]); it | ||
| * differs from elf_path (the resolved host path) under path translation | ||
| * or a FUSE-materialized temp. | ||
| */ | ||
| int guest_argc; | ||
| const char **guest_argv; | ||
|
|
||
| /* GDB Remote Serial Protocol port (0 disables the stub) and whether | ||
| * to halt before the first guest instruction. | ||
| */ | ||
| int gdb_port; | ||
| bool gdb_stop_on_entry; | ||
|
|
||
| /* Per-iteration vCPU run timeout. 0 disables (no alarm()). */ | ||
| int timeout_sec; | ||
|
|
||
| bool verbose; | ||
| } launch_args_t; | ||
|
|
||
| /* Diagnostic for rejecting --gdb on an x86_64 (Rosetta) guest, shared by | ||
| * main()'s early static-ELF probe and elfuse_launch's authoritative | ||
| * post-bring-up check so the two sites cannot print divergent messages | ||
| * (tests/test-rosetta-cli.sh pins this text). | ||
| */ | ||
| #define LAUNCH_GDB_X86_64_MSG \ | ||
| "--gdb is not supported for x86_64 guests; the current stub " \ | ||
| "only exposes the translated aarch64 view" | ||
|
|
||
| /* Bring up the guest VM, run it to exit / signal / timeout, tear down, | ||
| * return the exit code. Returns 1 on bring-up failure (with a log | ||
| * message) and the guest's exit status otherwise. | ||
| */ | ||
| int elfuse_launch(const launch_args_t *args); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.