Skip to main content

sys_rs/
asm.rs

1use capstone::{prelude::*, Insn};
2use nix::errno::Errno;
3use std::fmt;
4
5use crate::diag::{Error, Result};
6
7/// A disassembled instruction with human-friendly fields.
8///
9/// This type stores the instruction address, mnemonic and operands as strings
10/// so callers can format or inspect them without depending on capstone types.
11pub struct Instruction {
12    address: u64,
13    mnemonic: String,
14    operands: String,
15}
16
17impl Instruction {
18    #[must_use]
19    /// Create an `Instruction` from a capstone `Insn`.
20    ///
21    /// Converts a capstone `Insn` into the crate's lightweight `Instruction`
22    /// representation by copying the instruction address, mnemonic and
23    /// operand string. This allows callers to own and format instruction
24    /// data without keeping capstone types around.
25    ///
26    /// # Arguments
27    ///
28    /// * `insn` - A reference to a capstone `Insn` to convert.
29    ///
30    /// # Returns
31    ///
32    /// An owned `Instruction` containing the address, mnemonic and operands
33    /// extracted from `insn`.
34    pub fn new(insn: &Insn) -> Self {
35        Self {
36            address: insn.address(),
37            mnemonic: insn.mnemonic().unwrap_or("").to_string(),
38            operands: insn.op_str().unwrap_or("").to_string(),
39        }
40    }
41
42    #[must_use]
43    /// Return the instruction address.
44    ///
45    /// # Returns
46    ///
47    /// The virtual memory address where this instruction is located.
48    pub fn address(&self) -> u64 {
49        self.address
50    }
51
52    #[must_use]
53    /// Return true when the mnemonic represents a call instruction.
54    ///
55    /// # Returns
56    ///
57    /// `true` when the instruction mnemonic contains the substring
58    /// "call" (for example `callq`), otherwise `false`.
59    pub fn is_call(&self) -> bool {
60        self.mnemonic.contains("call")
61    }
62}
63
64impl fmt::Display for Instruction {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        write!(
67            f,
68            "{:#x}: {}\t{}",
69            self.address, self.mnemonic, self.operands
70        )
71    }
72}
73
74/// A light wrapper around capstone used to disassemble buffers.
75pub struct Parser {
76    capstone: Capstone,
77}
78
79impl Parser {
80    /// Create a new instruction `Parser` for the host architecture (`x86_64`).
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if the underlying capstone builder fails.
85    pub fn new() -> Result<Self> {
86        Ok(Self {
87            capstone: Capstone::new()
88                .x86()
89                .mode(arch::x86::ArchMode::Mode64)
90                .syntax(arch::x86::ArchSyntax::Att)
91                .detail(true)
92                .build()?,
93        })
94    }
95
96    /// Disassemble a single instruction from `opcode` at address `addr`.
97    ///
98    /// # Arguments
99    ///
100    /// * `opcode` - Bytes containing at least one instruction.
101    /// * `addr` - Virtual address corresponding to the start of `opcode`.
102    ///
103    /// # Errors
104    ///
105    /// Returns an error when disassembly fails or no instruction could be
106    /// decoded.
107    pub fn get_instruction_from(
108        &self,
109        opcode: &[u8],
110        addr: u64,
111    ) -> Result<Instruction> {
112        let instructions = self.capstone.disasm_count(opcode, addr, 1)?;
113        Ok(Instruction::new(
114            instructions
115                .iter()
116                .next()
117                .ok_or_else(|| Error::from(Errno::ENOEXEC))?,
118        ))
119    }
120
121    /// Disassemble all instructions present in `code`, returning a vector of
122    /// `Instruction` instances.
123    ///
124    /// # Arguments
125    ///
126    /// * `code` - Buffer containing machine code.
127    /// * `addr` - Base virtual address of `code`.
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if the disassembly operation fails.
132    pub fn get_all_instructions_from(
133        &self,
134        code: &[u8],
135        addr: u64,
136    ) -> Result<Vec<Instruction>> {
137        let instructions = self.capstone.disasm_all(code, addr)?;
138        Ok(instructions.iter().map(Instruction::new).collect())
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_parser_get_instruction_from() {
148        let parser = Parser::new().expect("Failed to create parser");
149
150        let opcode: [u8; 5] = [0xe8, 0x05, 0x00, 0x00, 0x00];
151        let addr: u64 = 0x1000;
152
153        let instruction = parser
154            .get_instruction_from(&opcode, addr)
155            .expect("Failed to get instruction");
156
157        assert_eq!(instruction.address(), addr);
158        assert_eq!(instruction.is_call(), true);
159        assert_eq!(instruction.mnemonic, "callq");
160        assert_eq!(instruction.operands, "0x100a");
161    }
162
163    #[test]
164    fn test_instruction_fmt() {
165        let inst = Instruction {
166            address: 0x1000,
167            mnemonic: "mov".into(),
168            operands: "rax, rbx".into(),
169        };
170        let s = format!("{}", inst);
171        assert!(s.contains("0x1000"));
172        assert!(s.contains("mov"));
173        assert!(s.contains("rax, rbx"));
174    }
175}