bpf_script/
lib.rs

1//! [![Build Status](https://github.com/arcjustin/bpf-script/workflows/build/badge.svg)](https://github.com/arcjustin/bpf-script/actions?query=workflow%3Abuild)
2//! [![crates.io](https://img.shields.io/crates/v/bpf-script.svg)](https://crates.io/crates/bpf-script)
3//! [![mio](https://docs.rs/bpf-script/badge.svg)](https://docs.rs/bpf-script/)
4//! [![Lines of Code](https://tokei.rs/b1/github/arcjustin/bpf-script?category=code)](https://tokei.rs/b1/github/arcjustin/bpf-script?category=code)
5//!
6//! A small scripting language and compiler for creating eBPF programs at runtime without bcc or llvm.
7//!
8//! The intent behind building this crate was to primarily learn more about BPF internals and, secondly, to provide a dependency-free way of writing BPF programs, using a higher-level language, that could be compiled at run-time without the need to shell out to a compiler and load/patch BPF from an ELF file.
9//!
10//! The syntax for the language resembles Rust with a lot of features stripped out. For example, a simple u/k probe program that calls a helper and returns the value looks like so:
11//! ```ignore
12//! fn(regs: &bpf_user_pt_regs_t)
13//!     a = get_current_uid_gid()
14//!     map_push_elem(queue, &a, 0)
15//!     return a
16//! ```
17//!
18//! This crate is made to work together with the following crates but they are not required:
19//! - `btf` A BTF parsing library.
20//! - `bpf-script-derive` Allows you to seamlessly share types between Rust and this compiler.
21//! - `bpf-api` Creating programs, probes, maps, etc.
22//!
23//! ## Usage
24//!
25//! ```rust
26//! use bpf_script::compiler::Compiler;
27//! use bpf_script::types::{AddToTypeDatabase, TypeDatabase};
28//!
29//! let mut types = TypeDatabase::default();
30//! u32::add_to_database(&mut types).expect("Failed to add type");
31//!
32//! let mut compiler = Compiler::create(&types);
33//! compiler.compile(r#"
34//!     fn(a: u32)
35//!         return a
36//! "#).expect("Compilation failed");
37//!
38//! for ins in compiler.get_instructions() {
39//!     println!("{}", ins);
40//! }
41//! ```
42//!
43//! ## TODO
44//!
45//! * Add control flow.
46//! * Write more thorough tests.
47//!
48//! ## License
49//!
50//! * [MIT license](http://opensource.org/licenses/MIT)
51mod formats;
52mod optimizer;
53
54pub mod compiler;
55pub mod error;
56pub mod types;
57
58#[cfg(test)]
59mod tests {
60    use crate::compiler::Compiler;
61    use crate::error::Result;
62    use crate::types::{AddToTypeDatabase, Field, TypeDatabase};
63    use bpf_ins::{Instruction, Register};
64
65    #[repr(C, align(1))]
66    struct LargeType {
67        pub a: u64,
68        pub b: u32,
69        pub c: u16,
70        pub d: u8,
71    }
72
73    impl AddToTypeDatabase for LargeType {
74        fn add_to_database(database: &mut TypeDatabase) -> Result<usize> {
75            let a_id = u64::add_to_database(database)?;
76            let b_id = u32::add_to_database(database)?;
77            let c_id = u16::add_to_database(database)?;
78            let d_id = u8::add_to_database(database)?;
79            database.add_struct_by_ids(
80                Some("LargeType"),
81                &[("a", a_id), ("b", b_id), ("c", c_id), ("d", d_id)],
82            )
83        }
84    }
85
86    fn compile_and_compare(prog: &str, expected: &[Instruction]) {
87        let mut database = TypeDatabase::default();
88
89        LargeType::add_to_database(&mut database).expect("Failed to add type.");
90
91        database
92            .add_integer(Some("int"), 4, true)
93            .expect("Failed to add type.");
94
95        let u64id = database
96            .add_integer(Some("__u64"), 8, false)
97            .expect("Failed to add type.");
98
99        let iov_base = Field {
100            offset: 0,
101            type_id: u64id,
102        };
103
104        let iov_len = Field {
105            offset: 64,
106            type_id: u64id,
107        };
108
109        database
110            .add_struct(
111                Some("iovec"),
112                &[("iov_base", iov_base), ("iov_len", iov_len)],
113            )
114            .expect("Failed to add type.");
115
116        let mut compiler = Compiler::create(&database);
117        compiler.compile(prog).unwrap();
118
119        let instructions = compiler.get_instructions();
120        assert_eq!(instructions.len(), expected.len());
121        for (i, ins) in instructions.iter().enumerate() {
122            assert_eq!(ins, &expected[i]);
123        }
124    }
125
126    #[test]
127    fn empty_program() {
128        let prog = r#"
129            fn()
130        "#;
131
132        let expected = [
133            Instruction::mov64(Register::R0, 0), // r0 = 0
134            Instruction::exit(),                 // exit
135        ];
136
137        compile_and_compare(prog, &expected);
138    }
139
140    #[test]
141    fn return_immediate() {
142        let prog = r#"
143            fn()
144              return 300
145        "#;
146
147        let expected = [
148            Instruction::mov64(Register::R0, 300), // r0 = 300
149            Instruction::exit(),                   // exit
150        ];
151
152        compile_and_compare(prog, &expected);
153    }
154
155    #[test]
156    fn return_input_value() {
157        let prog = r#"
158            fn(a: int)
159              return a
160        "#;
161
162        let expected = [
163            Instruction::storex64(Register::R10, -8, Register::R1), // *(r10 - 8) = r1
164            Instruction::loadx32(Register::R0, Register::R10, -8),  // r0 = *(r10 - 8)
165            Instruction::exit(),                                    // exit
166        ];
167
168        compile_and_compare(prog, &expected);
169    }
170
171    #[test]
172    fn assign_fields() {
173        let prog = r#"
174            fn()
175              vec: iovec = 0
176              vec.iov_base = 100
177              vec.iov_len = 200
178        "#;
179
180        let expected = [
181            Instruction::store64(Register::R10, -16, 0), // *(r10 - 16) = 0
182            Instruction::store64(Register::R10, -8, 0),  // *(r10 - 8) = 0
183            Instruction::store64(Register::R10, -16, 100), // *(r10 - 16) = 100
184            Instruction::store64(Register::R10, -8, 200), // *(r10 - 8) = 200
185            Instruction::mov64(Register::R0, 0),         // r0 = 0
186            Instruction::exit(),                         // exit
187        ];
188
189        compile_and_compare(prog, &expected);
190    }
191
192    #[test]
193    fn assign_fields_from_fields() {
194        let prog = r#"
195            fn(vec: &iovec)
196              vec_copy: iovec = 0
197              vec_copy.iov_base = vec.iov_base
198              vec_copy.iov_len = vec.iov_len
199              return 50
200        "#;
201
202        let expected = [
203            Instruction::storex64(Register::R10, -8, Register::R1), // *(r10 - 8) = r1
204            Instruction::store64(Register::R10, -24, 0),            // *(r10 - 24) = 0
205            Instruction::store64(Register::R10, -16, 0),            // *(r10 - 16) = 0
206            Instruction::loadx64(Register::R6, Register::R10, -8),  // r6 = *(r10 - 8)
207            Instruction::movx64(Register::R1, Register::R10),       // r1 = r10
208            Instruction::add64(Register::R1, -24),                  // r1 -= 24
209            Instruction::mov64(Register::R2, 8),                    // r2 = 8
210            Instruction::movx64(Register::R3, Register::R6),        // r3 = r6
211            Instruction::call(4),                                   // call #4 (probe_read)
212            Instruction::loadx64(Register::R6, Register::R10, -8),  // r6 = *(r10 - 8)
213            Instruction::add64(Register::R6, 8),                    // r6 += 8
214            Instruction::movx64(Register::R1, Register::R10),       // r3 = r6
215            Instruction::add64(Register::R1, -16),                  // r1 -= 16
216            Instruction::mov64(Register::R2, 8),                    // r2 = 8
217            Instruction::movx64(Register::R3, Register::R6),        // r3 = r6
218            Instruction::call(4),                                   // call #4 (probe_read)
219            Instruction::mov64(Register::R0, 50),                   // r0 = 50
220            Instruction::exit(),                                    // exit
221        ];
222
223        compile_and_compare(prog, &expected);
224    }
225
226    #[test]
227    fn assign_function_call() {
228        let prog = r#"
229            fn()
230                a: __u64 = get_current_uid_gid()
231        "#;
232
233        let expected = [
234            Instruction::call(15), // call #15 (get_current_uid_gid)
235            Instruction::storex64(Register::R10, -8, Register::R0), // *(r10 - 8) = r0
236            Instruction::mov64(Register::R0, 0), // r0 = 0
237            Instruction::exit(),   // exit
238        ];
239
240        compile_and_compare(prog, &expected);
241    }
242
243    #[test]
244    fn return_function_call() {
245        let prog = r#"
246            fn()
247                a: __u64 = 100
248                return get_current_uid_gid()
249        "#;
250
251        let expected = [
252            Instruction::store64(Register::R10, -8, 100), // *(r10 - 8) = 100
253            Instruction::call(15),                        // call #15 (get_current_uid_gid)
254            Instruction::exit(),                          // exit
255        ];
256
257        compile_and_compare(prog, &expected);
258    }
259
260    #[test]
261    fn return_nested_function_call() {
262        let prog = r#"
263            fn()
264                return get_current_uid_gid(get_current_uid_gid())
265        "#;
266
267        let expected = [
268            Instruction::call(15), // call #15 (get_current_uid_gid)
269            Instruction::movx64(Register::R1, Register::R0), // r1 = r0
270            Instruction::call(15), // call #15 (get_current_uid_gid)
271            Instruction::exit(),   // exit
272        ];
273
274        compile_and_compare(prog, &expected);
275    }
276
277    #[test]
278    fn test_zero_init() {
279        let prog = r#"
280            fn()
281                type: LargeType = 0
282        "#;
283
284        let expected = [
285            Instruction::store64(Register::R10, -15, 0), // *(r10 - 15) = 0
286            Instruction::store32(Register::R10, -7, 0),  // *(w10 - 15) = 0
287            Instruction::store16(Register::R10, -3, 0),  // *(h10 - 15) = 0
288            Instruction::store8(Register::R10, -1, 0),   // *(b10 - 15) = 0
289            Instruction::mov64(Register::R0, 0),         // r0 = 0
290            Instruction::exit(),                         // exit
291        ];
292
293        compile_and_compare(prog, &expected);
294    }
295}