bpf_rs/
insns.rs

1// TODO: #![warn(missing_docs)]
2// TODO: #![warn(missing_doc_code_examples)]
3//! Primitives for the eBPF instruction set. See [kernel docs](https://www.kernel.org/doc/html/latest/bpf/instruction-set.html)
4//! for the canonical details
5//!
6//! The exports here should allow for the creation of simple eBPF programs that can be loaded into the kernel.
7//! The functions provide a convenient away to create valid instructions.
8//!
9//! The exported functions currently return the underlying libbpf_sys's `bpf_insn` binding
10//! so loading the program  through other libbpf_sys functions should work.
11//! In the future, we should provide convenient functions to encapsulate this.
12//!
13//! # Instruction set (ISA) versions
14//!
15//! Not all of the instructions currently available were released at the same time. Instructions (mostly for jump ops)
16//! have been added over time, resulting in different versions of the eBPF instruction set. We will denote
17//! if an operation is part of the v2 or v3 instruction set.
18//!
19//! For more info, see [BPF Design Q&A](https://www.kernel.org/doc/html/latest/bpf/bpf_design_QA.html#q-why-bpf-jlt-and-bpf-jle-instructions-were-not-introduced-in-the-beginning)
20//! and [Paul Chaignon's blog post](https://pchaigno.github.io/bpf/2021/10/20/ebpf-instruction-sets.html)
21//!
22//! # Example
23//!
24//! As an example use case of the primitives here, for feature detection we can run a small eBPF
25//! program that determines if [bounded loops](https://lwn.net/Articles/794934/)
26//! (introduced in the v5.3 kernel) are supported:
27//!
28//!```
29//! # fn load_insns<S>(v: Vec<S>) -> bool { true }
30//! use bpf_rs::insns::*;
31//! // Inspired by bpftool's feature probing
32//! let bounded_loops_insns = vec![
33//!     mov64_imm(Register::R0, 10),
34//!     alu64_imm(AluOp::SUB, Register::R0, 1),
35//!     jmp_imm(JmpOp::JNE, Register::R0, 0, -2),
36//!     exit(),
37//! ];
38//! // Returns true if program was successfully loaded into the kernel
39//! let bounded_loops_supported: bool = load_insns(bounded_loops_insns);
40//!```
41//!
42use libbpf_sys as sys;
43use num_enum::{IntoPrimitive, TryFromPrimitive};
44
45/// Instruction classes
46///
47/// **Note**: 32-bit ALU ops are denoted with [`Class::ALU`] and 64-bit ALU ops are
48/// [`Class::ALU64`] yet 32-bit jump ops are in [`Class::JMP32`] and 64-bit jump ops are in [`Class::JMP`].
49#[repr(u8)]
50#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, Hash)]
51pub enum Class {
52    /// Immediate loads
53    LD = sys::BPF_LD as u8,
54    /// Register loads
55    LDX = sys::BPF_LDX as u8,
56    /// Immediate stores
57    ST = sys::BPF_ST as u8,
58    /// Register stores
59    STX = sys::BPF_STX as u8,
60    /// Arithmetic operations (32-bit)
61    ALU = sys::BPF_ALU as u8,
62    /// Arithmetic operation (64-bit)
63    ALU64 = sys::BPF_ALU64 as u8,
64    /// Jump operations (64-bit)
65    JMP = sys::BPF_JMP as u8,
66    /// Jump operations (32-bit)
67    JMP32 = sys::BPF_JMP32 as u8,
68}
69
70/// # eBPF Registers
71///
72/// Quoting the [kernel documentation](https://www.kernel.org/doc/html/latest/bpf/instruction-set.html#registers-and-calling-convention)
73/// on eBPF registers:
74///
75/// > eBPF has **10 general purpose registers** and a read-only frame pointer register, all of which are 64-bits wide.
76/// >
77/// > The eBPF calling convention is defined as:
78/// >
79/// >  - `R0`: return value from function calls, and exit value for eBPF programs
80/// >
81/// >  - `R1` - `R5`: arguments for function calls
82/// >
83/// >  - `R6` - `R9`: callee saved registers that function calls will preserve
84/// >
85/// >  - `R10`: read-only frame pointer to access stack
86/// >
87/// > `R0` - `R5` are scratch registers and eBPF programs needs to spill/fill them if necessary across calls.
88///
89/// Source: [kernel tree](https://github.com/torvalds/linux/blob/d569e86915b7f2f9795588591c8d5ea0b66481cb/tools/include/uapi/linux/bpf.h#L53)
90#[repr(u8)]
91#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum Register {
93    /// Usually used as either the return value in function calls or as the exit value in programs
94    R0 = sys::BPF_REG_0 as u8,
95    ///
96    R1 = sys::BPF_REG_1 as u8,
97    ///
98    R2 = sys::BPF_REG_2 as u8,
99    ///
100    R3 = sys::BPF_REG_3 as u8,
101    ///
102    R4 = sys::BPF_REG_4 as u8,
103    ///
104    R5 = sys::BPF_REG_5 as u8,
105    ///
106    R6 = sys::BPF_REG_6 as u8,
107    ///
108    R7 = sys::BPF_REG_7 as u8,
109    ///
110    R8 = sys::BPF_REG_8 as u8,
111    ///
112    R9 = sys::BPF_REG_9 as u8,
113    /// Read-only frame pointer register
114    R10 = sys::BPF_REG_10 as u8,
115}
116
117/// Arithmetic instructions
118///
119/// These are meant to be used with the BPF_ALU and BPF_ALU64 instruction classes.
120///
121/// In the pseudo-code described below, `dst` and `src` can refer to registers or immediate values
122/// depending on other bits set within the opcode.
123///
124/// Source: [kernel tree](https://github.com/torvalds/linux/blob/d569e86915b7f2f9795588591c8d5ea0b66481cb/tools/include/uapi/linux/bpf_common.h#L31)
125#[repr(u8)]
126#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, Hash)]
127pub enum AluOp {
128    /// `dst += src`
129    ADD = sys::BPF_ADD as u8,
130    /// `dst -= src`
131    SUB = sys::BPF_SUB as u8,
132    /// `dst *= src`
133    MUL = sys::BPF_MUL as u8,
134    /// `dst /= src`
135    DIV = sys::BPF_DIV as u8,
136    /// `dst |= src`
137    OR = sys::BPF_OR as u8,
138    /// `dst &= src`
139    AND = sys::BPF_AND as u8,
140    /// `dst <<= src`
141    LSH = sys::BPF_LSH as u8,
142    /// `dst >>= src`
143    RSH = sys::BPF_RSH as u8,
144    /// `dst = ~src`
145    NEG = sys::BPF_NEG as u8,
146    /// `dst %= src`
147    MOD = sys::BPF_MOD as u8,
148    /// `dst ^= src`
149    XOR = sys::BPF_XOR as u8,
150    /// `dst = src`
151    MOV = sys::BPF_MOV as u8,
152    /// `dst >>= src` (with sign extension)
153    ARSH = sys::BPF_ARSH as u8,
154    /// Byte swap operations. See [kernel docs](https://www.kernel.org/doc/html/latest/bpf/instruction-set.html#byte-swap-instructions)
155    END = sys::BPF_END as u8,
156}
157
158/// Jump operations
159///
160/// To be used with the BPF_JMP and BPF_JMP32 instruction classes
161///
162/// See [kernel docs](https://www.kernel.org/doc/html/latest/bpf/instruction-set.html#jump-instructions)
163#[repr(u8)]
164#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, Hash)]
165pub enum JmpOp {
166    /// Only allowed with the BPF_JMP instruction class
167    JA = sys::BPF_JA as u8,
168    JEQ = sys::BPF_JEQ as u8,
169    JGT = sys::BPF_JGT as u8,
170    JGE = sys::BPF_JGE as u8,
171    JSET = sys::BPF_JSET as u8,
172    JNE = sys::BPF_JNE as u8,
173    JSGT = sys::BPF_JSGT as u8,
174    JSGE = sys::BPF_JSGE as u8,
175    CALL = sys::BPF_CALL as u8,
176    EXIT = sys::BPF_EXIT as u8,
177    /// Part of [ISA v2](./#instruction-set-isa-versions)
178    JLT = sys::BPF_JLT as u8,
179    /// Part of [ISA v2](./#instruction-set-isa-versions)
180    JLE = sys::BPF_JLE as u8,
181    /// Part of [ISA v2](./#instruction-set-isa-versions)
182    JSLT = sys::BPF_JSLT as u8,
183    /// Part of [ISA v2](./#instruction-set-isa-versions)
184    JSLE = sys::BPF_JSLE as u8,
185}
186
187#[repr(u8)]
188#[derive(Debug, TryFromPrimitive, IntoPrimitive, Clone, Copy, PartialEq, Eq, Hash)]
189pub enum SrcOp {
190    K = sys::BPF_K as u8,
191    X = sys::BPF_X as u8,
192}
193
194// Since Rust lacks native support for bitfields, rust-bindgen tries its best.
195// Hopefully this is good enough, but if not we'll need helpers from libbpf-sys.
196fn create_bpf_insn(code: u8, dst: u8, src: u8, off: i16, imm: i32) -> sys::bpf_insn {
197    sys::bpf_insn {
198        code,
199        _bitfield_align_1: [],
200        _bitfield_1: sys::bpf_insn::new_bitfield_1(dst, src),
201        off,
202        imm,
203    }
204}
205
206pub fn alu64_imm(op: AluOp, dst: Register, imm: i32) -> sys::bpf_insn {
207    create_bpf_insn(
208        u8::from(op) | u8::from(SrcOp::K) | u8::from(Class::ALU64),
209        dst.into(),
210        0,
211        0,
212        imm,
213    )
214}
215
216pub fn mov64_imm(dst: Register, imm: i32) -> sys::bpf_insn {
217    alu64_imm(AluOp::MOV, dst, imm)
218}
219
220pub fn alu64_reg(op: AluOp, dst: Register, src: Register) -> sys::bpf_insn {
221    create_bpf_insn(
222        u8::from(op) | u8::from(SrcOp::X) | u8::from(Class::ALU64),
223        dst.into(),
224        src.into(),
225        0,
226        0,
227    )
228}
229
230pub fn mov64_reg(dst: Register, src: Register) -> sys::bpf_insn {
231    alu64_reg(AluOp::MOV, dst, src)
232}
233
234pub fn jmp_imm(jmp: JmpOp, dst: Register, imm: i32, off: i16) -> sys::bpf_insn {
235    create_bpf_insn(
236        u8::from(jmp) | u8::from(SrcOp::K) | u8::from(Class::JMP),
237        dst.into(),
238        0,
239        off,
240        imm,
241    )
242}
243
244pub fn jmp32_imm(jmp: JmpOp, dst: Register, imm: i32, off: i16) -> sys::bpf_insn {
245    create_bpf_insn(
246        u8::from(jmp) | u8::from(SrcOp::K) | u8::from(Class::JMP32),
247        dst.into(),
248        0,
249        off,
250        imm,
251    )
252}
253
254pub fn exit() -> sys::bpf_insn {
255    create_bpf_insn(u8::from(JmpOp::EXIT) | u8::from(Class::JMP), 0, 0, 0, 0)
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261    use bpfdeploy_libbpf_sys as sys;
262
263    #[test]
264    fn test_abi_compat() {
265        let dst = Register::R2;
266        let imm = 123123;
267
268        vec![
269            (
270                unsafe { sys::_BPF_MOV64_IMM(dst.into(), imm) },
271                mov64_imm(dst, imm),
272            ),
273            (
274                unsafe { sys::_BPF_ALU64_IMM(AluOp::MOV.into(), dst.into(), imm) },
275                alu64_imm(AluOp::MOV, dst, imm),
276            ),
277            (
278                unsafe { sys::_BPF_JMP_IMM(JmpOp::JNE.into(), dst.into(), 32, 10) },
279                jmp_imm(JmpOp::JNE, dst, 32, 10),
280            ),
281            (
282                unsafe { sys::_BPF_JMP32_IMM(JmpOp::JNE.into(), dst.into(), 1000, 500) },
283                jmp32_imm(JmpOp::JNE, dst, 1000, 500),
284            ),
285            (unsafe { sys::_BPF_EXIT_INSN() }, exit()),
286        ]
287        .iter()
288        .for_each(|(expected_insn, observed_insn)| {
289            assert_eq!(expected_insn.code, observed_insn.code);
290            assert_eq!(expected_insn.dst_reg(), observed_insn.dst_reg());
291            assert_eq!(expected_insn.src_reg(), observed_insn.src_reg());
292            assert_eq!(expected_insn.off, observed_insn.off);
293            assert_eq!(expected_insn.imm, observed_insn.imm);
294        })
295    }
296}