---
name: query
description: Choose the rllvm-query tool that answers a question about a captured program, and report its answer without over-claiming. Use for questions about definitions, callers, callees, reachability, function pointers or external calls in captured bitcode, and every time an rllvm-query answer is about to be reported.
---

# Querying captured bitcode

Load the program first with `load_catalog` or `inventory` (the `capture`
skill). With more than one catalog loaded, pass `catalog`.

Without the MCP server, the same queries run as
`rllvm-query --catalog catalog.json <query>`, with kebab-case names
(`indirect-targets`). Output is text with its caveats in a footer; `--json`
prints the envelope described below, and `--full` adds scope, analysis and
uncertainty to the text.

Each invocation reloads the whole catalog, which on a large program takes
seconds. For many queries, pipe them on stdin to `rllvm-query --catalog
catalog.json` with no query argument, one per line as written on the command
line: the catalog loads once, each text answer follows a `== <query>` line,
and `--json` prints one envelope per line. Every line is checked before the
catalog is read.

Loads also reuse each module's extracted facts from a persistent cache keyed
by its content hash, so a rerun over the same bitcode skips re-extraction.
`analysis.cache` (and `--full`) reports hits, misses and disk use; a cached
answer is identical to an uncached one. `rllvm-query cache` reports the
cache's location and per-generation disk use, and `rllvm-query cache clear
[--stale]` prunes it — `--stale` keeps this binary's own generation and
removes only ones an older or newer LLVM left behind.

When a warning about the facts cache appears, offer `rllvm-query cache clear
--stale` or `cache clear` and run neither without the user's approval.

## Choosing the query

| Question | Tool |
| --- | --- |
| Where is X defined, and in which configurations? | `defs` |
| What runs at `file:line`? | `at` |
| Who calls X? What does X call? | `callers`, `callees` |
| Where is X's address taken? | `uses` |
| Who can call X through a pointer? | `uses` for address-taken sites, then `indirect_targets` at each |
| What can this indirect call reach? | `indirect_targets` at the call site (`heuristics: true` adds the address-taken inventory) |
| Which functions could an unresolved indirect call reach, grouped by the record field it dispatches through or the slot it reads its callee from? | `resolution_candidates` (`resolution-candidates`): candidates, never edges; no walk follows them |
| What comparisons, clamps, masks or moduli constrain a value, such as a length handed to a copy? | `guards` (`--at file:line` with `--operand N`, counting from 0, or `--return`; or `--function F --local NAME`, adding `--at file:line` for a local reused for several values, which reads it at that use (a read of the value it holds there, possibly through another variable that holds the same value inside the local's scope); a name several variables share, such as a shadowed local, is refused with the line each is declared at, though a variable of an inlined callee does not count, and `--declared-at file:line` picks the one declared on that line, refusing a line none or several are declared on; `--from F` bounds a parameter's callers): evidence only, so an empty list is not "unbounded"; reads the anchor's module through `opt` from `llvm_bindir`, so over MCP it needs `load_catalog`, not `inventory` |
| Can A reach B — for example, is a vulnerable function reachable? | `reach`; if it finds no path, `closure` to see where the search stopped |
| Every function on some path from A to B, and the edges among them | `slice`; `emit_module` (`--emit-module OUT.bc`) also writes their definitions as one module |
| Everything that reaches X, or that X reaches | `closure` with `direction` `in` or `out` |
| What does the program call outside itself? | `externals` |
| Which Rust functions can C call — the FFI surface? | `ffi_exports` |

A name can be the mangled symbol, the full demangled reading, or a bare
identifier. A bare identifier matches a C++ demangled reading that contains
it as a whole identifier; a C symbol has no reading, so a bare C name
resolves only to its own exact symbol. When a bare name matches nothing,
find the exact symbol with `at`, `callers` or `callees`, then query that.

The file in a `file:line` matches as the debug info recorded it, or as any
path that names the same file from the working directory: relative,
absolute, or through a symlink.

## Recording resolved indirect calls

To close gaps in a call graph, use the `complete-callgraph` skill; it runs
the loop below.

When you resolve an unresolved indirect call by reading the code, record it
in the call-graph overlay. The overlay is a file beside the catalog,
`<catalog stem>.overlay.jsonl` unless another is named; the catalog is never
changed. Records are the same over MCP and the command line:

- `{"op":"add","via_field":{"record":"ops","offset":8},"to":"h3","confidence":"high","provenance":["init: o->handler = h3"]}`
  attaches to every unresolved site through `ops@8`; `"via_slot": <a
  group's slot>` instead covers every unresolved site reading that slot,
  and `"site": <call site id>` names one. `to` is an exact symbol naming one definition
  (ODR copies count as one), or `{"module_id", "symbol"}`. `provenance` is
  required.
- `{"op":"verify","edge":<key>,"tool":...,"verdict":"confirmed|refuted|inconclusive"}`
  and `{"op":"retract","edge":<key>,"reason":...}` name an edge's key.
- `{"op":"annotate","anchor":{"kind":"argument","site":"clamp.c:14","operand":2},"verdict":"infeasible","confidence":"high","provenance":["clamp.c:13: n = len < CAP ? len : CAP"]}`
  is your own feasibility verdict on a value, never proof. `anchor` is an
  `argument` (`site`, `operand`, counting from 0), a `return` (`site`) or a
  `local` (`function`, `name`), which carries no use-site or declaration line,
  so a local reused for several values, or shadowed, cannot be annotated; `site`
  is `file:line` or a call site id; optional `from` names one function.
  `verdict` is `feasible`, `infeasible` or `conditional`. It is checked against
  the catalog on write, reading the anchor's module (over MCP, a program loaded
  with `load_catalog`): an anchor that does not resolve, or is ambiguous, is
  rejected. rllvm-query never writes one itself.
- `{"op":"verify","annotation":{"anchor":<anchor>,"from":...},"tool":...,"verdict":"confirmed|refuted|inconclusive"}`
  and `{"op":"retract","annotation":{"anchor":<anchor>,"from":...},"reason":...}`
  name an annotation by its anchor and `from` (omitted when it has none).
- A batch applies all or none; an error names the bad record. An edge on a
  bounded or direct site is rejected.
- After a rebuild the overlay refuses to load, naming both fingerprints. It
  also refuses one written by an rllvm-query that numbers call sites
  differently. To start again, move or delete the file, or name another
  with `--overlay` (MCP: `path`).
- A save or compaction refuses when another writer changed the file since
  it was opened; reopen it and record again.

Over MCP:

- `record_edges` takes them as `records`, attaching the overlay beside the
  catalog first if none is loaded. They are walked at once but stay in
  memory until `save_overlay`, the only tool that writes: it appends them
  and reports how many were `saved`.
- `load_overlay` attaches the default file, or `path` — required for a
  program loaded with `inventory`. It refuses to replace an overlay holding
  unsaved records unless `discard_pending` is true.
- `list_overlay` shows the edges, the sites they cover, the `annotations`
  with their `verdict`, and `pending` unsaved records.
- Unloading a catalog drops its overlay and reports how many unsaved records
  went with it; reloading a rebuilt catalog drops an overlay bound to the
  old build. Save before unloading, reloading, or ending the session;
  unsaved records are not written on exit.

On the command line, pipe them as JSON lines to `rllvm-query --catalog
catalog.json overlay record`, which saves at once; `--overlay PATH` names
another file. `overlay list` shows edges and the sites they cover, then
annotations with their verdict; `overlay compact` rewrites the file as the
current edges and annotations.

Overlay edges are hypotheses, never proof: no answer uses them unless asked.
`reach`, `closure` and `slice` walk them with `include_overlay`
(`--include-overlay`), skipping refuted edges and any below `min_confidence`
(`--min-confidence low|medium|high`, default `low`). A file `--overlay` names
must exist for a walk to read it. Over MCP they need an overlay that
`load_overlay` or `record_edges` attached; without one, `include_overlay` is
an error, never a direct-only answer.

- Every step through one is `kind: agent` with its `confidence`,
  `provenance` and `verdict`; quote the provenance when reporting it.
  `uncertainty.agent_path_steps` counts them. Non-zero means the
  path is not proven: report it as a hypothesis, never as reachability.
- `closure` lists functions reached only through agent edges in
  `uncertainty.agent_reached`; `uncertainty.overlay` gives `covered_sites` of
  `unresolved_sites`.
- A `slice` counts its agent edges the same way. To check one edge in scope,
  `emit_module` writes the slice's definitions, cut out with the
  `llvm-extract` and `llvm-nm` in the configured `llvm_bindir` and joined
  with the configured `llvm-link`, as one small `.bc` to verify or re-read;
  the answer gains `emitted`. An alias comes with the function it stands
  for. It needs a program loaded with `load_catalog`, writes nothing for an
  empty slice, and refuses when a `static` function or variable that stays
  in one module's part shares its name with any function or variable
  another part holds, since cutting it out makes it external.
  Compiler-private data such as string literals is not checked: same-named
  copies from two parts merge into one declaration.

Annotations are a separate opt-in: `reach` and `slice` take
`include_annotations` (`--annotations`, default off, independent of
`include_overlay`). It surfaces, under `uncertainty.annotated_flows`, each
`infeasible` or `conditional` annotation, not refuted, whose anchor lies on
the returned path, labeled `agent`. It is a hypothesis, never proof of
unreachability, and the path is never removed. An annotation recorded with a
`from` attaches only when the walk's `from` names that same single function;
a `from` matching several functions shows none of them. The JSON omits
`annotated_flows` when none attach. It is an error with no overlay attached
(over MCP, none loaded: call `load_overlay` or `record_edges`) or an overlay
holding no annotations.

## Is a vulnerable function present and reachable?

Ask per build: features and configurations change what is compiled in.

1. `defs` on the function. No result: it is not in this captured program.
2. `callers`. None: it is linked but has no captured direct caller; check
   `uses` for its address being taken before concluding anything.
3. `reach` from each entry point to the function: `main`, or the exported
   API — `ffi_exports` lists a Rust library's. A path is evidence; no path is
   not proof (see below).
4. `callees` on the entry point shows what it actually does on the way.

## Reading the answer

Report what the answer supports, and say what it does not:

- **`scope` is not coverage.** `scope` is what the catalog claims; `analysis`
  is what actually parsed. Report failed, missing, unsupported or unbuilt
  modules; an answer covers only what was analyzed.
- **No path is not unreachable.** An empty `reach` means no path over
  *resolved* edges. Mention `uncertainty.indirect_call_sites` and the
  frontier; say "no path found", never "unreachable".
- **`!callees` is an upper bound.** `llvm_target_bound` says an indirect call
  cannot target anything outside the set — not that it calls every member.
- **Address-taken lists are heuristics.** They appear in `indirect_targets`
  only when called with `heuristics: true`, and are never call edges.
- **Say how the name matched.** `resolution[].matched` is `mangled`,
  `demangled` or `fuzzy`; absent means the name matched nothing, which you
  report as no match, not silence. A fuzzy match is a guess to confirm with
  the user.
- **Know what a source digest proves.** `status_basis` is what `source_status`
  was checked against: `compiler` or `capture` dates from the build,
  `inventory` only from when the catalog was written. `source_status`:
  `modified` means locations may be off, `missing` means the file is gone,
  `unknown` means no digest was recorded to check.
- **Code without bitcode is invisible.** Assembly, prebuilt libraries and the
  Rust standard library contribute no functions; calls into them appear in
  `externals` as unbound. `indirect_targets`' `assumptions` state that
  `dlopen` and callbacks registered by uncaptured code escape its bound.
- **An FFI surface is only as complete as its attribution.** `ffi_exports`
  lists Rust definitions exported under an unmangled name, each attributed
  to Rust by debug info or by the module's producer.
  `uncertainty.functions_of_unknown_language` counts unmangled definitions
  it could not attribute and did not search — non-zero for code built
  without `-g`, or compiler-generated code such as a Rust binary's C
  `main`. Report it; a per-object catalog avoids it.
- **A field is evidence, not a target.** An indirect site's `via_field` and a
  use's `field` name the record field (`ops@8`: record and byte offset) the
  pointer is loaded from or stored into. No field means the IR proved none,
  not that there is none. `basis` says which IR evidence spoke; the member
  `name`, from debug info, is display only.
- **So is a slot.** An indirect site's `via_slot` names the one location its
  callee is read from: a `global`, a function's `param`, or the location an
  accessor `returned` the address of. A slot group's candidates are the
  functions found flowing into it through stores, arguments and accessors in
  the captured program; a path the facts do not follow, such as `memcpy` or
  pointer arithmetic, can add more.
- **A guard list covers one function.** `guards` traces the value inside the
  anchor's function only. A guard's `depth` is 0 on the value itself, or on
  a value it only widens (the bound holds across zext or sext when the
  guard's signedness matches the cast's), and N on a value N steps up the
  chain it is computed from, a truncation counting as a step; that is not by
  itself a bound on the value. Its `affine` gives the anchored value as
  `scale * guarded + offset` when constant arithmetic links them. Both
  coefficients are decimal strings of integers that may exceed 64 bits:
  parse them as big integers, never as JSON numbers. `verified` means the
  bound soundly carries to the anchored value. Report a guard with
  `verified: false` as a lead, never a bound, with its `caveat`: the
  relation is not affine, the guard bounds a value computed from the
  anchored one (such as a clamp of it used later), no branch edge shows the
  comparison held at the use, the bound wraps when carried to the anchored
  value, or it could not be mapped (no constant the guard's integer holds,
  no width, or an interval that admits no value, such as `ult 0`).
  A comparison is verified only through its branch's edge into the use, one
  every path to the use crosses (a loop's backedge into the block it enters
  is fine), and its `predicate` is the one that holds on that edge, which
  may negate the source condition: past `if (x > 16) return;` it reads
  `ule 16`. Report a verified comparison's predicate as holding at the use;
  one with no deciding edge keeps its predicate as written. A `return`
  anchor, or a `local` without `at`, sits before the comparisons that test
  the value, so they are leads there: for their bound, anchor at the later
  use, `at` that use for a local or an argument anchor on the call that
  consumes a returned value. A clamp of the anchored value made of a
  comparison (a branch or select clamp, not an intrinsic umin or saturation)
  also lists that comparison, which bounds the anchored value itself,
  verified only through its branch's edge into the use. For a clamp, mask,
  modulo or saturation, `verified` does not mean it runs on every path:
  report its bound as unconditional only when it is `verified` and its
  `relation` is `dominates` or `defines`; a verified one that is on_path is
  conditional. The numeric cap lives in `bound`. A verified guard with no
  `bound` is capped by a runtime value, the guard's operand: a sound
  relation, not a numeric limit, so never report a number for it; to reach
  one, anchor on that operand and trace it. A load is followed to the
  nearest store before it in its block, which overwrites any earlier one; a
  call, another store or any other operation between them (an atomicrmw, a
  cmpxchg, a volatile load), which is never skipped to reach an earlier
  store, or stores only in other blocks make every guard reached through the
  load a lead, with that caveat, and report the trace lost there. A
  `relation` of `dominates` runs before the use on every path but does not
  say which way its branch goes (for a verified comparison, its `predicate`
  does); one that is on_path lies on some path to the use, not every one.
  `crossings` name the call where the value enters or leaves: ask `guards`
  there next. `from` only keeps crossings from callers on a path from one
  named function; nothing between it and the anchor was examined.
  `uncertainty.value_trace_lost` means the list is partial. `guards`
  promotes the anchor's module with `mem2reg`, so a local held in a register
  — any promotable scalar — is traced whatever optimization level the
  capture used, `-O0` included. A value `mem2reg` cannot promote, such as a
  struct field or an address-taken local, stays a memory load: a lost trace
  unless the load is neither volatile nor atomic and a store of its type,
  also neither, to the same address runs before it. Addresses match only
  syntactically (the same pointer, or a `getelementptr` alike), so a field
  of a global never matches. Capture at `-O0` for a faithful call graph; it
  does not stop `guards` from tracing promotable scalars. It does keep small
  helpers out of line: a clamp inside one, such as a `min` function, is not
  traced into, so its return appears under `crossings` with no guard. `-Og`
  inlines such helpers and their clamps are found; capture there when bounds
  matter more than call-graph faithfulness.
- **ODR copies are one definition.** A template or `inline` body emitted into
  many translation units is one function; plain `weak` copies may differ and
  stay ambiguous.
