use super::bpf_instr_info::{BpfInstrInfo, BpfOpcode};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BpfInstr {
pub opcode: BpfOpcode,
pub dst_reg: u8,
pub src_reg: u8,
pub offset: i16,
pub imm: i32,
}
impl BpfInstr {
pub fn new(opcode: BpfOpcode, dst_reg: u8, src_reg: u8, offset: i16, imm: i32) -> Self {
Self {
opcode,
dst_reg,
src_reg,
offset,
imm,
}
}
pub fn exit() -> Self {
Self {
opcode: BpfOpcode::EXIT,
dst_reg: 0,
src_reg: 0,
offset: 0,
imm: 0,
}
}
pub fn mov(dst: u8, src: u8) -> Self {
Self {
opcode: BpfOpcode::MOV,
dst_reg: dst,
src_reg: src,
offset: 0,
imm: 0,
}
}
pub fn mov_imm(dst: u8, imm: i32) -> Self {
Self {
opcode: BpfOpcode::MOV,
dst_reg: dst,
src_reg: 0,
offset: 0,
imm,
}
}
pub fn mov64(dst: u8, imm: u64) -> Self {
let lo = imm as u32;
let _hi = (imm >> 32) as u32;
Self {
opcode: BpfOpcode::LD_DW,
dst_reg: dst,
src_reg: 0,
offset: 0,
imm: lo as i32,
}
}
pub fn add(dst: u8, src: u8) -> Self {
Self {
opcode: BpfOpcode::ADD,
dst_reg: dst,
src_reg: src,
offset: 0,
imm: 0,
}
}
pub fn add_imm(dst: u8, imm: i32) -> Self {
Self {
opcode: BpfOpcode::ADD,
dst_reg: dst,
src_reg: 0,
offset: 0,
imm,
}
}
pub fn jmp(opcode: BpfOpcode, dst: u8, src: u8, offset: i16) -> Self {
assert!(BpfInstrInfo::is_branch(&opcode));
Self {
opcode,
dst_reg: dst,
src_reg: src,
offset,
imm: 0,
}
}
pub fn ja(offset: i16) -> Self {
Self {
opcode: BpfOpcode::JA,
dst_reg: 0,
src_reg: 0,
offset,
imm: 0,
}
}
pub fn call(func_id: i32) -> Self {
Self {
opcode: BpfOpcode::CALL,
dst_reg: 0,
src_reg: 0,
offset: 0,
imm: func_id,
}
}
}
impl Default for BpfInstr {
fn default() -> Self {
Self {
opcode: BpfOpcode::EXIT,
dst_reg: 0,
src_reg: 0,
offset: 0,
imm: 0,
}
}
}
pub struct BpfMCEncoder {
pub big_endian: bool,
}
impl Default for BpfMCEncoder {
fn default() -> Self {
Self { big_endian: false }
}
}
impl BpfMCEncoder {
pub fn new() -> Self {
Self::default()
}
pub fn big_endian() -> Self {
Self { big_endian: true }
}
pub fn encode_instr(instr: &BpfInstr) -> Vec<u8> {
let opcode_byte = encode_opcode_byte(instr);
let dst_reg = instr.dst_reg & 0xF;
let src_reg = instr.src_reg & 0xF;
let offset = instr.offset;
let imm = instr.imm;
let regs: u8 = (src_reg << 4) | dst_reg;
let offset_u16 = offset as u16;
let mut encoded = encode_instr_raw(opcode_byte, regs, offset_u16, imm);
let size = BpfInstrInfo::instr_size(&instr.opcode) as usize;
encoded.truncate(size * 8);
encoded
}
pub fn encode_instrs(instrs: &[BpfInstr]) -> Vec<u8> {
let mut buf = Vec::new();
for instr in instrs {
let raw = Self::encode_instr(instr);
buf.extend_from_slice(&raw);
}
buf
}
pub fn decode_instr(bytes: &[u8]) -> Option<BpfInstr> {
if bytes.len() < 8 {
return None;
}
let opcode_byte = bytes[0];
let regs = bytes[1];
let dst_reg = regs & 0xF;
let src_reg = (regs >> 4) & 0xF;
let offset = i16::from_le_bytes([bytes[2], bytes[3]]);
let imm = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let opcode = decode_opcode(opcode_byte)?;
Some(BpfInstr {
opcode,
dst_reg,
src_reg,
offset,
imm,
})
}
pub fn decode_instrs(bytes: &[u8]) -> Vec<BpfInstr> {
let mut instrs = Vec::new();
let mut pos = 0;
while pos + 8 <= bytes.len() {
if let Some(instr) = Self::decode_instr(&bytes[pos..pos + 8]) {
pos += 8;
instrs.push(instr);
} else {
break;
}
}
instrs
}
pub fn encode_to_hex(instr: &BpfInstr) -> String {
let bytes = Self::encode_instr(instr);
bytes
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<_>>()
.join(" ")
}
pub fn disasm(bytes: &[u8]) -> Option<String> {
let instr = Self::decode_instr(bytes)?;
let mnemonic = BpfInstrInfo::get_mnemonic(&instr.opcode);
Some(format!(
"{} r{}, r{}, {}, {}",
mnemonic, instr.dst_reg, instr.src_reg, instr.offset, instr.imm
))
}
}
fn encode_instr_raw(opcode: u8, regs: u8, offset: u16, imm: i32) -> Vec<u8> {
let mut buf = vec![0u8; 8];
buf[0] = opcode;
buf[1] = regs;
buf[2..4].copy_from_slice(&offset.to_le_bytes());
buf[4..8].copy_from_slice(&imm.to_le_bytes());
buf
}
fn encode_opcode_byte(instr: &BpfInstr) -> u8 {
use super::bpf_instr_info::{alu_op, class, jump_op, source};
let uses_src = BpfInstrInfo::uses_src_reg(&instr.opcode);
let src_bit = if uses_src && instr.src_reg != 0 {
source::X
} else {
source::K
};
match instr.opcode {
BpfOpcode::ADD => class::ALU64 | src_bit | alu_op::ADD,
BpfOpcode::SUB => class::ALU64 | src_bit | alu_op::SUB,
BpfOpcode::MUL => class::ALU64 | src_bit | alu_op::MUL,
BpfOpcode::DIV => class::ALU64 | src_bit | alu_op::DIV,
BpfOpcode::SDIV => class::ALU64 | src_bit | alu_op::DIV,
BpfOpcode::OR => class::ALU64 | src_bit | alu_op::OR,
BpfOpcode::AND => class::ALU64 | src_bit | alu_op::AND,
BpfOpcode::LSH => class::ALU64 | src_bit | alu_op::LSH,
BpfOpcode::RSH => class::ALU64 | src_bit | alu_op::RSH,
BpfOpcode::NEG => class::ALU64 | alu_op::NEG,
BpfOpcode::MOD => class::ALU64 | src_bit | alu_op::MOD,
BpfOpcode::SMOD => class::ALU64 | src_bit | alu_op::MOD,
BpfOpcode::XOR => class::ALU64 | src_bit | alu_op::XOR,
BpfOpcode::MOV => class::ALU64 | src_bit | alu_op::MOV,
BpfOpcode::ARSH => class::ALU64 | src_bit | alu_op::ARSH,
BpfOpcode::ADD32 => class::ALU | src_bit | alu_op::ADD,
BpfOpcode::SUB32 => class::ALU | src_bit | alu_op::SUB,
BpfOpcode::MUL32 => class::ALU | src_bit | alu_op::MUL,
BpfOpcode::DIV32 => class::ALU | src_bit | alu_op::DIV,
BpfOpcode::SDIV32 => class::ALU | src_bit | alu_op::DIV,
BpfOpcode::OR32 => class::ALU | src_bit | alu_op::OR,
BpfOpcode::AND32 => class::ALU | src_bit | alu_op::AND,
BpfOpcode::LSH32 => class::ALU | src_bit | alu_op::LSH,
BpfOpcode::RSH32 => class::ALU | src_bit | alu_op::RSH,
BpfOpcode::NEG32 => class::ALU | alu_op::NEG,
BpfOpcode::MOD32 => class::ALU | src_bit | alu_op::MOD,
BpfOpcode::SMOD32 => class::ALU | src_bit | alu_op::MOD,
BpfOpcode::XOR32 => class::ALU | src_bit | alu_op::XOR,
BpfOpcode::MOV32 => class::ALU | src_bit | alu_op::MOV,
BpfOpcode::ARSH32 => class::ALU | src_bit | alu_op::ARSH,
BpfOpcode::JEQ => class::JMP | source::X | jump_op::JEQ,
BpfOpcode::JGT => class::JMP | source::X | jump_op::JGT,
BpfOpcode::JGE => class::JMP | source::X | jump_op::JGE,
BpfOpcode::JLT => class::JMP | source::X | jump_op::JLT,
BpfOpcode::JLE => class::JMP | source::X | jump_op::JLE,
BpfOpcode::JSET => class::JMP | source::X | jump_op::JSET,
BpfOpcode::JNE => class::JMP | source::X | jump_op::JNE,
BpfOpcode::JSGT => class::JMP | source::X | jump_op::JSGT,
BpfOpcode::JSGE => class::JMP | source::X | jump_op::JSGE,
BpfOpcode::JSLT => class::JMP | source::X | jump_op::JSLT,
BpfOpcode::JSLE => class::JMP | source::X | jump_op::JSLE,
BpfOpcode::JEQ32 => class::JMP32 | source::X | jump_op::JEQ,
BpfOpcode::JGT32 => class::JMP32 | source::X | jump_op::JGT,
BpfOpcode::JGE32 => class::JMP32 | source::X | jump_op::JGE,
BpfOpcode::JLT32 => class::JMP32 | source::X | jump_op::JLT,
BpfOpcode::JLE32 => class::JMP32 | source::X | jump_op::JLE,
BpfOpcode::JSET32 => class::JMP32 | source::X | jump_op::JSET,
BpfOpcode::JNE32 => class::JMP32 | source::X | jump_op::JNE,
BpfOpcode::JSGT32 => class::JMP32 | source::X | jump_op::JSGT,
BpfOpcode::JSGE32 => class::JMP32 | source::X | jump_op::JSGE,
BpfOpcode::JSLT32 => class::JMP32 | source::X | jump_op::JSLT,
BpfOpcode::JSLE32 => class::JMP32 | source::X | jump_op::JSLE,
_ => BpfInstrInfo::get_opcode_byte(&instr.opcode),
}
}
fn decode_opcode(opcode: u8) -> Option<BpfOpcode> {
let class_code = opcode & 0x07;
let alu_jmp_code = opcode & 0xF0;
match class_code {
0x00 => {
match opcode & 0xF8 {
0x18 => Some(BpfOpcode::LD_DW),
_ => None,
}
}
0x01 => {
match opcode & 0xF8 {
0x10 => Some(BpfOpcode::LDX_B),
0x08 => Some(BpfOpcode::LDX_H),
0x00 => Some(BpfOpcode::LDX_W),
0x18 => Some(BpfOpcode::LDX_DW),
_ => None,
}
}
0x02 => {
match opcode & 0xF8 {
0x18 => Some(BpfOpcode::ST_DW),
0x00 => Some(BpfOpcode::ST_DW_IMM),
_ => None,
}
}
0x03 => {
let stx_code = opcode & 0xF0;
match stx_code {
0x10 => Some(BpfOpcode::STX_B),
0x08 => Some(BpfOpcode::STX_H),
0x00 => Some(BpfOpcode::STX_W),
0x18 => Some(BpfOpcode::STX_DW),
0xC0 => Some(BpfOpcode::XADD),
_ => None,
}
}
0x04 => {
match alu_jmp_code {
0x00 => Some(BpfOpcode::ADD32),
0x10 => Some(BpfOpcode::SUB32),
0x20 => Some(BpfOpcode::MUL32),
0x30 => Some(BpfOpcode::DIV32),
0x40 => Some(BpfOpcode::OR32),
0x50 => Some(BpfOpcode::AND32),
0x60 => Some(BpfOpcode::LSH32),
0x70 => Some(BpfOpcode::RSH32),
0x80 => Some(BpfOpcode::NEG32),
0x90 => Some(BpfOpcode::MOD32),
0xA0 => Some(BpfOpcode::XOR32),
0xB0 => Some(BpfOpcode::MOV32),
0xC0 => Some(BpfOpcode::ARSH32),
0xD0 => {
if opcode & 0x08 != 0 {
Some(BpfOpcode::END_BE16)
} else {
Some(BpfOpcode::END_LE16)
}
}
_ => None,
}
}
0x05 => {
match alu_jmp_code {
0x00 => Some(BpfOpcode::JA),
0x10 => Some(BpfOpcode::JEQ),
0x20 => Some(BpfOpcode::JGT),
0x30 => Some(BpfOpcode::JGE),
0xA0 => Some(BpfOpcode::JLT),
0xB0 => Some(BpfOpcode::JLE),
0x40 => Some(BpfOpcode::JSET),
0x50 => Some(BpfOpcode::JNE),
0x60 => Some(BpfOpcode::JSGT),
0x70 => Some(BpfOpcode::JSGE),
0xC0 => Some(BpfOpcode::JSLT),
0xD0 => Some(BpfOpcode::JSLE),
0x80 => Some(BpfOpcode::CALL),
0x90 => Some(BpfOpcode::EXIT),
_ => None,
}
}
0x06 => {
match alu_jmp_code {
0x10 => Some(BpfOpcode::JEQ32),
0x20 => Some(BpfOpcode::JGT32),
0x30 => Some(BpfOpcode::JGE32),
0xA0 => Some(BpfOpcode::JLT32),
0xB0 => Some(BpfOpcode::JLE32),
0x40 => Some(BpfOpcode::JSET32),
0x50 => Some(BpfOpcode::JNE32),
0x60 => Some(BpfOpcode::JSGT32),
0x70 => Some(BpfOpcode::JSGE32),
0xC0 => Some(BpfOpcode::JSLT32),
0xD0 => Some(BpfOpcode::JSLE32),
_ => None,
}
}
0x07 => {
match alu_jmp_code {
0x00 => Some(BpfOpcode::ADD),
0x10 => Some(BpfOpcode::SUB),
0x20 => Some(BpfOpcode::MUL),
0x30 => Some(BpfOpcode::DIV),
0x40 => Some(BpfOpcode::OR),
0x50 => Some(BpfOpcode::AND),
0x60 => Some(BpfOpcode::LSH),
0x70 => Some(BpfOpcode::RSH),
0x80 => Some(BpfOpcode::NEG),
0x90 => Some(BpfOpcode::MOD),
0xA0 => Some(BpfOpcode::XOR),
0xB0 => Some(BpfOpcode::MOV),
0xC0 => Some(BpfOpcode::ARSH),
_ => None,
}
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_exit() {
let instr = BpfInstr::exit();
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes.len(), 8);
assert_eq!(bytes[0], 0x95); assert_eq!(bytes[1], 0x00); assert_eq!(&bytes[2..], &[0u8; 6]); }
#[test]
fn test_encode_mov_reg() {
let instr = BpfInstr::mov(1, 2);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0xBF); assert_eq!(bytes[1], 0x21); }
#[test]
fn test_encode_mov_imm() {
let instr = BpfInstr::mov_imm(0, 42);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0xB7);
let imm = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
assert_eq!(imm, 42);
}
#[test]
fn test_encode_add_reg() {
let instr = BpfInstr::add(3, 4);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0x0F); assert_eq!(bytes[1], 0x43); }
#[test]
fn test_encode_add_imm() {
let instr = BpfInstr::add_imm(1, 100);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0x07); let imm = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
assert_eq!(imm, 100);
}
#[test]
fn test_encode_ja() {
let instr = BpfInstr::ja(5);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0x05); let offset = i16::from_le_bytes([bytes[2], bytes[3]]);
assert_eq!(offset, 5);
}
#[test]
fn test_encode_call() {
let instr = BpfInstr::call(7);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0x85); let imm = i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
assert_eq!(imm, 7);
}
#[test]
fn test_encode_jmp_conditional() {
let instr = BpfInstr::jmp(BpfOpcode::JEQ, 1, 2, 3);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes[0], 0x1D); let offset = i16::from_le_bytes([bytes[2], bytes[3]]);
assert_eq!(offset, 3);
}
#[test]
fn test_encode_decode_roundtrip_exit() {
let instr = BpfInstr::exit();
let bytes = BpfMCEncoder::encode_instr(&instr);
let decoded = BpfMCEncoder::decode_instr(&bytes).expect("decode should succeed");
assert_eq!(decoded.opcode, BpfOpcode::EXIT);
}
#[test]
fn test_encode_decode_roundtrip_mov() {
let instr = BpfInstr::mov(5, 6);
let bytes = BpfMCEncoder::encode_instr(&instr);
let decoded = BpfMCEncoder::decode_instr(&bytes).expect("decode should succeed");
assert_eq!(decoded.opcode, BpfOpcode::MOV);
assert_eq!(decoded.dst_reg, 5);
assert_eq!(decoded.src_reg, 6);
}
#[test]
fn test_encode_decode_roundtrip_add_imm() {
let instr = BpfInstr::add_imm(0, 0x12345678);
let bytes = BpfMCEncoder::encode_instr(&instr);
let decoded = BpfMCEncoder::decode_instr(&bytes).expect("decode should succeed");
assert_eq!(decoded.opcode, BpfOpcode::ADD);
assert_eq!(decoded.imm, 0x12345678);
}
#[test]
fn test_decode_invalid_short() {
let bytes = [0x95, 0x00]; assert!(BpfMCEncoder::decode_instr(&bytes).is_none());
}
#[test]
fn test_decode_invalid_opcode() {
let bytes = [0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(BpfMCEncoder::decode_instr(&bytes).is_none());
}
#[test]
fn test_encode_instrs_multiple() {
let instrs = vec![
BpfInstr::mov_imm(1, 10),
BpfInstr::mov_imm(2, 20),
BpfInstr::add(0, 1),
BpfInstr::add(0, 2),
BpfInstr::exit(),
];
let bytes = BpfMCEncoder::encode_instrs(&instrs);
assert_eq!(bytes.len(), 5 * 8);
}
#[test]
fn test_decode_instrs() {
let instrs = vec![BpfInstr::mov_imm(0, 0), BpfInstr::exit()];
let bytes = BpfMCEncoder::encode_instrs(&instrs);
let decoded = BpfMCEncoder::decode_instrs(&bytes);
assert_eq!(decoded.len(), 2);
assert_eq!(decoded[0].opcode, BpfOpcode::MOV);
assert_eq!(decoded[1].opcode, BpfOpcode::EXIT);
}
#[test]
fn test_encode_to_hex() {
let instr = BpfInstr::exit();
let hex = BpfMCEncoder::encode_to_hex(&instr);
assert!(!hex.is_empty());
assert!(
hex.starts_with("95"),
"hex should start with 95, got: {}",
hex
);
}
#[test]
fn test_disasm() {
let instr = BpfInstr::exit();
let bytes = BpfMCEncoder::encode_instr(&instr);
let disasm = BpfMCEncoder::disasm(&bytes);
assert!(disasm.is_some());
assert!(disasm.unwrap().contains("exit"));
}
#[test]
fn test_encode_all_jump_kinds() {
let jumps = vec![
BpfOpcode::JEQ,
BpfOpcode::JGT,
BpfOpcode::JGE,
BpfOpcode::JLT,
BpfOpcode::JLE,
BpfOpcode::JSET,
BpfOpcode::JNE,
BpfOpcode::JSGT,
BpfOpcode::JSGE,
BpfOpcode::JSLT,
BpfOpcode::JSLE,
];
for op in jumps {
let instr = BpfInstr::jmp(op, 1, 2, 10);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes.len(), 8);
let decoded = BpfMCEncoder::decode_instr(&bytes);
assert!(decoded.is_some(), "Failed to decode {:?}", op);
assert_eq!(decoded.unwrap().opcode, op);
}
}
#[test]
fn test_encode_lddw_size() {
let instr = BpfInstr::mov64(1, 0xDEADBEEF_CAFEBABE);
let bytes = BpfMCEncoder::encode_instr(&instr);
assert_eq!(bytes.len(), 8); }
#[test]
fn test_default_instr() {
let instr = BpfInstr::default();
assert_eq!(instr.opcode, BpfOpcode::EXIT);
assert_eq!(instr.dst_reg, 0);
assert_eq!(instr.src_reg, 0);
}
#[test]
fn test_big_endian_encoder() {
let encoder = BpfMCEncoder::big_endian();
assert!(encoder.big_endian);
}
#[test]
fn test_encode_decode_jmp32() {
let instr = BpfInstr::jmp(BpfOpcode::JEQ32, 2, 3, 7);
let bytes = BpfMCEncoder::encode_instr(&instr);
let decoded = BpfMCEncoder::decode_instr(&bytes).expect("decode");
assert_eq!(decoded.opcode, BpfOpcode::JEQ32);
assert_eq!(decoded.offset, 7);
}
}