idakit 0.2.0

Idiomatic Rust bindings for IDA Pro's idalib kernel
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
//! Decodes machine instructions into an owned [`Instruction`] and its semantic operands.
//!
//! [`Database::decode`](crate::Database::decode) turns the bytes at an [`Address`] into an owned [`Instruction`],
//! with mnemonic, operands, and control-flow facts all resolved on the kernel thread and
//! baked in, so the value carries no borrow and can be analyzed on any worker thread. This
//! is the raw-disassembly counterpart to the decompiler ctree, staying ISA-shaped (it
//! does not lift to an IR) and, like the ctree, materializing owned data rather than
//! handing back a `!Send` view over kernel structures.
//!
//! Operands are modelled *semantically* as [`OperandKind`], a small closed set
//! (register / memory / immediate / branch target). IDA's raw operand-type byte is an
//! open space (x86 alone uses values above the documented range for YMM/ZMM/mask
//! registers), so mirroring it would be a trap. Instead the per-processor decoder folds
//! every raw type into one of these kinds. An [`Instruction`] that exists is therefore fully and
//! faithfully decoded, because an unsupported processor or an operand the decoder cannot model
//! becomes a [`DecodeError`] rather than a partial or fallback value.

mod data_type;
mod decode;
mod evex;
mod iter;
mod register;

pub use data_type::OperandDataType;
pub use evex::{FpControl, Masking, RoundMode};
pub use iter::{Instructions, InstructionsIn};
pub use register::{Register, RegisterClass};

pub(crate) use decode::classify;

use serde::{Deserialize, Serialize};
use snafu::Snafu;

use crate::Database;
use crate::address::Address;

impl Database {
    /// Decodes the instruction at `address` into an owned, `Send` [`Instruction`].
    ///
    /// Mnemonic, semantic operands, and control-flow facts are all resolved here on the
    /// kernel thread. An [`Instruction`] that is returned is faithfully decoded, with no
    /// partial or fallback result. An operand the model cannot represent exactly (an
    /// unmodelled register or value type, a malformed payload) is a loud error, never a guess.
    ///
    /// # Errors
    /// [`DecodeError::NotCode`] if no instruction decodes at `address`, or
    /// [`DecodeError::UnsupportedProcessor`] if the database's processor has no decoder (only
    /// x86/x64 are modelled).
    #[doc(alias("decode_insn"))]
    pub fn decode(&self, address: Address) -> Result<Instruction, DecodeError> {
        let data = self.decode_insn(address);
        classify(&data, address)
    }
}

/// Why decoding an instruction failed.
///
/// [`NotCode`](Self::NotCode) is an ordinary outcome (probing an address that isn't an
/// instruction), so it is a distinct, cheaply matched error rather than a variant of the
/// crate-wide [`Error`](crate::Error). A [`From`] conversion still lets `?` flatten it into
/// an [`Error`](crate::Error) where that's wanted.
#[derive(Debug, Snafu, PartialEq, Eq)]
#[snafu(visibility(pub(crate)))]
pub enum DecodeError {
    /// No instruction decodes at `address`, because the bytes there are data or undefined.
    #[snafu(display("no instruction at {address:#x}"))]
    NotCode {
        /// The address probed.
        address: u64,
    },
    /// The database's processor has no wired decoder (only x86/x64 are modelled).
    #[snafu(display("no instruction decoder for this processor (x86/x64 only)"))]
    UnsupportedProcessor,
    /// A supported processor produced an operand this decoder cannot model. Unreachable
    /// for x86, which enumerates all of its operand types; a loud safety net, not a normal
    /// path.
    #[snafu(display("unmodeled operand {slot} (raw operand type {operand_type}) at {address:#x}"))]
    UnsupportedOperand {
        /// Address of the instruction.
        address: u64,
        /// The operand slot that could not be modelled.
        slot: u8,
        /// The raw operand-type byte the decoder did not recognize.
        #[doc(alias("optype_t"))]
        operand_type: u8,
    },

    /// A register operand referred to a register in no modelled [`RegisterClass`] (flags,
    /// fpu/sse control-status, or a number outside the register file). Rejected loudly rather
    /// than mislabeled `GeneralPurpose`; empirically never emitted for a real x86 operand.
    #[snafu(display("unmodeled register {register_number} at operand {slot}, {address:#x}"))]
    UnsupportedRegister {
        /// Address of the instruction.
        address: u64,
        /// The operand slot carrying the register.
        slot: u8,
        /// The processor-local register number that has no modelled class.
        #[doc(alias("regnum"))]
        register_number: u8,
    },

    /// An operand's value type fell outside the modeled [`OperandDataType`] domain, since
    /// only this IDA version's set of value types is modeled, so a newer version's value is
    /// a deliberate break, not a silent `Void`.
    #[snafu(display("unmodeled data type {data_type} at operand {slot}, {address:#x}"))]
    UnsupportedDataType {
        /// Address of the instruction.
        address: u64,
        /// The operand slot carrying the value type.
        slot: u8,
        /// The raw value-type byte outside the modeled domain.
        #[doc(alias("dtype"))]
        data_type: u8,
    },

    /// A modelled operand kind arrived with a payload that contradicts it, such as a near
    /// branch whose target did not resolve, or a register operand with no register. A facade
    /// contract violation; empirically impossible, kept as a loud guard rather than a panic.
    #[snafu(display("malformed operand {slot} at {address:#x}: {reason}"))]
    MalformedOperand {
        /// Address of the instruction.
        address: u64,
        /// The offending operand slot.
        slot: u8,
        /// What made the operand malformed.
        reason: &'static str,
    },
}

/// The instruction-set architecture a decoded instruction was read under.
///
/// A closed set that grows only when a decoder is *implemented*, so decoding under a
/// processor with no wired decoder is a [`DecodeError::UnsupportedProcessor`], not a
/// variant here. Adding a decoder is a deliberate, breaking widening.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Isa {
    /// 32-bit x86.
    X86,
    /// 64-bit x86-64.
    X64,
}

/// An owned, `Send` snapshot of a decoded instruction, from [`Database::decode`].
///
/// Keyed by its [`Address`]; fall-through is `address + len` and branch destinations are plain
/// [`Address`]s, so an instruction stream needs no interning, just an address-ordered
/// sequence of these. Everything the kernel had to resolve (the mnemonic, register names,
/// control-flow classification) is already here; nothing on an [`Instruction`] calls back into
/// the kernel.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("insn_t"))]
pub struct Instruction {
    /// Address of the instruction.
    pub address: Address,
    /// Encoded length in bytes.
    pub len: u8,
    /// The architecture this was decoded under; makes `canonical_code`, register numbers, and
    /// the mnemonic self-describing off-thread.
    pub isa: Isa,
    /// Processor-local canonical instruction id (x86 `NN_*`). Numeric and cheap to match;
    /// meaningful only together with [`isa`](Self::isa). This is the trustable machine
    /// identity, of which `mnemonic` is the human projection.
    #[doc(alias("itype"))]
    pub canonical_code: u16,
    /// IDA's canonical mnemonic, resolved at decode.
    pub mnemonic: Box<str>,
    /// Explicit operands in encoding order. Trailing empty operand slots are dropped, so
    /// `ops.len()` is the real operand count. The EVEX write-mask is not an entry here; it is
    /// [`masking`](Self::masking).
    pub ops: Vec<Operand>,
    /// Control-flow classification, resolved on the kernel thread.
    pub flow: Flow,
    /// The EVEX write-mask, when this AVX-512 instruction selects one (`k1`..`k7`). `None` for
    /// unmasked and non-EVEX instructions.
    pub masking: Option<Masking>,
    /// The EVEX embedded floating-point control on a register-form instruction (static rounding
    /// or exception suppression). `None` when absent; embedded broadcast is on the memory operand
    /// instead, as [`Memory::broadcast`].
    pub fp_control: Option<FpControl>,
}

impl Instruction {
    /// Every register this instruction references, in operand order.
    ///
    /// Each register operand comes first, then the base, index, and segment registers of
    /// each memory operand. Immediates and branch targets contribute none.
    pub fn registers(&self) -> impl Iterator<Item = &Register> {
        self.ops.iter().flat_map(|op| {
            let regs: [Option<&Register>; 3] = match &op.kind {
                OperandKind::Register(r) => [Some(r), None, None],
                OperandKind::Memory(m) => [m.base.as_ref(), m.index.as_ref(), m.segment.as_ref()],
                _ => [None, None, None],
            };
            regs.into_iter().flatten()
        })
    }
}

/// One operand of an [`Instruction`].
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("op_t"))]
pub struct Operand {
    /// The operand's original slot index (0-based). Void slots are dropped from
    /// [`ops`](Instruction::ops), so a slot's position in that vector need not equal this; anything
    /// keyed by IDA's per-operand slots correlates through `slot`.
    pub slot: u8,
    /// The operand's byte offset within the encoded instruction.
    #[doc(alias("offb"))]
    pub byte_offset: u8,
    /// What the operand refers to.
    pub kind: OperandKind,
    /// The operand's value type.
    pub data_type: OperandDataType,
    /// Whether the instruction reads and/or writes this operand.
    pub access: Access,
}

/// The semantic classification of an operand.
///
/// Closed on purpose, since the per-processor decoder maps *every* raw operand type (including
/// the SIMD/mask register types x86 encodes above the documented range) into one of
/// these. A future operand *category* is a deliberate, breaking widening; an unknown raw
/// byte is a [`DecodeError`], never a new variant callers must pre-guard.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[doc(alias("op_t"))]
pub enum OperandKind {
    /// A register, of any class (folds every register operand type).
    Register(Register),
    /// A memory reference: `seg:[base + index*scale + disp]`.
    Memory(Memory),
    /// An immediate constant. Signedness is carried by the operand's
    /// [`data_type`](Operand::data_type).
    Immediate {
        /// The immediate value.
        value: u64,
    },
    /// A near (intra-segment) code target, resolved to an address.
    Near(Address),
    /// A far (inter-segment) code target.
    Far {
        /// Segment selector.
        selector: u16,
        /// Offset within the target segment.
        offset: u64,
    },
}

/// A structured memory operand of the form `segment:[base + index*scale + disp]`.
///
/// Decoded from IDA's own REX-aware addressing accessors, so the register components are
/// real, not parsed out of rendered text. Which fields are populated encodes the
/// addressing form (a bare `[disp]` has no `base`/`index`; a RIP-relative reference IDA
/// folded to an absolute address populates `target`).
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Memory {
    /// Base register, if any.
    pub base: Option<Register>,
    /// Index register, if any.
    pub index: Option<Register>,
    /// Index scale multiplier (1, 2, 4, or 8).
    pub scale: u8,
    /// Signed displacement.
    pub displacement: i64,
    /// Segment-override register. Currently always `None`, since reliably distinguishing an
    /// explicit override from the default segment is deferred, so this is left unpopulated
    /// rather than guessed.
    pub segment: Option<Register>,
    /// The static target address, when IDA resolved the reference to one (direct memory
    /// operands, including RIP-relative that the kernel folded to an absolute).
    pub target: Option<Address>,
    /// The EVEX embedded-broadcast factor N of a `{1toN}` operand: one memory element is read and
    /// fanned out to N vector lanes, as IDA renders it. `None` for an ordinary memory operand.
    pub broadcast: Option<u8>,
}

/// Whether an instruction reads and/or writes a given operand.
///
/// Both bits come from the instruction's *canonical* per-operand feature flags: a static
/// approximation keyed on the instruction type, not value-accurate dataflow. It does not
/// account for conditional or implicit access; precise use/def analysis is a separate,
/// deferred concern. The two bits are independent (an operand may be neither, either, or
/// both), so they are not collapsed into a single read/write/read-write enum.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Access {
    /// The instruction reads this operand's value.
    pub read: bool,
    /// The instruction writes this operand.
    pub written: bool,
}

/// Control-flow facts about an instruction, resolved on the kernel thread.
///
/// `is_call`/`is_ret`/`is_indirect` come from the processor's own predicates (richer than
/// the raw feature bits); `stops` reports whether execution falls through to `address + len`.
/// `target` is the static destination of a *direct* branch or call, when one exists
/// (the single fact CFG assembly needs), hoisted here so each [`Instruction`] is a
/// self-contained CFG input.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Flow {
    /// A call instruction.
    pub is_call: bool,
    /// A return instruction.
    pub is_ret: bool,
    /// A branch (conditional or unconditional).
    pub is_jump: bool,
    /// The branch/call target is computed (register or memory), not a static address.
    pub is_indirect: bool,
    /// Execution does not fall through to `address + len`.
    pub stops: bool,
    /// Static destination of a direct branch/call, when known.
    pub target: Option<Address>,
}

#[cfg(test)]
mod tests {
    use assert2::assert;

    use super::*;
    use crate::address::Address;

    const fn assert_send<T: Send>() {}

    // `Instruction` is owned precisely so it can leave the kernel thread; a later non-`Send` field
    // would defeat that, so pin the guarantee at compile time.
    const _: () = assert_send::<Instruction>();

    fn reg(name: &str) -> Register {
        Register {
            number: 0,
            class: RegisterClass::GeneralPurpose,
            width: 8,
            name: name.into(),
        }
    }

    fn op(kind: OperandKind) -> Operand {
        Operand {
            slot: 0,
            byte_offset: 0,
            kind,
            data_type: OperandDataType::Qword,
            access: Access::default(),
        }
    }

    #[test]
    fn registers_walks_operand_and_memory_components_in_order() {
        let insn = Instruction {
            address: Address::try_new(0x1000).expect("valid"),
            len: 4,
            isa: Isa::X64,
            canonical_code: 0,
            mnemonic: "lea".into(),
            ops: vec![
                op(OperandKind::Register(reg("rax"))),
                op(OperandKind::Memory(Memory {
                    base: Some(reg("rbx")),
                    index: Some(reg("rcx")),
                    scale: 1,
                    displacement: 0,
                    segment: None,
                    target: None,
                    broadcast: None,
                })),
                op(OperandKind::Immediate { value: 5 }),
            ],
            masking: None,
            fp_control: None,
            flow: Flow {
                is_call: false,
                is_ret: false,
                is_jump: false,
                is_indirect: false,
                stops: false,
                target: None,
            },
        };
        let names: Vec<&str> = insn.registers().map(|r| r.name.as_ref()).collect();
        assert!(names == ["rax", "rbx", "rcx"]);
    }

    fn sample_instruction() -> Instruction {
        Instruction {
            address: Address::try_new(0x1000).expect("valid"),
            len: 4,
            isa: Isa::X64,
            canonical_code: 0,
            mnemonic: "lea".into(),
            ops: vec![
                op(OperandKind::Register(reg("rax"))),
                op(OperandKind::Memory(Memory {
                    base: Some(reg("rbx")),
                    index: Some(reg("rcx")),
                    scale: 1,
                    displacement: -8,
                    segment: None,
                    target: Some(Address::try_new(0x2000).expect("valid")),
                    broadcast: None,
                })),
                op(OperandKind::Immediate { value: 5 }),
                op(OperandKind::Near(Address::try_new(0x3000).expect("valid"))),
                op(OperandKind::Far {
                    selector: 0x33,
                    offset: 0x400,
                }),
            ],
            masking: None,
            fp_control: None,
            flow: Flow {
                is_call: false,
                is_ret: false,
                is_jump: true,
                is_indirect: false,
                stops: true,
                target: Some(Address::try_new(0x3000).expect("valid")),
            },
        }
    }

    // Round-trips every OperandKind variant so the derived Serialize/Deserialize on Instruction
    // and its components stays exercised end to end.
    #[test]
    fn instruction_serde_roundtrip() {
        let insn = sample_instruction();
        let json = serde_json::to_string(&insn).expect("serialize");
        let back: Instruction = serde_json::from_str(&json).expect("deserialize");
        assert!(back == insn);
    }

    #[test]
    fn instruction_hash_usable_in_set() {
        use std::collections::HashSet;
        let mut set = HashSet::new();
        assert!(set.insert(sample_instruction()));
        assert!(!set.insert(sample_instruction()));
    }
}