| name | xrefs |
| description | Analyze IDA cross-references. Use when asked about callers, callees, imports, data refs, call graphs, or dependency chains. |
| metadata | {"argument-hint":"[function-name or address]"} |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
Trigger Intents
Use this skill when user asks:
- "Who calls this?" / "What does this call?"
- "Where is this string/import referenced?"
- "Show call graph dependencies."
Route to:
grep for candidate entity lookup by name/pattern before relationship analysis
analysis for broader triage context
decompiler for semantic interpretation after graph narrowing
disassembly for instruction-level call-site proof
Do This First (Warm-Start Sequence)
SELECT COUNT(*) AS xref_count FROM xrefs;
SELECT module, COUNT(*) AS import_count
FROM imports
GROUP BY module
ORDER BY import_count DESC;
SELECT printf('0x%X', to_addr) AS callee, COUNT(*) AS callers
FROM xrefs
WHERE is_code = 1
GROUP BY to_addr
ORDER BY callers DESC
LIMIT 20;
Interpretation guidance:
- Use relation counts to prioritize hotspots before expensive deep analysis.
- Prefer indexed filters (
to_addr/from_addr) for fast response.
Failure and Recovery
- Full-scan query too slow:
- Add
to_addr = X or from_addr = X constraints.
- Target unresolved by name:
- Resolve/verify address first through the
names table or explicit EA literals.
- Sparse results:
- Pivot through
imports, strings, or disasm_calls joins.
- Looking for references to a struct/union FIELD:
- Use
struct_member_xrefs (filter by type_name/type_ordinal/member_id).
Do NOT LIKE-scan instructions.disasm / instruction_operands.text for the
field name — it is the slow, crash-adjacent pattern this table replaces.
- Target lives inside a static struct or dispatch table:
Handoff Patterns
xrefs -> decompiler for top candidate function semantics.
xrefs -> analysis for campaign-level synthesis.
xrefs -> annotations to persist relationship findings.
xrefs
Cross-references - the most important table for understanding code relationships.
| Column | Type | Description |
|---|
from_addr | INT | Source address (who references) |
to_addr | INT | Target address (what is referenced) |
type | INT | Xref type code (never the ordinary-flow code, see below) |
is_code | INT | 1=code xref (call/jump), 0=data xref |
from_func | INT | Pre-computed containing function address (NULL when not in a function) |
Semantics: the surface exposes real references only — ordinary fall-through
flow edges (fl_F: each instruction "referencing" the next) are excluded on both
the full scan and the to_addr/from_addr/from_func fast paths, matching
IDA's Ctrl-X view and the other xsql tools. Counts therefore reflect
calls/jumps/data refs, not instruction adjacency.
SELECT printf('0x%X', from_addr) as caller FROM xrefs WHERE to_addr = 0x401000 AND is_code = 1;
SELECT printf('0x%X', to_addr) as target FROM xrefs WHERE from_addr >= 0x401000 AND from_addr < 0x401100;
struct_member_xrefs
Cross-references to a specific struct/union member (Issue 11). Resolves the
member's IDA tid and enumerates xrefs directly — use this instead of LIKE
scans over instructions.disasm / instruction_operands.text. Requires a
filter on type_ordinal, type_name, or member_id (an unfiltered query
errors). Embedded members are expanded recursively with a dotted member_path.
| Column | Type | Description |
|---|
type_ordinal / type_name | INT/TEXT | Queried (top-level) type |
member_index | INT | Leaf index within its immediate parent type |
member_name / member_path | TEXT | Leaf name / dotted path (payload.leaf.data) |
member_offset / member_offset_bits | INT | Cumulative offset within the queried type |
member_id | INT | Member tid (also a filter key) |
xref_from | INT | Referencing address |
xref_to | INT | Member tid (== member_id) |
xref_type | INT | Raw IDA xref type code |
xref_kind | TEXT | read / write / offset / call / jump / flow |
function_addr / function_name | INT/TEXT | Containing function (NULL if none) |
operand_index | INT | Operand referencing the member (best-effort; NULL) |
instruction_text | TEXT | Disasm line at xref_from (NULL if not an instruction) |
access_offset / access_size | INT | Access sub-offset / width (best-effort; NULL) |
SELECT member_path, xref_kind, function_name, instruction_text
FROM struct_member_xrefs
WHERE type_name = '_EH3_EXCEPTION_REGISTRATION' AND member_name = 'TryLevel';
SELECT member_path, count(*) AS refs FROM struct_member_xrefs
WHERE type_ordinal = (SELECT ordinal FROM types WHERE name = 'db_entry_t')
GROUP BY member_path;
imports
Imported functions from external libraries.
| Column | Type | Description |
|---|
addr | INT | Import address (IAT entry) |
name | TEXT | Import name |
module | TEXT | Module/DLL name |
ordinal | INT | Import ordinal |
folder_path | TEXT | Writable import folder path |
full_path | TEXT | Full import dirtree path |
SELECT name FROM imports WHERE module LIKE '%kernel32%';
UPDATE imports
SET folder_path = 'idasql/imports/network'
WHERE name LIKE '%socket%' OR name LIKE '%connect%' OR name LIKE '%send%';
Convenience Views
callers
Who calls each function. Caller names are resolved by the view from the funcs and names tables.
| Column | Type | Description |
|---|
func_addr | INT | Target function address |
caller_addr | INT | Xref source address |
caller_name | TEXT | Calling function name |
caller_func_addr | INT | Calling function start (from from_func) |
Underlying query:
SELECT x.to_addr as func_addr, x.from_addr as caller_addr,
COALESCE((SELECT name FROM names WHERE addr = x.from_func LIMIT 1), printf('sub_%X', x.from_func)) as caller_name,
x.from_func as caller_func_addr
FROM xrefs x WHERE x.is_code = 1 AND x.from_func != 0
SELECT caller_name, printf('0x%X', caller_addr) as from_addr
FROM callers WHERE func_addr = 0x401000;
SELECT printf('0x%X', func_addr) as addr, COUNT(*) as callers
FROM callers GROUP BY func_addr ORDER BY callers DESC LIMIT 10;
callees
What each function calls. Inverse of callers view. Uses from_func for efficient function-level grouping.
| Column | Type | Description |
|---|
func_addr | INT | Calling function address (from from_func) |
func_name | TEXT | Calling function name |
callee_addr | INT | Called address |
callee_name | TEXT | Called function/symbol name |
SELECT callee_name, printf('0x%X', callee_addr) as addr
FROM callees WHERE func_name LIKE '%main%';
SELECT func_name, COUNT(*) as call_count
FROM callees GROUP BY func_addr ORDER BY call_count DESC LIMIT 10;
string_refs
Pre-joined view of string cross-references with containing function info.
| Column | Type | Description |
|---|
string_addr | INT | Address of the string |
string_value | TEXT | String content |
string_length | INT | String length |
ref_addr | INT | Address of the referencing instruction |
func_addr | INT | Containing function address |
func_name | TEXT | Containing function name |
SELECT string_value, func_name FROM string_refs WHERE func_addr = 0x401000;
SELECT string_value, func_name FROM string_refs WHERE string_value LIKE '%password%';
SELECT string_value, COUNT(*) as ref_count
FROM string_refs GROUP BY string_addr ORDER BY ref_count DESC LIMIT 10;
data_refs
Cached table of data (non-code) cross-references with containing function info.
Whole-program aggregates are supported.
| Column | Type | Description |
|---|
from_addr | INT | Source address of the reference |
to_addr | INT | Target data address |
from_func_addr | INT | Containing function address |
from_func_name | TEXT | Containing function name |
ref_type | INT | Xref type code |
SELECT * FROM data_refs WHERE from_func_addr = 0x401000;
SELECT from_func_name, COUNT(*) as data_ref_count
FROM data_refs GROUP BY from_func_addr ORDER BY data_ref_count DESC LIMIT 10;
call_graph
Table-valued function for BFS call graph traversal. Uses HIDDEN parameters for traversal control.
| Column | Type | Description |
|---|
func_addr | INT | Function address in the graph |
func_name | TEXT | Function name |
depth | INT | BFS depth from start |
parent_addr | INT | Parent function address in the traversal |
start | INT | HIDDEN — Starting function address |
direction | TEXT | HIDDEN — 'down' (callees), 'up' (callers), or 'both' |
max_depth | INT | HIDDEN — Maximum traversal depth |
Always provide WHERE constraints for all 3 hidden parameters.
SELECT func_name, depth FROM call_graph
WHERE start = (SELECT addr FROM funcs WHERE name = 'main')
AND direction = 'down' AND max_depth = 5;
SELECT func_name, depth FROM call_graph
WHERE start = 0x405000 AND direction = 'up' AND max_depth = 10;
SELECT func_name, depth FROM call_graph
WHERE start = 0x401000 AND direction = 'both' AND max_depth = 3;
SELECT DISTINCT sr.string_value, sr.func_name
FROM call_graph cg
JOIN string_refs sr ON sr.func_addr = cg.func_addr
WHERE cg.start = 0x401000 AND cg.direction = 'down' AND cg.max_depth = 3;
SELECT DISTINCT i.module, i.name as api
FROM call_graph cg
JOIN disasm_calls dc ON dc.func_addr = cg.func_addr
JOIN imports i ON dc.callee_addr = i.addr
WHERE cg.start = 0x401000 AND cg.direction = 'down' AND cg.max_depth = 5
ORDER BY i.module, i.name;
Performance: BFS with visited set. O(reachable functions). Always constrain hidden params.
Use this pattern when the destination is an import.
shortest_path
Table-valued function for finding the shortest call path between two functions. Uses bidirectional BFS.
| Column | Type | Description |
|---|
step | INT | Step number in the path (0 = source) |
func_addr | INT | Function address at this step |
func_name | TEXT | Function name at this step |
from_addr | INT | HIDDEN — Source function address |
to_addr | INT | HIDDEN — Destination function address |
max_depth | INT | HIDDEN — Maximum search depth |
Always provide WHERE constraints for all 3 hidden parameters.
Both endpoints must resolve to functions. Imported API addresses from imports
are not valid shortest_path endpoints.
SELECT step, func_name FROM shortest_path
WHERE from_addr = (SELECT addr FROM funcs WHERE name = 'main')
AND to_addr = (SELECT addr FROM funcs WHERE name = 'target_func')
AND max_depth = 20;
SELECT COUNT(*) > 0 as reachable FROM shortest_path
WHERE from_addr = 0x401000 AND to_addr = 0x405000 AND max_depth = 20;
SELECT sp.step, sp.func_name,
(SELECT COUNT(*) FROM disasm_calls dc WHERE dc.func_addr = sp.func_addr) as calls_made,
(SELECT COUNT(*) FROM string_refs sr WHERE sr.func_addr = sp.func_addr) as strings_used
FROM shortest_path sp
WHERE sp.from_addr = 0x401000 AND sp.to_addr = 0x405000 AND sp.max_depth = 20
ORDER BY sp.step;
Performance: Bidirectional BFS. O(b^(d/2)) where b is branching factor and d is path length. Returns empty result set if no path exists within max_depth.
Table-First Cross-Reference Queries
SELECT from_addr, to_addr, type, is_code, from_func
FROM xrefs
WHERE to_addr = 0x401000;
SELECT from_addr, to_addr, type, is_code, from_func
FROM xrefs
WHERE from_addr = 0x401000;
SELECT from_addr, to_addr, type, is_code, from_func
FROM xrefs
WHERE from_func = 0x401000;
grep
Use grep to resolve internal symbols, types, and members before doing relationship analysis. Use imports when the callee may exist only as an imported API.
Canonical usage lives in ../grep/SKILL.md.
For canonical schema and owner mapping, see ../connect/references/schema-catalog.md (grep).
SELECT name, kind, addr
FROM grep
WHERE pattern = 'main%' AND kind = 'function'
ORDER BY name;
SELECT module, name, addr
FROM imports
WHERE name LIKE 'CreateFile%'
ORDER BY module, name;
SELECT caller_name, printf('0x%X', caller_addr) AS from_addr
FROM callers
WHERE func_addr = (
SELECT addr
FROM imports
WHERE name = 'CreateFileW'
ORDER BY name
LIMIT 1
);
Performance Rules
Constraint Pushdown
The xrefs table has optimized filters using efficient IDA SDK APIs:
| Filter | Cost | Behavior |
|---|
to_addr = X | 0.5 | O(xrefs to X) — fast, uses IDA's xref index |
from_addr = X | 0.5 | O(xrefs from X) — fast, uses IDA's xref index |
from_func = X | 1.0 | O(callees of X) — uses XrefsFromFuncIterator |
No equality filter on to_addr / from_addr / from_func | — | Falls back to a cache of xrefs to function entry points only — avoid relying on it for complete import/data/non-function coverage |
Always filter xrefs by to_addr, from_addr, or from_func. Avoid unconstrained scans.
SELECT * FROM xrefs WHERE to_addr = 0x401000;
SELECT * FROM xrefs WHERE from_addr = 0x401000;
SELECT * FROM xrefs WHERE from_func = 0x401000;
SELECT * FROM xrefs WHERE is_code = 1;
Common Xref Patterns
Find Most Called Functions
SELECT f.name, COUNT(*) as caller_count
FROM funcs f
JOIN xrefs x ON f.addr = x.to_addr
WHERE x.is_code = 1
GROUP BY f.addr
ORDER BY caller_count DESC
LIMIT 10;
Find Functions Calling a Specific API
SELECT DISTINCT (SELECT name FROM funcs WHERE from_addr >= addr AND from_addr < end_addr LIMIT 1) as caller
FROM xrefs
WHERE to_addr = (SELECT addr FROM imports WHERE name = 'CreateFileW');
String Cross-Reference Analysis
SELECT s.content, (SELECT name FROM funcs WHERE x.from_addr >= addr AND x.from_addr < end_addr LIMIT 1) as used_by
FROM strings s
JOIN xrefs x ON s.addr = x.to_addr
WHERE s.content LIKE '%password%';
Import Dependency Map
SELECT f.name as func_name, i.module, COUNT(*) as api_count
FROM funcs f
JOIN disasm_calls dc ON dc.func_addr = f.addr
JOIN imports i ON dc.callee_addr = i.addr
GROUP BY f.addr, i.module
ORDER BY f.name, api_count DESC;
Data Section References
SELECT
f.name,
s.name as segment,
COUNT(*) as data_refs
FROM funcs f
JOIN xrefs x ON x.from_addr BETWEEN f.addr AND f.end_addr
JOIN segments s ON x.to_addr BETWEEN s.start_addr AND s.end_addr
WHERE s.class = 'DATA' AND x.is_code = 0
GROUP BY f.addr, s.name
ORDER BY data_refs DESC
LIMIT 20;
Advanced Xref Patterns (CTEs and Recursive Queries)
Prefer call_graph over manual CTEs. The call_graph table uses C++ BFS with a visited set — correct BFS depths, no duplicate expansion on diamond-shaped call graphs, and early termination.
Before (recursive CTE — no visited tracking, exponential on diamond graphs):
WITH RECURSIVE call_chain(root, current_func, depth) AS (
SELECT 0x401000, callee_addr, 1
FROM disasm_calls WHERE func_addr = 0x401000
UNION ALL
SELECT cc.root, dc.callee_addr, cc.depth + 1
FROM call_chain cc
JOIN disasm_calls dc ON dc.func_addr = cc.current_func
WHERE cc.depth < 10
)
SELECT DISTINCT current_func, MIN(depth) FROM call_chain GROUP BY current_func;
After (call_graph table — C++ BFS with visited set, correct depths):
SELECT func_addr, func_name, depth FROM call_graph
WHERE start = 0x401000 AND direction = 'down' AND max_depth = 10;
Recursive Call Graph — Forward Traversal
Note: For targeted traversal, prefer the call_graph table which uses C++ BFS with visited tracking. Use recursive CTEs only when you need custom join logic (e.g., filtering by callee properties at each step).
Find all functions reachable from a starting function (up to depth 5):
SELECT func_name, depth FROM call_graph
WHERE start = (SELECT addr FROM funcs WHERE name = 'main')
AND direction = 'down' AND max_depth = 5;
WITH RECURSIVE cg AS (
SELECT addr as func_addr, name, 0 as depth
FROM funcs WHERE name = 'main'
UNION ALL
SELECT f.addr, f.name, cg.depth + 1
FROM cg
JOIN disasm_calls dc ON dc.func_addr = cg.func_addr
JOIN funcs f ON f.addr = dc.callee_addr
WHERE cg.depth < 5
AND dc.callee_addr != 0
)
SELECT DISTINCT func_addr, name, MIN(depth) as min_depth
FROM cg
GROUP BY func_addr
ORDER BY min_depth, name;
Recursive Call Graph — Reverse (Who Calls This?)
Note: For targeted traversal, prefer the call_graph table with direction = 'up'. Use recursive CTEs only when you need custom join logic.
Trace callers transitively up to depth 5:
SELECT func_name, depth FROM call_graph
WHERE start = 0x401000 AND direction = 'up' AND max_depth = 5;
WITH RECURSIVE callers_cte AS (
SELECT DISTINCT dc.func_addr, 1 as depth
FROM disasm_calls dc
WHERE dc.callee_addr = 0x401000
UNION ALL
SELECT DISTINCT dc.func_addr, c.depth + 1
FROM callers_cte c
JOIN disasm_calls dc ON dc.callee_addr = c.func_addr
WHERE c.depth < 5
)
SELECT (SELECT name FROM funcs WHERE func_addr >= addr AND func_addr < end_addr LIMIT 1) as caller, MIN(depth) as distance
FROM callers_cte
GROUP BY func_addr
ORDER BY distance, caller;
CTE: Functions That Both Call malloc AND Check NULL
WITH malloc_callers AS (
SELECT DISTINCT func_addr
FROM disasm_calls
WHERE callee_name LIKE '%malloc%'
),
null_checkers AS (
SELECT DISTINCT func_addr
FROM ctree_v_comparisons
WHERE rhs_num = 0 AND op_name = 'cot_eq'
)
SELECT f.name
FROM funcs f
JOIN malloc_callers m ON f.addr = m.func_addr
JOIN null_checkers n ON f.addr = n.func_addr;
CTE: Memory Allocation Without Free (Potential Leaks)
WITH allocators AS (
SELECT func_addr, COUNT(*) as alloc_count
FROM disasm_calls
WHERE callee_name LIKE '%alloc%' OR callee_name LIKE '%malloc%'
GROUP BY func_addr
),
freers AS (
SELECT func_addr, COUNT(*) as free_count
FROM disasm_calls
WHERE callee_name LIKE '%free%'
GROUP BY func_addr
)
SELECT f.name,
COALESCE(a.alloc_count, 0) as allocations,
COALESCE(r.free_count, 0) as frees
FROM funcs f
LEFT JOIN allocators a ON f.addr = a.func_addr
LEFT JOIN freers r ON f.addr = r.func_addr
WHERE a.alloc_count > 0 AND COALESCE(r.free_count, 0) = 0
ORDER BY allocations DESC;
EXISTS: Functions With at Least One String Reference
More efficient than JOIN + DISTINCT for existence checks:
SELECT f.name
FROM funcs f
WHERE EXISTS (
SELECT 1 FROM xrefs x
JOIN strings s ON x.to_addr = s.addr
WHERE x.from_addr BETWEEN f.addr AND f.end_addr
);
EXISTS: Leaf Functions (No Outgoing Calls)
SELECT f.name, f.size
FROM funcs f
WHERE NOT EXISTS (
SELECT 1 FROM disasm_calls dc
WHERE dc.func_addr = f.addr
)
ORDER BY f.size DESC;
See Also
disassembly — call-site context (disasm_at, disasm_calls, operand-level reads).
data — string/data targets behind a to_addr; the canonical pivot when xrefs returns .rdata-only rows.
decompiler — calling-code logic and indirect-call resolution via callee types.