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).
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. Use pointer arithmetic for now. (This will be addressed in a future release.)
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 by the lower layer.
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.spandcpu.cpsrfor the processor state,cpu.idfor the processor index within VM (from 0 upwards, read-only),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_el0andcpu.tpidrro_el0for EL1-visible system registers.
Writing these fields will modify the corresponding processor state upon return to VM.
There’s a similar pseudo-struct thread with the following fields:
thread.pidandthread.tidfor UNIX-style PID / thread ID,thread.namefor name of the process (generally limited to 16 characters).
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);
void print_str(strlit s, void *p);
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 VM to take a debug trap immediately after the hook returns
u64 mapped(type *p);
check if VM memory location is valid; the type determines the size of location
u64 mapped(void *p, u64 z);
check if VM 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
s32 strcmp(void *p, void *q);
s32 strncmp(void *p, void *q, u64 max);
compare two strings (note that string literals are automatically cast to void *)
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.