Parcourir la source

Add lib module with conditional imports depending on std/no_std.

This commit introduces a lib module in `lib.rs` responsible for
reexporting all imports from the standard library used by the crate.

It also adjusts all dependencies of rbpf so that they depend on the
standard library only if the `std` feature is used.

This was done to make rbpf compatible with `no_std`. This compatibility
is needed to run rbpf on embedded devices where the standard library
isn't available.

Main difficulties:

- println! macro isn't available on `no_std`. A workaround was
  introduced to use the `log` crate and call logging macros such as
  `warn!` or `info!` in places where println is used when we have access
  to the standard library. Users of the crate can then define their
  custom logger elsewhere in the code and thanks to the log crate it
  will be called by rbpf and print messages using the provided
  implementation.

- the VM interpreter depends on std::io::Error and std::io::ErrorKind.
  Those structs aren't available on no_std. We introduce a simple
  implementation of these two which exposes the same API and use it
  under `no_std` as a drop-in replacement. An important note is that the
  original ErrorKind enum has more than 40 available entries whereas our
  implemnentation only provides the one that is currently used
  throughout the rbpf code (ErrorKind:Other). Should a dependency on new
  variants be added in the future, the replacement ErrorKind (found in
  no_std_error.rs) would have to be updated to maintain compatibility.
  The reason we don't include all variants right now is that not all of
  those error kinds are meaningful in `no_std` context.

- the assembler and asm_parser depend on `combine` crate and use the
  EasyParser from there by default. The problem is that the EasyParser
  is not available in no_std. We get around this issue by using the
  regular Parser when std is not available. Note that the difference
  between the two parser implementations is the quality of error
  messages. The EasyParser is able to extract much more information and
  report it in an error. The regular one simply returns "invalid parse".
  Because of this, two tests of the assembler needed to be updated to
  conditionally check for different error messages when running
  with/without std.

Signed-off-by: SzymonKubica <szymo.kubica@gmail.com>
SzymonKubica il y a 1 an
Parent
commit
86d2586d36
15 fichiers modifiés avec 308 ajouts et 118 suppressions
  1. 31 5
      Cargo.toml
  2. 9 4
      README.md
  3. 1 0
      examples/rbpf_plugin.rs
  4. 51 14
      src/asm_parser.rs
  5. 1 1
      src/assembler.rs
  6. 2 8
      src/cranelift.rs
  7. 15 3
      src/disassembler.rs
  8. 1 0
      src/ebpf.rs
  9. 6 1
      src/helpers.rs
  10. 1 0
      src/insn_builder.rs
  11. 1 3
      src/interpreter.rs
  12. 141 75
      src/lib.rs
  13. 42 0
      src/no_std_error.rs
  14. 1 1
      src/verifier.rs
  15. 5 3
      tests/misc.rs

+ 31 - 5
Cargo.toml

@@ -24,10 +24,15 @@ include = [
 
 [dependencies]
 
-combine = "4.6"
-libc = "0.2"
-time = "0.2"
-byteorder = "1.2"
+# Default features (std) are disabled so that the dependencies don't pull in the
+# standard library when the crate is compiled for no_std
+byteorder = { version = "1.2", default-features = false }
+log = {version = "0.4.21", default-features = false }
+combine = { version = "4.6", default-features = false }
+
+# Optional Dependencies when using the standard library
+libc = { version = "0.2", optional = true }
+time = { version = "0.2", optional = true }
 
 # Optional Dependencies for the CraneLift JIT
 cranelift-codegen = { version = "0.99", optional = true }
@@ -44,7 +49,8 @@ hex = "0.4.3"
 
 [features]
 default = ["std"]
-std = []
+cargo-clippy = []
+std = ["dep:time", "dep:libc", "combine/std"]
 cranelift = [
     "dep:cranelift-codegen",
     "dep:cranelift-frontend",
@@ -52,3 +58,23 @@ cranelift = [
     "dep:cranelift-native",
     "dep:cranelift-module",
 ]
+
+# Examples that depend on the standard library should be disabled when
+# testing the `no_std` configuration.
+[[example]]
+name = "disassemble"
+required-features = ["std"]
+
+[[example]]
+name = "load_elf"
+required-features = ["std"]
+
+[[example]]
+name = "uptime"
+required-features = ["std"]
+
+[[example]]
+name = "to_json"
+
+[[example]]
+name = "rbpf_plugin"

+ 9 - 4
README.md

@@ -447,9 +447,12 @@ fn main() {
     let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
 
     // We register a helper function, that can be called by the program, into
-    // the VM.
-    vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX,
-                       helpers::bpf_trace_printf).unwrap();
+    // the VM. The `bpf_trace_printf` is only available when we have access to
+    // the standard library.
+    #[cfg(feature = "std")] {
+        vm.register_helper(helpers::BPF_TRACE_PRINTK_IDX,
+                           helpers::bpf_trace_printf).unwrap();
+    }
 
     // This kind of VM takes a reference to the packet data, but does not need
     // any reference to the metadata buffer: a fixed buffer is handled
@@ -479,7 +482,9 @@ let prog = assemble("add64 r1, 0x605
                      neg64 r2
                      exit").unwrap();
 
-println!("{:?}", prog);
+#[cfg(feature = "std")] {
+    println!("{:?}", prog);
+}
 ```
 
 The above snippet will produce:

+ 1 - 0
examples/rbpf_plugin.rs

@@ -15,6 +15,7 @@ fn _unwind(a: u64, _b: u64, _c: u64, _d: u64, _e: u64) -> u64
 // It reads the program from stdin.
 fn main() {
     let mut args: Vec<String> = std::env::args().collect();
+    #[allow(unused_mut)] // In no_std the jit variable isn't mutated.
     let mut jit : bool = false;
     let mut cranelift : bool = false;
     let mut program_text = String::new();

+ 51 - 14
src/asm_parser.rs

@@ -8,11 +8,14 @@
 
 use combine::parser::char::{alpha_num, char, digit, hex_digit, spaces, string};
 use combine::stream::position::{self};
+#[cfg(feature = "std")]
+use combine::EasyParser;
 use combine::{
-    attempt, between, eof, many, many1, one_of, optional, sep_by, EasyParser, ParseError, Parser,
-    Stream,
+    attempt, between, eof, many, many1, one_of, optional, sep_by, ParseError, Parser, Stream,
 };
 
+use crate::lib::*;
+
 /// Operand of an instruction.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Operand {
@@ -97,19 +100,30 @@ where
 ///
 /// The instructions are not validated and may have invalid names and operand types.
 pub fn parse(input: &str) -> Result<Vec<Instruction>, String> {
-    match spaces()
-        .with(many(instruction()).skip(eof()))
-        .easy_parse(position::Stream::new(input))
+    let mut with = spaces().with(many(instruction()).skip(eof()));
+
+    #[cfg(feature = "std")]
+    {
+        match with.easy_parse(position::Stream::new(input)) {
+            Ok((insts, _)) => Ok(insts),
+            Err(err) => Err(err.to_string()),
+        }
+    }
+    #[cfg(not(feature = "std"))]
     {
-        Ok((insts, _)) => Ok(insts),
-        Err(err) => Err(err.to_string()),
+        match with.parse(position::Stream::new(input)) {
+            Ok((insts, _)) => Ok(insts),
+            Err(err) => Err(err.to_string()),
+        }
     }
+
 }
 
 #[cfg(test)]
 mod tests {
 
     use super::{ident, instruction, integer, operand, parse, register, Instruction, Operand};
+    use crate::lib::*;
     use combine::Parser;
 
     // Unit tests for the different kinds of parsers.
@@ -566,26 +580,49 @@ exit
         );
     }
 
+    /// When running without `std` the `EasyParser` provided by `combine`
+    /// cannot be used. Because of this we need to use the `Parser` and the
+    /// error messages are different.
     #[test]
     fn test_error_eof() {
+        let expected_error;
+        #[cfg(feature = "std")] {
+            expected_error = Err(
+                "Parse error at line: 1, column: 6\nUnexpected end of input\nExpected digit\n".to_string()
+            );
+        }
+        #[cfg(not(feature = "std"))] {
+            expected_error = Err(
+                "unexpected parse".to_string()
+            );
+        }
         // Unexpected end of input in a register name.
         assert_eq!(
             parse("lsh r"),
-            Err(
-                "Parse error at line: 1, column: 6\nUnexpected end of input\nExpected digit\n"
-                    .to_string()
-            )
+            expected_error
         );
     }
 
+    /// When running without `std` the `EasyParser` provided by `combine`
+    /// cannot be used. Because of this we need to use the `Parser` and the
+    /// error messages are different.
     #[test]
     fn test_error_unexpected_character() {
+        let expected_error;
+        #[cfg(feature = "std")] {
+            expected_error = Err(
+                "Parse error at line: 2, column: 1\nUnexpected `^`\nExpected letter or digit, whitespaces, `r`, `-`, `+`, `[` or end of input\n".to_string()
+            );
+        }
+        #[cfg(not(feature = "std"))] {
+            expected_error = Err(
+                "unexpected parse".to_string()
+            );
+        }
         // Unexpected character at end of input.
         assert_eq!(
             parse("exit\n^"),
-            Err(
-                "Parse error at line: 2, column: 1\nUnexpected `^`\nExpected letter or digit, whitespaces, `r`, `-`, `+`, `[` or end of input\n".to_string()
-            )
+            expected_error
         );
     }
 

+ 1 - 1
src/assembler.rs

@@ -6,10 +6,10 @@
 use asm_parser::{Instruction, Operand, parse};
 use ebpf;
 use ebpf::Insn;
-use std::collections::HashMap;
 use self::InstructionType::{AluBinary, AluUnary, LoadAbs, LoadInd, LoadImm, LoadReg, StoreImm,
                             StoreReg, JumpUnconditional, JumpConditional, Call, Endian, NoOperand};
 use asm_parser::Operand::{Integer, Memory, Register, Nil};
+use crate::lib::*;
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 enum InstructionType {

+ 2 - 8
src/cranelift.rs

@@ -1,12 +1,5 @@
 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
-use std::{
-    collections::{BTreeMap, HashMap, HashSet},
-    convert::TryInto,
-    io::ErrorKind,
-    mem::ManuallyDrop,
-};
-
 use cranelift_codegen::{
     entity::EntityRef,
     ir::{
@@ -26,6 +19,7 @@ use crate::ebpf::{
     self, Insn, BPF_ALU_OP_MASK, BPF_JEQ, BPF_JGE, BPF_JGT, BPF_JLE, BPF_JLT, BPF_JMP32, BPF_JNE,
     BPF_JSET, BPF_JSGE, BPF_JSGT, BPF_JSLE, BPF_JSLT, BPF_X, STACK_SIZE, BPF_IND,
 };
+use crate::lib::*;
 
 use super::Error;
 
@@ -1197,7 +1191,7 @@ impl CraneliftProgram {
     /// module, since it's not guaranteed to be valid after the module is dropped.
     pub(crate) fn get_main_function(&self) -> JittedFunction {
         let function_ptr = self.module.get_finalized_function(self.main_id);
-        unsafe { std::mem::transmute(function_ptr) }
+        unsafe { mem::transmute(function_ptr) }
     }
 
     /// Execute this module by calling the main function

+ 15 - 3
src/disassembler.rs

@@ -4,7 +4,12 @@
 //! Functions in this module are used to handle eBPF programs with a higher level representation,
 //! for example to disassemble the code into a human-readable format.
 
+use log::warn;
+#[cfg(not(feature = "std"))]
+use log::info;
+
 use ebpf;
+use crate::lib::*;
 
 #[inline]
 fn alu_imm_str(name: &str, insn: &ebpf::Insn) -> String {
@@ -20,7 +25,7 @@ fn alu_reg_str(name: &str, insn: &ebpf::Insn) -> String {
 fn byteswap_str(name: &str, insn: &ebpf::Insn) -> String {
     match insn.imm {
         16 | 32 | 64 => {},
-        _ => println!("[Disassembler] Warning: Invalid offset value for {name} insn")
+        _ => warn!("[Disassembler] Warning: Invalid offset value for {name} insn")
     }
     format!("{name}{} r{}", insn.imm, insn.dst)
 }
@@ -384,7 +389,14 @@ pub fn to_insn_vec(prog: &[u8]) -> Vec<HLInsn> {
 /// exit
 /// ```
 pub fn disassemble(prog: &[u8]) {
-    for insn in to_insn_vec(prog) {
-        println!("{}", insn.desc);
+    #[cfg(feature = "std")] {
+        for insn in to_insn_vec(prog) {
+            println!("{}", insn.desc);
+        }
+    }
+    #[cfg(not(feature = "std"))] {
+        for insn in to_insn_vec(prog) {
+            info!("{}", insn.desc);
+        }
     }
 }

+ 1 - 0
src/ebpf.rs

@@ -15,6 +15,7 @@
 //! the list of the operation codes: <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md>
 
 use byteorder::{ByteOrder, LittleEndian};
+use crate::lib::*;
 
 /// Maximum number of instructions in an eBPF program.
 pub const PROG_MAX_INSNS: usize = 1000000;

+ 6 - 1
src/helpers.rs

@@ -16,9 +16,10 @@
 //! value. Hence some helpers have unused arguments, or return a 0 value in all cases, in order to
 //! respect this convention.
 
+#[cfg(feature = "std")]
 extern crate libc;
 
-use std::u64;
+use crate::lib::*;
 
 // Helpers associated to kernel helpers
 // See also linux/include/uapi/linux/bpf.h in Linux kernel sources.
@@ -47,6 +48,7 @@ pub const BPF_KTIME_GETNS_IDX: u32 = 5;
 #[allow(dead_code)]
 #[allow(unused_variables)]
 #[allow(deprecated)]
+#[cfg(feature = "std")]
 pub fn bpf_time_getns (unused1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
     time::precise_time_ns()
 }
@@ -94,6 +96,7 @@ pub const BPF_TRACE_PRINTK_IDX: u32 = 6;
 /// program is run.
 #[allow(dead_code)]
 #[allow(unused_variables)]
+#[cfg(feature = "std")]
 pub fn bpf_trace_printf (unused1: u64, unused2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
     println!("bpf_trace_printf: {arg3:#x}, {arg4:#x}, {arg5:#x}");
     let size_arg = | x | {
@@ -191,6 +194,7 @@ pub fn memfrob (ptr: u64, len: u64, unused3: u64, unused4: u64, unused5: u64) ->
 /// ```
 #[allow(dead_code)]
 #[allow(unused_variables)]
+#[cfg(feature = "std")] // sqrt is only available when using `std`
 pub fn sqrti (arg1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
     (arg1 as f64).sqrt() as u64
 }
@@ -258,6 +262,7 @@ pub fn strcmp (arg1: u64, arg2: u64, arg3: u64, unused4: u64, unused5: u64) -> u
 /// ```
 #[allow(dead_code)]
 #[allow(unused_variables)]
+#[cfg(feature = "std")]
 pub fn rand (min: u64, max: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
     let mut n = unsafe {
         (libc::rand() as u64).wrapping_shl(32) + libc::rand() as u64

+ 1 - 0
src/insn_builder.rs

@@ -4,6 +4,7 @@
 //! Module provides API to create eBPF programs by Rust programming language
 
 use ebpf::*;
+use crate::lib::*;
 
 /// Represents single eBPF instruction
 pub trait Instruction: Sized {

+ 1 - 3
src/interpreter.rs

@@ -5,10 +5,8 @@
 // Copyright 2016 6WIND S.A. <quentin.monnet@6wind.com>
 //      (Translation to Rust, MetaBuff/multiple classes addition, hashmaps for helpers)
 
-use std::collections::HashMap;
-use std::io::{Error, ErrorKind};
-
 use ebpf;
+use crate::lib::*;
 
 fn check_mem(addr: u64, len: usize, access_type: &str, insn_ptr: usize,
              mbuff: &[u8], mem: &[u8], stack: &[u8]) -> Result<(), Error> {

+ 141 - 75
src/lib.rs

@@ -26,10 +26,17 @@
         unreadable_literal
     )
 )]
+// Configures the crate to be `no_std` when `std` feature is disabled.
+#![cfg_attr(not(feature = "std"), no_std)]
 
 extern crate byteorder;
 extern crate combine;
+#[cfg(feature = "std")]
 extern crate time;
+extern crate log;
+
+#[cfg(not(feature = "std"))]
+extern crate alloc;
 
 #[cfg(feature = "cranelift")]
 extern crate cranelift_codegen;
@@ -43,9 +50,7 @@ extern crate cranelift_module;
 extern crate cranelift_native;
 
 use byteorder::{ByteOrder, LittleEndian};
-use std::collections::HashMap;
-use std::io::{Error, ErrorKind};
-use std::u32;
+use crate::lib::*;
 
 mod asm_parser;
 pub mod assembler;
@@ -59,6 +64,60 @@ mod interpreter;
 #[cfg(all(not(windows), feature = "std"))]
 mod jit;
 mod verifier;
+#[cfg(not(feature = "std"))]
+mod no_std_error;
+
+/// Reexports all the types needed from the `std`, `core`, and `alloc`
+/// crates. This avoids elaborate import wrangling having to happen in every
+/// module. Inspired by the design used in `serde`.
+pub mod lib {
+    mod core {
+        #[cfg(not(feature = "std"))]
+        pub use core::*;
+        #[cfg(feature = "std")]
+        pub use std::*;
+    }
+
+    pub use self::core::convert::TryInto;
+    pub use self::core::mem;
+    pub use self::core::mem::ManuallyDrop;
+    pub use self::core::ptr;
+
+    pub use self::core::{u32, u64, f64};
+
+    #[cfg(feature = "std")]
+    pub use std::println;
+
+    #[cfg(not(feature = "std"))]
+    pub use alloc::vec;
+    #[cfg(not(feature = "std"))]
+    pub use alloc::vec::Vec;
+    #[cfg(feature = "std")]
+    pub use std::vec::Vec;
+
+    #[cfg(not(feature = "std"))]
+    pub use alloc::string::{String, ToString};
+    #[cfg(feature = "std")]
+    pub use std::string::{String, ToString};
+
+    // In no_std we cannot use randomness for hashing, thus we need to use
+    // BTree-based implementations of Maps and Sets. The cranelift module uses
+    // BTrees by default, hence we need to expose it twice here.
+    #[cfg(not(feature = "std"))]
+    pub use alloc::collections::{BTreeMap as HashMap, BTreeMap, BTreeSet as HashSet, BTreeSet};
+    #[cfg(feature = "std")]
+    pub use std::collections::{BTreeMap, HashMap, HashSet};
+
+    /// In no_std we use a custom implementation of the error which acts as a
+    /// replacement for the io Error.
+    #[cfg(not(feature = "std"))]
+    pub use crate::no_std_error::{Error, ErrorKind};
+    #[cfg(feature = "std")]
+    pub use std::io::{Error, ErrorKind};
+
+    #[cfg(not(feature = "std"))]
+    pub use alloc::format;
+}
 
 /// eBPF verification function that returns an error if the program does not meet its requirements.
 ///
@@ -189,7 +248,7 @@ impl<'a> EbpfVmMbuff<'a> {
     /// # Examples
     ///
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     ///
     /// // Define a simple verifier function.
@@ -253,6 +312,7 @@ impl<'a> EbpfVmMbuff<'a> {
     /// // Register a helper.
     /// // On running the program this helper will print the content of registers r3, r4 and r5 to
     /// // standard output.
+    /// # #[cfg(feature = "std")]
     /// vm.register_helper(6, helpers::bpf_trace_printf).unwrap();
     /// ```
     pub fn register_helper(&mut self, key: u32, function: Helper) -> Result<(), Error> {
@@ -506,7 +566,7 @@ impl<'a> EbpfVmMbuff<'a> {
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  See `mul_loop` test.
         let mem_ptr = match mem.len() {
-            0 => std::ptr::null_mut(),
+            0 => ptr::null_mut(),
             _ => mem.as_ptr() as *mut u8,
         };
 
@@ -687,7 +747,7 @@ impl<'a> EbpfVmFixedMbuff<'a> {
     /// # Examples
     ///
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     ///
     /// // Define a simple verifier function.
@@ -724,37 +784,39 @@ impl<'a> EbpfVmFixedMbuff<'a> {
     /// # Examples
     ///
     /// ```
-    /// use rbpf::helpers;
-    ///
-    /// // This program was compiled with clang, from a C program containing the following single
-    /// // instruction: `return bpf_trace_printk("foo %c %c %c\n", 10, 1, 2, 3);`
-    /// let prog = &[
-    ///     0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
-    ///     0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
-    ///     0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
-    ///     0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
-    ///     0x2d, 0x12, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 6 instructions
-    ///     0x71, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r1
-    ///     0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
-    ///     0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
-    ///     0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
-    ///     0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
-    ///     0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
-    ///     0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
-    /// ];
-    ///
-    /// let mem = &mut [
-    ///     0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x09,
-    /// ];
-    ///
-    /// // Instantiate a VM.
-    /// let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
-    ///
-    /// // Register a helper. This helper will store the result of the square root of r1 into r0.
-    /// vm.register_helper(1, helpers::sqrti);
-    ///
-    /// let res = vm.execute_program(mem).unwrap();
-    /// assert_eq!(res, 3);
+    /// #[cfg(feature = "std")] {
+    ///     use rbpf::helpers;
+    ///
+    ///     // This program was compiled with clang, from a C program containing the following single
+    ///     // instruction: `return bpf_trace_printk("foo %c %c %c\n", 10, 1, 2, 3);`
+    ///     let prog = &[
+    ///         0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r0, 0
+    ///         0x79, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem from r1[0x40] to r2
+    ///         0x07, 0x02, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, // add r2, 5
+    ///         0x79, 0x11, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, // load mem_end from r1[0x50] to r1
+    ///         0x2d, 0x12, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // if r2 > r1 skip 6 instructions
+    ///         0x71, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // load r2 (= *(mem + 5)) into r1
+    ///         0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
+    ///         0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
+    ///         0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
+    ///         0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
+    ///         0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
+    ///         0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
+    ///     ];
+    ///
+    ///     let mem = &mut [
+    ///         0xaa, 0xbb, 0x11, 0x22, 0xcc, 0x09,
+    ///     ];
+    ///
+    ///     // Instantiate a VM.
+    ///     let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
+    ///
+    ///     // Register a helper. This helper will store the result of the square root of r1 into r0.
+    ///     vm.register_helper(1, helpers::sqrti);
+    ///
+    ///     let res = vm.execute_program(mem).unwrap();
+    ///     assert_eq!(res, 3);
+    /// }
     /// ```
     pub fn register_helper(
         &mut self,
@@ -903,7 +965,7 @@ impl<'a> EbpfVmFixedMbuff<'a> {
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  See `mul_loop` test.
         let mem_ptr = match mem.len() {
-            0 => std::ptr::null_mut(),
+            0 => ptr::null_mut(),
             _ => mem.as_ptr() as *mut u8,
         };
 
@@ -1005,7 +1067,7 @@ impl<'a> EbpfVmFixedMbuff<'a> {
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  See `mul_loop` test.
         let mem_ptr = match mem.len() {
-            0 => std::ptr::null_mut(),
+            0 => ptr::null_mut(),
             _ => mem.as_ptr() as *mut u8,
         };
 
@@ -1126,7 +1188,7 @@ impl<'a> EbpfVmRaw<'a> {
     /// # Examples
     ///
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     ///
     /// // Define a simple verifier function.
@@ -1163,30 +1225,32 @@ impl<'a> EbpfVmRaw<'a> {
     /// # Examples
     ///
     /// ```
-    /// use rbpf::helpers;
+    /// #[cfg(feature = "std")] {
+    ///     use rbpf::helpers;
     ///
-    /// let prog = &[
-    ///     0x79, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxdw r1, r1[0x00]
-    ///     0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
-    ///     0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
-    ///     0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
-    ///     0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
-    ///     0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
-    ///     0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
-    /// ];
+    ///     let prog = &[
+    ///         0x79, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ldxdw r1, r1[0x00]
+    ///         0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
+    ///         0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
+    ///         0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
+    ///         0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
+    ///         0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
+    ///         0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
+    ///     ];
     ///
-    /// let mem = &mut [
-    ///     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
-    /// ];
+    ///     let mem = &mut [
+    ///         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+    ///     ];
     ///
-    /// // Instantiate a VM.
-    /// let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
+    ///     // Instantiate a VM.
+    ///     let mut vm = rbpf::EbpfVmRaw::new(Some(prog)).unwrap();
     ///
-    /// // Register a helper. This helper will store the result of the square root of r1 into r0.
-    /// vm.register_helper(1, helpers::sqrti);
+    ///     // Register a helper. This helper will store the result of the square root of r1 into r0.
+    ///     vm.register_helper(1, helpers::sqrti);
     ///
-    /// let res = vm.execute_program(mem).unwrap();
-    /// assert_eq!(res, 0x10000000);
+    ///     let res = vm.execute_program(mem).unwrap();
+    ///     assert_eq!(res, 0x10000000);
+    /// }
     /// ```
     pub fn register_helper(
         &mut self,
@@ -1471,7 +1535,7 @@ impl<'a> EbpfVmNoData<'a> {
     /// # Examples
     ///
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     ///
     /// // Define a simple verifier function.
@@ -1508,25 +1572,27 @@ impl<'a> EbpfVmNoData<'a> {
     /// # Examples
     ///
     /// ```
-    /// use rbpf::helpers;
+    /// #[cfg(feature = "std")] {
+    ///     use rbpf::helpers;
     ///
-    /// let prog = &[
-    ///     0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // mov r1, 0x010000000
-    ///     0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
-    ///     0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
-    ///     0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
-    ///     0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
-    ///     0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
-    ///     0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
-    /// ];
+    ///     let prog = &[
+    ///         0xb7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // mov r1, 0x010000000
+    ///         0xb7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r2, 0
+    ///         0xb7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r3, 0
+    ///         0xb7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r4, 0
+    ///         0xb7, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r5, 0
+    ///         0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // call helper with key 1
+    ///         0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // exit
+    ///     ];
     ///
-    /// let mut vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
+    ///     let mut vm = rbpf::EbpfVmNoData::new(Some(prog)).unwrap();
     ///
-    /// // Register a helper. This helper will store the result of the square root of r1 into r0.
-    /// vm.register_helper(1, helpers::sqrti).unwrap();
+    ///     // Register a helper. This helper will store the result of the square root of r1 into r0.
+    ///     vm.register_helper(1, helpers::sqrti).unwrap();
     ///
-    /// let res = vm.execute_program().unwrap();
-    /// assert_eq!(res, 0x1000);
+    ///     let res = vm.execute_program().unwrap();
+    ///     assert_eq!(res, 0x1000);
+    /// }
     /// ```
     pub fn register_helper(
         &mut self,

+ 42 - 0
src/no_std_error.rs

@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: (Apache-2.0 OR MIT)
+
+//! This module provides a simple implementation of the Error struct that is
+//! used as a drop-in replacement for `std::io::Error` when using `rbpf` in `no_std`.
+
+use crate::lib::String;
+
+/// Implementation of Error for no_std applications.
+/// Ensures that the existing code can use it with the same interface
+/// as the Error from std::io::Error.
+#[derive(Debug)]
+pub struct Error {
+    #[allow(dead_code)]
+    kind: ErrorKind,
+    #[allow(dead_code)]
+    error: String,
+}
+
+impl Error {
+    /// New function exposing the same signature as `std::io::Error::new`.
+    #[allow(dead_code)]
+    pub fn new<S: Into<String>>(kind: ErrorKind, error: S) -> Error {
+        Error {
+            kind,
+            error: error.into(),
+        }
+    }
+}
+
+/// The current version of `rbpf` only uses the [`Other`](ErrorKind::Other) variant
+/// from the [std::io::ErrorKind] enum. If a dependency on other variants were
+/// introduced in the future, this enum needs to be updated accordingly to maintain
+/// compatibility with the real `ErrorKind`. The reason all available variants
+/// aren't included in the first place is that [std::io::ErrorKind] exposes
+/// 40 variants, and not all of them are meaningful under `no_std`.
+#[derive(Debug)]
+pub enum ErrorKind {
+    /// The no_std code only uses this variant.
+    #[allow(dead_code)]
+    Other,
+}
+

+ 1 - 1
src/verifier.rs

@@ -20,7 +20,7 @@
 
 
 use ebpf;
-use std::io::{Error, ErrorKind};
+use crate::lib::*;
 
 fn reject<S: AsRef<str>>(msg: S) -> Result<(), Error> {
     let full_msg = format!("[Verifier] Error: {}", msg.as_ref());

+ 5 - 3
tests/misc.rs

@@ -19,8 +19,9 @@
 
 extern crate rbpf;
 
-use std::io::{Error, ErrorKind};
+use rbpf::lib::{Error, ErrorKind};
 use rbpf::assembler::assemble;
+#[cfg(feature = "std")]
 use rbpf::helpers;
 
 // The following two examples have been compiled from C with the following command:
@@ -90,6 +91,7 @@ use rbpf::helpers;
 // instead.
 
 #[test]
+#[cfg(feature = "std")]
 fn test_vm_block_port() {
     // To load the bytecode from an object file instead of using the hardcoded instructions,
     // use the additional crates commented at the beginning of this file (and also add them to your
@@ -170,8 +172,8 @@ fn test_vm_block_port() {
     assert_eq!(res, 0xffffffff);
 }
 
-#[cfg(all(not(windows), feature = "std"))]
 #[test]
+#[cfg(all(not(windows), feature = "std"))]
 fn test_jit_block_port() {
     // To load the bytecode from an object file instead of using the hardcoded instructions,
     // use the additional crates commented at the beginning of this file (and also add them to your
@@ -309,8 +311,8 @@ fn test_vm_mbuff_with_rust_api() {
 }
 
 // Program and memory come from uBPF test ldxh.
-#[cfg(all(not(windows), feature = "std"))]
 #[test]
+#[cfg(all(not(windows), feature = "std"))]
 fn test_jit_mbuff() {
     let prog = &[
         // Load mem from mbuff into R1