Skip to main content

objdiff_core/arch/
arm.rs

1use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec};
2use core::fmt::Write;
3
4use anyhow::{Result, bail};
5use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
6use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
7use unarm::FormatValue as _;
8
9use crate::{
10    arch::{Arch, OPCODE_DATA, OPCODE_INVALID, RelocationOverride, RelocationOverrideTarget},
11    diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
12    obj::{
13        InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, SectionKind,
14        Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
15    },
16};
17
18#[derive(Debug)]
19pub struct ArchArm {
20    /// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
21    disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
22    detected_version: Option<unarm::Version>,
23    endianness: object::Endianness,
24}
25
26impl ArchArm {
27    pub fn new(file: &object::File) -> Result<Self> {
28        let endianness = file.endianness();
29        match file {
30            object::File::Elf32(_) => {
31                // The disasm_modes mapping is populated later in the post_init step so that we have access to merged sections.
32                let disasm_modes = BTreeMap::new();
33                let detected_version = Self::elf_detect_arm_version(file)?;
34                Ok(Self { disasm_modes, detected_version, endianness })
35            }
36            _ => bail!("Unsupported file format {:?}", file.format()),
37        }
38    }
39
40    fn elf_detect_arm_version(file: &object::File) -> Result<Option<unarm::Version>> {
41        // Check ARM attributes
42        if let Some(arm_attrs) = file.sections().find(|s| {
43            s.kind() == object::SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES)
44                && s.name() == Ok(".ARM.attributes")
45        }) {
46            let attr_data = arm_attrs.uncompressed_data()?;
47            let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
48                object::Endianness::Little => arm_attr::Endian::Little,
49                object::Endianness::Big => arm_attr::Endian::Big,
50            })?;
51            for subsection in build_attrs.subsections() {
52                let subsection = subsection?;
53                if !subsection.is_aeabi() {
54                    continue;
55                }
56                // Only checking first CpuArch tag. Others may exist, but that's very unlikely.
57                let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
58                    if let Tag::CpuArch(cpu_arch) = tag { Some(cpu_arch) } else { None }
59                });
60                match cpu_arch {
61                    Some(CpuArch::V4) => return Ok(Some(unarm::Version::V4)),
62                    Some(CpuArch::V4T) => return Ok(Some(unarm::Version::V4T)),
63                    Some(CpuArch::V5TE) => return Ok(Some(unarm::Version::V5Te)),
64                    Some(CpuArch::V5TEJ) => return Ok(Some(unarm::Version::V5Tej)),
65                    Some(CpuArch::V6) => return Ok(Some(unarm::Version::V6)),
66                    Some(CpuArch::V6K) => return Ok(Some(unarm::Version::V6K)),
67                    Some(arch) => bail!("ARM arch {} not supported", arch),
68                    None => {}
69                };
70            }
71        }
72
73        Ok(None)
74    }
75
76    fn get_mapping_symbols(
77        sections: &[Section],
78        symbols: &[Symbol],
79    ) -> BTreeMap<usize, Vec<DisasmMode>> {
80        sections
81            .iter()
82            .enumerate()
83            .filter(|(_, section)| section.kind == SectionKind::Code)
84            .map(|(index, _)| {
85                let mut mapping_symbols: Vec<_> = symbols
86                    .iter()
87                    .filter(|s| s.section.map(|i| i == index).unwrap_or(false))
88                    .filter_map(DisasmMode::from_symbol)
89                    .collect();
90                mapping_symbols.sort_unstable_by_key(|x| x.address);
91                (index, mapping_symbols)
92            })
93            .collect()
94    }
95
96    fn unarm_options(&self, diff_config: &DiffObjConfig) -> unarm::Options {
97        let mut extensions = unarm::Extensions::none();
98        if diff_config.arm_vfp_v2 {
99            extensions = extensions.with(unarm::Extension::VfpV2);
100        }
101        unarm::Options {
102            version: match diff_config.arm_arch_version {
103                ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::Version::V5Te),
104                ArmArchVersion::V4 => unarm::Version::V4,
105                ArmArchVersion::V4t => unarm::Version::V4T,
106                ArmArchVersion::V5t => unarm::Version::V5T,
107                ArmArchVersion::V5te => unarm::Version::V5Te,
108                ArmArchVersion::V5tej => unarm::Version::V5Tej,
109                ArmArchVersion::V6 => unarm::Version::V6,
110                ArmArchVersion::V6k => unarm::Version::V6K,
111            },
112            extensions,
113            av: diff_config.arm_av_registers,
114            r9_use: match diff_config.arm_r9_usage {
115                ArmR9Usage::GeneralPurpose => unarm::R9Use::R9,
116                ArmR9Usage::Sb => unarm::R9Use::Sb,
117                ArmR9Usage::Tr => unarm::R9Use::Tr,
118            },
119            sl: diff_config.arm_sl_usage,
120            fp: diff_config.arm_fp_usage,
121            ip: diff_config.arm_ip_usage,
122            ual: diff_config.arm_unified_syntax,
123        }
124    }
125
126    fn parse_ins_ref(
127        &self,
128        ins_ref: InstructionRef,
129        code: &[u8],
130        diff_config: &DiffObjConfig,
131    ) -> Result<unarm::Ins> {
132        let code = match (self.endianness, ins_ref.size) {
133            (object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
134            (object::Endianness::Little, 4) => {
135                u32::from_le_bytes([code[0], code[1], code[2], code[3]])
136            }
137            (object::Endianness::Big, 2) => u16::from_be_bytes([code[0], code[1]]) as u32,
138            (object::Endianness::Big, 4) => {
139                u32::from_be_bytes([code[0], code[1], code[2], code[3]])
140            }
141            _ => bail!("Invalid instruction size {}", ins_ref.size),
142        };
143
144        let thumb = ins_ref.opcode & (1 << 15) == 0;
145        let discriminant = ins_ref.opcode & !(1 << 15);
146        let pc = ins_ref.address as u32;
147        let options = self.unarm_options(diff_config);
148
149        let ins = if ins_ref.opcode == OPCODE_DATA {
150            match ins_ref.size {
151                4 => unarm::Ins::Word(code),
152                2 => unarm::Ins::HalfWord(code as u16),
153                _ => bail!("Invalid data size {}", ins_ref.size),
154            }
155        } else if thumb {
156            unarm::parse_thumb_with_discriminant(code, discriminant, pc, &options)
157        } else {
158            unarm::parse_arm_with_discriminant(code, discriminant, pc, &options)
159        };
160        Ok(ins)
161    }
162}
163
164impl Arch for ArchArm {
165    fn post_init(&mut self, sections: &[Section], symbols: &[Symbol], _symbol_indices: &[usize]) {
166        self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
167    }
168
169    fn scan_instructions_internal(
170        &self,
171        address: u64,
172        code: &[u8],
173        section_index: usize,
174        _relocations: &[Relocation],
175        diff_config: &DiffObjConfig,
176    ) -> Result<Vec<InstructionRef>> {
177        let start_addr = address as u32;
178        let end_addr = start_addr + code.len() as u32;
179
180        // Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
181        let fallback_mappings =
182            [DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
183        let mapping_symbols = self
184            .disasm_modes
185            .get(&section_index)
186            .map(|x| if x.is_empty() { &fallback_mappings } else { x.as_slice() })
187            .unwrap_or(&fallback_mappings);
188        let first_mapping_idx = mapping_symbols
189            .binary_search_by_key(&start_addr, |x| x.address)
190            .unwrap_or_else(|idx| idx.saturating_sub(1));
191        let mut mode = mapping_symbols[first_mapping_idx].mapping;
192
193        let mut mappings_iter = mapping_symbols
194            .iter()
195            .copied()
196            .skip(first_mapping_idx + 1)
197            .take_while(|x| x.address < end_addr);
198        let mut next_mapping = mappings_iter.next();
199
200        let min_ins_size = if mode == unarm::ParseMode::Thumb { 2 } else { 4 };
201        let ins_count = code.len() / min_ins_size;
202        let mut ops = Vec::<InstructionRef>::with_capacity(ins_count);
203
204        let options = self.unarm_options(diff_config);
205
206        let mut address = start_addr;
207        while address < end_addr {
208            while let Some(next) = next_mapping.filter(|x| address >= x.address) {
209                // Change mapping
210                mode = next.mapping;
211                next_mapping = mappings_iter.next();
212            }
213
214            let data = &code[(address - start_addr) as usize..];
215            if data.len() < min_ins_size {
216                // Push the remainder as data
217                ops.push(InstructionRef {
218                    address: address as u64,
219                    size: data.len() as u8,
220                    opcode: OPCODE_DATA,
221                    branch_dest: None,
222                });
223                break;
224            }
225
226            // Check how many bytes we can/should read
227            let bytes_until_next_mapping =
228                next_mapping.map(|m| (m.address - address) as usize).unwrap_or(usize::MAX);
229            let num_code_bytes = if data.len() >= 4 && bytes_until_next_mapping >= 4 {
230                if mode == unarm::ParseMode::Data && address & 3 != 0 {
231                    // 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword
232                    2
233                } else {
234                    // Read 4 bytes even for Thumb, as the parser will determine if it's a 2 or 4 byte instruction
235                    4
236                }
237            } else if mode != unarm::ParseMode::Arm {
238                2
239            } else {
240                // Invalid instruction size
241                ops.push(InstructionRef {
242                    address: address as u64,
243                    size: min_ins_size as u8,
244                    opcode: OPCODE_INVALID,
245                    branch_dest: None,
246                });
247                address += min_ins_size as u32;
248                continue;
249            };
250
251            let code = match num_code_bytes {
252                4 => match self.endianness {
253                    object::Endianness::Little => {
254                        u32::from_le_bytes([data[0], data[1], data[2], data[3]])
255                    }
256                    object::Endianness::Big => {
257                        if mode != unarm::ParseMode::Thumb {
258                            u32::from_be_bytes([data[0], data[1], data[2], data[3]])
259                        } else {
260                            // For 4-byte Thumb instructions, read two 16-bit halfwords in big endian
261                            u32::from_be_bytes([data[2], data[3], data[0], data[1]])
262                        }
263                    }
264                },
265                2 => match self.endianness {
266                    object::Endianness::Little => u16::from_le_bytes([data[0], data[1]]) as u32,
267                    object::Endianness::Big => u16::from_be_bytes([data[0], data[1]]) as u32,
268                },
269                _ => unreachable!(),
270            };
271
272            let (opcode, ins, ins_size) = match mode {
273                unarm::ParseMode::Arm => {
274                    let ins = unarm::parse_arm(code, address, &options);
275                    let opcode = ins.discriminant() | (1 << 15);
276                    (opcode, ins, 4)
277                }
278                unarm::ParseMode::Thumb => {
279                    let (ins, size) = unarm::parse_thumb(code, address, &options);
280                    let opcode = ins.discriminant();
281                    (opcode, ins, size)
282                }
283                unarm::ParseMode::Data => (
284                    OPCODE_DATA,
285                    if num_code_bytes == 4 {
286                        unarm::Ins::Word(code)
287                    } else {
288                        unarm::Ins::HalfWord(code as u16)
289                    },
290                    num_code_bytes,
291                ),
292            };
293
294            let branch_dest = match ins {
295                unarm::Ins::B { target, .. }
296                | unarm::Ins::Bl { target, .. }
297                | unarm::Ins::Blx { target: unarm::BlxTarget::Direct(target), .. } => {
298                    Some(target.addr)
299                }
300                _ => None,
301            };
302
303            ops.push(InstructionRef {
304                address: address as u64,
305                size: ins_size as u8,
306                opcode,
307                branch_dest: branch_dest.map(|x| x as u64),
308            });
309            address += ins_size;
310        }
311
312        Ok(ops)
313    }
314
315    fn display_instruction(
316        &self,
317        resolved: ResolvedInstructionRef,
318        diff_config: &DiffObjConfig,
319        cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
320    ) -> Result<()> {
321        let ins = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
322
323        let options = self.unarm_options(diff_config);
324        let mut string_fmt = unarm::StringFormatter::new(&options);
325        ins.write_opcode(&mut string_fmt)?;
326        let opcode = string_fmt.into_string();
327        cb(InstructionPart::opcode(opcode, resolved.ins_ref.opcode))?;
328
329        let mut args_formatter =
330            ArgsFormatter { options: &options, cb, resolved: &resolved, skip_leading_space: true };
331        ins.write_params(&mut args_formatter)?;
332        Ok(())
333    }
334
335    fn relocation_override(
336        &self,
337        _file: &object::File<'_>,
338        section: &object::Section,
339        address: u64,
340        relocation: &object::Relocation,
341    ) -> Result<Option<RelocationOverride>> {
342        match relocation.flags() {
343            // Handle ELF implicit relocations
344            object::RelocationFlags::Elf { r_type } => {
345                if relocation.has_implicit_addend() {
346                    // R_ARM_V4BX marks a `bx` instruction so the linker can optionally
347                    // rewrite it for ARMv4 interworking. Drops the addend since it's meaningless.
348                    if r_type == elf::R_ARM_V4BX {
349                        return Ok(Some(RelocationOverride {
350                            target: RelocationOverrideTarget::Skip,
351                            addend: 0,
352                        }));
353                    }
354
355                    let section_data = section.data()?;
356                    let address = address as usize;
357                    let addend = match r_type {
358                        // ARM calls
359                        elf::R_ARM_PC24
360                        | elf::R_ARM_XPC25
361                        | elf::R_ARM_CALL
362                        | elf::R_ARM_JUMP24 => {
363                            let data = section_data[address..address + 4].try_into()?;
364                            let addend = self.endianness.read_i32(data);
365                            let imm24 = addend & 0xffffff;
366                            (imm24 << 2) << 8 >> 8
367                        }
368
369                        // Thumb calls
370                        elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
371                            let data = section_data[address..address + 2].try_into()?;
372                            let high = self.endianness.read_i16(data) as i32;
373                            let data = section_data[address + 2..address + 4].try_into()?;
374                            let low = self.endianness.read_i16(data) as i32;
375
376                            let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
377                            (imm22 << 1) << 9 >> 9
378                        }
379
380                        // Thumb unconditional branch (B, 11-bit offset)
381                        elf::R_ARM_THM_PC11 => {
382                            let data = section_data[address..address + 2].try_into()?;
383                            let insn = self.endianness.read_u16(data) as i32;
384                            let imm11 = insn & 0x7ff;
385                            (imm11 << 1) << 20 >> 20
386                        }
387
388                        // Thumb conditional branch (B<cond>, 8-bit offset)
389                        elf::R_ARM_THM_PC9 => {
390                            let data = section_data[address..address + 2].try_into()?;
391                            let insn = self.endianness.read_u16(data) as i32;
392                            let imm8 = insn & 0xff;
393                            (imm8 << 1) << 23 >> 23
394                        }
395
396                        // Data
397                        elf::R_ARM_ABS32 => {
398                            let data = section_data[address..address + 4].try_into()?;
399                            self.endianness.read_i32(data)
400                        }
401
402                        flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
403                    };
404                    Ok(Some(RelocationOverride {
405                        target: RelocationOverrideTarget::Keep,
406                        addend: addend as i64,
407                    }))
408                } else {
409                    Ok(None)
410                }
411            }
412            _ => Ok(None),
413        }
414    }
415
416    fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
417        match flags {
418            RelocationFlags::Elf(r_type) => match r_type {
419                elf::R_ARM_NONE => Some("R_ARM_NONE"),
420                elf::R_ARM_ABS32 => Some("R_ARM_ABS32"),
421                elf::R_ARM_REL32 => Some("R_ARM_REL32"),
422                elf::R_ARM_ABS16 => Some("R_ARM_ABS16"),
423                elf::R_ARM_ABS8 => Some("R_ARM_ABS8"),
424                elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"),
425                elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"),
426                elf::R_ARM_PC24 => Some("R_ARM_PC24"),
427                elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
428                elf::R_ARM_CALL => Some("R_ARM_CALL"),
429                elf::R_ARM_THM_PC11 => Some("R_ARM_THM_PC11"),
430                elf::R_ARM_THM_PC9 => Some("R_ARM_THM_PC9"),
431                elf::R_ARM_V4BX => Some("R_ARM_V4BX"),
432                _ => None,
433            },
434            _ => None,
435        }
436    }
437
438    fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
439        match flags {
440            RelocationFlags::Elf(r_type) => match r_type {
441                elf::R_ARM_NONE => 0,
442                elf::R_ARM_ABS32 => 4,
443                elf::R_ARM_REL32 => 4,
444                elf::R_ARM_ABS16 => 2,
445                elf::R_ARM_ABS8 => 1,
446                elf::R_ARM_THM_PC22 => 4,
447                elf::R_ARM_THM_XPC22 => 4,
448                elf::R_ARM_PC24 => 4,
449                elf::R_ARM_XPC25 => 4,
450                elf::R_ARM_CALL => 4,
451                elf::R_ARM_THM_PC11 => 2,
452                elf::R_ARM_THM_PC9 => 2,
453                _ => 1,
454            },
455            _ => 1,
456        }
457    }
458
459    fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
460        if kind == SymbolKind::Function { address & !1 } else { address }
461    }
462
463    fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
464        let mut flags = SymbolFlagSet::default();
465        if DisasmMode::from_object_symbol(symbol).is_some() {
466            flags |= SymbolFlag::Hidden;
467        }
468        flags
469    }
470
471    fn infer_function_size(
472        &self,
473        symbol: &Symbol,
474        section: &Section,
475        mut next_address: u64,
476    ) -> Result<u64> {
477        // TODO: This should probably check the disasm mode and trim accordingly,
478        // but self.disasm_modes isn't populated until post_init, so it needs a refactor.
479
480        // Trim any trailing 2-byte zeroes from the end (padding)
481        while next_address >= symbol.address + 2
482            && let Some(data) = section.data_range(next_address - 2, 2)
483            && data == [0u8; 2]
484        {
485            next_address -= 2;
486            if let Some(relocation) = section.relocation_at(next_address, 2) {
487                // Avoid cutting trailing relocations in half.
488                next_address += self.data_reloc_size(relocation.flags) as u64;
489                break;
490            }
491        }
492        Ok(next_address.saturating_sub(symbol.address))
493    }
494}
495
496#[derive(Clone, Copy, Debug)]
497struct DisasmMode {
498    address: u32,
499    mapping: unarm::ParseMode,
500}
501
502impl DisasmMode {
503    fn from_object_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
504        sym.name()
505            .ok()
506            .and_then(unarm::ParseMode::from_mapping_symbol)
507            .map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
508    }
509
510    fn from_symbol(sym: &Symbol) -> Option<Self> {
511        unarm::ParseMode::from_mapping_symbol(&sym.name)
512            .map(|mapping| DisasmMode { address: sym.address as u32, mapping })
513    }
514}
515
516pub struct ArgsFormatter<'a> {
517    options: &'a unarm::Options,
518    cb: &'a mut dyn FnMut(InstructionPart) -> Result<()>,
519    resolved: &'a ResolvedInstructionRef<'a>,
520    skip_leading_space: bool,
521}
522
523impl ArgsFormatter<'_> {
524    fn write(&mut self, part: InstructionPart) -> core::fmt::Result {
525        (self.cb)(part).map_err(|_| core::fmt::Error)
526    }
527
528    fn write_opaque<F>(&mut self, value: F) -> core::fmt::Result
529    where F: unarm::FormatValue {
530        let mut string_fmt = unarm::StringFormatter::new(self.options);
531        value.write(&mut string_fmt)?;
532        self.write(InstructionPart::opaque(string_fmt.into_string()))?;
533        Ok(())
534    }
535}
536
537impl Write for ArgsFormatter<'_> {
538    fn write_str(&mut self, s: &str) -> core::fmt::Result { self.write(InstructionPart::basic(s)) }
539}
540
541impl unarm::FormatIns for ArgsFormatter<'_> {
542    fn options(&self) -> &unarm::Options { self.options }
543
544    fn write_ins(&mut self, ins: &unarm::Ins) -> core::fmt::Result {
545        let mut string_fmt = unarm::StringFormatter::new(self.options);
546        ins.write_opcode(&mut string_fmt)?;
547        let opcode = string_fmt.into_string();
548        self.write(InstructionPart::Opcode(Cow::Owned(opcode), self.resolved.ins_ref.opcode))?;
549        ins.write_params(self)
550    }
551
552    fn write_space(&mut self) -> core::fmt::Result {
553        if self.skip_leading_space {
554            self.skip_leading_space = false;
555            Ok(())
556        } else {
557            self.write_str(" ")
558        }
559    }
560
561    fn write_separator(&mut self) -> core::fmt::Result { self.write(InstructionPart::separator()) }
562
563    fn write_uimm(&mut self, uimm: u32) -> core::fmt::Result {
564        if let Some(resolved) = self.resolved.relocation
565            && let RelocationFlags::Elf(elf::R_ARM_ABS32) = resolved.relocation.flags
566        {
567            return self.write(InstructionPart::reloc());
568        }
569        self.write(InstructionPart::unsigned(uimm))
570    }
571
572    fn write_simm(&mut self, simm: i32) -> core::fmt::Result {
573        self.write(InstructionPart::signed(simm))
574    }
575
576    fn write_branch_target(&mut self, branch_target: unarm::BranchTarget) -> core::fmt::Result {
577        if let Some(resolved) = self.resolved.relocation {
578            match resolved.relocation.flags {
579                RelocationFlags::Elf(elf::R_ARM_THM_XPC22)
580                | RelocationFlags::Elf(elf::R_ARM_THM_PC22)
581                | RelocationFlags::Elf(elf::R_ARM_PC24)
582                | RelocationFlags::Elf(elf::R_ARM_XPC25)
583                | RelocationFlags::Elf(elf::R_ARM_CALL)
584                | RelocationFlags::Elf(elf::R_ARM_THM_PC11)
585                | RelocationFlags::Elf(elf::R_ARM_THM_PC9) => {
586                    return self.write(InstructionPart::reloc());
587                }
588                _ => {}
589            }
590        }
591        self.write(InstructionPart::branch_dest(branch_target.addr))
592    }
593
594    fn write_reg(&mut self, reg: unarm::Reg) -> core::fmt::Result { self.write_opaque(reg) }
595
596    fn write_status_reg(&mut self, status_reg: unarm::StatusReg) -> core::fmt::Result {
597        self.write_opaque(status_reg)
598    }
599
600    fn write_status_fields(&mut self, status_fields: unarm::StatusFields) -> core::fmt::Result {
601        self.write_opaque(status_fields)
602    }
603
604    fn write_shift_op(&mut self, shift_op: unarm::ShiftOp) -> core::fmt::Result {
605        self.write_opaque(shift_op)
606    }
607
608    fn write_coproc(&mut self, coproc: unarm::Coproc) -> core::fmt::Result {
609        self.write_opaque(coproc)
610    }
611
612    fn write_co_reg(&mut self, co_reg: unarm::CoReg) -> core::fmt::Result {
613        self.write_opaque(co_reg)
614    }
615
616    fn write_aif_flags(&mut self, aif_flags: unarm::AifFlags) -> core::fmt::Result {
617        self.write_opaque(aif_flags)
618    }
619
620    fn write_endianness(&mut self, endianness: unarm::Endianness) -> core::fmt::Result {
621        self.write_opaque(endianness)
622    }
623
624    fn write_sreg(&mut self, sreg: unarm::Sreg) -> core::fmt::Result { self.write_opaque(sreg) }
625
626    fn write_dreg(&mut self, dreg: unarm::Dreg) -> core::fmt::Result { self.write_opaque(dreg) }
627
628    fn write_fpscr(&mut self, fpscr: unarm::Fpscr) -> core::fmt::Result { self.write_opaque(fpscr) }
629
630    fn write_addr_ldr_str(&mut self, addr_ldr_str: unarm::AddrLdrStr) -> core::fmt::Result {
631        addr_ldr_str.write(self)?;
632        if let unarm::AddrLdrStr::Pre {
633            rn: unarm::Reg::Pc,
634            offset: unarm::LdrStrOffset::Imm(offset),
635            ..
636        } = addr_ldr_str
637        {
638            let thumb = self.resolved.ins_ref.opcode & (1 << 15) == 0;
639            let pc_offset = if thumb { 4 } else { 8 };
640            let pc = (self.resolved.ins_ref.address as u32 & !3) + pc_offset;
641            self.write(InstructionPart::basic(" (->"))?;
642            self.write(InstructionPart::branch_dest(pc.wrapping_add(offset as u32)))?;
643            self.write(InstructionPart::basic(")"))?;
644        }
645        Ok(())
646    }
647}