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 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 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 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 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 let fallback_mappings =
182 [DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
183 let mapping_symbols = self
184 .disasm_modes
185 .get(§ion_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 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 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 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 2
233 } else {
234 4
236 }
237 } else if mode != unarm::ParseMode::Arm {
238 2
239 } else {
240 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 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 object::RelocationFlags::Elf { r_type } => {
345 if relocation.has_implicit_addend() {
346 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 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 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 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 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 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 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 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}