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 new RSyscall instance. new instances come with their internal SDB fields already allocated and default platform values set β€” BUT πŸ’€ no syscall data is loaded until r_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 use r_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 example android -> 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 allocated RSyscallItem describing the syscall (name, number, swi and argument info). the caller owns the returned item and must free it with r_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 -1 on 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 whole RSyscallItem. lowkey efficient, highkey iconic πŸ’…

🌸 LISTING 🌸

  • RList * r_syscall_list(RSyscall *ctx) β€” enumerates the syscalls currently available for the loaded configuration. returns a newly allocated RList of RSyscallItem objects, 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 example io, sr or vendor-specific namespaces) indexed by num. 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 tries r_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 allocated RSyscallItem. the format is typically a comma separated string describing the swi, 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 -1 on 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