Language Features
Overview
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]
tocpu.x[30]
for 64-bit GPRs (note that register index must be a constant),cpu.w[0]
tocpu.w[30]
for 32-bit GPRs,cpu.pc
,cpu.sp
andcpu.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
andcpu.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.