facebookexperimental/reverie/syscall-interception

Claude Code and agent skill by facebookexperimental/reverie

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

How a Reverie tool observes and rewrites guest syscalls — declaring subscriptions, the handle_syscall_event hook, inject vs. tail_inject, typed syscalls via reverie-syscalls, reading/writing guest memory, and the per-backend trap mechanisms (ptrace seccomp, DBT, KVM hypercalls). Read when writing or debugging syscall handlers.

Install

npx vskill@latest install facebookexperimental/reverie/syscall-interception

SKILL.md

Syscall Interception

This is the day-to-day work of a Reverie tool: decide which syscalls to trap, inspect/modify their arguments, and let them proceed, replace them, or inject new ones. Start from the reverie-architecture skill for the big picture; this skill is the how-to for the Tool/Guest syscall path.

1. Declare what you trap: subscriptions

Tool::subscriptions(cfg) -> Subscription (reverie/src/tool.rs:153, reverie/src/subscription.rs) is where you opt into events. Only subscribed syscalls stop in your tool; everything else runs untrapped. This is the ptrace performance model — an unsubscribed syscall costs nothing, an observed one pays the ptrace-stop tax — so subscribe to the smallest set you need.

  • Subscription::all() — trap every syscall (what a strace-like or Detcore-like

tool wants).

  • Subscription::none() then add specific Sysnos — for sparse tools.
  • Also gate CPU events (cpuid/rdtsc), signals, and timers here.

2. The main hook: handle_syscall_event

async fn handle_syscall_event<T: Guest<Self>>(&self, guest: &mut T, syscall: Syscall) -> Result<i64, Errno> (reverie/src/tool.rs:234). You are called before the guest's syscall executes and you own the result. Typical shapes:

rust
// (a) Observe, then let the original run unchanged and return to the handler.
let ret = guest.inject(syscall).await?;
// ... record ret ...
Ok(ret)
// (b) Replace with a different syscall and resume the guest directly
// (no return to this handler — cheaper, and required for exit*).
guest.tail_inject(replacement).await // -> Never
// (c) Emulate entirely: skip the syscall, synthesize a return value.
Ok(virtualized_value)
// (d) Fail-closed: an unsupported syscall under a strict tool.
Err(Errno::ENOSYS)

inject vs. tail_inject (reverie/src/guest.rs:131 / :175):

  • guest.inject(s).await? -> i64 runs s and returns to your handler, so

you can post-process the result (edit returned buffers, record it, chain more syscalls). inject_with_retry handles EINTR-style restarts.

  • guest.tail_inject(s).await -> Never runs s as the guest's final action

for this event and resumes the guest — control never comes back. It's the low-overhead default when you don't need the result.

  • Footgun: tail_inject(exit)/tail_inject(exit_group) never returns, so a

tool that must record anything at process/thread exit must do it before the tail_inject (the KVM tools codify "record Exit before tail_inject").

3. Typed syscalls & guest memory (reverie-syscalls)

Handlers receive a decoded Syscall (the #[non_exhaustive] enum Syscall from `reverie-syscalls`, generated by the syscall_list! macro) rather than raw registers. Each variant is a typed struct (generated by typed_syscall!) implementing SyscallInfo (reverie-syscalls/src/syscalls/mod.rs: NAME, NUMBER: Sysno, into_parts() -> (Sysno, SyscallArgs)), with typed getters and with_* setters. Pointer arguments are guest addresses — dereference them through the guest's memory interface, never the host's:

  • guest.memory() (reverie/src/guest.rs:73) returns a MemoryAccess

(reverie-memory). Its two primitives are read_vectored/write_vectored; the provided helpers you'll actually call are read/write/read_exact/ write_exact/read_value/write_value/read_cstring/reader/writer (e.g. read a pathname a syscall points at, or write a virtualized struct back into a guest buffer after inject). Addressing types: Addr<T>, AddrMut<T>, AddrSlice, LocalMemory (the in-process impl DBT uses).

  • guest.regs() / guest.set_regs() (:82/:98) for raw register access when

you need it (e.g. rewriting a syscall number/args before injection).

  • To edit a syscall's arguments, construct a modified typed syscall with a

with_* builder and inject that — e.g. chaos does Syscall::Read(read.with_len(1.min(read.len()))).

  • To print a syscall, use Displayable: syscall.display(&mem) or

syscall.display_with_outputs(&mem) (what strace/chrome_trace use). Ordering trap: for execve/execveat, format before injecting — a successful exec wipes the old image and its argument memory.

4. Other trapped events

Same handle_* family on Tool (reverie/src/tool.rs): handle_cpuid_event (:250) and handle_rdtsc_event (:267) virtualize CPU identity/timestamps; handle_signal_event (:280) sees signal delivery; handle_timer_event (:289) fires from guest.set_timer[_precise] (reverie/src/guest.rs:215), driven by the RCB logical clock (read_clock, :228). Subscribe to each in subscriptions or the backend won't trap it.

5. Per-backend trap mechanism

The handler code is backend-agnostic, but how the syscall is trapped differs (see the benchmark memory for the cost model):

BackendTrap mechanismCost profile
ptrace (reverie-ptrace)seccomp-BPF filter raises PTRACE_EVENT_SECCOMP only for subscribed syscalls; supervisor handles it in a ptrace stop~0 for unsubscribed; ~26–40 µs per observed syscall
DBT (reverie-dbt)DynamoRIO rewrites the guest's code stream; a clean-call thunk lands in the tool .so on every syscallflat ~2 µs every syscall (no free fast path)
KVM (reverie-kvm)guest runs in a VM; syscalls surface as VM exits / hypercalls routed to the toolVM-exit bounded
e9patch / liteinst (reverie-preload)in-process instruction rewriting + an LD_PRELOAD + seccomp/SIGSYS runtime that redirects syscalls in-processin-process, no supervisor round-trip

Practical consequences you will hit:

  • The KVM/DBT executor must enumerate the syscalls it services; an

un-enumerated syscall falls through to a default ENOSYS arm. Adding support means adding both the classification and the executor arm (this is the recurring "KVM ratchet" and "determinize family" work in the memory index).

  • Out-of-process backends run GlobalState in a separate coordinator over

reverie-rpc-transport; send_rpc from a handler crosses a UDS, so keep RPC payloads bincode-serializable and coarse.

6. Reference tools to copy from

In reverie-examples (build/run instructions in the testing-tools skill):

  • `strace` / `strace_minimal` — subscribe-all + decode + print; the canonical

"observe every syscall" tool.

  • `counter1` / `counter2` — subscribe to a syscall, count it, aggregate into

GlobalState (in-process vs. UDS coordinator).

  • `noop` — the minimal Tool skeleton; start here for a new tool.
  • `chaos` — injects scheduling perturbation; shows signal/timer handling.

Post-facto human-review criteria

Apply post-facto-human-review exactly when a PR contains at least one of these four triggers:

  1. new syscall support, after verifying AUTONOMOUS-BOT-IMPLEMENTED at the

new dispatch/classification entry and TODO-HUMAN-REVIEW(PR-id) at the implementation or determinization block;

  1. a Reverie API/core-abstraction change to the Tool, Guest, Backend,

or syscall-interception model;

  1. a new determinization strategy; or
  2. a core DetCore scheduling change affecting how programs are scheduled,

especially race search. Trigger 4 is always labeled.

Routine backend parity toward the golden ptrace reference implementation is not a trigger merely because it changes a non-ptrace backend. It is labeled only if it also meets one of the four triggers.

Every PR description starts with Plain Language Summary and Project Impact, stating the substantive outcome and its connection to the product vision or owner request. It also requires mandatory Determinism (why the change is deterministic plus its logic or informal proof), and Validation. KVM PRs also require Relationship to gVisor. A labeled PR additionally requires Human Review Required, naming the specific numbered trigger rather than vague prose such as "backend change". The syscall tags above verify trigger 1; they are not blanket backend-change markers. Hermit PR #1151, which moved slowdown into virtual-time/epoch scheduling, is the canonical good example for trigger 4.

Related skills

  • reverie-architecture — the Tool/Guest/Backend contracts these hooks live on.
  • adding-a-backend — implementing the trap mechanism for a new backend.
  • testing-tools — how to build and run the example tools above.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
5

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/facebookexperimental/reverie/syscall-interception/badge)](https://verified-skill.com/skills/facebookexperimental/reverie/syscall-interception)

Something wrong with this skill or its page? Tell us.