use super::bpf_instr_info::{BpfInstrInfo, BpfOpcode};
use super::bpf_mc_encoder::{BpfInstr, BpfMCEncoder};
pub struct BpfAsmPrinter {
pub print_bytes: bool,
pub print_offsets: bool,
pub print_comments: bool,
pub comment_column: usize,
}
impl Default for BpfAsmPrinter {
fn default() -> Self {
Self {
print_bytes: false,
print_offsets: true,
print_comments: false,
comment_column: 40,
}
}
}
impl BpfAsmPrinter {
pub fn new() -> Self {
Self::default()
}
pub fn print_instr(&self, instr: &BpfInstr) -> String {
self.print_instr_at(instr, 0)
}
pub fn print_instr_at(&self, instr: &BpfInstr, offset: usize) -> String {
let mut line = String::new();
if self.print_offsets {
line.push_str(&format!("{:>4x}: ", offset));
}
if self.print_bytes {
let bytes = BpfMCEncoder::encode_instr(instr);
for &b in &bytes {
line.push_str(&format!("{:02x} ", b));
}
let expected_len = 8 * 3; while line.len() < line.len().max(expected_len) {
line.push(' ');
}
}
let mnemonic = BpfInstrInfo::get_mnemonic(&instr.opcode);
line.push_str(mnemonic);
match instr.opcode {
BpfOpcode::EXIT => {
}
BpfOpcode::JA => {
let offset = instr.offset as i16;
if offset >= 0 {
line.push_str(&format!(" +{}", offset));
} else {
line.push_str(&format!(" -{}", -offset));
}
}
BpfOpcode::CALL => {
line.push_str(&format!(" {}", instr.imm));
}
BpfOpcode::LD_DW => {
let reg = super::bpf_instr_info::reg_name(instr.dst_reg);
let val = instr.imm as u64;
line.push_str(&format!(" {}, 0x{:x}", reg, val));
}
BpfOpcode::NEG | BpfOpcode::NEG32 => {
let reg = super::bpf_instr_info::reg_name(instr.dst_reg);
line.push_str(&format!(" {}", reg));
}
BpfOpcode::END_LE16
| BpfOpcode::END_LE32
| BpfOpcode::END_LE64
| BpfOpcode::END_BE16
| BpfOpcode::END_BE32
| BpfOpcode::END_BE64 => {
let reg = super::bpf_instr_info::reg_name(instr.dst_reg);
line.push_str(&format!(" {}, {}", reg, instr.imm));
}
op if BpfInstrInfo::is_branch(&op) => {
let dst = super::bpf_instr_info::reg_name(instr.dst_reg);
let src = super::bpf_instr_info::reg_name(instr.src_reg);
let off = instr.offset as i16;
let off_str = if off >= 0 {
format!("+{}", off)
} else {
format!("-{}", -off)
};
line.push_str(&format!(" {}, {}, {}", dst, src, off_str));
}
BpfOpcode::ST_DW
| BpfOpcode::ST_DW_IMM
| BpfOpcode::ST_B
| BpfOpcode::ST_H
| BpfOpcode::ST_W
| BpfOpcode::STX_B
| BpfOpcode::STX_H
| BpfOpcode::STX_W
| BpfOpcode::STX_DW
| BpfOpcode::STX_DW_IMM => {
let dst = super::bpf_instr_info::reg_name(instr.dst_reg);
let src = super::bpf_instr_info::reg_name(instr.src_reg);
if instr.offset != 0 {
line.push_str(&format!(" [{} + {}], {}", dst, instr.offset as i16, src));
} else {
line.push_str(&format!(" [{}], {}", dst, src));
}
}
BpfOpcode::LDX_B | BpfOpcode::LDX_H | BpfOpcode::LDX_W | BpfOpcode::LDX_DW => {
let dst = super::bpf_instr_info::reg_name(instr.dst_reg);
let src = super::bpf_instr_info::reg_name(instr.src_reg);
if instr.offset != 0 {
line.push_str(&format!(" {}, [{} + {}]", dst, src, instr.offset as i16));
} else {
line.push_str(&format!(" {}, [{}]", dst, src));
}
}
BpfOpcode::XADD
| BpfOpcode::XADD32
| BpfOpcode::FETCH_ADD
| BpfOpcode::FETCH_AND
| BpfOpcode::FETCH_OR
| BpfOpcode::FETCH_XOR => {
let dst = super::bpf_instr_info::reg_name(instr.dst_reg);
let src = super::bpf_instr_info::reg_name(instr.src_reg);
if instr.offset != 0 {
line.push_str(&format!(" [{} + {}], {}", dst, instr.offset as i16, src));
} else {
line.push_str(&format!(" [{}], {}", dst, src));
}
}
_ => {
let dst = super::bpf_instr_info::reg_name(instr.dst_reg);
if BpfInstrInfo::uses_src_reg(&instr.opcode) && instr.src_reg != 0 {
let src = super::bpf_instr_info::reg_name(instr.src_reg);
line.push_str(&format!(" {}, {}", dst, src));
} else {
line.push_str(&format!(" {}, {}", dst, instr.imm));
}
}
}
if self.print_comments {
let padding = if self.comment_column > line.len() {
self.comment_column - line.len()
} else {
1
};
for _ in 0..padding {
line.push(' ');
}
line.push_str(&format!(
"// opcode=0x{:02x} dst=r{} src=r{} off={} imm={}",
BpfInstrInfo::get_opcode_byte(&instr.opcode),
instr.dst_reg,
instr.src_reg,
instr.offset,
instr.imm
));
}
line
}
pub fn print_instrs(&self, instrs: &[BpfInstr]) -> String {
let mut output = String::new();
let mut offset: usize = 0;
for instr in instrs {
let line = self.print_instr_at(instr, offset);
output.push_str(&line);
output.push('\n');
offset += BpfInstrInfo::instr_size(&instr.opcode) as usize * 8;
}
output
}
pub fn print_with_bytes(&self, instrs: &[BpfInstr]) -> String {
let mut output = String::new();
let all_bytes = BpfMCEncoder::encode_instrs(instrs);
let mut byte_offset = 0;
for instr in instrs {
let instr_bytes = BpfMCEncoder::encode_instr(instr);
let byte_str: String = instr_bytes.iter().map(|b| format!("{:02x} ", b)).collect();
output.push_str(&format!("{:>4x}: {:<24}", byte_offset, byte_str));
let asm_part = self.print_instr(instr);
output.push_str(&asm_part);
output.push('\n');
byte_offset += instr_bytes.len();
}
output.push_str(&format!(
"// total: {} instructions, {} bytes\n",
instrs.len(),
all_bytes.len()
));
output
}
pub fn print_header(&self, name: &str) -> String {
format!("# BPF program: {}\n", name)
}
pub fn print_footer(&self, instr_count: usize, byte_count: usize) -> String {
format!("# {} instructions, {} bytes\n", instr_count, byte_count)
}
}
#[cfg(test)]
mod tests {
use super::super::bpf_mc_encoder::{BpfInstr, BpfMCEncoder};
use super::*;
fn make_instr(opcode: BpfOpcode, dst_reg: u8, src_reg: u8, offset: i16, imm: i32) -> BpfInstr {
BpfInstr {
opcode,
dst_reg,
src_reg,
offset,
imm,
}
}
#[test]
fn test_print_exit() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = make_instr(BpfOpcode::EXIT, 0, 0, 0, 0);
let output = printer.print_instr(&instr);
assert_eq!(output.trim(), "exit");
}
#[test]
fn test_print_add_registers() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = make_instr(BpfOpcode::ADD, 1, 2, 0, 0);
let output = printer.print_instr(&instr);
assert!(output.contains("add r1, r2"), "got: {}", output);
}
#[test]
fn test_print_ja() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = make_instr(BpfOpcode::JA, 0, 0, 5, 0);
let output = printer.print_instr(&instr);
assert!(output.contains("ja +5"), "got: {}", output);
}
#[test]
fn test_print_call() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = make_instr(BpfOpcode::CALL, 0, 0, 0, 1);
let output = printer.print_instr(&instr);
assert!(output.contains("call 1"), "got: {}", output);
}
#[test]
fn test_print_lddw() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = make_instr(BpfOpcode::LD_DW, 1, 0, 0, 0x42);
let output = printer.print_instr(&instr);
assert!(output.contains("lddw r1"), "got: {}", output);
}
#[test]
fn test_print_jeq() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = make_instr(BpfOpcode::JEQ, 1, 2, 3, 0);
let output = printer.print_instr(&instr);
assert!(output.contains("jeq r1, r2, +3"), "got: {}", output);
}
#[test]
fn test_print_mov_imm() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instr = BpfInstr {
opcode: BpfOpcode::MOV,
dst_reg: 0,
src_reg: 0,
offset: 0,
imm: 42,
};
let output = printer.print_instr(&instr);
assert!(output.contains("mov r0, 42"), "got: {}", output);
}
#[test]
fn test_print_multiple_instructions() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = false;
let instrs = vec![
make_instr(BpfOpcode::MOV, 1, 0, 0, 10),
make_instr(BpfOpcode::MOV, 2, 0, 0, 20),
make_instr(BpfOpcode::ADD, 0, 1, 0, 0),
make_instr(BpfOpcode::EXIT, 0, 0, 0, 0),
];
let output = printer.print_instrs(&instrs);
assert!(output.contains("mov r1, 10"));
assert!(output.contains("mov r2, 20"));
assert!(output.contains("add r0, r1"));
assert!(output.contains("exit"));
}
#[test]
fn test_print_with_offsets() {
let mut printer = BpfAsmPrinter::default();
printer.print_offsets = true;
let instrs = vec![make_instr(BpfOpcode::ADD, 1, 2, 0, 0)];
let output = printer.print_instrs(&instrs);
assert!(output.contains(" 0:"), "got: {}", output);
}
#[test]
fn test_print_with_bytes() {
let instrs = vec![make_instr(BpfOpcode::EXIT, 0, 0, 0, 0)];
let printer = BpfAsmPrinter::default();
let output = printer.print_with_bytes(&instrs);
assert!(
output.contains("0x") || output.contains(" 0:") );
}
#[test]
fn test_print_header_footer() {
let printer = BpfAsmPrinter::default();
let header = printer.print_header("my_prog");
assert!(header.contains("my_prog"));
let footer = printer.print_footer(5, 40);
assert!(footer.contains("5 instructions"));
}
#[test]
fn test_neg_special_format() {
let printer = BpfAsmPrinter::default();
let instr = make_instr(BpfOpcode::NEG, 3, 0, 0, 0);
let output = printer.print_instr(&instr);
assert!(
output.contains("neg r3") || output.contains("neg 3"),
"got: {}",
output
);
}
#[test]
fn test_ldx_format() {
let printer = BpfAsmPrinter::default();
let instr = make_instr(BpfOpcode::LDX_W, 1, 10, 0, 0);
let output = printer.print_instr(&instr);
assert!(output.contains("ldxw r1, [r10]"), "got: {}", output);
}
#[test]
fn test_ldx_with_offset() {
let printer = BpfAsmPrinter::default();
let instr = make_instr(BpfOpcode::LDX_W, 1, 10, -4, 0);
let output = printer.print_instr(&instr);
assert!(output.contains("-4"), "got: {}", output);
}
#[test]
fn test_print_empty() {
let printer = BpfAsmPrinter::default();
let output = printer.print_instrs(&[]);
assert!(output.is_empty());
}
}