Skip to main content

Language Features


The language used to write hooks is superficially similar to C; however, it has the following differences from it:

  • the only types supported are integer types and pointers to them,
  • there is no way to define functions; each hook is written like a function body,
  • variables may be automatically declared at assignment if the type can be fully derived from the right-hand side,
  • variables can be declared anywhere before use, including inside for(;;) loop initial statements,
  • variables are globally scoped,
  • character string literals are not of pointer type. Instead, they are a special strlittype (because pointers access VM memory instead, literal strings can't be pointers).

The supported control structures are: if, if-else, while, do-while, break, and for.

Built-in integer types can be referred to with standard C names (unsigned, long, etc.), stdint-style names (uint64_t), or short names (u64).

The unary & (reference) operator is not supported.

Variables declared with static keep their values between invocations of the hook, just like static variables inside a C function. There is a limit of 8 such variables in a given hook, imposed by the execution environment.

Skipping the existing instruction after a hook

A hook can perform an operation and skip/replace the existing instruction at the hooked address with return 1:

mon patch 0xfffffff0061b8ca4 cpu.x[0] = 0; return 1;

This example causes the hypervisor to write 0 to x0 and skip the existing instruction at 0xfffffff0061b8ca4.

Accessing the device state

Accessing the processor state is done by using a pseudo-struct CPU. The supported fields in that structure are:

  • cpu.x[0] to cpu.x[30] for 64-bit GPRs (note that register index must be a constant),
  • cpu.w[0] to cpu.w[30] for 32-bit GPRs,
  • cpu.pc, cpu.sp and cpu.cpsr for the processor state,
  • cpu.midr_el1, cpu.mpidr_el1, cpu.sctlr_el1, cpu.ttbr0_el1, cpu.ttbr1_el1, cpu.tcr_el1, cpu.spsr_el1, cpu.elr_el1, cpu.esr_el1, cpu.far_el1, cpu.par_el1, cpu.vbar_el1, cpu.isr_el1, cpu.contextidr_el1, cpu.tpidr_el1, cpu.tpidr_el0 and cpu.tpidrro_el0 for EL1-visible system registers.

Writing these fields will modify the corresponding processor state upon return to VM.

Accessing VM memory (kernel virtual address view) happens by dereferencing pointers. For instance,

print_int(“foo”, *(u64 *)0xfffffff001234568);

will print the value of a 64-bit word at 0xFFFFFFF001234568 in the VM.

Built-In Functions

The following functions are available:

void print(strlit s);

print a literal string.

void print_int(strlit s, u64 a);

print an integer with optional string prefix s (pass 0 to not print a string).

void print_str(strlit s, void *p, u64 z);

print a zero-terminated string from VM memory at p, maximum z bytes, optional prefix s.

void print_buf(strlit s, void *p, u64 z);

print a buffer from VM memory at p, maximum size z bytes, with optional prefix s.

void print_thread(strlit s, u64 t);

print info on thread t (pass 0 for current) with optional string prefix s.

void print_backtrace(strlit s, u64 d);

print backtrace, maximum depth d, with optional string prefix s.

void usleep(u32 usec);

delay for usec microseconds.

void debug(void);

cause the device to take a debug trap immediately after the hook returns.

u64 mapped(type *p);

check if the device memory location is valid. The type determines the size of the location.

u64 mapped(void *p, u64 z);

check if the device memory range is valid; the size is defined by z.

u64 min(u64 a, u64 b); u64 max(u64 a, u64 b);
s64 min(s64 a, s64 b); s64 max(s64 a, s64 b);

minimum/maximum for unsigned and signed values.

Additionally, the constant NULL is defined as 0.

The global[0] to global[63] are 64-bit numbers shared between all hooks installed on the VM.