Skip to content

Using aya-tool

Source Code

Full code for the example in this chapter is availble here

Very often you will need to use type definitions that your running Linux kernel uses in its source code. For example, you might need a definition of task_struct, because you are about to write a BPF program which receives an information about new scheduled process/task. Aya doesn't provide any definition of this structure. What should be done to get that definition? And we also need that definition in Rust, not in C.

That's what aya-tool is designed for. It's a tool which allows to generate Rust bindings for specific kernel structures.

It can be installed with the following command:

$ cargo install --git https://github.com/aya-rs/aya -- aya-tool

Ensure that you have bpftool installed in your system, aya-tool is not going to work without it.

The syntax of the command is:

$ aya-tool
aya-tool 0.1.0

USAGE:
    aya-tool <SUBCOMMAND>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    generate
    help         Prints this message or the help of the given subcommand(s)

Let's assume that we want to generate Rust definition of task_struct. Let's also assume that your project is called myapp. Your userspace part is in myapp subdirectory, your eBPF part is in myapp-ebpf. We need to generate the bindings for the eBPF part, which can be done with:

$ aya-tool generate task_struct > myapp-ebpf/src/vmlinux.rs

Generating for multiple types

You can also specify multiple types to generate, for example:

$ aya-tool generate task_struct dentry > vmlinux.rs
But in the following example, we will focus only on task_struct.

Then we can use vmlinux as a module with mod vmlinux in our eBPF program, like here:

myapp-ebpf/src/main.rs
#![no_std]
#![no_main]

#[allow(non_upper_case_globals)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[allow(dead_code)]
mod vmlinux;

use aya_bpf::{
    cty::{c_int, c_ulong},
    macros::{lsm, map},
    maps::HashMap,
    programs::LsmContext,
};

use vmlinux::task_struct;

#[map]
static mut PROCESSES: HashMap<i32, i32> = HashMap::with_max_entries(32768, 0);

#[lsm(name = "task_alloc")]
pub fn task_alloc(ctx: LsmContext) -> i32 {
    match unsafe { try_task_alloc(ctx) } {
        Ok(ret) => ret,
        Err(ret) => ret,
    }
}

unsafe fn try_task_alloc(ctx: LsmContext) -> Result<i32, i32> {
    let task: *const task_struct = ctx.arg(0);
    let _clone_flags: c_ulong = ctx.arg(1);
    let retval: c_int = ctx.arg(2);

    // Save the PID of a new process in map.
    let pid = (*task).pid;
    PROCESSES.insert(&pid, &pid, 0).map_err(|e| e as i32)?;

    // Handle results of previous LSM programs.
    if retval != 0 {
        return Ok(retval);
    }

    Ok(0)
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    unsafe { core::hint::unreachable_unchecked() }
}

Portability and different kernel versions

Structures generated by aya-tool are portable across different Linux kernel versions thanks to mechanism called BPF CO-RE. The structures are not simply generated from kernel headers. However, the target kernel (regardless of version) should have CONFIG_DEBUG_INFO_BTF option enabled.