Skip to main content

objdiff_core/arch/
x86.rs

1use alloc::{boxed::Box, format, vec::Vec};
2use core::cmp::Ordering;
3
4use anyhow::{Context, Result, anyhow, bail};
5use iced_x86::{
6    Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
7    Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register,
8};
9use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
10
11use crate::{
12    arch::{Arch, OPCODE_DATA, RelocationOverride, RelocationOverrideTarget},
13    diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
14    obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
15};
16
17#[derive(Debug)]
18pub struct ArchX86 {
19    arch: Architecture,
20    endianness: object::Endianness,
21}
22
23#[derive(Debug)]
24enum Architecture {
25    X86,
26    X86_64,
27}
28
29impl ArchX86 {
30    pub fn new(object: &object::File) -> Result<Self> {
31        let arch = match object.architecture() {
32            object::Architecture::I386 => Architecture::X86,
33            object::Architecture::X86_64 => Architecture::X86_64,
34            _ => bail!("Unsupported architecture for ArchX86: {:?}", object.architecture()),
35        };
36        Ok(Self { arch, endianness: object.endianness() })
37    }
38
39    fn decoder<'a>(&self, code: &'a [u8], address: u64) -> Decoder<'a> {
40        Decoder::with_ip(
41            match self.arch {
42                Architecture::X86 => 32,
43                Architecture::X86_64 => 64,
44            },
45            code,
46            address,
47            DecoderOptions::NONE,
48        )
49    }
50
51    fn formatter(&self, diff_config: &DiffObjConfig) -> Box<dyn iced_x86::Formatter> {
52        let mut formatter: Box<dyn iced_x86::Formatter> = match diff_config.x86_formatter {
53            X86Formatter::Intel => Box::new(IntelFormatter::new()),
54            X86Formatter::Gas => Box::new(GasFormatter::new()),
55            X86Formatter::Nasm => Box::new(NasmFormatter::new()),
56            X86Formatter::Masm => Box::new(MasmFormatter::new()),
57        };
58        formatter.options_mut().set_space_after_operand_separator(diff_config.space_between_args);
59        formatter
60    }
61
62    fn reloc_size(&self, flags: RelocationFlags) -> Option<usize> {
63        match self.arch {
64            Architecture::X86 => match flags {
65                RelocationFlags::Coff(typ) => match typ {
66                    pe::IMAGE_REL_I386_DIR16 | pe::IMAGE_REL_I386_REL16 => Some(2),
67                    pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
68                    _ => None,
69                },
70                RelocationFlags::Elf(typ) => match typ {
71                    elf::R_386_32
72                    | elf::R_386_PC32
73                    | elf::R_386_GOT32
74                    | elf::R_386_PLT32
75                    | elf::R_386_GOTOFF
76                    | elf::R_386_GOTPC => Some(4),
77                    elf::R_386_16 => Some(2),
78                    _ => None,
79                },
80            },
81            Architecture::X86_64 => match flags {
82                RelocationFlags::Coff(typ) => match typ {
83                    pe::IMAGE_REL_AMD64_ADDR32NB
84                    | pe::IMAGE_REL_AMD64_REL32
85                    | pe::IMAGE_REL_AMD64_REL32_1
86                    | pe::IMAGE_REL_AMD64_REL32_2
87                    | pe::IMAGE_REL_AMD64_REL32_3
88                    | pe::IMAGE_REL_AMD64_REL32_4
89                    | pe::IMAGE_REL_AMD64_REL32_5
90                    | pe::IMAGE_REL_AMD64_SECREL => Some(4),
91                    pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
92                    _ => None,
93                },
94                RelocationFlags::Elf(typ) => match typ {
95                    elf::R_X86_64_PC32 => Some(4),
96                    elf::R_X86_64_64 => Some(8),
97                    _ => None,
98                },
99            },
100        }
101    }
102}
103
104impl Arch for ArchX86 {
105    fn scan_instructions_internal(
106        &self,
107        address: u64,
108        code: &[u8],
109        _section_index: usize,
110        relocations: &[Relocation],
111        _diff_config: &DiffObjConfig,
112    ) -> Result<Vec<InstructionRef>> {
113        let mut out = Vec::with_capacity(code.len() / 2);
114        let mut decoder = self.decoder(code, address);
115        let mut instruction = Instruction::default();
116        let mut reloc_iter = relocations.iter().peekable();
117        'outer: while decoder.can_decode() {
118            let address = decoder.ip();
119            while let Some(reloc) = reloc_iter.peek() {
120                match reloc.address.cmp(&address) {
121                    Ordering::Less => {
122                        reloc_iter.next();
123                    }
124                    Ordering::Equal => {
125                        // If the instruction starts at a relocation, it's inline data
126                        let size = self.reloc_size(reloc.flags).with_context(|| {
127                            format!("Unsupported inline x86 relocation {:?}", reloc.flags)
128                        })?;
129                        if decoder.set_position(decoder.position() + size).is_ok() {
130                            decoder.set_ip(address + size as u64);
131                            out.push(InstructionRef {
132                                address,
133                                size: size as u8,
134                                opcode: OPCODE_DATA,
135                                branch_dest: None,
136                            });
137
138                            reloc_iter.next();
139
140                            // support .byte arrays after jump tables (they're typically known as indirect tables)
141
142                            let indirect_array_address = address.wrapping_add(size as u64);
143                            let indirect_array_pos = decoder.position();
144
145                            let max_size = code.len().saturating_sub(indirect_array_pos);
146
147                            let indirect_array_size = reloc_iter
148                                .peek()
149                                .map(|next_reloc| {
150                                    next_reloc.address.saturating_sub(indirect_array_address)
151                                        as usize
152                                })
153                                .unwrap_or(max_size)
154                                .min(max_size);
155
156                            if indirect_array_size > 0 {
157                                for i in 0..indirect_array_size {
158                                    out.push(InstructionRef {
159                                        address: indirect_array_address + i as u64,
160                                        size: 1,
161                                        opcode: OPCODE_DATA,
162                                        branch_dest: None,
163                                    });
164                                }
165                                // move decoder to after the array (there can be multiple jump+indirect tables in one function)
166                                let _ =
167                                    decoder.set_position(indirect_array_pos + indirect_array_size);
168                                decoder.set_ip(indirect_array_address + indirect_array_size as u64);
169                            }
170
171                            continue 'outer;
172                        }
173                    }
174                    Ordering::Greater => break,
175                }
176            }
177            decoder.decode_out(&mut instruction);
178            let branch_dest = match instruction.op0_kind() {
179                OpKind::NearBranch16 => Some(instruction.near_branch16() as u64),
180                OpKind::NearBranch32 => Some(instruction.near_branch32() as u64),
181                OpKind::NearBranch64 => Some(instruction.near_branch64()),
182                _ => None,
183            };
184            out.push(InstructionRef {
185                address,
186                size: instruction.len() as u8,
187                opcode: instruction.mnemonic() as u16,
188                branch_dest,
189            });
190        }
191        // Strip trailing int3 instructions (alignment padding) for x64
192        if matches!(self.arch, Architecture::X86_64) {
193            while out.last().is_some_and(|i| i.opcode == iced_x86::Mnemonic::Int3 as u16) {
194                out.pop();
195            }
196        }
197        Ok(out)
198    }
199
200    fn display_instruction(
201        &self,
202        resolved: ResolvedInstructionRef,
203        diff_config: &DiffObjConfig,
204        cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
205    ) -> Result<()> {
206        if resolved.ins_ref.opcode == OPCODE_DATA {
207            let (mnemonic, imm) = match resolved.ins_ref.size {
208                1 => (".byte", resolved.code[0] as u64),
209                2 => (".word", self.endianness.read_u16(resolved.code.try_into()?) as u64),
210                4 => (".dword", self.endianness.read_u32(resolved.code.try_into()?) as u64),
211                _ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
212            };
213            cb(InstructionPart::opcode(mnemonic, OPCODE_DATA))?;
214            if resolved.relocation.is_some() {
215                cb(InstructionPart::reloc())?;
216            } else {
217                cb(InstructionPart::unsigned(imm))?;
218            }
219            return Ok(());
220        }
221
222        let mut decoder = self.decoder(resolved.code, resolved.ins_ref.address);
223        let mut formatter = self.formatter(diff_config);
224        let mut instruction = Instruction::default();
225        decoder.decode_out(&mut instruction);
226
227        // Determine where to insert relocation in instruction output.
228        // We replace the immediate or displacement with a placeholder value since the formatter
229        // doesn't provide enough information to know which number is the displacement inside a
230        // memory operand.
231        let mut reloc_replace = None;
232        if let Some(reloc) = resolved.relocation {
233            const PLACEHOLDER: u64 = 0x7BDE3E7D; // chosen by fair dice roll. guaranteed to be random.
234            let reloc_offset = reloc.relocation.address - resolved.ins_ref.address;
235            let reloc_size = self.reloc_size(reloc.relocation.flags).unwrap_or(usize::MAX);
236            let offsets = decoder.get_constant_offsets(&instruction);
237            if reloc_offset == offsets.displacement_offset() as u64
238                && reloc_size == offsets.displacement_size()
239            {
240                instruction.set_memory_displacement64(PLACEHOLDER);
241                // Formatter always writes the displacement as Int32
242                reloc_replace = Some((OpKind::Memory, 4, PLACEHOLDER));
243            } else if reloc_offset == offsets.immediate_offset() as u64
244                && reloc_size == offsets.immediate_size()
245            {
246                let is_branch = matches!(
247                    instruction.op0_kind(),
248                    OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64
249                );
250                let op_kind = if is_branch {
251                    instruction.op0_kind()
252                } else {
253                    match reloc_size {
254                        2 => OpKind::Immediate16,
255                        4 => OpKind::Immediate32,
256                        8 => OpKind::Immediate64,
257                        _ => OpKind::default(),
258                    }
259                };
260                if is_branch {
261                    instruction.set_near_branch64(PLACEHOLDER);
262                } else {
263                    instruction.set_immediate32(PLACEHOLDER as u32);
264                }
265                reloc_replace = Some((op_kind, reloc_size, PLACEHOLDER));
266            }
267        }
268
269        let mut output =
270            InstructionFormatterOutput { cb, reloc_replace, error: None, skip_next: false };
271        formatter.format(&instruction, &mut output);
272        if let Some(error) = output.error.take() {
273            return Err(error);
274        }
275        Ok(())
276    }
277
278    fn relocation_override(
279        &self,
280        _file: &object::File<'_>,
281        section: &object::Section,
282        address: u64,
283        relocation: &object::Relocation,
284    ) -> Result<Option<RelocationOverride>> {
285        if !relocation.has_implicit_addend() {
286            return Ok(None);
287        }
288        let addend = match self.arch {
289            Architecture::X86 => match relocation.flags() {
290                object::RelocationFlags::Coff {
291                    typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32,
292                }
293                | object::RelocationFlags::Elf {
294                    r_type:
295                        elf::R_386_32
296                        | elf::R_386_PC32
297                        | elf::R_386_GOT32
298                        | elf::R_386_PLT32
299                        | elf::R_386_GOTOFF
300                        | elf::R_386_GOTPC,
301                } => {
302                    let data =
303                        section.data()?[address as usize..address as usize + 4].try_into()?;
304                    self.endianness.read_i32(data) as i64
305                }
306                flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
307            },
308            Architecture::X86_64 => match relocation.flags() {
309                object::RelocationFlags::Coff {
310                    typ:
311                        pe::IMAGE_REL_AMD64_ADDR32NB
312                        | pe::IMAGE_REL_AMD64_REL32
313                        | pe::IMAGE_REL_AMD64_REL32_1
314                        | pe::IMAGE_REL_AMD64_REL32_2
315                        | pe::IMAGE_REL_AMD64_REL32_3
316                        | pe::IMAGE_REL_AMD64_REL32_4
317                        | pe::IMAGE_REL_AMD64_REL32_5
318                        | pe::IMAGE_REL_AMD64_SECREL,
319                }
320                | object::RelocationFlags::Elf { r_type: elf::R_X86_64_32 | elf::R_X86_64_PC32 } => {
321                    let data =
322                        section.data()?[address as usize..address as usize + 4].try_into()?;
323                    self.endianness.read_i32(data) as i64
324                }
325                object::RelocationFlags::Coff { typ: pe::IMAGE_REL_AMD64_ADDR64 }
326                | object::RelocationFlags::Elf { r_type: elf::R_X86_64_64 } => {
327                    let data =
328                        section.data()?[address as usize..address as usize + 8].try_into()?;
329                    self.endianness.read_i64(data)
330                }
331                flags => bail!("Unsupported x86-64 implicit relocation {flags:?}"),
332            },
333        };
334        Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
335    }
336
337    fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
338        match self.arch {
339            Architecture::X86 => match flags {
340                RelocationFlags::Coff(typ) => match typ {
341                    pe::IMAGE_REL_I386_DIR32 => Some("IMAGE_REL_I386_DIR32"),
342                    pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
343                    _ => None,
344                },
345                RelocationFlags::Elf(typ) => match typ {
346                    elf::R_386_32 => Some("R_386_32"),
347                    elf::R_386_PC32 => Some("R_386_PC32"),
348                    elf::R_386_GOT32 => Some("R_386_GOT32"),
349                    elf::R_386_PLT32 => Some("R_386_PLT32"),
350                    elf::R_386_GOTOFF => Some("R_386_GOTOFF"),
351                    elf::R_386_GOTPC => Some("R_386_GOTPC"),
352                    elf::R_386_16 => Some("R_386_16"),
353                    _ => None,
354                },
355            },
356            Architecture::X86_64 => match flags {
357                RelocationFlags::Coff(typ) => match typ {
358                    pe::IMAGE_REL_AMD64_ADDR64 => Some("IMAGE_REL_AMD64_ADDR64"),
359                    pe::IMAGE_REL_AMD64_ADDR32NB => Some("IMAGE_REL_AMD64_ADDR32NB"),
360                    pe::IMAGE_REL_AMD64_REL32 => Some("IMAGE_REL_AMD64_REL32"),
361                    pe::IMAGE_REL_AMD64_REL32_1 => Some("IMAGE_REL_AMD64_REL32_1"),
362                    pe::IMAGE_REL_AMD64_REL32_2 => Some("IMAGE_REL_AMD64_REL32_2"),
363                    pe::IMAGE_REL_AMD64_REL32_3 => Some("IMAGE_REL_AMD64_REL32_3"),
364                    pe::IMAGE_REL_AMD64_REL32_4 => Some("IMAGE_REL_AMD64_REL32_4"),
365                    pe::IMAGE_REL_AMD64_REL32_5 => Some("IMAGE_REL_AMD64_REL32_5"),
366                    pe::IMAGE_REL_AMD64_SECREL => Some("IMAGE_REL_AMD64_SECREL"),
367                    _ => None,
368                },
369                _ => None,
370            },
371        }
372    }
373
374    fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
375        self.reloc_size(flags).unwrap_or(1)
376    }
377
378    fn infer_function_size(
379        &self,
380        symbol: &Symbol,
381        section: &Section,
382        next_address: u64,
383    ) -> Result<u64> {
384        let Ok(size) = (next_address - symbol.address).try_into() else {
385            return Ok(next_address.saturating_sub(symbol.address));
386        };
387        let Some(code) = section.data_range(symbol.address, size) else {
388            return Ok(0);
389        };
390        // Decode instructions to find the last non-NOP instruction
391        let mut decoder = self.decoder(code, symbol.address);
392        let mut instruction = Instruction::default();
393        let mut new_address = 0;
394        let mut reloc_iter = section.relocations.iter().peekable();
395        'outer: while decoder.can_decode() {
396            let address = decoder.ip();
397            while let Some(reloc) = reloc_iter.peek() {
398                match reloc.address.cmp(&address) {
399                    Ordering::Less => {
400                        reloc_iter.next();
401                    }
402                    Ordering::Equal => {
403                        // If the instruction starts at a relocation, it's inline data
404                        let reloc_size = self.reloc_size(reloc.flags).with_context(|| {
405                            format!("Unsupported inline x86 relocation {:?}", reloc.flags)
406                        })?;
407                        if decoder.set_position(decoder.position() + reloc_size).is_ok() {
408                            new_address = address + reloc_size as u64;
409                            decoder.set_ip(new_address);
410                            continue 'outer;
411                        }
412                    }
413                    Ordering::Greater => break,
414                }
415            }
416            decoder.decode_out(&mut instruction);
417            if instruction.mnemonic() != iced_x86::Mnemonic::Nop {
418                new_address = instruction.next_ip();
419            }
420        }
421        Ok(new_address.saturating_sub(symbol.address))
422    }
423}
424
425struct InstructionFormatterOutput<'a> {
426    cb: &'a mut dyn FnMut(InstructionPart<'_>) -> Result<()>,
427    reloc_replace: Option<(OpKind, usize, u64)>,
428    error: Option<anyhow::Error>,
429    skip_next: bool,
430}
431
432impl InstructionFormatterOutput<'_> {
433    fn push_signed(&mut self, mut value: i64) {
434        if self.error.is_some() {
435            return;
436        }
437        // The formatter writes the '-' operator and then gives us a negative value,
438        // so convert it to a positive value to avoid double negatives
439        if value < 0 {
440            value = value.wrapping_abs();
441        }
442        if let Err(e) = (self.cb)(InstructionPart::signed(value)) {
443            self.error = Some(e);
444        }
445    }
446}
447
448impl FormatterOutput for InstructionFormatterOutput<'_> {
449    fn write(&mut self, text: &str, kind: FormatterTextKind) {
450        if self.error.is_some() {
451            return;
452        }
453        // Skip whitespace after the mnemonic
454        if self.skip_next {
455            self.skip_next = false;
456            if kind == FormatterTextKind::Text && text == " " {
457                return;
458            }
459        }
460        match kind {
461            FormatterTextKind::Text | FormatterTextKind::Punctuation => {
462                if let Err(e) = (self.cb)(InstructionPart::basic(text)) {
463                    self.error = Some(e);
464                }
465            }
466            FormatterTextKind::Prefix
467            | FormatterTextKind::Keyword
468            | FormatterTextKind::Operator => {
469                if let Err(e) = (self.cb)(InstructionPart::opaque(text)) {
470                    self.error = Some(e);
471                }
472            }
473            _ => self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind)),
474        }
475    }
476
477    fn write_mnemonic(&mut self, instruction: &Instruction, text: &str) {
478        if self.error.is_some() {
479            return;
480        }
481        if let Err(e) = (self.cb)(InstructionPart::opcode(text, instruction.mnemonic() as u16)) {
482            self.error = Some(e);
483        }
484        // Skip whitespace after the mnemonic
485        self.skip_next = true;
486    }
487
488    fn write_number(
489        &mut self,
490        instruction: &Instruction,
491        _operand: u32,
492        instruction_operand: Option<u32>,
493        _text: &str,
494        value: u64,
495        number_kind: NumberKind,
496        kind: FormatterTextKind,
497    ) {
498        if self.error.is_some() {
499            return;
500        }
501
502        if let (Some(operand), Some((target_op_kind, reloc_size, target_value))) =
503            (instruction_operand, self.reloc_replace)
504        {
505            #[allow(clippy::match_like_matches_macro)]
506            if instruction.op_kind(operand) == target_op_kind
507                && match (number_kind, reloc_size) {
508                    (NumberKind::Int8 | NumberKind::UInt8, 1)
509                    | (NumberKind::Int16 | NumberKind::UInt16, 2)
510                    | (NumberKind::Int32 | NumberKind::UInt32, 4)
511                    | (NumberKind::Int64 | NumberKind::UInt64, 4) // x86_64
512                    | (NumberKind::Int64 | NumberKind::UInt64, 8) => true,
513                    _ => false,
514                }
515                && value == target_value
516            {
517                if let Err(e) = (self.cb)(InstructionPart::reloc()) {
518                    self.error = Some(e);
519                }
520                return;
521            }
522        }
523
524        if let FormatterTextKind::LabelAddress | FormatterTextKind::FunctionAddress = kind {
525            if let Err(e) = (self.cb)(InstructionPart::branch_dest(value)) {
526                self.error = Some(e);
527            }
528            return;
529        }
530
531        match number_kind {
532            NumberKind::Int8 => self.push_signed(value as i8 as i64),
533            NumberKind::Int16 => self.push_signed(value as i16 as i64),
534            NumberKind::Int32 => self.push_signed(value as i32 as i64),
535            NumberKind::Int64 => self.push_signed(value as i64),
536            NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
537                if let Err(e) = (self.cb)(InstructionPart::unsigned(value)) {
538                    self.error = Some(e);
539                }
540            }
541        }
542    }
543
544    fn write_decorator(
545        &mut self,
546        _instruction: &Instruction,
547        _operand: u32,
548        _instruction_operand: Option<u32>,
549        text: &str,
550        _decorator: DecoratorKind,
551    ) {
552        if self.error.is_some() {
553            return;
554        }
555        if let Err(e) = (self.cb)(InstructionPart::basic(text)) {
556            self.error = Some(e);
557        }
558    }
559
560    fn write_register(
561        &mut self,
562        _instruction: &Instruction,
563        _operand: u32,
564        _instruction_operand: Option<u32>,
565        text: &str,
566        _register: Register,
567    ) {
568        if self.error.is_some() {
569            return;
570        }
571        if let Err(e) = (self.cb)(InstructionPart::opaque(text)) {
572            self.error = Some(e);
573        }
574    }
575}
576
577#[cfg(test)]
578mod test {
579    use super::*;
580    use crate::obj::{Relocation, ResolvedRelocation};
581
582    #[test]
583    fn test_scan_instructions() {
584        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
585        let code = [
586            0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x04, 0x85, 0x00,
587            0x00, 0x00, 0x00,
588        ];
589        let scanned =
590            arch.scan_instructions_internal(0, &code, 0, &[], &DiffObjConfig::default()).unwrap();
591        assert_eq!(scanned.len(), 2);
592        assert_eq!(scanned[0].address, 0);
593        assert_eq!(scanned[0].size, 10);
594        assert_eq!(scanned[0].opcode, iced_x86::Mnemonic::Mov as u16);
595        assert_eq!(scanned[0].branch_dest, None);
596        assert_eq!(scanned[1].address, 10);
597        assert_eq!(scanned[1].size, 7);
598        assert_eq!(scanned[1].opcode, iced_x86::Mnemonic::Mov as u16);
599        assert_eq!(scanned[1].branch_dest, None);
600    }
601
602    #[test]
603    fn test_process_instruction() {
604        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
605        let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
606        let opcode = iced_x86::Mnemonic::Mov as u16;
607        let mut parts = Vec::new();
608        arch.display_instruction(
609            ResolvedInstructionRef {
610                ins_ref: InstructionRef { address: 0x1234, size: 10, opcode, branch_dest: None },
611                code: &code,
612                ..Default::default()
613            },
614            &DiffObjConfig::default(),
615            &mut |part| {
616                parts.push(part.into_static());
617                Ok(())
618            },
619        )
620        .unwrap();
621        assert_eq!(parts, &[
622            InstructionPart::opcode("mov", opcode),
623            InstructionPart::opaque("dword"),
624            InstructionPart::basic(" "),
625            InstructionPart::opaque("ptr"),
626            InstructionPart::basic(" "),
627            InstructionPart::basic("["),
628            InstructionPart::opaque("ebp"),
629            InstructionPart::opaque("-"),
630            InstructionPart::signed(152i64),
631            InstructionPart::basic("]"),
632            InstructionPart::basic(","),
633            InstructionPart::basic(" "),
634            InstructionPart::unsigned(0u64),
635        ]);
636    }
637
638    #[test]
639    fn test_process_instruction_with_reloc_1() {
640        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
641        let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
642        let opcode = iced_x86::Mnemonic::Mov as u16;
643        let mut parts = Vec::new();
644        arch.display_instruction(
645            ResolvedInstructionRef {
646                ins_ref: InstructionRef { address: 0x1234, size: 10, opcode, branch_dest: None },
647                code: &code,
648                relocation: Some(ResolvedRelocation {
649                    relocation: &Relocation {
650                        flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
651                        address: 0x1234 + 6,
652                        target_symbol: 0,
653                        addend: 0,
654                    },
655                    symbol: &Default::default(),
656                }),
657                ..Default::default()
658            },
659            &DiffObjConfig::default(),
660            &mut |part| {
661                parts.push(part.into_static());
662                Ok(())
663            },
664        )
665        .unwrap();
666        assert_eq!(parts, &[
667            InstructionPart::opcode("mov", opcode),
668            InstructionPart::opaque("dword"),
669            InstructionPart::basic(" "),
670            InstructionPart::opaque("ptr"),
671            InstructionPart::basic(" "),
672            InstructionPart::basic("["),
673            InstructionPart::opaque("ebp"),
674            InstructionPart::opaque("-"),
675            InstructionPart::signed(152i64),
676            InstructionPart::basic("]"),
677            InstructionPart::basic(","),
678            InstructionPart::basic(" "),
679            InstructionPart::reloc(),
680        ]);
681    }
682
683    #[test]
684    fn test_process_instruction_with_reloc_2() {
685        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
686        let code = [0x8b, 0x04, 0x85, 0x00, 0x00, 0x00, 0x00];
687        let opcode = iced_x86::Mnemonic::Mov as u16;
688        let mut parts = Vec::new();
689        arch.display_instruction(
690            ResolvedInstructionRef {
691                ins_ref: InstructionRef { address: 0x1234, size: 7, opcode, branch_dest: None },
692                code: &code,
693                relocation: Some(ResolvedRelocation {
694                    relocation: &Relocation {
695                        flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
696                        address: 0x1234 + 3,
697                        target_symbol: 0,
698                        addend: 0,
699                    },
700                    symbol: &Default::default(),
701                }),
702                ..Default::default()
703            },
704            &DiffObjConfig::default(),
705            &mut |part| {
706                parts.push(part.into_static());
707                Ok(())
708            },
709        )
710        .unwrap();
711        assert_eq!(parts, &[
712            InstructionPart::opcode("mov", opcode),
713            InstructionPart::opaque("eax"),
714            InstructionPart::basic(","),
715            InstructionPart::basic(" "),
716            InstructionPart::basic("["),
717            InstructionPart::opaque("eax"),
718            InstructionPart::opaque("*"),
719            InstructionPart::signed(4),
720            InstructionPart::opaque("+"),
721            InstructionPart::reloc(),
722            InstructionPart::basic("]"),
723        ]);
724    }
725
726    #[test]
727    fn test_process_instruction_with_reloc_3() {
728        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
729        let code = [0xe8, 0x00, 0x00, 0x00, 0x00];
730        let opcode = iced_x86::Mnemonic::Call as u16;
731        let mut parts = Vec::new();
732        arch.display_instruction(
733            ResolvedInstructionRef {
734                ins_ref: InstructionRef { address: 0x1234, size: 5, opcode, branch_dest: None },
735                code: &code,
736                relocation: Some(ResolvedRelocation {
737                    relocation: &Relocation {
738                        flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_REL32),
739                        address: 0x1234 + 1,
740                        target_symbol: 0,
741                        addend: 0,
742                    },
743                    symbol: &Default::default(),
744                }),
745                ..Default::default()
746            },
747            &DiffObjConfig::default(),
748            &mut |part| {
749                parts.push(part.into_static());
750                Ok(())
751            },
752        )
753        .unwrap();
754        assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
755    }
756
757    #[test]
758    fn test_process_instruction_with_reloc_4() {
759        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
760        let code = [0x8b, 0x15, 0xa4, 0x21, 0x7e, 0x00];
761        let opcode = iced_x86::Mnemonic::Mov as u16;
762        let mut parts = Vec::new();
763        arch.display_instruction(
764            ResolvedInstructionRef {
765                ins_ref: InstructionRef { address: 0x1234, size: 6, opcode, branch_dest: None },
766                code: &code,
767                relocation: Some(ResolvedRelocation {
768                    relocation: &Relocation {
769                        flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
770                        address: 0x1234 + 2,
771                        target_symbol: 0,
772                        addend: 0,
773                    },
774                    symbol: &Default::default(),
775                }),
776                ..Default::default()
777            },
778            &DiffObjConfig::default(),
779            &mut |part| {
780                parts.push(part.into_static());
781                Ok(())
782            },
783        )
784        .unwrap();
785        assert_eq!(parts, &[
786            InstructionPart::opcode("mov", opcode),
787            InstructionPart::opaque("edx"),
788            InstructionPart::basic(","),
789            InstructionPart::basic(" "),
790            InstructionPart::basic("["),
791            InstructionPart::reloc(),
792            InstructionPart::basic("]"),
793        ]);
794    }
795
796    #[test]
797    fn test_process_x86_64_instruction_with_reloc_1() {
798        let arch = ArchX86 { arch: Architecture::X86_64, endianness: object::Endianness::Little };
799        let code = [0x48, 0x8b, 0x05, 0x00, 0x00, 0x00, 0x00];
800        let opcode = iced_x86::Mnemonic::Mov as u16;
801        let mut parts = Vec::new();
802        arch.display_instruction(
803            ResolvedInstructionRef {
804                ins_ref: InstructionRef { address: 0x1234, size: 7, opcode, branch_dest: None },
805                code: &code,
806                relocation: Some(ResolvedRelocation {
807                    relocation: &Relocation {
808                        flags: RelocationFlags::Coff(pe::IMAGE_REL_AMD64_REL32),
809                        address: 0x1234 + 3,
810                        target_symbol: 0,
811                        addend: 0,
812                    },
813                    symbol: &Default::default(),
814                }),
815                ..Default::default()
816            },
817            &DiffObjConfig::default(),
818            &mut |part| {
819                parts.push(part.into_static());
820                Ok(())
821            },
822        )
823        .unwrap();
824        assert_eq!(parts, &[
825            InstructionPart::opcode("mov", opcode),
826            InstructionPart::opaque("rax"),
827            InstructionPart::basic(","),
828            InstructionPart::basic(" "),
829            InstructionPart::basic("["),
830            InstructionPart::reloc(),
831            InstructionPart::basic("]"),
832        ]);
833    }
834
835    #[test]
836    fn test_process_x86_64_instruction_with_reloc_2() {
837        let arch = ArchX86 { arch: Architecture::X86_64, endianness: object::Endianness::Little };
838        let code = [0xe8, 0x00, 0x00, 0x00, 0x00];
839        let opcode = iced_x86::Mnemonic::Call as u16;
840        let mut parts = Vec::new();
841        arch.display_instruction(
842            ResolvedInstructionRef {
843                ins_ref: InstructionRef { address: 0x1234, size: 5, opcode, branch_dest: None },
844                code: &code,
845                relocation: Some(ResolvedRelocation {
846                    relocation: &Relocation {
847                        flags: RelocationFlags::Coff(pe::IMAGE_REL_AMD64_REL32),
848                        address: 0x1234 + 1,
849                        target_symbol: 0,
850                        addend: 0,
851                    },
852                    symbol: &Default::default(),
853                }),
854                ..Default::default()
855            },
856            &DiffObjConfig::default(),
857            &mut |part| {
858                parts.push(part.into_static());
859                Ok(())
860            },
861        )
862        .unwrap();
863        assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
864    }
865
866    #[test]
867    fn test_display_1_byte_inline_data() {
868        let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
869        let code = [0xAB];
870        let mut parts = Vec::new();
871        arch.display_instruction(
872            ResolvedInstructionRef {
873                ins_ref: InstructionRef {
874                    address: 0x1234,
875                    size: 1,
876                    opcode: OPCODE_DATA,
877                    branch_dest: None,
878                },
879                code: &code,
880                ..Default::default()
881            },
882            &DiffObjConfig::default(),
883            &mut |part| {
884                parts.push(part.into_static());
885                Ok(())
886            },
887        )
888        .unwrap();
889        assert_eq!(parts, &[
890            InstructionPart::opcode(".byte", OPCODE_DATA),
891            InstructionPart::unsigned(0xABu64),
892        ]);
893    }
894}