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
strlit
type (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.sp
andcpu.cpsr
for the processor state,cpu.id
for 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_el0
andcpu.tpidrro_el0
for 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.pid
andthread.tid
for UNIX-style PID / thread ID,thread.name
for 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.