codehelion_artifact/
x86.rs1use iced_x86::{Decoder, DecoderOptions, OpKind};
10use object::Architecture;
11
12use crate::NormalizedInstructions;
13
14pub const X86_NORMALIZATION_VERSION: &str = "x86-operand-shape-v1";
16
17#[must_use]
19pub const fn supports_normalized_duplicates(architecture: Architecture) -> bool {
20 matches!(architecture, Architecture::I386 | Architecture::X86_64)
21}
22
23#[must_use]
30pub fn normalize_x86(code: &[u8], architecture: Architecture) -> Option<NormalizedInstructions> {
31 let bitness = match architecture {
32 Architecture::I386 => 32,
33 Architecture::X86_64 => 64,
34 _ => return None,
35 };
36 let mut decoder = Decoder::with_ip(bitness, code, 0, DecoderOptions::NONE);
37 let mut normalized = Vec::new();
38 while decoder.can_decode() {
39 let instruction = decoder.decode();
40 if instruction.is_invalid() {
41 return None;
42 }
43 normalized.extend((instruction.code() as u32).to_le_bytes());
44 normalized.push(u8::try_from(instruction.op_count()).ok()?);
45 for operand in 0..instruction.op_count() {
46 let kind = instruction.op_kind(operand);
47 normalized.push(kind as u8);
48 if kind == OpKind::Memory {
49 normalized.push(instruction.memory_size() as u8);
52 normalized.push(u8::try_from(instruction.memory_index_scale()).ok()?);
53 normalized.push(u8::try_from(instruction.memory_displ_size()).ok()?);
54 }
55 }
56 }
57 Some(NormalizedInstructions {
58 version: X86_NORMALIZATION_VERSION.to_owned(),
59 bytes: normalized,
60 })
61}
62
63#[must_use]
68pub fn trim_inferred_padding(code: &[u8], architecture: Architecture) -> &[u8] {
69 if !matches!(architecture, Architecture::I386 | Architecture::X86_64) {
70 return code;
71 }
72 let end = code
73 .iter()
74 .rposition(|byte| !matches!(byte, 0x00 | 0x90 | 0xcc))
75 .map_or(0, |index| index + 1);
76 &code[..end]
77}
78
79#[cfg(test)]
80#[allow(clippy::unwrap_used)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn normalization_ignores_immediate_values_but_not_instruction_shape() {
86 let first = normalize_x86(&[0xb8, 1, 0, 0, 0, 0xc3], Architecture::X86_64).unwrap();
87 let second = normalize_x86(&[0xb8, 2, 0, 0, 0, 0xc3], Architecture::X86_64).unwrap();
88 let call = normalize_x86(&[0xe8, 1, 0, 0, 0, 0xc3], Architecture::X86_64).unwrap();
89
90 assert_eq!(first.version, X86_NORMALIZATION_VERSION);
91 assert_eq!(first, second);
92 assert_ne!(first, call);
93 assert!(normalize_x86(&[0x0f], Architecture::X86_64).is_none());
94 assert!(normalize_x86(&[0xc3], Architecture::Aarch64).is_none());
95 }
96
97 #[test]
98 fn inferred_x86_ranges_drop_only_conventional_trailing_padding() {
99 assert_eq!(
100 trim_inferred_padding(&[0x90, 0xc3, 0x00, 0x90, 0xcc], Architecture::X86_64),
101 &[0x90, 0xc3]
102 );
103 assert_eq!(
104 trim_inferred_padding(&[0xc3, 0x00], Architecture::Aarch64),
105 &[0xc3, 0x00]
106 );
107 }
108
109 #[test]
110 fn normalized_duplicate_capability_is_explicit_for_each_architecture() {
111 assert!(supports_normalized_duplicates(Architecture::X86_64));
112 assert!(!supports_normalized_duplicates(Architecture::Aarch64));
113 }
114}