Ver código fonte

eBPF assembler

Supports the same syntax as ubpf, plus optional "64" suffixes for ALU
mnemonics.
Rich Lane 8 anos atrás
pai
commit
4f35aae74e
4 arquivos alterados com 657 adições e 1 exclusões
  1. 3 1
      src/asm_parser.rs
  2. 225 0
      src/assembler.rs
  3. 1 0
      src/lib.rs
  4. 428 0
      tests/assembler.rs

+ 3 - 1
src/asm_parser.rs

@@ -13,7 +13,7 @@ use combine::{between, eof, many, many1, one_of, optional, Parser, ParseError, P
 use combine::primitives::{Error, Info};
 
 /// Operand of an instruction.
-#[derive(Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub enum Operand {
     /// Register number.
     Register(i64),
@@ -21,6 +21,8 @@ pub enum Operand {
     Integer(i64),
     /// Register number and offset.
     Memory(i64, i64),
+    /// Used for pattern matching.
+    Nil,
 }
 
 /// Parsed instruction.

+ 225 - 0
src/assembler.rs

@@ -0,0 +1,225 @@
+// Copyright 2017 Rich Lane <lanerl@gmail.com>
+//
+// Licensed under the Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0> or
+// the MIT license <http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+
+
+//! This module translates eBPF assembly language to binary.
+
+use asm_parser::{Instruction, Operand, parse};
+use ebpf;
+use ebpf::Insn;
+use std::collections::HashMap;
+use self::InstructionType::{AluBinary, AluUnary, LoadImm, LoadReg, StoreImm, StoreReg,
+                            JumpUnconditional, JumpConditional, Call, Endian, NoOperand};
+use asm_parser::Operand::{Integer, Memory, Register, Nil};
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum InstructionType {
+    AluBinary,
+    AluUnary,
+    LoadImm,
+    LoadReg,
+    StoreImm,
+    StoreReg,
+    JumpUnconditional,
+    JumpConditional,
+    Call,
+    Endian(i64),
+    NoOperand,
+}
+
+fn make_instruction_map() -> HashMap<String, (InstructionType, u8)> {
+    let mut result = HashMap::new();
+
+    let alu_binary_ops = [("add", ebpf::BPF_ADD),
+                          ("sub", ebpf::BPF_SUB),
+                          ("mul", ebpf::BPF_MUL),
+                          ("div", ebpf::BPF_DIV),
+                          ("or", ebpf::BPF_OR),
+                          ("and", ebpf::BPF_AND),
+                          ("lsh", ebpf::BPF_LSH),
+                          ("rsh", ebpf::BPF_RSH),
+                          ("mod", ebpf::BPF_MOD),
+                          ("xor", ebpf::BPF_XOR),
+                          ("mov", ebpf::BPF_MOV),
+                          ("arsh", ebpf::BPF_ARSH)];
+
+    let mem_sizes =
+        [("w", ebpf::BPF_W), ("h", ebpf::BPF_H), ("b", ebpf::BPF_B), ("dw", ebpf::BPF_DW)];
+
+    let jump_conditions = [("jeq", ebpf::BPF_JEQ),
+                           ("jgt", ebpf::BPF_JGT),
+                           ("jge", ebpf::BPF_JGE),
+                           ("jset", ebpf::BPF_JSET),
+                           ("jne", ebpf::BPF_JNE),
+                           ("jsgt", ebpf::BPF_JSGT),
+                           ("jsge", ebpf::BPF_JSGE)];
+
+    {
+        let mut entry = |name: &str, inst_type: InstructionType, opc: u8| {
+            result.insert(name.to_string(), (inst_type, opc))
+        };
+
+        // Miscellaneous.
+        entry("exit", NoOperand, ebpf::EXIT);
+        entry("ja", JumpUnconditional, ebpf::JA);
+        entry("call", Call, ebpf::CALL);
+        entry("lddw", LoadImm, ebpf::LD_DW_IMM);
+
+        // AluUnary.
+        entry("neg", AluUnary, ebpf::NEG64);
+        entry("neg32", AluUnary, ebpf::NEG32);
+        entry("neg64", AluUnary, ebpf::NEG64);
+
+        // AluBinary.
+        for &(name, opc) in &alu_binary_ops {
+            entry(name, AluBinary, ebpf::BPF_ALU64 | opc);
+            entry(&format!("{}32", name), AluBinary, ebpf::BPF_ALU | opc);
+            entry(&format!("{}64", name), AluBinary, ebpf::BPF_ALU64 | opc);
+        }
+
+        // Load, StoreImm, and StoreReg.
+        for &(suffix, size) in &mem_sizes {
+            entry(&format!("ldx{}", suffix),
+                  LoadReg,
+                  ebpf::BPF_MEM | ebpf::BPF_LDX | size);
+            entry(&format!("st{}", suffix),
+                  StoreImm,
+                  ebpf::BPF_MEM | ebpf::BPF_ST | size);
+            entry(&format!("stx{}", suffix),
+                  StoreReg,
+                  ebpf::BPF_MEM | ebpf::BPF_STX | size);
+        }
+
+        // JumpConditional.
+        for &(name, condition) in &jump_conditions {
+            entry(name, JumpConditional, ebpf::BPF_JMP | condition);
+        }
+
+        // Endian.
+        for &size in &[16, 32, 64] {
+            entry(&format!("be{}", size), Endian(size), ebpf::BE);
+            entry(&format!("le{}", size), Endian(size), ebpf::LE);
+        }
+    }
+
+    result
+}
+
+fn insn(opc: u8, dst: i64, src: i64, off: i64, imm: i64) -> Result<Insn, String> {
+    if dst < 0 || dst >= 16 {
+        return Err(format!("Invalid destination register {}", dst));
+    }
+    if dst < 0 || src >= 16 {
+        return Err(format!("Invalid source register {}", src));
+    }
+    if off < -32768 || off >= 32768 {
+        return Err(format!("Invalid offset {}", off));
+    }
+    if imm < -2147483648 || imm >= 4294967296 {
+        return Err(format!("Invalid immediate {}", imm));
+    }
+    Ok(Insn {
+        opc: opc,
+        dst: dst as u8,
+        src: src as u8,
+        off: off as i16,
+        imm: imm as i32,
+    })
+}
+
+// TODO Use slice patterns when available and remove this function.
+fn operands_tuple(operands: &[Operand]) -> Result<(Operand, Operand, Operand), String> {
+    match operands.len() {
+        0 => Ok((Nil, Nil, Nil)),
+        1 => Ok((operands[0], Nil, Nil)),
+        2 => Ok((operands[0], operands[1], Nil)),
+        3 => Ok((operands[0], operands[1], operands[2])),
+        _ => Err("Too many operands".to_string()),
+    }
+}
+
+fn encode(inst_type: InstructionType, opc: u8, operands: &[Operand]) -> Result<Insn, String> {
+    let (a, b, c) = try!(operands_tuple(operands));
+    match (inst_type, a, b, c) {
+        (AluBinary, Register(dst), Register(src), Nil) => insn(opc | ebpf::BPF_X, dst, src, 0, 0),
+        (AluBinary, Register(dst), Integer(imm), Nil) => insn(opc | ebpf::BPF_K, dst, 0, 0, imm),
+        (AluUnary, Register(dst), Nil, Nil) => insn(opc, dst, 0, 0, 0),
+        (LoadReg, Register(dst), Memory(src, off), Nil) |
+        (StoreReg, Memory(dst, off), Register(src), Nil) => insn(opc, dst, src, off, 0),
+        (StoreImm, Memory(dst, off), Integer(imm), Nil) => insn(opc, dst, 0, off, imm),
+        (NoOperand, Nil, Nil, Nil) => insn(opc, 0, 0, 0, 0),
+        (JumpUnconditional, Integer(off), Nil, Nil) => insn(opc, 0, 0, off, 0),
+        (JumpConditional, Register(dst), Register(src), Integer(off)) => {
+            insn(opc | ebpf::BPF_X, dst, src, off, 0)
+        }
+        (JumpConditional, Register(dst), Integer(imm), Integer(off)) => {
+            insn(opc | ebpf::BPF_K, dst, 0, off, imm)
+        }
+        (Call, Integer(imm), Nil, Nil) => insn(opc, 0, 0, 0, imm),
+        (Endian(size), Register(dst), Nil, Nil) => insn(opc, dst, 0, 0, size),
+        (LoadImm, Register(dst), Integer(imm), Nil) => insn(opc, dst, 0, 0, (imm << 32) >> 32),
+        _ => Err(format!("Unexpected operands: {:?}", operands)),
+    }
+}
+
+fn assemble_internal(parsed: &[Instruction]) -> Result<Vec<Insn>, String> {
+    let instruction_map = make_instruction_map();
+    let mut result: Vec<Insn> = vec![];
+    for instruction in parsed {
+        let name = instruction.name.as_str();
+        match instruction_map.get(name) {
+            Some(&(inst_type, opc)) => {
+                match encode(inst_type, opc, &instruction.operands) {
+                    Ok(insn) => result.push(insn),
+                    Err(msg) => return Err(format!("Failed to encode {}: {}", name, msg)),
+                }
+                // Special case for lddw.
+                if let LoadImm = inst_type {
+                    if let Integer(imm) = instruction.operands[1] {
+                        result.push(insn(0, 0, 0, 0, imm >> 32).unwrap());
+                    }
+                }
+            }
+            None => return Err(format!("Invalid instruction {:?}", name)),
+        }
+    }
+    Ok(result)
+}
+
+/// Parse assembly source and translate to binary.
+///
+/// # Examples
+///
+/// ```
+/// use rbpf::assembler::assemble;
+/// let prog = assemble("add64 r1, 0x605
+///                      mov64 r2, 0x32
+///                      mov64 r1, r0
+///                      be16 r0
+///                      neg64 r8
+///                      exit");
+/// println!("{:?}", prog);
+/// ```
+///
+/// This will produce the following output:
+///
+/// ```test
+/// Ok([0x07, 0x01, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00,
+///     0xb7, 0x02, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
+///     0xbf, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+///     0xdc, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+///     0x87, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+///     0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
+/// ```
+pub fn assemble(src: &str) -> Result<Vec<u8>, String> {
+    let parsed = try!(parse(src));
+    let insns = try!(assemble_internal(&parsed));
+    let mut result: Vec<u8> = vec![];
+    for insn in insns {
+        result.extend_from_slice(&insn.to_array());
+    }
+    Ok(result)
+}

+ 1 - 0
src/lib.rs

@@ -21,6 +21,7 @@ use std::collections::HashMap;
 extern crate libc;
 extern crate combine;
 
+pub mod assembler;
 pub mod asm_parser;
 pub mod disassembler;
 pub mod ebpf;

+ 428 - 0
tests/assembler.rs

@@ -0,0 +1,428 @@
+// Copyright 2017 Rich Lane <lanerl@gmail.com>
+//
+// Licensed under the Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0> or
+// the MIT license <http://opensource.org/licenses/MIT>, at your option. This file may not be
+// copied, modified, or distributed except according to those terms.
+extern crate rbpf;
+
+use rbpf::assembler::assemble;
+use rbpf::ebpf;
+
+fn asm(src: &str) -> Result<Vec<ebpf::Insn>, String> {
+    Ok(ebpf::to_insn_vec(&try!(assemble(src))))
+}
+
+fn insn(opc: u8, dst: u8, src: u8, off: i16, imm: i32) -> ebpf::Insn {
+    ebpf::Insn {
+        opc: opc,
+        dst: dst,
+        src: src,
+        off: off,
+        imm: imm,
+    }
+}
+
+#[test]
+fn test_empty() {
+    assert_eq!(asm(""), Ok(vec![]));
+}
+
+// Example for InstructionType::NoOperand.
+#[test]
+fn test_exit() {
+    assert_eq!(asm("exit"), Ok(vec![insn(ebpf::EXIT, 0, 0, 0, 0)]));
+}
+
+// Example for InstructionType::AluBinary.
+#[test]
+fn test_add64() {
+    assert_eq!(asm("add64 r1, r3"),
+               Ok(vec![insn(ebpf::ADD64_REG, 1, 3, 0, 0)]));
+    assert_eq!(asm("add64 r1, 5"),
+               Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 5)]));
+}
+
+// Example for InstructionType::AluUnary.
+#[test]
+fn test_neg64() {
+    assert_eq!(asm("neg64 r1"), Ok(vec![insn(ebpf::NEG64, 1, 0, 0, 0)]));
+}
+
+// Example for InstructionType::LoadReg.
+#[test]
+fn test_ldxw() {
+    assert_eq!(asm("ldxw r1, [r2+5]"),
+               Ok(vec![insn(ebpf::LD_W_REG, 1, 2, 5, 0)]));
+}
+
+// Example for InstructionType::StoreImm.
+#[test]
+fn test_stw() {
+    assert_eq!(asm("stw [r2+5], 7"),
+               Ok(vec![insn(ebpf::ST_W_IMM, 2, 0, 5, 7)]));
+}
+
+// Example for InstructionType::StoreReg.
+#[test]
+fn test_stxw() {
+    assert_eq!(asm("stxw [r2+5], r8"),
+               Ok(vec![insn(ebpf::ST_W_REG, 2, 8, 5, 0)]));
+}
+
+// Example for InstructionType::JumpUnconditional.
+#[test]
+fn test_ja() {
+    assert_eq!(asm("ja +8"), Ok(vec![insn(ebpf::JA, 0, 0, 8, 0)]));
+    assert_eq!(asm("ja -3"), Ok(vec![insn(ebpf::JA, 0, 0, -3, 0)]));
+}
+
+// Example for InstructionType::JumpConditional.
+#[test]
+fn test_jeq() {
+    assert_eq!(asm("jeq r1, 4, +8"),
+               Ok(vec![insn(ebpf::JEQ_IMM, 1, 0, 8, 4)]));
+    assert_eq!(asm("jeq r1, r3, +8"),
+               Ok(vec![insn(ebpf::JEQ_REG, 1, 3, 8, 0)]));
+}
+
+// Example for InstructionType::Call.
+#[test]
+fn test_call() {
+    assert_eq!(asm("call 300"), Ok(vec![insn(ebpf::CALL, 0, 0, 0, 300)]));
+}
+
+// Example for InstructionType::Endian.
+#[test]
+fn test_be32() {
+    assert_eq!(asm("be32 r1"), Ok(vec![insn(ebpf::BE, 1, 0, 0, 32)]));
+}
+
+// Example for InstructionType::LoadImm.
+#[test]
+fn test_lddw() {
+    assert_eq!(asm("lddw r1, 0x1234abcd5678eeff"),
+               Ok(vec![insn(ebpf::LD_DW_IMM, 1, 0, 0, 0x5678eeff), insn(0, 0, 0, 0, 0x1234abcd)]));
+    assert_eq!(asm("lddw r1, 0xff11ee22dd33cc44"),
+               Ok(vec![insn(ebpf::LD_DW_IMM, 1, 0, 0, 0xdd33cc44u32 as i32),
+                       insn(0, 0, 0, 0, 0xff11ee22u32 as i32)]));
+}
+
+// Example for InstructionType::LoadReg.
+#[test]
+fn test_ldxdw() {
+    assert_eq!(asm("ldxdw r1, [r2+3]"),
+               Ok(vec![insn(ebpf::LD_DW_REG, 1, 2, 3, 0)]));
+}
+
+// Example for InstructionType::StoreImm.
+#[test]
+fn test_sth() {
+    assert_eq!(asm("sth [r1+2], 3"),
+               Ok(vec![insn(ebpf::ST_H_IMM, 1, 0, 2, 3)]));
+}
+
+// Example for InstructionType::StoreReg.
+#[test]
+fn test_stxh() {
+    assert_eq!(asm("stxh [r1+2], r3"),
+               Ok(vec![insn(ebpf::ST_H_REG, 1, 3, 2, 0)]));
+}
+
+// Test all supported AluBinary mnemonics.
+#[test]
+fn test_alu_binary() {
+    assert_eq!(asm("add r1, r2
+                    sub r1, r2
+                    mul r1, r2
+                    div r1, r2
+                    or r1, r2
+                    and r1, r2
+                    lsh r1, r2
+                    rsh r1, r2
+                    mod r1, r2
+                    xor r1, r2
+                    mov r1, r2
+                    arsh r1, r2"),
+               Ok(vec![insn(ebpf::ADD64_REG, 1, 2, 0, 0),
+                       insn(ebpf::SUB64_REG, 1, 2, 0, 0),
+                       insn(ebpf::MUL64_REG, 1, 2, 0, 0),
+                       insn(ebpf::DIV64_REG, 1, 2, 0, 0),
+                       insn(ebpf::OR64_REG, 1, 2, 0, 0),
+                       insn(ebpf::AND64_REG, 1, 2, 0, 0),
+                       insn(ebpf::LSH64_REG, 1, 2, 0, 0),
+                       insn(ebpf::RSH64_REG, 1, 2, 0, 0),
+                       insn(ebpf::MOD64_REG, 1, 2, 0, 0),
+                       insn(ebpf::XOR64_REG, 1, 2, 0, 0),
+                       insn(ebpf::MOV64_REG, 1, 2, 0, 0),
+                       insn(ebpf::ARSH64_REG, 1, 2, 0, 0)]));
+
+    assert_eq!(asm("add r1, 2
+                    sub r1, 2
+                    mul r1, 2
+                    div r1, 2
+                    or r1, 2
+                    and r1, 2
+                    lsh r1, 2
+                    rsh r1, 2
+                    mod r1, 2
+                    xor r1, 2
+                    mov r1, 2
+                    arsh r1, 2"),
+               Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::SUB64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MUL64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::DIV64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::OR64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::AND64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::LSH64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::RSH64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MOD64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::XOR64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MOV64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::ARSH64_IMM, 1, 0, 0, 2)]));
+
+    assert_eq!(asm("add64 r1, r2
+                    sub64 r1, r2
+                    mul64 r1, r2
+                    div64 r1, r2
+                    or64 r1, r2
+                    and64 r1, r2
+                    lsh64 r1, r2
+                    rsh64 r1, r2
+                    mod64 r1, r2
+                    xor64 r1, r2
+                    mov64 r1, r2
+                    arsh64 r1, r2"),
+               Ok(vec![insn(ebpf::ADD64_REG, 1, 2, 0, 0),
+                       insn(ebpf::SUB64_REG, 1, 2, 0, 0),
+                       insn(ebpf::MUL64_REG, 1, 2, 0, 0),
+                       insn(ebpf::DIV64_REG, 1, 2, 0, 0),
+                       insn(ebpf::OR64_REG, 1, 2, 0, 0),
+                       insn(ebpf::AND64_REG, 1, 2, 0, 0),
+                       insn(ebpf::LSH64_REG, 1, 2, 0, 0),
+                       insn(ebpf::RSH64_REG, 1, 2, 0, 0),
+                       insn(ebpf::MOD64_REG, 1, 2, 0, 0),
+                       insn(ebpf::XOR64_REG, 1, 2, 0, 0),
+                       insn(ebpf::MOV64_REG, 1, 2, 0, 0),
+                       insn(ebpf::ARSH64_REG, 1, 2, 0, 0)]));
+
+    assert_eq!(asm("add64 r1, 2
+                    sub64 r1, 2
+                    mul64 r1, 2
+                    div64 r1, 2
+                    or64 r1, 2
+                    and64 r1, 2
+                    lsh64 r1, 2
+                    rsh64 r1, 2
+                    mod64 r1, 2
+                    xor64 r1, 2
+                    mov64 r1, 2
+                    arsh64 r1, 2"),
+               Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::SUB64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MUL64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::DIV64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::OR64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::AND64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::LSH64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::RSH64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MOD64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::XOR64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MOV64_IMM, 1, 0, 0, 2),
+                       insn(ebpf::ARSH64_IMM, 1, 0, 0, 2)]));
+
+    assert_eq!(asm("add32 r1, r2
+                    sub32 r1, r2
+                    mul32 r1, r2
+                    div32 r1, r2
+                    or32 r1, r2
+                    and32 r1, r2
+                    lsh32 r1, r2
+                    rsh32 r1, r2
+                    mod32 r1, r2
+                    xor32 r1, r2
+                    mov32 r1, r2
+                    arsh32 r1, r2"),
+               Ok(vec![insn(ebpf::ADD32_REG, 1, 2, 0, 0),
+                       insn(ebpf::SUB32_REG, 1, 2, 0, 0),
+                       insn(ebpf::MUL32_REG, 1, 2, 0, 0),
+                       insn(ebpf::DIV32_REG, 1, 2, 0, 0),
+                       insn(ebpf::OR32_REG, 1, 2, 0, 0),
+                       insn(ebpf::AND32_REG, 1, 2, 0, 0),
+                       insn(ebpf::LSH32_REG, 1, 2, 0, 0),
+                       insn(ebpf::RSH32_REG, 1, 2, 0, 0),
+                       insn(ebpf::MOD32_REG, 1, 2, 0, 0),
+                       insn(ebpf::XOR32_REG, 1, 2, 0, 0),
+                       insn(ebpf::MOV32_REG, 1, 2, 0, 0),
+                       insn(ebpf::ARSH32_REG, 1, 2, 0, 0)]));
+
+    assert_eq!(asm("add32 r1, 2
+                    sub32 r1, 2
+                    mul32 r1, 2
+                    div32 r1, 2
+                    or32 r1, 2
+                    and32 r1, 2
+                    lsh32 r1, 2
+                    rsh32 r1, 2
+                    mod32 r1, 2
+                    xor32 r1, 2
+                    mov32 r1, 2
+                    arsh32 r1, 2"),
+               Ok(vec![insn(ebpf::ADD32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::SUB32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MUL32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::DIV32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::OR32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::AND32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::LSH32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::RSH32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MOD32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::XOR32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::MOV32_IMM, 1, 0, 0, 2),
+                       insn(ebpf::ARSH32_IMM, 1, 0, 0, 2)]));
+}
+
+// Test all supported AluUnary mnemonics.
+#[test]
+fn test_alu_unary() {
+    assert_eq!(asm("neg r1
+                    neg64 r1
+                    neg32 r1"),
+               Ok(vec![insn(ebpf::NEG64, 1, 0, 0, 0),
+                       insn(ebpf::NEG64, 1, 0, 0, 0),
+                       insn(ebpf::NEG32, 1, 0, 0, 0)]));
+}
+
+// Test all supported LoadReg mnemonics.
+#[test]
+fn test_load() {
+    assert_eq!(asm("ldxw r1, [r2+3]
+                    ldxh r1, [r2+3]
+                    ldxb r1, [r2+3]
+                    ldxdw r1, [r2+3]"),
+               Ok(vec![insn(ebpf::LD_W_REG, 1, 2, 3, 0),
+                       insn(ebpf::LD_H_REG, 1, 2, 3, 0),
+                       insn(ebpf::LD_B_REG, 1, 2, 3, 0),
+                       insn(ebpf::LD_DW_REG, 1, 2, 3, 0)]));
+}
+
+// Test all supported StoreImm mnemonics.
+#[test]
+fn test_store_imm() {
+    assert_eq!(asm("stw [r1+2], 3
+                    sth [r1+2], 3
+                    stb [r1+2], 3
+                    stdw [r1+2], 3"),
+               Ok(vec![insn(ebpf::ST_W_IMM, 1, 0, 2, 3),
+                       insn(ebpf::ST_H_IMM, 1, 0, 2, 3),
+                       insn(ebpf::ST_B_IMM, 1, 0, 2, 3),
+                       insn(ebpf::ST_DW_IMM, 1, 0, 2, 3)]));
+}
+
+// Test all supported StoreReg mnemonics.
+#[test]
+fn test_store_reg() {
+    assert_eq!(asm("stxw [r1+2], r3
+                    stxh [r1+2], r3
+                    stxb [r1+2], r3
+                    stxdw [r1+2], r3"),
+               Ok(vec![insn(ebpf::ST_W_REG, 1, 3, 2, 0),
+                       insn(ebpf::ST_H_REG, 1, 3, 2, 0),
+                       insn(ebpf::ST_B_REG, 1, 3, 2, 0),
+                       insn(ebpf::ST_DW_REG, 1, 3, 2, 0)]));
+}
+
+// Test all supported JumpConditional mnemonics.
+#[test]
+fn test_jump_conditional() {
+    assert_eq!(asm("jeq r1, r2, +3
+                    jgt r1, r2, +3
+                    jge r1, r2, +3
+                    jset r1, r2, +3
+                    jne r1, r2, +3
+                    jsgt r1, r2, +3
+                    jsge r1, r2, +3"),
+               Ok(vec![insn(ebpf::JEQ_REG, 1, 2, 3, 0),
+                       insn(ebpf::JGT_REG, 1, 2, 3, 0),
+                       insn(ebpf::JGE_REG, 1, 2, 3, 0),
+                       insn(ebpf::JSET_REG, 1, 2, 3, 0),
+                       insn(ebpf::JNE_REG, 1, 2, 3, 0),
+                       insn(ebpf::JSGT_REG, 1, 2, 3, 0),
+                       insn(ebpf::JSGE_REG, 1, 2, 3, 0)]));
+
+    assert_eq!(asm("jeq r1, 2, +3
+                    jgt r1, 2, +3
+                    jge r1, 2, +3
+                    jset r1, 2, +3
+                    jne r1, 2, +3
+                    jsgt r1, 2, +3
+                    jsge r1, 2, +3"),
+               Ok(vec![insn(ebpf::JEQ_IMM, 1, 0, 3, 2),
+                       insn(ebpf::JGT_IMM, 1, 0, 3, 2),
+                       insn(ebpf::JGE_IMM, 1, 0, 3, 2),
+                       insn(ebpf::JSET_IMM, 1, 0, 3, 2),
+                       insn(ebpf::JNE_IMM, 1, 0, 3, 2),
+                       insn(ebpf::JSGT_IMM, 1, 0, 3, 2),
+                       insn(ebpf::JSGE_IMM, 1, 0, 3, 2)]));
+}
+
+// Test all supported Endian mnemonics.
+#[test]
+fn test_endian() {
+    assert_eq!(asm("be16 r1
+                    be32 r1
+                    be64 r1
+                    le16 r1
+                    le32 r1
+                    le64 r1"),
+               Ok(vec![insn(ebpf::BE, 1, 0, 0, 16),
+                       insn(ebpf::BE, 1, 0, 0, 32),
+                       insn(ebpf::BE, 1, 0, 0, 64),
+                       insn(ebpf::LE, 1, 0, 0, 16),
+                       insn(ebpf::LE, 1, 0, 0, 32),
+                       insn(ebpf::LE, 1, 0, 0, 64)]));
+}
+
+#[test]
+fn test_large_immediate() {
+    assert_eq!(asm("add64 r1, 2147483647"),
+               Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, 2147483647)]));
+    assert_eq!(asm("add64 r1, 0xffffffff"),
+               Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, -1)]));
+    assert_eq!(asm("add64 r1, -2147483648"),
+               Ok(vec![insn(ebpf::ADD64_IMM, 1, 0, 0, -2147483648)]));
+}
+
+#[test]
+fn test_error_invalid_instruction() {
+    assert_eq!(asm("abcd"), Err("Invalid instruction \"abcd\"".to_string()));
+}
+
+#[test]
+fn test_error_unexpected_operands() {
+    assert_eq!(asm("add 1, 2"),
+               Err("Failed to encode add: Unexpected operands: [Integer(1), Integer(2)]"
+                   .to_string()));
+}
+
+#[test]
+fn test_error_too_many_operands() {
+    assert_eq!(asm("add 1, 2, 3, 4"),
+               Err("Failed to encode add: Too many operands".to_string()));
+}
+
+#[test]
+fn test_error_operands_out_of_range() {
+    assert_eq!(asm("add r16, r2"),
+               Err("Failed to encode add: Invalid destination register 16".to_string()));
+    assert_eq!(asm("add r1, r16"),
+               Err("Failed to encode add: Invalid source register 16".to_string()));
+    assert_eq!(asm("ja -32769"),
+               Err("Failed to encode ja: Invalid offset -32769".to_string()));
+    assert_eq!(asm("ja 32768"),
+               Err("Failed to encode ja: Invalid offset 32768".to_string()));
+    assert_eq!(asm("add r1, 4294967296"),
+               Err("Failed to encode add: Invalid immediate 4294967296".to_string()));
+    assert_eq!(asm("add r1, -2147483649"),
+               Err("Failed to encode add: Invalid immediate -2147483649".to_string()));
+}