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}