Browse 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 1 year ago
parent
commit
86d2586d36
15 changed files with 308 additions and 118 deletions
  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]
 [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
 # Optional Dependencies for the CraneLift JIT
 cranelift-codegen = { version = "0.99", optional = true }
 cranelift-codegen = { version = "0.99", optional = true }
@@ -44,7 +49,8 @@ hex = "0.4.3"
 
 
 [features]
 [features]
 default = ["std"]
 default = ["std"]
-std = []
+cargo-clippy = []
+std = ["dep:time", "dep:libc", "combine/std"]
 cranelift = [
 cranelift = [
     "dep:cranelift-codegen",
     "dep:cranelift-codegen",
     "dep:cranelift-frontend",
     "dep:cranelift-frontend",
@@ -52,3 +58,23 @@ cranelift = [
     "dep:cranelift-native",
     "dep:cranelift-native",
     "dep:cranelift-module",
     "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();
     let mut vm = rbpf::EbpfVmFixedMbuff::new(Some(prog), 0x40, 0x50).unwrap();
 
 
     // We register a helper function, that can be called by the program, into
     // 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
     // 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
     // any reference to the metadata buffer: a fixed buffer is handled
@@ -479,7 +482,9 @@ let prog = assemble("add64 r1, 0x605
                      neg64 r2
                      neg64 r2
                      exit").unwrap();
                      exit").unwrap();
 
 
-println!("{:?}", prog);
+#[cfg(feature = "std")] {
+    println!("{:?}", prog);
+}
 ```
 ```
 
 
 The above snippet will produce:
 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.
 // It reads the program from stdin.
 fn main() {
 fn main() {
     let mut args: Vec<String> = std::env::args().collect();
     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 jit : bool = false;
     let mut cranelift : bool = false;
     let mut cranelift : bool = false;
     let mut program_text = String::new();
     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::parser::char::{alpha_num, char, digit, hex_digit, spaces, string};
 use combine::stream::position::{self};
 use combine::stream::position::{self};
+#[cfg(feature = "std")]
+use combine::EasyParser;
 use combine::{
 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.
 /// Operand of an instruction.
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum Operand {
 pub enum Operand {
@@ -97,19 +100,30 @@ where
 ///
 ///
 /// The instructions are not validated and may have invalid names and operand types.
 /// The instructions are not validated and may have invalid names and operand types.
 pub fn parse(input: &str) -> Result<Vec<Instruction>, String> {
 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)]
 #[cfg(test)]
 mod tests {
 mod tests {
 
 
     use super::{ident, instruction, integer, operand, parse, register, Instruction, Operand};
     use super::{ident, instruction, integer, operand, parse, register, Instruction, Operand};
+    use crate::lib::*;
     use combine::Parser;
     use combine::Parser;
 
 
     // Unit tests for the different kinds of parsers.
     // 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]
     #[test]
     fn test_error_eof() {
     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.
         // Unexpected end of input in a register name.
         assert_eq!(
         assert_eq!(
             parse("lsh r"),
             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]
     #[test]
     fn test_error_unexpected_character() {
     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.
         // Unexpected character at end of input.
         assert_eq!(
         assert_eq!(
             parse("exit\n^"),
             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 asm_parser::{Instruction, Operand, parse};
 use ebpf;
 use ebpf;
 use ebpf::Insn;
 use ebpf::Insn;
-use std::collections::HashMap;
 use self::InstructionType::{AluBinary, AluUnary, LoadAbs, LoadInd, LoadImm, LoadReg, StoreImm,
 use self::InstructionType::{AluBinary, AluUnary, LoadAbs, LoadInd, LoadImm, LoadReg, StoreImm,
                             StoreReg, JumpUnconditional, JumpConditional, Call, Endian, NoOperand};
                             StoreReg, JumpUnconditional, JumpConditional, Call, Endian, NoOperand};
 use asm_parser::Operand::{Integer, Memory, Register, Nil};
 use asm_parser::Operand::{Integer, Memory, Register, Nil};
+use crate::lib::*;
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[derive(Clone, Copy, Debug, PartialEq)]
 enum InstructionType {
 enum InstructionType {

+ 2 - 8
src/cranelift.rs

@@ -1,12 +1,5 @@
 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
 
-use std::{
-    collections::{BTreeMap, HashMap, HashSet},
-    convert::TryInto,
-    io::ErrorKind,
-    mem::ManuallyDrop,
-};
-
 use cranelift_codegen::{
 use cranelift_codegen::{
     entity::EntityRef,
     entity::EntityRef,
     ir::{
     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,
     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,
     BPF_JSET, BPF_JSGE, BPF_JSGT, BPF_JSLE, BPF_JSLT, BPF_X, STACK_SIZE, BPF_IND,
 };
 };
+use crate::lib::*;
 
 
 use super::Error;
 use super::Error;
 
 
@@ -1197,7 +1191,7 @@ impl CraneliftProgram {
     /// module, since it's not guaranteed to be valid after the module is dropped.
     /// module, since it's not guaranteed to be valid after the module is dropped.
     pub(crate) fn get_main_function(&self) -> JittedFunction {
     pub(crate) fn get_main_function(&self) -> JittedFunction {
         let function_ptr = self.module.get_finalized_function(self.main_id);
         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
     /// 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,
 //! 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.
 //! for example to disassemble the code into a human-readable format.
 
 
+use log::warn;
+#[cfg(not(feature = "std"))]
+use log::info;
+
 use ebpf;
 use ebpf;
+use crate::lib::*;
 
 
 #[inline]
 #[inline]
 fn alu_imm_str(name: &str, insn: &ebpf::Insn) -> String {
 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 {
 fn byteswap_str(name: &str, insn: &ebpf::Insn) -> String {
     match insn.imm {
     match insn.imm {
         16 | 32 | 64 => {},
         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)
     format!("{name}{} r{}", insn.imm, insn.dst)
 }
 }
@@ -384,7 +389,14 @@ pub fn to_insn_vec(prog: &[u8]) -> Vec<HLInsn> {
 /// exit
 /// exit
 /// ```
 /// ```
 pub fn disassemble(prog: &[u8]) {
 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>
 //! the list of the operation codes: <https://github.com/iovisor/bpf-docs/blob/master/eBPF.md>
 
 
 use byteorder::{ByteOrder, LittleEndian};
 use byteorder::{ByteOrder, LittleEndian};
+use crate::lib::*;
 
 
 /// Maximum number of instructions in an eBPF program.
 /// Maximum number of instructions in an eBPF program.
 pub const PROG_MAX_INSNS: usize = 1000000;
 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
 //! value. Hence some helpers have unused arguments, or return a 0 value in all cases, in order to
 //! respect this convention.
 //! respect this convention.
 
 
+#[cfg(feature = "std")]
 extern crate libc;
 extern crate libc;
 
 
-use std::u64;
+use crate::lib::*;
 
 
 // Helpers associated to kernel helpers
 // Helpers associated to kernel helpers
 // See also linux/include/uapi/linux/bpf.h in Linux kernel sources.
 // 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(dead_code)]
 #[allow(unused_variables)]
 #[allow(unused_variables)]
 #[allow(deprecated)]
 #[allow(deprecated)]
+#[cfg(feature = "std")]
 pub fn bpf_time_getns (unused1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
 pub fn bpf_time_getns (unused1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
     time::precise_time_ns()
     time::precise_time_ns()
 }
 }
@@ -94,6 +96,7 @@ pub const BPF_TRACE_PRINTK_IDX: u32 = 6;
 /// program is run.
 /// program is run.
 #[allow(dead_code)]
 #[allow(dead_code)]
 #[allow(unused_variables)]
 #[allow(unused_variables)]
+#[cfg(feature = "std")]
 pub fn bpf_trace_printf (unused1: u64, unused2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 {
 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}");
     println!("bpf_trace_printf: {arg3:#x}, {arg4:#x}, {arg5:#x}");
     let size_arg = | 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(dead_code)]
 #[allow(unused_variables)]
 #[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 {
 pub fn sqrti (arg1: u64, unused2: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
     (arg1 as f64).sqrt() as 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(dead_code)]
 #[allow(unused_variables)]
 #[allow(unused_variables)]
+#[cfg(feature = "std")]
 pub fn rand (min: u64, max: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
 pub fn rand (min: u64, max: u64, unused3: u64, unused4: u64, unused5: u64) -> u64 {
     let mut n = unsafe {
     let mut n = unsafe {
         (libc::rand() as u64).wrapping_shl(32) + libc::rand() as u64
         (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
 //! Module provides API to create eBPF programs by Rust programming language
 
 
 use ebpf::*;
 use ebpf::*;
+use crate::lib::*;
 
 
 /// Represents single eBPF instruction
 /// Represents single eBPF instruction
 pub trait Instruction: Sized {
 pub trait Instruction: Sized {

+ 1 - 3
src/interpreter.rs

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

+ 141 - 75
src/lib.rs

@@ -26,10 +26,17 @@
         unreadable_literal
         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 byteorder;
 extern crate combine;
 extern crate combine;
+#[cfg(feature = "std")]
 extern crate time;
 extern crate time;
+extern crate log;
+
+#[cfg(not(feature = "std"))]
+extern crate alloc;
 
 
 #[cfg(feature = "cranelift")]
 #[cfg(feature = "cranelift")]
 extern crate cranelift_codegen;
 extern crate cranelift_codegen;
@@ -43,9 +50,7 @@ extern crate cranelift_module;
 extern crate cranelift_native;
 extern crate cranelift_native;
 
 
 use byteorder::{ByteOrder, LittleEndian};
 use byteorder::{ByteOrder, LittleEndian};
-use std::collections::HashMap;
-use std::io::{Error, ErrorKind};
-use std::u32;
+use crate::lib::*;
 
 
 mod asm_parser;
 mod asm_parser;
 pub mod assembler;
 pub mod assembler;
@@ -59,6 +64,60 @@ mod interpreter;
 #[cfg(all(not(windows), feature = "std"))]
 #[cfg(all(not(windows), feature = "std"))]
 mod jit;
 mod jit;
 mod verifier;
 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.
 /// eBPF verification function that returns an error if the program does not meet its requirements.
 ///
 ///
@@ -189,7 +248,7 @@ impl<'a> EbpfVmMbuff<'a> {
     /// # Examples
     /// # Examples
     ///
     ///
     /// ```
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     /// use rbpf::ebpf;
     ///
     ///
     /// // Define a simple verifier function.
     /// // Define a simple verifier function.
@@ -253,6 +312,7 @@ impl<'a> EbpfVmMbuff<'a> {
     /// // Register a helper.
     /// // Register a helper.
     /// // On running the program this helper will print the content of registers r3, r4 and r5 to
     /// // On running the program this helper will print the content of registers r3, r4 and r5 to
     /// // standard output.
     /// // standard output.
+    /// # #[cfg(feature = "std")]
     /// vm.register_helper(6, helpers::bpf_trace_printf).unwrap();
     /// vm.register_helper(6, helpers::bpf_trace_printf).unwrap();
     /// ```
     /// ```
     pub fn register_helper(&mut self, key: u32, function: Helper) -> Result<(), Error> {
     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).
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  See `mul_loop` test.
         //  See `mul_loop` test.
         let mem_ptr = match mem.len() {
         let mem_ptr = match mem.len() {
-            0 => std::ptr::null_mut(),
+            0 => ptr::null_mut(),
             _ => mem.as_ptr() as *mut u8,
             _ => mem.as_ptr() as *mut u8,
         };
         };
 
 
@@ -687,7 +747,7 @@ impl<'a> EbpfVmFixedMbuff<'a> {
     /// # Examples
     /// # Examples
     ///
     ///
     /// ```
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     /// use rbpf::ebpf;
     ///
     ///
     /// // Define a simple verifier function.
     /// // Define a simple verifier function.
@@ -724,37 +784,39 @@ impl<'a> EbpfVmFixedMbuff<'a> {
     /// # Examples
     /// # 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(
     pub fn register_helper(
         &mut self,
         &mut self,
@@ -903,7 +965,7 @@ impl<'a> EbpfVmFixedMbuff<'a> {
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  See `mul_loop` test.
         //  See `mul_loop` test.
         let mem_ptr = match mem.len() {
         let mem_ptr = match mem.len() {
-            0 => std::ptr::null_mut(),
+            0 => ptr::null_mut(),
             _ => mem.as_ptr() as *mut u8,
             _ => 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).
         //  in the kernel; anyway the verifier would prevent the use of uninitialized registers).
         //  See `mul_loop` test.
         //  See `mul_loop` test.
         let mem_ptr = match mem.len() {
         let mem_ptr = match mem.len() {
-            0 => std::ptr::null_mut(),
+            0 => ptr::null_mut(),
             _ => mem.as_ptr() as *mut u8,
             _ => mem.as_ptr() as *mut u8,
         };
         };
 
 
@@ -1126,7 +1188,7 @@ impl<'a> EbpfVmRaw<'a> {
     /// # Examples
     /// # Examples
     ///
     ///
     /// ```
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     /// use rbpf::ebpf;
     ///
     ///
     /// // Define a simple verifier function.
     /// // Define a simple verifier function.
@@ -1163,30 +1225,32 @@ impl<'a> EbpfVmRaw<'a> {
     /// # Examples
     /// # 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(
     pub fn register_helper(
         &mut self,
         &mut self,
@@ -1471,7 +1535,7 @@ impl<'a> EbpfVmNoData<'a> {
     /// # Examples
     /// # Examples
     ///
     ///
     /// ```
     /// ```
-    /// use std::io::{Error, ErrorKind};
+    /// use rbpf::lib::{Error, ErrorKind};
     /// use rbpf::ebpf;
     /// use rbpf::ebpf;
     ///
     ///
     /// // Define a simple verifier function.
     /// // Define a simple verifier function.
@@ -1508,25 +1572,27 @@ impl<'a> EbpfVmNoData<'a> {
     /// # Examples
     /// # 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(
     pub fn register_helper(
         &mut self,
         &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 ebpf;
-use std::io::{Error, ErrorKind};
+use crate::lib::*;
 
 
 fn reject<S: AsRef<str>>(msg: S) -> Result<(), Error> {
 fn reject<S: AsRef<str>>(msg: S) -> Result<(), Error> {
     let full_msg = format!("[Verifier] Error: {}", msg.as_ref());
     let full_msg = format!("[Verifier] Error: {}", msg.as_ref());

+ 5 - 3
tests/misc.rs

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