Skip to main content

objdiff_core/arch/
mod.rs

1use alloc::{
2    borrow::Cow,
3    boxed::Box,
4    format,
5    string::{String, ToString},
6    vec::Vec,
7};
8use core::{
9    any::Any,
10    fmt::{self, Debug},
11};
12
13use anyhow::{Result, bail};
14use object::Endian as _;
15
16use crate::{
17    diff::{
18        ConfigEnum, DiffObjConfig, DiffSide, PreferredStringEncoding,
19        display::{ContextItem, HoverItem, InstructionPart},
20    },
21    obj::{
22        FlowAnalysisResult, InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation,
23        RelocationFlags, ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet,
24        SymbolKind,
25    },
26    util::ReallySigned,
27};
28
29#[cfg(feature = "arm")]
30pub mod arm;
31#[cfg(feature = "arm64")]
32pub mod arm64;
33#[cfg(feature = "mips")]
34pub mod mips;
35#[cfg(feature = "ppc")]
36pub mod ppc;
37#[cfg(feature = "superh")]
38pub mod superh;
39#[cfg(feature = "x86")]
40pub mod x86;
41
42pub const OPCODE_INVALID: u16 = u16::MAX;
43pub const OPCODE_DATA: u16 = u16::MAX - 1;
44
45const SUPPORTED_ENCODINGS_WITH_NULL_TERM: [(&encoding_rs::Encoding, &str); 5] = [
46    (encoding_rs::UTF_8, "UTF-8"),
47    (encoding_rs::SHIFT_JIS, "Shift JIS"),
48    (encoding_rs::WINDOWS_1252, "Windows-1252"),
49    (encoding_rs::EUC_JP, "EUC-JP"),
50    (encoding_rs::BIG5, "Big5"),
51];
52const SUPPORTED_ENCODINGS_NO_NULL_TERM: [(&encoding_rs::Encoding, &str); 2] =
53    [(encoding_rs::UTF_16BE, "UTF-16BE"), (encoding_rs::UTF_16LE, "UTF-16LE")];
54
55#[derive(Debug, Clone, Default, PartialEq)]
56pub struct LiteralInfo {
57    pub literal: String,
58    pub label_override: Option<String>,
59    pub copy_string: Option<String>,
60    pub hidden: bool, // Only used when the user hasn't set a preferred string encoding
61    pub is_string: bool,
62}
63
64impl LiteralInfo {
65    pub fn hidden(&self, diff_config: Option<&DiffObjConfig>) -> bool {
66        let Some(diff_config) = diff_config else {
67            return self.hidden;
68        };
69        if !self.is_string {
70            return self.hidden;
71        }
72        if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
73            return self.hidden;
74        }
75        let Some(ref label) = self.label_override else {
76            return self.hidden;
77        };
78        *label != diff_config.preferred_string_encoding.name()
79    }
80}
81
82/// Represents the type of data associated with an instruction
83#[derive(PartialEq)]
84pub enum DataType {
85    Int8,
86    Int16,
87    Int32,
88    Int64,
89    Float,
90    Double,
91    Bytes,
92    String,
93}
94
95impl fmt::Display for DataType {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str(match self {
98            DataType::Int8 => "Int8",
99            DataType::Int16 => "Int16",
100            DataType::Int32 => "Int32",
101            DataType::Int64 => "Int64",
102            DataType::Float => "Float",
103            DataType::Double => "Double",
104            DataType::Bytes => "Bytes",
105            DataType::String => "String",
106        })
107    }
108}
109
110impl DataType {
111    pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
112        let mut strs = Vec::new();
113        for lit_info in self.display_literals(endian, bytes) {
114            let label = lit_info.label_override.unwrap_or_else(|| self.to_string());
115            strs.push(format!("{}: {:?}", label, lit_info.literal))
116        }
117        strs
118    }
119
120    pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<LiteralInfo> {
121        let mut strs = Vec::new();
122        if self.required_len().is_some_and(|l| bytes.len() < l) {
123            log::warn!(
124                "Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."
125            );
126            return strs;
127        }
128        let mut bytes = bytes;
129        if self.required_len().is_some_and(|l| bytes.len() > l) {
130            // If the symbol's size is larger a single instance of this data type, we take just the
131            // bytes necessary for one of them in order to display the first element of the array.
132            bytes = &bytes[0..self.required_len().unwrap()];
133            // TODO: Attempt to interpret large symbols as arrays of a smaller type and show all
134            // elements of the array instead. https://github.com/encounter/objdiff/issues/124
135            // However, note that the stride of an array can not always be determined just by the
136            // data type guessed by the single instruction accessing it. There can also be arrays of
137            // structs that contain multiple elements of different types, so if other elements after
138            // the first one were to be displayed in this manner, they may be inaccurate.
139        }
140
141        match self {
142            DataType::Int8 => {
143                let i = i8::from_ne_bytes(bytes.try_into().unwrap());
144                strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
145
146                if i < 0 {
147                    strs.push(LiteralInfo {
148                        literal: format!("{:#x}", ReallySigned(i)),
149                        ..Default::default()
150                    });
151                }
152            }
153            DataType::Int16 => {
154                let i = endian.read_i16(bytes.try_into().unwrap());
155                strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
156
157                if i < 0 {
158                    strs.push(LiteralInfo {
159                        literal: format!("{:#x}", ReallySigned(i)),
160                        ..Default::default()
161                    });
162                }
163            }
164            DataType::Int32 => {
165                let i = endian.read_i32(bytes.try_into().unwrap());
166                strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
167
168                if i < 0 {
169                    strs.push(LiteralInfo {
170                        literal: format!("{:#x}", ReallySigned(i)),
171                        ..Default::default()
172                    });
173                }
174            }
175            DataType::Int64 => {
176                let i = endian.read_i64(bytes.try_into().unwrap());
177                strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
178
179                if i < 0 {
180                    strs.push(LiteralInfo {
181                        literal: format!("{:#x}", ReallySigned(i)),
182                        ..Default::default()
183                    });
184                }
185            }
186            DataType::Float => {
187                let bytes: [u8; 4] = bytes.try_into().unwrap();
188                strs.push(LiteralInfo {
189                    literal: format!("{:?}f", match endian {
190                        object::Endianness::Little => f32::from_le_bytes(bytes),
191                        object::Endianness::Big => f32::from_be_bytes(bytes),
192                    }),
193                    ..Default::default()
194                });
195            }
196            DataType::Double => {
197                let bytes: [u8; 8] = bytes.try_into().unwrap();
198                strs.push(LiteralInfo {
199                    literal: format!("{:?}", match endian {
200                        object::Endianness::Little => f64::from_le_bytes(bytes),
201                        object::Endianness::Big => f64::from_be_bytes(bytes),
202                    }),
203                    ..Default::default()
204                });
205            }
206            DataType::Bytes => {
207                strs.push(LiteralInfo { literal: format!("{bytes:#?}"), ..Default::default() });
208            }
209            DataType::String => {
210                if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
211                    let str_bytes = &bytes[..nul_idx];
212                    // Special case to display (ASCII) as the label for ASCII-only strings.
213                    let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
214                    if !had_errors && cow.is_ascii() {
215                        let string = format!("{cow}");
216                        let copy_string = escape_special_ascii_characters(&string);
217                        strs.push(LiteralInfo {
218                            literal: string,
219                            label_override: Some("ASCII".into()),
220                            copy_string: Some(copy_string),
221                            hidden: false,
222                            is_string: true,
223                        });
224                    }
225                    for (encoding, encoding_name) in SUPPORTED_ENCODINGS_WITH_NULL_TERM {
226                        let (cow, _, had_errors) = encoding.decode(str_bytes);
227                        if !had_errors {
228                            let string = format!("{cow}");
229                            let copy_string = escape_special_ascii_characters(&string);
230                            // Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
231                            let hidden = encoding.is_ascii_compatible() && cow.is_ascii();
232                            strs.push(LiteralInfo {
233                                literal: string,
234                                label_override: Some(encoding_name.into()),
235                                copy_string: Some(copy_string),
236                                hidden,
237                                is_string: true,
238                            });
239                        }
240                    }
241                }
242
243                for (encoding, encoding_name) in SUPPORTED_ENCODINGS_NO_NULL_TERM {
244                    let (cow, _, had_errors) = encoding.decode(bytes);
245                    if had_errors {
246                        continue;
247                    }
248                    let trimmed = cow.trim_end_matches('\0');
249                    if !trimmed.is_empty() {
250                        let copy_string = escape_special_ascii_characters(trimmed);
251                        strs.push(LiteralInfo {
252                            literal: trimmed.to_string(),
253                            label_override: Some(encoding_name.into()),
254                            copy_string: Some(copy_string),
255                            hidden: false,
256                            is_string: true,
257                        });
258                    }
259                }
260            }
261        }
262
263        strs
264    }
265
266    fn required_len(&self) -> Option<usize> {
267        match self {
268            DataType::Int8 => Some(1),
269            DataType::Int16 => Some(2),
270            DataType::Int32 => Some(4),
271            DataType::Int64 => Some(8),
272            DataType::Float => Some(4),
273            DataType::Double => Some(8),
274            DataType::Bytes => None,
275            DataType::String => None,
276        }
277    }
278}
279
280impl dyn Arch {
281    /// Generate a list of instructions references (offset, size, opcode) from the given code.
282    ///
283    /// See [`scan_instructions_internal`] for more details.
284    pub fn scan_instructions(
285        &self,
286        resolved: ResolvedSymbol,
287        diff_config: &DiffObjConfig,
288    ) -> Result<Vec<InstructionRef>> {
289        let mut result = self.scan_instructions_internal(
290            resolved.symbol.address,
291            resolved.data,
292            resolved.section_index,
293            &resolved.section.relocations,
294            diff_config,
295        )?;
296
297        let function_start = resolved.symbol.address;
298        let function_end = function_start + resolved.symbol.size;
299
300        // Remove any branch destinations that are outside the function range
301        for ins in result.iter_mut() {
302            if let Some(branch_dest) = ins.branch_dest
303                && (branch_dest < function_start || branch_dest >= function_end)
304            {
305                ins.branch_dest = None;
306            }
307        }
308
309        // Resolve relocation targets within the same function to branch destinations
310        let mut ins_iter = result.iter_mut().peekable();
311        'outer: for reloc in resolved
312            .section
313            .relocations
314            .iter()
315            .skip_while(|r| r.address < function_start)
316            .take_while(|r| r.address < function_end)
317        {
318            let ins = loop {
319                let Some(ins) = ins_iter.peek_mut() else {
320                    break 'outer;
321                };
322                if reloc.address < ins.address {
323                    continue 'outer;
324                }
325                let ins = ins_iter.next().unwrap();
326                if reloc.address >= ins.address && reloc.address < ins.address + ins.size as u64 {
327                    break ins;
328                }
329            };
330            // Clear existing branch destination for instructions with relocations
331            ins.branch_dest = None;
332            let Some(target) = resolved.obj.symbols.get(reloc.target_symbol) else {
333                continue;
334            };
335            if target.section != Some(resolved.section_index) {
336                continue;
337            }
338            let Some(target_address) = target.address.checked_add_signed(reloc.addend) else {
339                continue;
340            };
341            // If the target address is within the function range, set it as a branch destination
342            if target_address >= function_start && target_address < function_end {
343                ins.branch_dest = Some(target_address);
344            }
345        }
346
347        Ok(result)
348    }
349
350    /// Parse an instruction to gather its mnemonic and arguments for more detailed comparison.
351    ///
352    /// This is called only when we need to compare the arguments of an instruction.
353    pub fn process_instruction(
354        &self,
355        resolved: ResolvedInstructionRef,
356        diff_config: &DiffObjConfig,
357    ) -> Result<ParsedInstruction> {
358        let mut mnemonic = None;
359        let mut args = Vec::with_capacity(8);
360        let mut relocation_emitted = false;
361        self.display_instruction(resolved, diff_config, &mut |part| {
362            match part {
363                InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
364                InstructionPart::Arg(arg) => {
365                    if arg == InstructionArg::Reloc {
366                        relocation_emitted = true;
367                        // If the relocation was resolved to a branch destination, emit that instead.
368                        if let Some(dest) = resolved.ins_ref.branch_dest {
369                            args.push(InstructionArg::BranchDest(dest));
370                            return Ok(());
371                        }
372                    }
373                    args.push(arg.into_static());
374                }
375                _ => {}
376            }
377            Ok(())
378        })?;
379        // If the instruction has a relocation, but we didn't format it in the display, add it to
380        // the end of the arguments list.
381        if resolved.relocation.is_some() && !relocation_emitted {
382            args.push(InstructionArg::Reloc);
383        }
384        Ok(ParsedInstruction {
385            ins_ref: resolved.ins_ref,
386            mnemonic: mnemonic.unwrap_or_default(),
387            args,
388        })
389    }
390}
391
392pub trait Arch: Any + Debug + Send + Sync {
393    /// Finishes arch-specific initialization that must be done after sections have been combined.
394    fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], _symbol_indices: &[usize]) {
395    }
396
397    /// Generate a list of instructions references (offset, size, opcode) from the given code.
398    ///
399    /// The opcode IDs are used to generate the initial diff. Implementations should do as little
400    /// parsing as possible here: just enough to identify the base instruction opcode, size, and
401    /// possible branch destination (for visual representation). As needed, instructions are parsed
402    /// via `process_instruction` to compare their arguments.
403    fn scan_instructions_internal(
404        &self,
405        address: u64,
406        code: &[u8],
407        section_index: usize,
408        relocations: &[Relocation],
409        diff_config: &DiffObjConfig,
410    ) -> Result<Vec<InstructionRef>>;
411
412    /// Format an instruction for display.
413    ///
414    /// Implementations should call the callback for each part of the instruction: usually the
415    /// mnemonic and arguments, plus any separators and visual formatting.
416    fn display_instruction(
417        &self,
418        resolved: ResolvedInstructionRef,
419        diff_config: &DiffObjConfig,
420        cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
421    ) -> Result<()>;
422
423    /// Generate a list of fake relocations from the given code that represent pooled data accesses.
424    fn generate_pooled_relocations(
425        &self,
426        _address: u64,
427        _code: &[u8],
428        _relocations: &[Relocation],
429        _symbols: &[Symbol],
430    ) -> Vec<Relocation> {
431        Vec::new()
432    }
433
434    // Perform detailed data flow analysis
435    fn data_flow_analysis(
436        &self,
437        _obj: &Object,
438        _symbol: &Symbol,
439        _code: &[u8],
440        _relocations: &[Relocation],
441    ) -> Option<Box<dyn FlowAnalysisResult>> {
442        None
443    }
444
445    fn relocation_override(
446        &self,
447        _file: &object::File<'_>,
448        _section: &object::Section,
449        _address: u64,
450        _relocation: &object::Relocation,
451    ) -> Result<Option<RelocationOverride>> {
452        Ok(None)
453    }
454
455    fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
456
457    fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
458
459    fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address }
460
461    fn extra_symbol_flags(&self, _symbol: &object::Symbol) -> SymbolFlagSet {
462        SymbolFlagSet::default()
463    }
464
465    fn guess_data_type(
466        &self,
467        _resolved: ResolvedInstructionRef,
468        _bytes: &[u8],
469    ) -> Option<DataType> {
470        None
471    }
472
473    fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
474
475    fn symbol_context(&self, _obj: &Object, _symbol_index: usize) -> Vec<ContextItem> { Vec::new() }
476
477    fn instruction_hover(
478        &self,
479        _obj: &Object,
480        _resolved: ResolvedInstructionRef,
481    ) -> Vec<HoverItem> {
482        Vec::new()
483    }
484
485    fn instruction_context(
486        &self,
487        _obj: &Object,
488        _resolved: ResolvedInstructionRef,
489    ) -> Vec<ContextItem> {
490        Vec::new()
491    }
492
493    fn infer_function_size(
494        &self,
495        symbol: &Symbol,
496        _section: &Section,
497        next_address: u64,
498    ) -> Result<u64> {
499        Ok(next_address.saturating_sub(symbol.address))
500    }
501}
502
503pub fn new_arch(object: &object::File, diff_side: DiffSide) -> Result<Box<dyn Arch>> {
504    use object::Object as _;
505    // Avoid unused warnings on non-mips builds
506    let _ = diff_side;
507
508    Ok(match object.architecture() {
509        #[cfg(feature = "ppc")]
510        object::Architecture::PowerPc | object::Architecture::PowerPc64 => {
511            Box::new(ppc::ArchPpc::new(object)?)
512        }
513        #[cfg(feature = "mips")]
514        object::Architecture::Mips => Box::new(mips::ArchMips::new(object, diff_side)?),
515        #[cfg(feature = "x86")]
516        object::Architecture::I386 | object::Architecture::X86_64 => {
517            Box::new(x86::ArchX86::new(object)?)
518        }
519        #[cfg(feature = "arm")]
520        object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
521        #[cfg(feature = "arm64")]
522        object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
523        #[cfg(feature = "superh")]
524        object::Architecture::SuperH => Box::new(superh::ArchSuperH::new(object)?),
525        arch => bail!("Unsupported architecture: {arch:?}"),
526    })
527}
528
529#[derive(Debug, Default)]
530pub struct ArchDummy {}
531
532impl ArchDummy {
533    pub fn new() -> Box<Self> { Box::new(Self {}) }
534}
535
536impl Arch for ArchDummy {
537    fn scan_instructions_internal(
538        &self,
539        _address: u64,
540        _code: &[u8],
541        _section_index: usize,
542        _relocations: &[Relocation],
543        _diff_config: &DiffObjConfig,
544    ) -> Result<Vec<InstructionRef>> {
545        Ok(Vec::new())
546    }
547
548    fn display_instruction(
549        &self,
550        _resolved: ResolvedInstructionRef,
551        _diff_config: &DiffObjConfig,
552        _cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
553    ) -> Result<()> {
554        Ok(())
555    }
556
557    fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
561pub enum RelocationOverrideTarget {
562    Keep,
563    Skip,
564    Symbol(object::SymbolIndex),
565    Section(object::SectionIndex),
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569pub struct RelocationOverride {
570    pub target: RelocationOverrideTarget,
571    pub addend: i64,
572}
573
574/// Escape ASCII characters such as \n or \t, but not Unicode characters such as \u{3000}.
575/// Suitable for copying to clipboard.
576fn escape_special_ascii_characters(value: &str) -> String {
577    let mut escaped = String::new();
578    escaped.push('"');
579    for c in value.chars() {
580        if c.is_ascii() {
581            for e in c.escape_default() {
582                escaped.push(e);
583            }
584        } else {
585            escaped.push(c);
586        }
587    }
588    escaped.push('"');
589    escaped
590}