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

import-kernel-struct

import-kernel-struct reads the vmlinux BTF blob at macroexpand time and generates typed accessor macros for kernel struct fields.

Syntax

(import-kernel-struct struct-name [field1 field2 ...])

struct-name is a symbol like task-struct. Hyphens are converted to underscores for the BTF lookup.

field1, field2, ... optionally restrict which fields to import. If omitted, all fields are imported (including those from anonymous nested structs/unions, which are flattened).

How it works

At macroexpand time, Whistler:

  1. Reads and parses /sys/kernel/btf/vmlinux (cached across expansions).
  2. Finds the struct by name in the BTF type table.
  3. Resolves each field's type through typedefs, const, volatile, etc.
  4. Generates accessor macros using kernel-load (which compiles to probe-read-kernel + stack buffer + load).

For a struct like task_struct, this generates:

;; Scalar/pointer fields:
(task-struct-pid ptr)   ;; -> (kernel-load u32 ptr OFFSET)
(task-struct-tgid ptr)  ;; -> (kernel-load u32 ptr OFFSET)
(task-struct-flags ptr) ;; -> (kernel-load u32 ptr OFFSET)

;; Embedded struct fields:
(task-struct-mm ptr)    ;; -> (+ ptr OFFSET)  (returns typed pointer)

;; Size constant:
+task-struct-size+      ;; total size in bytes

Pointer fields resolve to u64. Embedded structs return a pointer offset (no probe-read -- you access their sub-fields with further kernel-loads). A (as-STRUCT ptr) cast macro is also generated for type-safe pointer tagging.

Pointer chasing

Kernel-load accessors compose naturally for pointer chasing:

(import-kernel-struct task-struct pid tgid mm)
(import-kernel-struct mm-struct exe-file)
(import-kernel-struct file f-path)

;; Chase: current task -> mm -> exe_file
(let* ((task (get-current-task))
       (mm   (task-struct-mm task))
       (exe  (mm-struct-exe-file mm)))
  ;; exe is now a kernel pointer to struct file
  ...)

Each accessor checks the typed-ptr tag at macroexpand time. If you pass a mm-struct pointer to a task-struct accessor, you get a compile-time error. Use (as-task-struct ptr) to cast if intentional.

CO-RE compatibility

The offsets come from the build host's BTF. For CO-RE relocatable programs, use core-load / core-ctx-load instead. import-kernel-struct is best suited for programs that will run on the same kernel they were compiled on.

Saved SBCL images

If you ship a saved SBCL image (sb-ext:save-lisp-and-die) containing the Whistler compiler and compile BPF programs on each target host, the parsed BTF needs to come from the target kernel, not the build host's. The cache is invalidated automatically on image restart via sb-ext:*init-hooks*, and trimmed before save via sb-ext:*save-hooks*. Call reset-vmlinux-btf-cache manually if you need to force a re-read mid-session (e.g. after switching *vmlinux-btf-path*).

Note that anything already macroexpanded into the image — every import-kernel-struct accessor, every defprog, every (ctx field) access — bakes the build host's offsets into the generated code. Those won't refresh. The supported workflow is: ship an image containing the compiler only, call defprog / compile-file* on each target.

Example

(import-kernel-struct task-struct pid tgid comm)

(defstruct exec-event
  (pid  u32)
  (tgid u32)
  (comm (array u8 16)))

(defmap events :type :ringbuf :max-entries 4096)

(defprog trace-exec (:type :kprobe
                     :section "kprobe/__x64_sys_execve"
                     :license "GPL")
  (let ((task (get-current-task)))
    (with-ringbuf (ev events (sizeof exec-event))
      (setf (exec-event-pid ev)  (task-struct-pid task)
            (exec-event-tgid ev) (task-struct-tgid task))
      (probe-read-kernel (exec-event-comm-ptr ev) 16
                         (+ task (task-struct-comm task)))))
  0)

Key points:

  • kernel-load handles the probe-read-kernel dance automatically.
  • Pointer fields return u64 values you can pass to further accessors.
  • +STRUCT-SIZE+ is useful for probe-read-kernel buffer sizing.