Skip to content
Back to skills

R2xsql Xrefs

ASecurity

Analyze radare2 cross-references via r2xsql — callers, callees, data references, jump targets. Use when asked who calls a function, what a function calls, or what code touches a string/import.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
toolsbashsqlperformance

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 17, 2026

npx -y skills add NetVar1337/agent-skill-canon --skill r2xsql-xrefs --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of R2xsql Xrefs?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for R2xsql Xrefs
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/netvar1337-r2xsql-xrefs/badge)](https://www.skillsdirectory.com/skills/netvar1337-r2xsql-xrefs)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: r2xsql-xrefs
description: "Analyze radare2 cross-references via r2xsql — callers, callees, data references, jump targets. Use when asked who calls a function, what a function calls, or what code touches a string/import."
allowed-tools:
  - Bash
  - Read
---

## When to use

Pick this skill when the question involves **edges in the code graph**:

- "who calls function X?"
- "what does function X call into?"
- "where is string Y used?"
- "which functions reach this import?"

For inspecting a function's own code, use `disassembly`. For finding
string/import name patterns, use `data`.

## Tables

| table   | source                | columns                                                                |
|---------|-----------------------|------------------------------------------------------------------------|
| `xrefs` | per-func `afxj @ <fn>`| `from_addr`, `to_addr`, `type`, `from_func`, `is_code`, `is_data`, `kind` |

Unfiltered scans walk `aflj` and issue `afxj @ <fn>` per function (the
global `axj` only reports refs at the current seek, so it returns nothing
on a fresh session). Point lookups push down: `WHERE to_addr = X` issues
`axtj @ X`, `WHERE from_addr = X` issues `axfj @ X`, and
`WHERE from_func = X` issues `afxj @ X` — one function's references, so `X`
must be the function's **start** address.

## Start with the views

Three views answer most cross-reference questions directly, with names already
resolved, and they carry the same columns in every tool of this family — so a
query written here works against idasql/bnsql/ghidrasql unchanged.

| view | one row per | columns |
|---|---|---|
| `callers` | edge into a function | `func_addr`, `caller_addr`, `caller_name`, `caller_func_addr` |
| `callees` | edge out of a function | `func_addr`, `func_name`, `callee_addr`, `callee_name` |
| `string_refs` | code→string reference | `string_addr`, `string_value`, `string_length`, `ref_addr`, `func_addr`, `func_name` |

```sql
-- who calls this function, by name
SELECT caller_name, caller_addr FROM callers WHERE func_addr = 0x401000;

-- what this function calls, imports included
SELECT callee_name, callee_addr FROM callees WHERE func_addr = 0x401000;

-- which function reads which string
SELECT func_name, string_value FROM string_refs WHERE string_value LIKE '%license%';
```

An edge is a **code** reference whose target is a function *or an import*
(a tail call compiles to a jump, so `CODE`/`JUMP` count, and in radare2 an
import is not a function — calls into imports would be missed by a
functions-only rule). `caller_name`/`func_name`/`callee_name` never come back
NULL; an unresolvable name falls back to `sub_<ADDR>`. References belonging to
no function are excluded — see the `from_func` caveat below.

Drop to the `xrefs` table itself when you need data references, a specific
`type`, or an edge whose target is neither a function nor an import.

### `from_func` is an address

`from_func` is the **address** of the function the reference originates in — not
a name — so it joins directly against `funcs.addr`, and the whole call graph can
be walked in address space. It is **NULL** when the source lies outside every
analyzed function, so use `LEFT JOIN` (or `WHERE from_func IS NOT NULL`) when
that case matters.

```sql
-- name the caller
SELECT f.name AS caller, x.from_addr, x.type
FROM xrefs x LEFT JOIN funcs f ON f.addr = x.from_func
WHERE x.to_addr = 0x401000;
```

### `is_code` / `is_data`

Two 0/1 columns derived from `type`, so you never have to spell out radare2's
strings: `is_code = 1` for `CALL`/`CODE`/`JUMP`/`ICOD`, `is_data = 1` for
`DATA`/`STRN`. A `UNKN`/`NULL` ref is **neither** — no row is ever both, so the
pair partitions the classified refs cleanly.

### `kind` — r2xsql's own small vocabulary, not a family standard

**Deviation, not a family standard — read this before assuming portability.**
`kind` is a free normalization of `type` (no new radare2 command — it's
computed from the value already fetched for that column) into a smaller,
human-facing set: `call` / `jump` / `code` / `data` / `unknown`. It is
r2xsql's OWN classification, a deliberate, owner-approved deviation — no
vocabulary has been agreed across the tool family yet for `xrefs` (see
`entities.kind` in the `grep` skill for the same kind of deviation on a
different table). Never assume a sibling tool's `kind` spelling matches
these values.

| `type`          | `kind`    | why                                                          |
|-----------------|-----------|---------------------------------------------------------------|
| `CALL`          | `call`    | the single most common query shape ("who calls X") keeps its own bucket |
| `JUMP`          | `jump`    | a distinct control-flow shape from a call (no return expected) |
| `CODE`, `ICOD`  | `code`    | `ICOD` (indirect code) carries no more specific call/jump shape than `CODE`, so it groups with it |
| `DATA`, `STRN`  | `data`    | a string ref is a data ref to a specific kind of memory; `kind` stays coarse and drops that subtype |
| `UNKN`, `NULL`, absent | `unknown` | mirrors `is_code`/`is_data` already treating these as neither |

```sql
-- every call edge, without naming the type string
SELECT from_addr, to_addr FROM xrefs WHERE kind = 'call';

-- kind-by-kind breakdown
SELECT kind, COUNT(*) AS n FROM xrefs GROUP BY kind;
```

### `type` values

These are radare2's own strings, and they are the complete set — matching on
anything else silently returns zero rows:

| Value | Meaning |
|---|---|
| `CALL` | a call |
| `CODE` | a code reference |
| `JUMP` | a jump |
| `ICOD` | an indirect code reference |
| `DATA` | a memory reference (read/write) |
| `STRN` | a string reference |
| `UNKN` | unknown |
| `NULL` | undefined |

> **There is no `STRING`.** A string reference is `STRN`. `WHERE type =
> 'STRING'` is not an error — it just matches nothing, which is why this is easy
> to get wrong and hard to notice.

```sql
-- string references: STRN, not STRING
SELECT from_addr, to_addr FROM xrefs WHERE type = 'STRN';

-- all code-flow edges — is_code says the same thing without the type list
SELECT * FROM xrefs WHERE is_code = 1;
```

## Common queries

```sql
-- callers of a function
SELECT x.from_addr, f.name AS caller, x.type
FROM xrefs x LEFT JOIN funcs f ON f.addr = x.from_func
WHERE x.to_addr = 0x401000;

-- everything a function calls (CALL edges out of it)
SELECT DISTINCT x.to_addr, f.name AS callee
FROM xrefs x
LEFT JOIN funcs f ON f.addr = x.to_addr
WHERE x.from_addr IN (
  SELECT addr FROM instructions WHERE func_addr = 0x401000
)
AND x.type = 'CALL';

-- callers of every CryptDecrypt-ish import
SELECT DISTINCT f.name AS caller, i.name AS import
FROM imports i
JOIN xrefs x ON x.to_addr = i.addr
LEFT JOIN funcs f ON f.addr = x.from_func
WHERE i.name LIKE 'Crypt%';

-- code that references a known string
WITH s AS (SELECT addr FROM strings WHERE content = 'license check failed')
SELECT x.from_addr, f.name AS in_func, x.type
FROM xrefs x
JOIN s ON x.to_addr = s.addr
LEFT JOIN funcs f ON f.addr = x.from_func;

-- "who reaches X" — 2-hop reverse call graph. from_func and to_addr are the
-- same key, so both hops join on addresses and names resolve once, at the end.
WITH direct AS (
  SELECT DISTINCT from_func AS fn FROM xrefs
  WHERE to_addr = 0x401000 AND type = 'CALL' AND from_func IS NOT NULL
)
SELECT DISTINCT f.name AS caller_of_caller
FROM xrefs x
JOIN direct d ON d.fn = x.to_addr
JOIN funcs  f ON f.addr = x.from_func
WHERE x.type = 'CALL';
```

## Joins

- `xrefs.to_addr` ⨝ `funcs.addr` resolves CALL targets to function
  rows (name, size, complexity).
- `xrefs.from_func` ⨝ `funcs.addr` names the **source** function — both
  ends of an edge are addresses in the same space.
- `xrefs.to_addr` ⨝ `imports.addr` resolves IAT references to import
  names.
- `xrefs.to_addr` ⨝ `strings.addr` finds the string for a DATA ref.

## Performance / pushdown

Prefer `to_addr = ...` and `from_addr = ...` predicates for interactive
drilling; they use scoped radare2 commands instead of enumerating every
function.

`r2xsql-full` reads this table directly from radare2's in-process analysis
state rather than running those commands — same rows, roughly an order of
magnitude faster on a large binary, where an unfiltered scan would otherwise
cost one command per function. The pipe-only `r2xsql` drives a separate
radare2 process, so it enumerates `afxj` across every function once per query;
there, cache a broad result into a temp table if you will reuse it:

```sql
CREATE TEMP TABLE callers_of_x AS
SELECT * FROM xrefs WHERE to_addr = 0x401000;
```

## Caveats

- Indirect calls (`call qword [rip+...]`) resolve through r2's emul
  flag — sometimes the `to_addr` is the **slot**, not the resolved
  target. Re-run `aaaa` (or `aae` for emulation) to harvest more.
- **radare2's two enumeration shapes do not agree, and the `xrefs` table exposes
  both.** An unfiltered scan walks each analyzed function, so it cannot see a
  reference whose source lies outside every function; a `WHERE to_addr = X`
  lookup uses radare2's xref index, which can. So the same logical question can
  return more rows when phrased so that it pushes down. Every such extra row has
  `from_func IS NULL` — it belongs to no function by definition. Add
  `AND from_func IS NOT NULL` when you need a phrasing-independent answer; the
  three views above already do, which is part of why they are the better default.
  Running `aaaa` analyzes more code and shrinks the gap.

Files in this skill

  • SKILL.md9.3 KB
  • agents/openai.yaml43 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.