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, 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, read, write, 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, run_elf, list_tasks, kill_task |
| System | read_sysinfo, write_sysinfo, 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 |
| 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, list_dir, list_dir_path, list_mounts, run_fs_check |
| Memory | malloc, realloc, free — on the kernel's shared userland heap |
| Ports, serial | read_port, write_port, serial_init, serial_read, serial_write |
| Networking | new_packet, send_packet, net_*, bind, listen, 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 does this so that its web browser and its Chat and IRC windows can share one queue. libcr2's frame buffer is 1518 bytes, because the kernel delivers frames with the card's 4-byte CRC still attached.
The kernel-side model (driver registration, port binding, per-tick frame delivery) is described in Networking Overview.
Known Issues
- The syscall number is loaded into
RDXonly. The kernel reads it fromRAX(syscall_handlerdoesmov rdx, raxbefore the dispatcher). A call therefore works only whenRAXhappens to hold the number already. libc++r2 and libgor2 loadRAX; a fix inlibcr2is to add"a"(number)to the inline assembly insyscall.c. R9is not in the clobber list. Every syscall destroysR9; a caller whose compiler keeps a live value there acrossint 0x7fgets the previous return value instead. libc++r2 hit this in practice (see libc++r2); the fix here is to add"r9"to the clobbers.- The third argument never reaches the kernel.
syscall()has a four-argument prototype, but the dispatcher takes two. memcpytakes auint16_tlength and silently truncates copies over 65535 bytes.