Lunatik

probeModule

Places kprobes on kernel functions and runs Lua handlers when they are hit.

A probe goes on a kernel symbol, or on an address as syscall.address, syscall.table and linux.lookup return one. Its pre handler runs before the probed instruction and its post handler after it. What a handler returns is ignored, so a probe observes the function and cannot skip it, and the error of a handler that raises goes to the kernel log. A handler receives the registers of the probed CPU as a probe.regs, whose dump() prints them to the kernel log.

A probe fires wherever the probed code runs, inside an interrupt handler too, with preemption or interrupts off, so the script runs in a hardirq runtime, lunatik run -c hardirq <script>, whose lock turns interrupts off, and its handlers must not sleep. A probe must not be placed where the probed CPU holds a lock its handler’s Lua takes again, since the handler then spins on it with interrupts off: a function the scheduler runs under a runqueue lock, enqueue_task_fair or activate_task among them, where an allocation the handler makes can wake kswapd and print can wake a console’s waiter, and either wakeup takes that lock; a function that runs under the allocator’s own locks, a zone’s lock or a slab node’s list_lock, where the handler’s allocation takes them; and a function the timekeeping update runs while it holds the clock’s sequence for write, update_vsyscall among them, where linux.time waits on that sequence. Nor may it be placed on code that runs in NMI, the perf overflow path among it, where an allocation can take a slab or zone lock the interrupted code holds.

See examples/systrack and examples/dropreason.

Usage

-- run with `lunatik run -c hardirq <script>`
local probe = require("probe")

local reported = false

local function pre(symbol, regs)
  if not reported then
    reported = true
    print(symbol)
    regs:dump()
  end
end

probe.new("do_sys_openat2", {pre = pre})

Class probe

probe:disable () Disables the probe: it stays registered, and a hit runs no handler until enable.
probe:enable () Enables the probe: a hit runs its handlers, as it does from new until a disable.
probe:stop () Unregisters and stops the probe.

Class probe_regs

probe_regs:argument (n) Reads an argument of the probed function.
probe_regs:dump () Prints the registers to the kernel log.

probe

new (symbol, handlers) Creates and registers a new kprobe.

Class probe in detail

Represents a registered kprobe.
probe:disable ()
Disables the probe: it stays registered, and a hit runs no handler until enable.

Raises

if the probe has been stopped, if the percpu object owns this probe, not allowed once the runtime is armed (its script body has returned): disable_kprobe sleeps, and an armed runtime runs with its lock held and interrupts off; or the error disable_kprobe returns
probe:enable ()
Enables the probe: a hit runs its handlers, as it does from new until a disable.

Raises

if the probe has been stopped, if the percpu object owns this probe, not allowed once the runtime is armed (its script body has returned): enable_kprobe sleeps, and an armed runtime runs with its lock held and interrupts off; or the error enable_kprobe returns
probe:stop ()
Unregisters and stops the probe. From a handler this raises: the way to stop delivering there is to return early on a flag the script owns, and the kprobe is unregistered when the runtime stops. A to-be-closed variable holding the probe stops it the same way.

Raises

if the percpu object owns this probe, or not allowed once the runtime is armed (its script body has returned): unregister_kprobe sleeps, and an armed runtime runs with its lock held and interrupts off

Class probe_regs in detail

The registers of the CPU a probe hit, which a handler receives. A userdata reused for every hit of every probe of the runtime: while a handler runs it holds the registers of that hit, and it is cleared when the handler returns, so a script that keeps it and reads it outside a handler gets an error rather than a pointer into a frame that is gone.
probe_regs:argument (n)
Reads an argument of the probed function. Reads through regs_get_kernel_argument(), which guesses the register mapping: what it returns is not the argument past the registers the architecture passes arguments in, nor after a parameter 16 bytes or larger.

Parameters

  • n integer position of the argument, counting from zero

Returns

    integer the argument, as the register holds it

Raises

closed object outside a handler; out of bounds if n is negative; EOPNOTSUPP where the architecture does not select CONFIG_HAVE_FUNCTION_ARG_ACCESS_API
probe_regs:dump ()
Prints the registers to the kernel log.

Raises

closed object outside a handler

probe in detail

new (symbol, handlers)
Creates and registers a new kprobe. In a percpu script the runtimes share one kprobe per symbol or address: the first registration installs it, the others attach their handlers, and a call reaches the runtime of the CPU it ran on. Probing a target another probe already holds does not fail: both handlers run on a hit, so two symbols the kernel resolves to one address each count the other’s calls. In a percpu script, repeating a target of the set is refused instead.

Parameters

  • symbol string or lightuserdata kernel symbol name or address
  • handlers table table with optional pre and post callback functions; each receives the symbol (string) or the address as given (lightuserdata), and the probe.regs of the hit. The table is read once, to decide whether the kernel installs a post handler, so a post added to it afterwards never fires; a pre added afterwards does

Returns

    probe the probe, which probe.new keeps for its runtime: dropping it stops nothing, and the kprobe stays until stop or the end of the runtime, or of the set in a percpu script

Raises

bad field '<field>' (function expected, got <type>) if pre or post is present and not a function; runtime context mismatch unless the runtime is hardirq; not allowed while the runtime closes from a finalizer that runs at its close; the kernel’s errno if it refuses the registration, ENOENT for a symbol it does not have; in a percpu script, if this runtime already registered the same symbol or address, or if another runtime of the set registered this target with a different post handler; or not allowed once the runtime is armed (its script body has returned): register_kprobe sleeps, and an armed runtime runs with its lock held and interrupts off