Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Memory Access

load

Read a value of a given type from a pointer at a byte offset:

(load type ptr offset)
(load type ptr)          ; offset defaults to 0

Types: u8, u16, u32, u64. The result type matches the load type.

(let ((ethertype (load u16 data 12))
      (protocol  (load u8  ip   9)))
  ...)

store

Write a value to memory:

(store type ptr offset value)
(store u32 event 0 src-addr)
(store u8  event 10 proto)

ctx

Read from or write to the BPF program context (the ctx pointer passed by the kernel). The context structure varies by program type -- for XDP it contains data, data_end, and data_meta; for cgroup-sock-addr it contains user_ip4, user_port, etc.

ctx is a setf-able place. The preferred form uses field names, which the compiler resolves from the program type's context struct:

;; Read a context field by name
(ctx field-name)

;; Write a context field
(setf (ctx field-name) value)

;; Array fields require an index
(ctx user-ip6 0)                  ; first u32 of IPv6 address
(setf (ctx user-ip6 2) value)     ; write third element
;; XDP: read packet bounds
(defprog my-xdp (:type :xdp ...)
  (let ((data     (ctx data))
        (data-end (ctx data-end)))
    ...))

;; cgroup/connect4: redirect destination
(defprog connect4 (:type :cgroup-sock-addr ...)
  (let ((ip (ctx user-ip4)))
    (setf (ctx user-ip4) +localhost-nbo+)
    (setf (ctx user-port) (htons 8080))))

The compiler knows which context struct each program type uses (xdp_md, __sk_buff, bpf_sock_addr, bpf_sock_ops) and resolves field names to types and offsets at compile time.

Legacy syntax: (ctx type offset) with explicit type and numeric offset still works for backward compatibility:

(ctx u32 4)              ; equivalent to (ctx user-ip4) in cgroup-sock-addr
(setf (ctx u32 4) val)   ; equivalent to (setf (ctx user-ip4) val)

Note: ctx-load and ctx-store are deprecated. Use (ctx ...) and (setf (ctx ...) ...) instead.

stack-addr

Take the address of a stack-allocated variable (analogous to &var in C). Useful for passing pointers to BPF helpers that expect pointer arguments.

(let ((key u32 0))
  (map-lookup my-map (stack-addr key)))

atomic-add

Atomically add a value to memory at a given offset:

(atomic-add ptr offset value)
(atomic-add ptr offset value type)   ; type defaults to u64
(when-let ((p (map-lookup counters key)))
  (atomic-add p 0 1))

The offset must be aligned to the type width.

memset

Fill a region of memory with a byte value. Both offset and nbytes must be compile-time constants. When the value is a compile-time constant, the compiler emits widened stores (u64/u32/u16) for efficiency.

(memset ptr offset value nbytes)
;; Zero 32 bytes starting at offset 0
(memset buf 0 0 32)

;; Fill 16 bytes with 0xFF
(memset key 16 #xFF 16)

memcpy

Copy bytes between memory regions. All offsets and nbytes must be compile-time constants. The compiler uses the widest possible loads/stores (u64, then u32, u16, u8) for efficiency.

(memcpy dst dst-offset src src-offset nbytes)
(memcpy event 0 data 14 20)   ; copy 20 bytes of IP header

pt_regs access

For kprobe and uprobe programs, function arguments are available through pt_regs accessors. These are compile-time macros that expand to ctx-load with architecture-specific offsets (x86-64 and aarch64 supported).

MacroDescription
(pt-regs-parm1)First argument
(pt-regs-parm2)Second argument
(pt-regs-parm3)Third argument
(pt-regs-parm4)Fourth argument
(pt-regs-parm5)Fifth argument
(pt-regs-parm6)Sixth argument
(pt-regs-ret)Return value
(defprog trace-open (:type :kprobe :section "kprobe/do_sys_open" :license "GPL")
  (let ((filename-ptr (pt-regs-parm2)))
    (probe-read-user-str buf 256 filename-ptr)
    ...))

Warning

pt-regs accessors are architecture-specific. Currently x86-64 and aarch64 are supported. Compiling on an unsupported architecture produces a compile-time error.