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}