libcr2 (C)¶
libcr2 is the C library for the r2 kernel: thin wrappers around every syscall, a small printf, string and memory helpers, and a TCP/IP stack. It is the oldest part of the SDK and most of the shipped applications are built on it.
| Header | Covers |
|---|---|
syscall.h |
Syscall numbers (SyscallNo_T), the raw syscall() entry, one wrapper per syscall, and the structures of the newer calls (WriteRange_T, MemInfo_T) |
types.h |
Structures the kernel reads and writes: SysInfo_T, RTC_T, Entry_T, VfsDirEntry_T, MountInfo_T, FsStat_T, TaskInfo_T, FBInfo_T, FsckReport_T, MousePacket_T, … TaskInfo_T is 28 bytes and includes the task's last rip. |
printf.h |
printf with a minimal set of conversions |
string.h, mem.h, bytes.h |
strlen, memcmp, memcpy, byte-order helpers |
args.h |
Argument parsing helpers |
net.h |
SLIP decoding, IPv4/ICMP/TCP parsing, ARP, a DHCP client, and a small TCP socket layer (bind, listen, tcp_read, tcp_write, tcp_close) |
Building the Library¶
In the c/ directory:
make libcr2
produces the static archive c/libcr2.a. Programs link it with -lcr2.
crt0¶
The kernel has no C runtime, so every program is linked with _crt0.o, assembled from c/libcr2/_crt0.asm:
nasm -f elf64 -o _crt0.o c/libcr2/_crt0.asm
_start reads argc and argv from the frame the kernel left on the stack, switches to a private stack in .bss (1.5 MiB by default), calls main(argc, argv), and passes its return value to the exit syscall. The stack size can be changed per program with -DR2_STACK_KB=<n>: code, data, .bss and this stack all share the one 2 MiB frame at 0x600_000, so a program with large static buffers has to take the room from the stack.
Building a Program¶
Each program lives in its own directory under c/ with a Makefile that compiles freestanding, links _crt0.o + objects + libcr2.a with the shared c/linker.ld:
compile:
@cd ${BUILD_DIR} && gcc -c -O2 -flto -m64 -static -nostdlib -nostdinc \
-mno-red-zone -ffreestanding -I .. -I ../../libcr2/ ${SOURCE_FILES}
link:
@gcc -nostdinc -nostdlib -nostartfiles ${BUILD_DIR}/*.o \
-L .. -lcr2 -static -Xlinker "-T../linker.ld" -o ${NAME}.elf
c/Makefile.tmpl holds the same rules for reuse. -mno-red-zone is required: the kernel interrupts userland, and an interrupt frame would land in the red zone below rsp.
The linker script places .text at 0x600000 with .data and .bss following, in two PT_LOAD segments (R+X, R+W).
A minimal program:
#include "syscall.h"
int main(int argc, char **argv) {
print((const uint8_t *)"Hello, world!\n");
return 0;
}
API at a Glance¶
| Area | Functions |
|---|---|
| Process | exit (also r2_exit), run_elf, list_tasks, kill_task |
| System | read_sysinfo, write_sysinfo, set_user, read_rtc, get_ticks, sleep_ms, read_meminfo |
| Console | print, printf, clear_screen |
| Input | pipe_subscribe, pipe_read, pipe_unsubscribe (keyboard); pipe_mouse_subscribe, pipe_mouse_read, pipe_mouse_unsubscribe |
| Graphics | get_fb_info, write_pixel, write_vga, blit_buffer, blit_buffer_scaled, map_vram, set_video_mode, get_kernel_font; capture: capture_framebuffer, capture_framebuffer_rgb24_scaled, and capture_framebuffer_rgb24_scaled_if_new, which skips a snapshot the caller already has (0x1d metadata) |
| Audio | play_freq, play_midi_file, stop_speaker |
| Files | read_file, read_file_at, write_file, write_file_at, rename_file, delete_file, write_subdir, chdir (also r2_chdir), list_dir, list_dir_path, list_mounts, fs_stat (a mount's size and format, syscall 0x40; the FS_TYPE_* and FS_FORMAT_* constants name what it returns), run_fs_check |
| Memory | malloc, realloc, free — on the kernel's shared userland heap, its extension included; shared_heap_contains(ptr, size) says whether a whole range lies in it without touching it (syscall 0x43; on older kernels only the original 4 MiB can be checked) |
| Ports, serial | read_port, write_port, serial_init, serial_read, serial_write |
| IPC | send_data, receive_data, receive_data_nb (syscalls 0x36, 0x35) |
| Networking | new_packet, send_packet, send_eth_frame, net_register, net_bind_port, net_unbind_port, get_net_status, get_net_stats, get_net_ports, net_port_owner, get_net_config, set_net_config, net_*; sockets: bind, listen, tcp_read, tcp_write, tcp_close (also read, write, close), on_tcp_packet |
For bigger files, prefer read_file_at / write_file_at (syscalls 0x39 / 0x3a): read_file is never told the size of its buffer, and write_file always writes exactly one 512-byte block.
Networking¶
net.c implements, on top of the raw packet syscalls, everything a server needs: SLIP decoding for the serial link, ARP, ICMP echo, a DHCP client and a passive TCP socket pool. The ETH driver uses it to register as the machine's Ethernet driver and obtain an address; GARN, TNT and CHAT bind TCP ports on top of it.
By default the Ethernet driver reads frames from the process's kernel queue (syscall 0x35). A process has only one queue, so a program that runs a second network stack next to libcr2's cannot let both read it: each would take and discard the other's frames. Such a program reads the queue itself and passes libcr2 its frames through net_set_frame_source(fn). The callback fills a buffer with one frame and returns its length, or 0 when no frame is waiting; a blocking call waits for one. Passing NULL returns libcr2 to the kernel queue. Memento uses this to share its own browser-engine networking
with Chat, IRC and network tools; hosted Web and Telegram processes have their
own queues. libcr2's frame buffer is 1518 bytes to accommodate the RTL8139's
attached 4-byte FCS; the Intel backend strips it in hardware.
Current TCP sockets keep the incoming local destination address and use it as the reply source, allowing services to answer connections made to the WireGuard tunnel address. Relink older server binaries to get the fix. New outbound tunnel connections require the network stack to select that source address explicitly; the physical configuration read by get_net_config remains the Ethernet configuration.
The kernel-side model (driver registration, port binding, per-tick frame delivery and tunnel routing) is described in Networking Overview.
Network monitoring¶
get_net_stats(&stats) reads system physical NIC totals and their millisecond
timestamp (0x45, NetStats_T, 40 bytes). get_net_ports(&table) reads full
64-bit TCP owner PIDs, 16-byte process names and the global driver's PID
(0x46, NetPortTable_T, 528 bytes). net_port_owner(port, &owner) scans a
fresh table and returns 0 if found, 0xfe if unbound, or a syscall error.
Reads return 0 on success, 0xff on older kernels; get_net_ports may
return 0xfa (busy, retry). See the counter semantics and ABI layouts.
With a C Library¶
libcr2 also works next to the small C library of the TCC port, which programs compiled on r2 link against. Its headers notice that library by <sys/r2libc.h> on the include path (R2_LIBC, in types.h) and then leave the C names to it: the integer types, printf, strlen, memcpy, and five calls of libcr2's own, which are known there by other names:
| Name in an app built here | Name next to the C library |
|---|---|
exit(pid, code) |
r2_exit(pid, code) |
chdir(path) |
r2_chdir(path) |
read(sock, buf, max) |
tcp_read(sock, buf, max) |
write(sock, buf, len) |
tcp_write(sock, buf, len) |
close(sock) |
tcp_close(sock) |
The names on the right exist in every build, so the apps here, built with -nostdinc, have both. On r2, tcc -o srv.elf srv.c -lcr2 builds a program on libcr2's TCP/IP stack.
Known Issues¶
memcpytakes auint16_tlength and silently truncates copies over 65535 bytes. Programs on the TCC port's C library get that library'smemcpyinstead.
syscall() loads the number into both RAX (what the kernel reads) and RDX (what older kernels read), passes its third argument in RCX (read by 0x1d with its opt-in bit, and by 0x44, which requires a frame length for operation 3 and zero otherwise), and lists R9 as clobbered. Earlier versions loaded RDX only and left R9 out of the clobbers; programs built against them should be rebuilt.
Related documentation: SDK Overview, C++ Library, TCC, Ports and Networking ABI.