R_SYSCALL(3)
β¨ R_SYSCALL(3) β radare2 syscall information library β¨
hey bestie π«Ά welcome to section 3 of the Library Functions Manual, aka the place where we talk about r_syscall, the little syscall brain that radare2 keeps in its pocket. pull up a chair, this one's a whole vibe πΈ
π NAME π
r_syscall β the radare2 syscall information library, no cap π
π SYNOPSIS π
#include <r_syscall.h>
that's it. that's the include. iconic behavior tbh π
π¦ DESCRIPTION π¦
okay so the r_syscall API is a lightweight, architecture- and OS-aware syscall database that radare2 components like the assembler, disassembler and analyzer literally cannot function without. it hands out utilities to load platform-specific syscall tables, query syscall names and numbers, parse syscall item descriptions, and get at related system registers and I/O port mappings. it's giving "tiny library, huge responsibilities" π
the main character here is RSyscall, which holds the loaded SDB databases plus some lil lookup helpers. the API is built for fast in-process lookups and keeps ownership shallow: you (the caller) allocate and free the context with the provided constructors, and you share references with r_syscall_ref(RSyscall *sc). sharing is caring fr fr π
π INITIALIZATION π
want a syscall context? you make one, you destroy one. here's the whole lifecycle:
RSyscall * r_syscall_new(void)β allocates and returns a brand newRSyscallinstance. new instances come with their internal SDB fields already allocated and default platform values set β BUT π no syscall data is loaded untilr_syscall_setup(RSyscall *s, const char *arch, int bits, const char *cpu, const char *os)gets called. so don't come for her before setup, she's not ready yet.void r_syscall_free(RSyscall *ctx)β decrements the reference counter and frees the context once no more references remain. the API uses plain and simple reference counting, so user_syscall_ref(RSyscall *sc)to create an additional reference when you're sharing the object between subsystems (for example, binding the analyzer and the assembler to the same syscall database). teamwork makes the dream work π«Ά
the constructor returns a new, zero-initialized context with internal SDB containers all ready to be loaded by r_syscall_setup(RSyscall *s, const char *arch, int bits, const char *cpu, const char *os).
π SETUP π
loading the correct syscall and sysregs tables for your target platform is the whole job of:
bool r_syscall_setup(RSyscall *s, const char *arch, int bits, const char *cpu, const char *os)
call this right after creating the context, or literally any time the target architecture / OS changes (she's flexible like that β¨). the function picks the appropriate SDB file (or embedded gperf table) using the patterns:
"syscall/<os>-<arch>-<bits>""sysregs/<arch>-<bits>-<cpu>"
typical usage: the assembler/disassembler initializes its local syscall context and calls r_syscall_setup with the exact same parameters used to configure the engine. here's the example straight outta libr/main/rasm2.c:
as->a->syscall = r_syscall_new();
r_syscall_setup (as->a->syscall, arch, bits, as->opt.cpu, as->opt.kernel);
note, bestie: the function normalizes common aliases (for exampleandroid->linux) and may switch internal defaults for certain architectures (e.g. x86 syscall port tables). so don't be confused when the vibes shift under the hood π
π LOOKUP BY NUMBER AND NAME π
the main operations let you map from numbers to names (and to fancier structured items), and from names to numbers. these helpers are the most-used ones in the analysis and assembly codepaths β main character energy, truly.
RSyscallItem * r_syscall_get(RSyscall *ctx, int num, int swi)β hand her a syscall number and an optional software interrupt selector (swi), and she returns a freshly allocatedRSyscallItemdescribing the syscall (name, number,swiand argument info). the caller owns the returned item and must free it withr_syscall_item_free(RSyscallItem *si). yes you, you own it, free it, don't ghost it π
example (used in libr/core/canal.c to label discovered SWI instructions):
int sig = r_syscall_get_num (core->anal->syscall, sysnumstr);
RSyscallItem *si = r_syscall_get (core->anal->syscall, snv, -1);
if (si) {
// annotate flags or print name
r_flag_set_next (core->flags, r_strf ("syscall.%s", si->name), cur, 1);
r_syscall_item_free (si);
} else {
r_flag_set_next (core->flags, r_strf ("syscall.%d", snv), cur, 1);
}
int r_syscall_get_num(RSyscall *ctx, const char *str)β looks up the numerical identifier for a syscall by name. returns-1on errors or when the database isn't loaded. super common when an assembler or scripting helper needs to turn textual syscall names into numbers so it can emit proper immediates. it's giving translator bestie π§
example β get the syscall number for write:
int write_num = r_syscall_get_num (sc, "write");
const char * r_syscall_get_i(RSyscall *ctx, int num, int swi)β returns the raw string key for a syscall entry (often the dotted key stored in the SDB). use this when you only need the textual mapping and don't wanna allocate a wholeRSyscallItem. lowkey efficient, highkey iconic π
πΈ LISTING πΈ
RList * r_syscall_list(RSyscall *ctx)β enumerates the syscalls currently available for the loaded configuration. returns a newly allocatedRListofRSyscallItemobjects, and guess what β the caller is responsible for deallocating the list and the items. use this when you're building UIs or dump utilities that need the complete picture of the available syscall set. full inventory, no crumbs left behind β¨
RList *list = r_syscall_list (sc);
// iterate and free using r_list_foreach or r_list_pop
π SYSREGS AND I/O PORTS π
some platforms hand out named system registers (sysregs) and well-known I/O port names. these helpers query the loaded sysregs DB or fall back to small built-in tables:
const char * r_syscall_sysreg(RSyscall *s, const char *type, ut64 num)β returns a string describing a system register or resource of a given type (for exampleio,sror vendor-specific namespaces) indexed bynum. heads up: the string points into the SDB's internal storage and must not be freed by the caller. hands off, bestie πconst char * r_syscall_get_io(RSyscall *s, int ioport)β a convenience wrapper to get an I/O port name by number. internally it triesr_syscall_sysreg (s, "io", ioport)first, then consults a small in-memory table for common ports (for example on x86). two-step queen π
π¦ SYSITEM CREATION AND LIFETIME π¦
RSyscallItem * r_syscall_item_new_from_string(const char *name, const char *s)β parses a compact SDB-style syscall description string and returns an allocatedRSyscallItem. the format is typically a comma separated string describing theswi, number, argument count and argument descriptors. this helper is used internally by the lookup functions, and it's exposed for tools that need to build items from raw SDB values. diy syscall item, we love to see it π οΈvoid r_syscall_item_free(RSyscallItem *si)β frees an item previously returned by the API. that's the whole job. free her and let her rest π
β SOFTWARE INTERRUPTS (SWI) β
some architectures multiplex syscall tables through different SWI numbers or selectors, and the API exposes a small helper to grab the current default SWI for the loaded database:
int r_syscall_get_swi(RSyscall *s)β returns the default software interrupt number used by the currently loaded syscall table, or-1on error. simple, clean, slay β¨
typical callers pass -1 for the swi parameter of r_syscall_get(RSyscall *ctx, int num, int swi), which makes the implementation substitute the configured default value (see the internal getswi helper used by the library). so -1 here is not an error, it's a "you pick for me" π
π₯ EXAMPLES π₯
this section has practical code snippets extracted from radare2 sources showing the common usage patterns. snack time π
1) binding and sharing a syscall context between analyzer and assembler:
// when initializing core subsystems
core->rasm->syscall = r_syscall_ref (core->anal->syscall); // share reference
// when creating an independent assembler context (rasm2 example)
as->a->syscall = r_syscall_new();
r_syscall_setup (as->a->syscall, arch, bits, as->opt.cpu, as->opt.kernel);
2) annotating discovered SWI instructions in the analysis pipeline (from libr/core/canal.c):
int snv = (arch == R2_ARCH_THUMB)? op.val: (int)r_reg_getv (core->anal->reg, sn);
if (snv > 0 && snv < 0xFFFF) {
RSyscallItem *si = r_syscall_get (core->anal->syscall, snv, -1);
if (si) {
r_flag_set_next (core->flags, r_strf ("syscall.%s", si->name), cur, 1);
r_syscall_item_free (si);
} else {
r_flag_set_next (core->flags, r_strf ("syscall.%d", snv), cur, 1);
}
}
3) looking up a numeric value from a textual name for assembly and scripting helpers:
int num = r_syscall_get_num (sc, "write");
if (num >= 0) {
// emit syscall immediate or use it in analysis
}
4) enumerating and printing all loaded syscalls:
RList *list = r_syscall_list (sc);
RListIter *it;
RSyscallItem *si;
r_list_foreach (list, it, si) {
printf ("%s: swi=%d num=%d args=%d0, si->name, si->swi, si->num, si->args);
}
r_list_free (list);
π SEE ALSO π
and that's the whole page, bestie π«Ά go forth and query syscalls responsibly. this was brought to you by the Debian squad, dated September 20, 2025 β mwah πβ¨π
π¦ where this page comes from
- alpine 3.24: radare2-doc 6.1.6-r0 Β· GPL-3.0-or-later AND LGPL-2.0-or-later AND LGPL-2.1-or-later
- arch rolling: radare2 6.2.2-1 Β· GPL-3.0-only AND LGPL-3.0-only
- nixos 26.05: radare2 6.1.8
- nixpkgs unstable: radare2 6.2.4
- opensuse-leap 16.0: radare2 6.2.2-bp160.1.1 Β· GPL-3.0-only AND LGPL-3.0-only
- opensuse-leap 16.0: radare2 6.2.2-bp160.1.1 Β· GPL-3.0-only AND LGPL-3.0-only
- opensuse-leap 16.0: radare2 6.2.2-bp160.1.1 Β· GPL-3.0-only AND LGPL-3.0-only
- opensuse-leap 16.0: radare2 6.2.2-bp160.1.1 Β· GPL-3.0-only AND LGPL-3.0-only
- ubuntu resolute: radare2 6.0.7+ds-1 Β· LGPL-3.0-only
- ubuntu stonking: radare2 6.1.8+ds-1 Β· LGPL-3.0-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms