voyant is domain specific language based on the eBPF instruction set (insn) and system calls;
ulike other eBPF tools, it is designed to be lightweight and easy extendable,There are three aspectes that can account for my option;
-
Ligtht tool First of all, no LLVM, indeed LLVM is an exceptional tool for building complier backends, but LLVM is counted in ten millions, the light weight is one of my goals and the rules out LLVM.
-
Easy: Due to its lightweight design, our DSL is easier to install and can perform effectively even in resource-constrained environments.
-
Clear: The third, our dsl will offer the similarly level of expressivity as general-purpose programming language, also extending the semantics in certain aspectes;
git clone
cd lang
make
sudo ./voyant main.vy
Currently, our DSL supports two types of mounting targets: one is kernel functions, and the other is tracepoints. I recommend using tracepoints whenever possible, as they are more stable.
#[syscalls]; //<-----event name
probe [tracepoint] {
....
}
#kprobe; //<-----kprobe string
probe [kprobe] {
...
}
The out function is similar to the printf function in C. It is typically used to send data from the runtime of our program back to user space.
#syscalls;
probe sys_enter_execve {
out("%s", "Hello, World!");
}
The :=
symbol is used to declare a variable in the current scope. currently, we do not support reassigning values to variables.
#syscalls;
probe sys_enter_execve {
a := 1;
out("%d\n", a);
}
#syscalls;
probe sys_enter_execve{
a := 4 * 2;
b := 4 + 2;
c := 4 - 2;
d := 4 / 2;
out("a:%d b:%d c:%d d:%d\n", a, b, c, d);
}
Our DSL supports several common helper functions, which are essentially the same as those found in eBPF.
#syscalls;
probe sys_enter_open {
out("%-18d %-16s %-6d\n", pid(), comm(), cpu());
}
- Map: Using the
map[comm()]
statement, we can create a map where the keys are generated by the comm() function.
#syscalls;
probe sys_enter_open{
map[comm()] := pid();
}
- Method Call Operator: The |> operator is a special operator that indicates method call semantics. in this case, map's value init zero
#syscalls;
probe sys_enter_execve {
enter[comm()] |> count();
}
probe sys_exit_execve {
exit[comm()] |> count();
}
- map in muti probes
#syscalls;
probe sys_enter_open {
enter[pid()] := args->filename;
}
probe sys_exit_open {
ret := args->ret;
out("%-18d %-16s %-6d %s\n", pid(), comm(), ret, enter[pid()]);
}
Begin is a special probe used to perform tasks before program compilation, such as outputting some prompt messages.
#syscalls;
BEGIN {
out("%-18s %-16s %-6s\n", "PID", "COMM", "FILE");
}
probe sys_enter_open {
out("%-18d %-16s %-6d\n", pid(), comm(), cpu());
}
In our DSL, we can get trace point parameter information using args->filename
, and the compiler will automatically infer the corresponding parameter type. For example, args->filename
is of type string."
#syscalls;
probe sys_enter_open{
arg := arg->filename;
out("%-18d %-16s %-6s\n", pid(), comm(), arg);
}
Our DSL also supports simple if statements, and it will later support corresponding boolean expressions.
#syscalls;
probe sys_enter_execve {
if (cpu() >= 0) {
out("on cpu %d", cpu());
}
}
#kprobe;
probe dev_queue_xmit {
sk := (sk_buff*) arg0;
out("%d\n", sk->len);
}