From b9b6bb943663a0cd09d039383903d5725fbe034b Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 27 Jun 2025 05:02:32 +0000 Subject: visitor-oriented fmt this makes a decode+format benchmark drop from 75s to 14s... (decode is 5s) --- src/armv7/display.rs | 1438 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1438 insertions(+) create mode 100644 src/armv7/display.rs (limited to 'src/armv7/display.rs') diff --git a/src/armv7/display.rs b/src/armv7/display.rs new file mode 100644 index 0000000..8eaea26 --- /dev/null +++ b/src/armv7/display.rs @@ -0,0 +1,1438 @@ +use core::fmt; +#[allow(deprecated)] +use crate::yaxpeax_arch::{Colorize, ShowContextual, YaxColors}; +use crate::armv7::OperandVisitor; +use super::{Operand, Opcode, ShiftStyle, RegShift, RegShiftStyle, Reg, Bank, ConditionCode, ConditionedOpcode, Instruction, CReg, StatusRegMask}; +use super::NoContext; +use yaxpeax_arch::display::DisplaySink; + +trait DisplaySinkExt { + fn write_reg(&mut self, gpr_num: u8) -> Result<(), core::fmt::Error>; +} + +impl DisplaySinkExt for T { + #[inline(always)] + fn write_reg(&mut self, gpr_num: u8) -> Result<(), core::fmt::Error> { + self.span_start_register(); + let name = REG_NAMES.get(gpr_num as usize) + .unwrap_or_else(|| unsafe { core::hint::unreachable_unchecked() }); + unsafe { + self.write_lt_8(name)?; + } + self.span_end_register(); + Ok(()) + } +} + +/// an instruction and display settings to configure [`Display](fmt::Display)-formatting of the +/// instruction. +/// +/// ARM instructions do not currently have configurable display behavior, but when they do, this +/// will be the place to configure it. +#[cfg(feature="fmt")] +pub struct InstructionDisplayer<'instr> { + #[allow(dead_code)] + pub(crate) instr: &'instr Instruction, +} + +#[cfg(feature="fmt")] +impl<'instr> fmt::Display for InstructionDisplayer<'instr> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.instr) + } +} + +impl Instruction { + /// wrap a reference to this instruction to format the instruction later. + #[cfg(feature="fmt")] + pub fn display<'instr>(&'instr self) -> InstructionDisplayer<'instr> { + InstructionDisplayer { + instr: self + } + } + + fn write_conditioned_opcode(&self, opc: &Opcode, f: &mut T) -> Result<(), fmt::Error> { + unsafe { + f.write_lt_8(opc.name())?; + } + if self.s() { + f.write_char('s')?; + } + if self.w() { + f.write_fixed_size(".w")?; + } + if self.condition != ConditionCode::AL { + let name = self.condition.name(); + // all condition codes are two characters long + f.write_char(name[0] as char)?; + f.write_char(name[1] as char)?; + } + Ok(()) + } +} + +struct DisplayingOperandVisitor<'a, T> { + f: &'a mut T, +} + +static REG_NAMES: [&'static str; 16] = [ + "r0", "r1", "r2", "r3", + "r4", "r5", "r6", "r7", + "r8", "sb", "r10", "fp", + "ip", "sp", "lr", "pc", +]; + +impl DisplayingOperandVisitor<'_, T> { + fn emit_shift_type(&mut self, stype: ShiftStyle) -> Result<(), core::fmt::Error> { + // shifter names in armv7 are all three characters. + let name = stype.name(); + self.f.write_char(name[0] as char)?; + self.f.write_char(name[1] as char)?; + self.f.write_char(name[2] as char)?; + + Ok(()) + } + + fn format_reg_shift(&mut self, shift: RegShift) -> Result<(), core::fmt::Error> { + match shift.into_shift() { + RegShiftStyle::RegImm(imm_shift) => { + self.f.write_reg(imm_shift.shiftee().number())?; + if imm_shift.imm() != 0 || imm_shift.stype() != ShiftStyle::LSL { + self.f.write_fixed_size(", ")?; + self.emit_shift_type(imm_shift.stype())?; + self.f.write_char(' ')?; + let sh = imm_shift.imm(); + if sh >= 30 { + self.f.write_char('3')?; + self.f.write_char((sh - 30 + 0x30) as char)?; + } else if sh >= 20 { + self.f.write_char('2')?; + self.f.write_char((sh - 20 + 0x30) as char)?; + } else if sh >= 10 { + self.f.write_char('1')?; + self.f.write_char((sh - 10 + 0x30) as char)?; + } else { + self.f.write_char((sh + 0x30) as char)?; + } + } + } + RegShiftStyle::RegReg(reg_shift) => { + self.f.write_reg(reg_shift.shiftee().number())?; + self.f.write_fixed_size(", ")?; + self.emit_shift_type(reg_shift.stype())?; + self.f.write_char(' ')?; + self.f.write_reg(reg_shift.shifter().number())?; + }, + } + + Ok(()) + } +} + +impl crate::armv7::OperandVisitor for DisplayingOperandVisitor<'_, T> { + type Ok = (); + type Error = core::fmt::Error; + + fn visit_reglist(&mut self, mut list: u16) -> Result { + self.f.write_char('{')?; + let mut i = 0; + let mut tail = false; + while i < 16 { + if (list & 1) == 1 { + if tail { + self.f.write_fixed_size(", ")?; + } else { + tail = true; + } + self.f.write_reg(i)?; + } + i += 1; + list = list >> 1; + } + self.f.write_char('}')?; + Ok(()) + } + + fn visit_banked_reg(&mut self, bank: Bank, reg: u16) -> Result { + self.f.span_start_register(); + unsafe { + self.f.write_lt_8(REG_NAMES[reg as usize])?; + } + self.f.write_char('_')?; + self.f.write_fixed_size(bank.name())?; + self.f.span_end_register(); + Ok(()) + } + + fn visit_banked_spsr(&mut self, bank: Bank) -> Result { + self.f.span_start_register(); + self.f.write_fixed_size("spsr_")?; + self.f.write_fixed_size(bank.name())?; + self.f.span_end_register(); + Ok(()) + } + + fn visit_reg(&mut self, reg: Reg) -> Result { + self.f.write_reg(reg.number())?; + Ok(()) + } + + fn visit_reg_deref(&mut self, reg: Reg) -> Result { + self.f.write_char('[')?; + self.f.write_reg(reg.number())?; + self.f.write_char(']')?; + Ok(()) + } + + fn visit_reg_shift(&mut self, shift: RegShift) -> Result { + self.format_reg_shift(shift)?; + Ok(()) + } + + fn visit_reg_wback(&mut self, reg: Reg, wback: bool) -> Result { + self.f.write_reg(reg.number())?; + if wback { + self.f.write_char('!')?; + } + Ok(()) + } + + fn visit_reg_deref_postindex_reg_shift(&mut self, base: Reg, index: RegShift, add: bool, wback: bool) -> Result { + // TODO: + assert!(!wback); + self.f.write_char('[')?; + self.f.write_reg(base.number())?; + self.f.write_fixed_size("], ")?; + if !add { + self.f.write_char('-')?; + } + self.f.write_char(' ')?; + self.format_reg_shift(index)?; + Ok(()) + } + + fn visit_reg_deref_preindex_reg_shift(&mut self, base: Reg, index: RegShift, add: bool, wback: bool) -> Result { + self.f.write_char('[')?; + self.f.write_reg(base.number())?; + self.f.write_fixed_size(", ")?; + if !add { + self.f.write_char('-')?; + } + self.format_reg_shift(index)?; + self.f.write_char(']')?; + if wback { + self.f.write_char('!')?; + } + Ok(()) + } + + // TODO: post-index with writeback is a bug right..? + fn visit_reg_deref_postindex_offset(&mut self, base: Reg, offset: u16, add: bool, _wback: bool) -> Result { + self.f.write_char('[')?; + self.f.write_reg(base.number())?; + self.f.write_char(']')?; + + if offset != 0 { + self.f.write_fixed_size(", ")?; + if !add { + self.f.write_char('-')?; + } + self.f.write_prefixed_u16(offset)?; + } + Ok(()) + } + + fn visit_reg_deref_preindex_offset(&mut self, base: Reg, offset: u16, add: bool, wback: bool) -> Result { + self.f.write_char('[')?; + self.f.write_reg(base.number())?; + if offset != 0 { + self.f.write_fixed_size(", ")?; + if !add { + self.f.write_char('-')?; + } + self.f.write_prefixed_u16(offset)?; + } + self.f.write_char(']')?; + if wback { + self.f.write_char('!')?; + } + Ok(()) + } + + fn visit_reg_deref_postindex_reg(&mut self, base: Reg, offset: Reg, add: bool, wback: bool) -> Result { + self.f.write_char('[')?; + self.f.write_reg(base.number())?; + self.f.write_fixed_size("], ")?; + if !add { + self.f.write_char('-')?; + } + self.f.write_reg(offset.number())?; + if wback { + self.f.write_char('!')?; + } + Ok(()) + } + + fn visit_reg_deref_preindex_reg(&mut self, base: Reg, offset: Reg, add: bool, wback: bool) -> Result { + self.f.write_char('[')?; + self.f.write_reg(base.number())?; + self.f.write_fixed_size(", ")?; + if !add { + self.f.write_char('-')?; + } + self.f.write_reg(offset.number())?; + self.f.write_char(']')?; + + if wback { + self.f.write_char('!')?; + } + Ok(()) + } + + fn visit_imm12(&mut self, imm: u16) -> Result { + self.f.write_prefixed_u16(imm) + } + + fn visit_imm32(&mut self, imm: u32) -> Result { + self.f.write_prefixed_u32(imm) + } + + fn visit_branch_offset(&mut self, offset: i32) -> Result { + self.f.write_char('$')?; + if offset >= 0 { + self.f.write_char('+')?; + } + self.f.write_prefixed_i32(offset << 2) + } + + fn visit_blx_offset(&mut self, offset: i32) -> Result { + self.f.write_char('$')?; + if offset >= 0 { + self.f.write_char('+')?; + } + self.f.write_prefixed_i32(offset << 1) + } + + fn visit_coprocessor_option(&mut self, nr: u8) -> Result { + self.f.write_char('{')?; + self.f.write_prefixed_u8(nr)?; + self.f.write_char('}')?; + Ok(()) + } + + fn visit_creg(&mut self, creg: CReg) -> Result { + self.f.span_start_register(); + self.f.write_char('c')?; + let num = creg.number(); + if num >= 10 { + self.f.write_char('1')?; + // num is at most 16 + self.f.write_char((num - 10 + 0x30) as char)?; + } else { + self.f.write_char((num + 0x30) as char)?; + } + self.f.span_end_register(); + Ok(()) + } + + fn visit_status_reg_mask(&mut self, mask: StatusRegMask) -> Result { + self.f.span_start_register(); + self.f.write_fixed_size(mask.name())?; + self.f.span_end_register(); + Ok(()) + } + + fn visit_apsr(&mut self) -> Result { + self.f.span_start_register(); + self.f.write_fixed_size("apsr")?; + self.f.span_end_register(); + Ok(()) + } + + fn visit_spsr(&mut self) -> Result { + self.f.span_start_register(); + self.f.write_fixed_size("spsr")?; + self.f.span_end_register(); + Ok(()) + } + + fn visit_cpsr(&mut self) -> Result { + self.f.span_start_register(); + self.f.write_fixed_size("cpsr")?; + self.f.span_end_register(); + Ok(()) + } + + fn visit_other(&mut self) -> Result { + Ok(()) + } +} + +#[allow(non_snake_case)] +pub(crate) fn visit_inst(instr: &Instruction, out: &mut T) -> fmt::Result { + // handle a few instruction aliasing cases first... + match instr.opcode { + Opcode::LDR => { + match instr.operands { + // TODO: should this be PostindexOffset? + [Operand::Reg(Rt), Operand::RegDerefPostindexOffset(Reg { bits: 13 }, 4, true, false), Operand::Nothing, Operand::Nothing] => { + out.span_start_opcode(); + instr.write_conditioned_opcode(&Opcode::POP, out)?; + out.span_end_opcode(); + + out.write_char(' ')?; + out.write_char('{')?; + out.span_start_register(); + out.write_reg(Rt.number())?; + out.span_end_register(); + out.write_char('}')?; + return Ok(()); + }, + _ => {} + } + }, + Opcode::STR => { + match instr.operands { + // TODO: should this be PreindexOffset? + [Operand::Reg(Rt), Operand::RegDerefPreindexOffset(Reg { bits: 13 }, 4, false, true), Operand::Nothing, Operand::Nothing] => { + out.span_start_opcode(); + instr.write_conditioned_opcode(&Opcode::PUSH, out)?; + out.span_end_opcode(); + + out.write_char(' ')?; + + out.write_char('{')?; + out.span_start_register(); + out.write_reg(Rt.number())?; + out.span_end_register(); + out.write_char('}')?; + return Ok(()); + }, + _ => {} + } + }, + Opcode::LDM(true, false, false, _usermode) => { + // TODO: what indicates usermode in the ARM syntax? + match instr.operands { + [Operand::RegWBack(Reg { bits: 13 }, true), Operand::RegList(list), Operand::Nothing, Operand::Nothing] => { + out.span_start_opcode(); + instr.write_conditioned_opcode(&Opcode::POP, out)?; + out.span_end_opcode(); + + out.write_char(' ')?; + + let mut visitor = DisplayingOperandVisitor { + f: out, + }; + visitor.visit_reglist(list)?; + return Ok(()); + } + _ => {} + } + } + Opcode::STM(false, true, false, _usermode) => { + // TODO: what indicates usermode in the ARM syntax? + match instr.operands { + [Operand::RegWBack(Reg { bits: 13 }, true), Operand::RegList(list), Operand::Nothing, Operand::Nothing] => { + out.span_start_opcode(); + instr.write_conditioned_opcode(&Opcode::PUSH, out)?; + out.span_end_opcode(); + + out.write_char(' ')?; + + let mut visitor = DisplayingOperandVisitor { + f: out, + }; + visitor.visit_reglist(list)?; + return Ok(()); + } + _ => {} + } + } + Opcode::STCL(coproc) | + Opcode::STC(coproc) | + Opcode::STC2L(coproc) | + Opcode::STC2(coproc) | + Opcode::LDC(coproc) | + Opcode::LDCL(coproc) | + Opcode::LDC2(coproc) | + Opcode::LDC2L(coproc) => { + out.span_start_opcode(); + unsafe { + out.write_lt_8(instr.opcode.name())?; + } + out.span_end_opcode(); + out.write_fixed_size(" p")?; + if coproc >= 10 { + out.write_char('1')?; + out.write_char((coproc - 10 + 0x30) as char)?; + } else { + out.write_char((coproc + 0x30) as char)?; + } + + let ops = instr.operands.iter(); + for op in ops { + if let Operand::Nothing = op { + break; + } + out.write_fixed_size(", ")?; + let mut op_visitor = DisplayingOperandVisitor { + f: out + }; + op.visit(&mut op_visitor)?; + } + + return Ok(()); + } + Opcode::MRRC2(coproc, opc) | + Opcode::MCRR2(coproc, opc) => { + out.span_start_opcode(); + unsafe { + out.write_lt_8(instr.opcode.name())?; + } + out.span_end_opcode(); + out.write_fixed_size(" p")?; + // coproc is a 3-bit field + out.write_char((coproc + 0x30) as char)?; + + out.write_fixed_size(", ")?; + // opc1 is a 3-bit field + out.write_char((opc + 0x30) as char)?; + + let ops = instr.operands.iter(); + for op in ops { + if let Operand::Nothing = op { + break; + } + out.write_fixed_size(", ")?; + let mut op_visitor = DisplayingOperandVisitor { + f: out + }; + op.visit(&mut op_visitor)?; + } + + return Ok(()); + } + Opcode::MRC2(coproc, opc1, opc2) | + Opcode::MCR2(coproc, opc1, opc2) | + Opcode::CDP2(coproc, opc1, opc2) => { + out.span_start_opcode(); + unsafe { + out.write_lt_8(instr.opcode.name())?; + } + out.span_end_opcode(); + out.write_fixed_size(" p")?; + // coproc is a 3-bit field + out.write_char((coproc + 0x30) as char)?; + + out.write_fixed_size(", ")?; + // opc1 is a 3-bit field + out.write_char((opc1 + 0x30) as char)?; + + let ops = instr.operands.iter(); + for op in ops { + if let Operand::Nothing = op { + break; + } + out.write_fixed_size(", ")?; + let mut op_visitor = DisplayingOperandVisitor { + f: out + }; + op.visit(&mut op_visitor)?; + } + + out.write_fixed_size(", ")?; + // opc2 is a 3-bit field + out.write_char((opc2 + 0x30) as char)?; + + return Ok(()); + } + _ => {} + } + + // no aliasing, just print the opcode itself + out.span_start_opcode(); + instr.write_conditioned_opcode(&instr.opcode, out)?; + // don't end the opcode yet because some include a few extra characters after the mnemonic. + + if instr.opcode == Opcode::IT { + let (Operand::Imm32(cond), Operand::Imm32(mask)) = (&instr.operands[0], &instr.operands[1]) else { + panic!("impossible it operand"); + }; + + let inv = cond & 1 == 1; + let condition = ConditionCode::build(*cond as u8); + if mask & 0b0001 != 0 { + // three flags + out.write_char(if inv ^ ((mask & 0b1000) != 0) { 'e' } else { 't' })?; + out.write_char(if inv ^ ((mask & 0b0100) != 0) { 'e' } else { 't' })?; + out.write_char(if inv ^ ((mask & 0b0010) != 0) { 'e' } else { 't' })?; + } else if mask & 0b0010 != 0 { + // two flags + out.write_char(if inv ^ ((mask & 0b1000) != 0) { 'e' } else { 't' })?; + out.write_char(if inv ^ ((mask & 0b0100) != 0) { 'e' } else { 't' })?; + } else if mask & 0b0100 != 0 { + // one flag + out.write_char(if inv ^ ((mask & 0b1000) != 0) { 'e' } else { 't' })?; + } else { + // no flags + } + out.span_end_opcode(); + + out.write_char(' ')?; + let name = condition.name(); + // all condition codes are two characters long + out.write_char(name[0] as char)?; + out.write_char(name[1] as char)?; + return Ok(()); + } + + out.span_end_opcode(); + + match instr.opcode { + Opcode::CPS(_) => { + if let Operand::Imm12(aif) = &instr.operands[0] { + let mut comma = false; + + if *aif != 0 { + out.write_char(' ')?; + if aif & 0b100 != 0 { out.write_char('a')?; } + if aif & 0b010 != 0 { out.write_char('i')?; } + if aif & 0b001 != 0 { out.write_char('f')?; } + // we wrote something for the first operand after all, so include a comma + comma = true; + } + if let Operand::Imm12(mode) = &instr.operands[1] { + if comma { out.write_char(',')?; } + out.write_fixed_size(" #")?; + out.write_prefixed_u16(*mode)?; + } + return Ok(()); + } else { + panic!("impossible cps operand"); + } + } + Opcode::SETEND => { + if let Operand::Imm12(i) = &instr.operands[0] { + out.write_char(' ')?; + if *i == 0 { + out.write_fixed_size("le")?; + } else { + out.write_fixed_size("be")?; + } + return Ok(()); + } else { + panic!("impossible setend operand"); + } + } + + _ => {} + } + + match instr.opcode { + // TODO: [add, pre, usermode] + Opcode::STM(_add, _pre, _wback, _usermode) | + Opcode::LDM(_add, _pre, _wback, _usermode) => { + match instr.operands { + [Operand::RegWBack(Rr, wback), Operand::RegList(list), Operand::Nothing, Operand::Nothing] => { + out.write_char(' ')?; + out.write_reg(Rr.number())?; + if wback { out.write_char('!')?; } + out.write_fixed_size(", ")?; + let mut op_visitor = DisplayingOperandVisitor { + f: out, + }; + op_visitor.visit_reglist(list)?; + return Ok(()); + }, + _ => { unreachable!(); } + } + }, + _ => {} + } + + let mut ops = instr.operands.iter(); + if let Some(first_op) = ops.next() { + if let Operand::Nothing = first_op { + return Ok(()); + } + out.write_char(' ')?; + let mut op_visitor = DisplayingOperandVisitor { + f: out + }; + first_op.visit(&mut op_visitor)?; + } else { + return Ok(()); + } + + for op in ops { + if let Operand::Nothing = op { + break; + } + out.write_fixed_size(", ")?; + let mut op_visitor = DisplayingOperandVisitor { + f: out + }; + op.visit(&mut op_visitor)?; + } + + Ok(()) +} + +#[allow(non_snake_case)] +#[allow(deprecated)] +impl ShowContextual for Instruction { + fn contextualize(&self, _colors: &Y, _address: u32, _context: Option<&NoContext>, out: &mut T) -> fmt::Result { + let mut f = yaxpeax_arch::display::FmtSink::new(out); + visit_inst(self, &mut f) + } +} + +#[allow(deprecated)] +impl Colorize for ConditionedOpcode { + fn colorize(&self, colors: &Y, out: &mut T) -> fmt::Result { + match self.0 { + Opcode::UDF | + Opcode::Invalid => { write!(out, "{}", colors.invalid_op(self)) }, + Opcode::TBB | + Opcode::TBH | + Opcode::CBZ | + Opcode::CBNZ | + Opcode::IT | + Opcode::B | + Opcode::BL | + Opcode::BLX | + Opcode::BX | + Opcode::BXJ => { write!(out, "{}", colors.control_flow_op(self)) }, + + Opcode::AND | + Opcode::EOR | + Opcode::ORR | + Opcode::ORN | + Opcode::LSL | + Opcode::LSR | + Opcode::ROR | + Opcode::ASR | + Opcode::RRX | + Opcode::BIC | + + Opcode::ADR | + Opcode::SUB | + Opcode::RSB | + Opcode::ADD | + Opcode::ADC | + Opcode::SBC | + Opcode::RSC | + + Opcode::QADD | + Opcode::QSUB | + Opcode::QDADD | + Opcode::QDSUB | + + Opcode::SADD16 | + Opcode::QADD16 | + Opcode::SHADD16 | + Opcode::SASX | + Opcode::QASX | + Opcode::SHASX | + Opcode::SSAX | + Opcode::QSAX | + Opcode::SHSAX | + Opcode::SSUB16 | + Opcode::QSUB16 | + Opcode::SHSUB16 | + Opcode::SADD8 | + Opcode::QADD8 | + Opcode::SHADD8 | + Opcode::SSUB8 | + Opcode::QSUB8 | + Opcode::SHSUB8 | + Opcode::UADD16 | + Opcode::UQADD16 | + Opcode::UHADD16 | + Opcode::UASX | + Opcode::UQASX | + Opcode::UHASX | + Opcode::USAX | + Opcode::UQSAX | + Opcode::UHSAX | + Opcode::USUB16 | + Opcode::UQSUB16 | + Opcode::UHSUB16 | + Opcode::UADD8 | + Opcode::UQADD8 | + Opcode::UHADD8 | + Opcode::USUB8 | + Opcode::UQSUB8 | + Opcode::UHSUB8 | + + Opcode::CLZ | + + Opcode::MUL | + Opcode::MLA | + Opcode::UMAAL | + Opcode::MLS | + Opcode::UMULL | + Opcode::UMLAL | + Opcode::SMLSD | + Opcode::SMMLA | + Opcode::SMMLS | + Opcode::USADA8 | + Opcode::USAD8 | + Opcode::SDIV | + Opcode::UDIV | + Opcode::SMLALD(_) | + Opcode::SMLSLD(_) | + Opcode::SMLAD | + Opcode::SMUSD | + Opcode::SMMUL | + Opcode::SMULW(_) | + Opcode::SMUAD | + Opcode::SMULL | + Opcode::SMUL(_, _) | + Opcode::SMAL(_, _) | + Opcode::SMLA(_, _) | + Opcode::SMLAW(_) | + Opcode::SMLAL | + Opcode::SMLAL_halfword(_, _) => { write!(out, "{}", colors.arithmetic_op(self)) }, + + Opcode::PUSH | + Opcode::POP => { write!(out, "{}", colors.stack_op(self)) }, + + Opcode::TST | + Opcode::TEQ | + Opcode::CMP | + Opcode::CMN => { write!(out, "{}", colors.comparison_op(self)) }, + + Opcode::LDRSH | + Opcode::LDRSHT | + Opcode::LDRSB | + Opcode::LDRSBT | + Opcode::STRD | + Opcode::LDRD | + Opcode::LDREXH | + Opcode::STREXH | + Opcode::LDREXB | + Opcode::STREXB | + Opcode::LDREXD | + Opcode::STREXD | + Opcode::LDREX | + Opcode::STREX | + Opcode::LDM(false, false, _, _) | + Opcode::LDM(false, true, _, _) | + Opcode::LDM(true, false, _, _) | + Opcode::LDM(true, true, _, _) | + Opcode::STM(false, false, _, _) | + Opcode::STM(false, true, _, _) | + Opcode::STM(true, false, _, _) | + Opcode::STM(true, true, _, _) | + Opcode::LDR | + Opcode::STR | + Opcode::LDRH | + Opcode::STRH | + Opcode::LDRB | + Opcode::STRB | + Opcode::LDRT | + Opcode::STRT | + Opcode::LDRHT | + Opcode::STRHT | + Opcode::LDRBT | + Opcode::STRBT | + Opcode::SWP | + Opcode::SWPB | + Opcode::MSR | + Opcode::MRS | + Opcode::CLREX | + Opcode::SXTAB | + Opcode::SXTAB16 | + Opcode::SXTAH | + Opcode::SXTB | + Opcode::SXTB16 | + Opcode::SXTH | + Opcode::UXTAB | + Opcode::UXTAB16 | + Opcode::UXTAH | + Opcode::UXTB | + Opcode::UXTB16 | + Opcode::UXTH | + Opcode::PKHTB | + Opcode::PKHBT | + Opcode::REV | + Opcode::REV16 | + Opcode::REVSH | + Opcode::SSAT | + Opcode::SSAT16 | + Opcode::SBFX | + Opcode::USAT | + Opcode::USAT16 | + Opcode::UBFX | + Opcode::BFI | + Opcode::BFC | + Opcode::RBIT | + Opcode::SEL | + Opcode::MOV | + Opcode::MOVT | + Opcode::MVN => { write!(out, "{}", colors.data_op(self)) }, + + Opcode::HINT | + Opcode::NOP | + Opcode::PLD | + Opcode::PLI | + Opcode::ISB | + Opcode::DMB | + Opcode::DSB | + Opcode::CSDB | + Opcode::SRS(_, _) | + Opcode::BKPT => { write!(out, "{}", colors.misc_op(self)) }, + + Opcode::DBG | + Opcode::CPS(_) | + Opcode::CPS_modeonly | + Opcode::SETEND | + Opcode::ENTERX | + Opcode::LEAVEX | + Opcode::YIELD | + Opcode::WFE | + Opcode::WFI | + Opcode::SEV | + Opcode::ERET | + Opcode::RFE(_, _) | + Opcode::HVC | + Opcode::SVC | + Opcode::SMC | + Opcode::LDC(_) | + Opcode::LDCL(_) | + Opcode::LDC2(_) | + Opcode::LDC2L(_) | + Opcode::STC(_) | + Opcode::STCL(_) | + Opcode::STC2(_) | + Opcode::STC2L(_) | + Opcode::MCRR2(_, _) | + Opcode::MCR2(_, _, _) | + Opcode::MRRC2(_, _) | + Opcode::MRC2(_, _, _) | + Opcode::MCRR(_, _) | + Opcode::MRRC(_, _) | + Opcode::CDP2(_, _, _) => { write!(out, "{}", colors.platform_op(self)) }, + } + } +} + +impl fmt::Display for Opcode { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + let mut f = yaxpeax_arch::display::FmtSink::new(f); + self.write_to_sink(&mut f) + } +} + +impl Opcode { + fn write_to_sink(&self, sink: &mut T) -> Result<(), fmt::Error> { + sink.write_str(self.name()) + } + + fn name(&self) -> &'static str { + match self { + Opcode::LDRSH => { "ldrsh" }, + Opcode::LDRSHT => { "ldrsht" }, + Opcode::LDRSB => { "ldrsb" }, + Opcode::LDRSBT => { "ldrsbt" }, + Opcode::STRD => { "strd" }, + Opcode::LDRD => { "ldrd" }, + Opcode::LDC(_) => { "ldc" }, + Opcode::LDCL(_) => { "ldcl" }, + Opcode::LDC2(_) => { "ldc2" }, + Opcode::LDC2L(_) => { "ldc2l" }, + Opcode::STC(_) => { "stc" }, + Opcode::STCL(_) => { "stcl" }, + Opcode::STC2(_) => { "stc2" }, + Opcode::STC2L(_) => { "stc2l" }, + Opcode::MCRR2(_, _) => { "mcrr2" }, + Opcode::MCR2(_, _, _) => { "mcr2" }, + Opcode::MRRC2(_, _) => { "mrrc2" }, + Opcode::MRC2(_, _, _) => { "mrc2" }, + Opcode::MCRR(_, _) => { "mcrr" }, + Opcode::MRRC(_, _) => { "mrrc" }, + Opcode::CDP2(_, _, _) => { "cdp2" }, + Opcode::SRS(true, true) => { "srsib" }, + Opcode::SRS(false, true) => { "srsia" }, + Opcode::SRS(true, false) => { "srsdb" }, + Opcode::SRS(false, false) => { "srsda" }, + Opcode::RFE(true, true) => { "rfeib" }, + Opcode::RFE(false, true) => { "rfeia" }, + Opcode::RFE(true, false) => { "rfedb" }, + Opcode::RFE(false, false) => { "rfeda" }, + Opcode::ERET => { "eret" }, + Opcode::HVC => { "hvc" }, + Opcode::BKPT => { "bkpt" }, + Opcode::SMC => { "smc" }, + Opcode::MOVT => { "movt" }, + Opcode::QADD => { "qadd" }, + Opcode::QSUB => { "qsub" }, + Opcode::QDADD => { "qdadd" }, + Opcode::QDSUB => { "qdsub" }, + Opcode::Invalid => { "invalid" }, + Opcode::POP => { "pop" }, + Opcode::PUSH => { "push" }, + Opcode::B => { "b" }, + Opcode::BL => { "bl" }, + Opcode::BLX => { "blx" }, + Opcode::BX => { "bx" }, + Opcode::BXJ => { "bxj" }, + Opcode::CLZ => { "clz" }, + Opcode::AND => { "and" }, + Opcode::EOR => { "eor" }, + Opcode::SUB => { "sub" }, + Opcode::RSB => { "rsb" }, + Opcode::ADD => { "add" }, + Opcode::ADC => { "adc" }, + Opcode::SBC => { "sbc" }, + Opcode::RSC => { "rsc" }, + Opcode::TST => { "tst" }, + Opcode::TEQ => { "teq" }, + Opcode::CMP => { "cmp" }, + Opcode::CMN => { "cmn" }, + Opcode::ORR => { "orr" }, + Opcode::MOV => { "mov" }, + Opcode::MSR => { "msr" }, + Opcode::MRS => { "mrs" }, + Opcode::BIC => { "bic" }, + Opcode::MVN => { "mvn" }, + Opcode::LSL => { "lsl" }, + Opcode::LSR => { "lsr" }, + Opcode::ASR => { "asr" }, + Opcode::RRX => { "rrx" }, + Opcode::ROR => { "ror" }, + Opcode::ADR => { "adr" }, + Opcode::LDREXH => { "ldrexh" }, + Opcode::STREXH => { "strexh" }, + Opcode::LDREXB => { "ldrexb" }, + Opcode::STREXB => { "strexb" }, + Opcode::LDREXD => { "ldrexd" }, + Opcode::STREXD => { "strexd" }, + Opcode::LDREX => { "ldrex" }, + Opcode::STREX => { "strex" }, + Opcode::LDM(false, false, _, _) => { "ldmda" }, + Opcode::LDM(false, true, _, _) => { "ldmdb" }, + // TODO: seems like these are backwards + Opcode::LDM(true, false, _, _) => { "ldm" }, + Opcode::LDM(true, true, _, _) => { "ldmia" }, + Opcode::STM(false, false, _, _) => { "stmda" }, + Opcode::STM(false, true, _, _) => { "stmdb" }, + // TODO: seems like these are backwards + Opcode::STM(true, false, _, _) => { "stm" }, + Opcode::STM(true, true, _, _) => { "stmia" }, + Opcode::LDR => { "ldr" }, + Opcode::STR => { "str" }, + Opcode::LDRH => { "ldrh" }, + Opcode::STRH => { "strh" }, + Opcode::LDRB => { "ldrb" }, + Opcode::STRB => { "strb" }, + Opcode::LDRT => { "ldrt" }, + Opcode::STRT => { "strt" }, + Opcode::LDRHT => { "ldrht" }, + Opcode::STRHT => { "strht" }, + Opcode::LDRBT => { "ldrbt" }, + Opcode::STRBT => { "strbt" }, + Opcode::SWP => { "swp" }, + Opcode::SWPB => { "swpb" }, + Opcode::SDIV => { "sdiv" }, + Opcode::UDIV => { "udiv" }, + Opcode::MUL => { "mul" }, + Opcode::MLA => { "mla" }, + Opcode::UMAAL => { "umaal" }, + Opcode::MLS => { "mls" }, + Opcode::UMULL => { "umull" }, + Opcode::UMLAL => { "umlal" }, + Opcode::SMULL => { "smull" }, + Opcode::SMLA(true, true) => { "smlatt" }, + Opcode::SMLA(true, false) => { "smlatb" }, + Opcode::SMLA(false, true) => { "smlabt" }, + Opcode::SMLA(false, false) => { "smlabb" }, + Opcode::SMLAL => { "smlal" }, + Opcode::SMLAL_halfword(true, true) => { "smlaltt" }, + Opcode::SMLAL_halfword(true, false) => { "smlaltb" }, + Opcode::SMLAL_halfword(false, true) => { "smlalbt" }, + Opcode::SMLAL_halfword(false, false) => { "smlalbb" }, + Opcode::SMUL(true, true) => { "smultt" }, + Opcode::SMUL(true, false) => { "smultb" }, + Opcode::SMUL(false, true) => { "smulbt" }, + Opcode::SMUL(false, false) => { "smulbb" }, + Opcode::SMAL(true, true) => { "smaltt" }, + Opcode::SMAL(true, false) => { "smaltb" }, + Opcode::SMAL(false, true) => { "smalbt" }, + Opcode::SMAL(false, false) => { "smalbb" }, + Opcode::SMLAW(true) => { "smlawt" }, + Opcode::SMLAW(false) => { "smlawb" }, + Opcode::SMULW(true) => { "smulwt" }, + Opcode::SMULW(false) => { "smulwb" }, + Opcode::SMLALD(true) => { "smlaldt" }, + Opcode::SMLALD(false) => { "smlaldb" }, + Opcode::SMLSLD(true) => { "smlsldt" }, + Opcode::SMLSLD(false) => { "smlsldb" }, + Opcode::SMLSD => { "smlsd" }, + Opcode::SMMLA => { "smmla" }, + Opcode::SMMLS => { "smmls" }, + Opcode::USADA8 => { "usada8" }, + Opcode::USAD8 => { "usad8" }, + Opcode::SMLAD => { "smlad" }, + Opcode::SMUSD => { "smusd" }, + Opcode::SMMUL => { "smmul" }, + Opcode::SMUAD => { "smuad" }, + Opcode::TBB => { "tbb" }, + Opcode::TBH => { "tbh" }, + Opcode::UDF => { "udf" }, + Opcode::SVC => { "svc" }, + Opcode::WFE => { "wfe" }, + Opcode::WFI => { "wfi" }, + Opcode::SEV => { "sev" }, + Opcode::CSDB => { "csdb" }, + Opcode::YIELD => { "yield" }, + Opcode::HINT => { "hint" }, + Opcode::NOP => { "nop" }, + Opcode::LEAVEX => { "leavex" }, + Opcode::ENTERX => { "enterx" }, + Opcode::CLREX => { "clrex" }, + Opcode::DSB => { "dsb" }, + Opcode::DMB => { "dmb" }, + Opcode::ISB => { "isb" }, + Opcode::SXTH => { "sxth" }, + Opcode::UXTH => { "uxth" }, + Opcode::SXTB16 => { "sxtb16" }, + Opcode::UXTB16 => { "uxtb16" }, + Opcode::SXTB => { "sxtb" }, + Opcode::UXTB => { "uxtb" }, + Opcode::SXTAH => { "sxtah" }, + Opcode::UXTAH => { "uxtah" }, + Opcode::SXTAB16 => { "sxtab16" }, + Opcode::UXTAB16 => { "uxtab16" }, + Opcode::SXTAB => { "sxtab" }, + Opcode::UXTAB => { "uxtab" }, + Opcode::CBZ => { "cbz" }, + Opcode::CBNZ => { "cbnz" }, + Opcode::SETEND => { "setend" }, + Opcode::CPS(true) => { "cpsid" }, + Opcode::CPS(false) => { "cpsie" }, + Opcode::CPS_modeonly => { "cps" }, + Opcode::REV => { "rev" }, + Opcode::REV16 => { "rev16" }, + Opcode::REVSH => { "revsh" }, + Opcode::IT => { "it" }, + Opcode::PKHTB => { "pkhtb" }, + Opcode::PKHBT => { "pkhbt" }, + Opcode::ORN => { "orn" }, + Opcode::SSAT => { "ssat" }, + Opcode::SSAT16 => { "ssat16" }, + Opcode::SBFX => { "sbfx" }, + Opcode::USAT => { "usat" }, + Opcode::USAT16 => { "usat16" }, + Opcode::UBFX => { "ubfx" }, + Opcode::BFI => { "bfi" }, + Opcode::BFC => { "bfc" }, + Opcode::DBG => { "dbg" }, + Opcode::PLD => { "pld" }, + Opcode::PLI => { "pli" }, + Opcode::RBIT => { "rbit" }, + Opcode::SEL => { "sel" }, + Opcode::SADD16 => { "sadd16" }, + Opcode::QADD16 => { "qadd16" }, + Opcode::SHADD16 => { "shadd16" }, + Opcode::SASX => { "sasx" }, + Opcode::QASX => { "qasx" }, + Opcode::SHASX => { "shasx" }, + Opcode::SSAX => { "ssax" }, + Opcode::QSAX => { "qsax" }, + Opcode::SHSAX => { "shsax" }, + Opcode::SSUB16 => { "ssub16" }, + Opcode::QSUB16 => { "qsub16" }, + Opcode::SHSUB16 => { "shsub16" }, + Opcode::SADD8 => { "sadd8" }, + Opcode::QADD8 => { "qadd8" }, + Opcode::SHADD8 => { "shadd8" }, + Opcode::SSUB8 => { "ssub8" }, + Opcode::QSUB8 => { "qsub8" }, + Opcode::SHSUB8 => { "shsub8" }, + Opcode::UADD16 => { "uadd16" }, + Opcode::UQADD16 => { "uqadd16" }, + Opcode::UHADD16 => { "uhadd16" }, + Opcode::UASX => { "uasx" }, + Opcode::UQASX => { "uqasx" }, + Opcode::UHASX => { "uhasx" }, + Opcode::USAX => { "usax" }, + Opcode::UQSAX => { "uqsax" }, + Opcode::UHSAX => { "uhsax" }, + Opcode::USUB16 => { "usub16" }, + Opcode::UQSUB16 => { "uqsub16" }, + Opcode::UHSUB16 => { "uhsub16" }, + Opcode::UADD8 => { "uadd8" }, + Opcode::UQADD8 => { "uqadd8" }, + Opcode::UHADD8 => { "uhadd8" }, + Opcode::USUB8 => { "usub8" }, + Opcode::UQSUB8 => { "uqsub8" }, + Opcode::UHSUB8 => { "uhsub8" }, + } + } +} + +impl fmt::Display for Bank { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name()) + } +} + +impl Bank { + fn name(&self) -> &'static str { + match self { + Bank::Usr => "usr", + Bank::Fiq => "fiq", + Bank::Irq => "irq", + Bank::Svc => "svc", + Bank::Abt => "abt", + Bank::Und => "und", + Bank::Mon => "mon", + Bank::Hyp => "hyp", + } + } +} + +impl fmt::Display for StatusRegMask { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(self.name()) + } +} + +impl StatusRegMask { + fn name(&self) -> &'static str { + match self { + StatusRegMask::CPSR_C => "cpsr_c", + StatusRegMask::CPSR_X => "cpsr_x", + StatusRegMask::CPSR_XC => "cpsr_xc", + StatusRegMask::APSR_G => "apsr_g", + StatusRegMask::CPSR_SC => "cpsr_sc", + StatusRegMask::CPSR_SX => "cpsr_sx", + StatusRegMask::CPSR_SXC => "cpsr_sxc", + StatusRegMask::APSR_NZCVQ => "apsr_nzcvq", + StatusRegMask::CPSR_FC => "cpsr_fc", + StatusRegMask::CPSR_FX => "cpsr_fx", + StatusRegMask::CPSR_FXC => "cpsr_fxc", + StatusRegMask::APSR_NZCVQG => "apsr_nzcvqg", + StatusRegMask::CPSR_FSC => "cpsr_fsc", + StatusRegMask::CPSR_FSX => "cpsr_fsx", + StatusRegMask::CPSR_FSXC => "cpsr_fsxc", + StatusRegMask::SPSR => "spsr", + StatusRegMask::SPSR_C => "spsr_c", + StatusRegMask::SPSR_X => "spsr_x", + StatusRegMask::SPSR_XC => "spsr_xc", + StatusRegMask::SPSR_S => "spsr_s", + StatusRegMask::SPSR_SC => "spsr_sc", + StatusRegMask::SPSR_SX => "spsr_sx", + StatusRegMask::SPSR_SXC => "spsr_sxc", + StatusRegMask::SPSR_F => "spsr_f", + StatusRegMask::SPSR_FC => "spsr_fc", + StatusRegMask::SPSR_FX => "spsr_fx", + StatusRegMask::SPSR_FXC => "spsr_fxc", + StatusRegMask::SPSR_FS => "spsr_fs", + StatusRegMask::SPSR_FSC => "spsr_fsc", + StatusRegMask::SPSR_FSX => "spsr_fsx", + StatusRegMask::SPSR_FSXC => "spsr_fsxc", + } + } +} + +#[allow(deprecated)] +impl Colorize for super::Operand { + fn colorize(&self, _colors: &Y, f: &mut T) -> fmt::Result { + let mut f = yaxpeax_arch::display::FmtSink::new(f); + let mut visitor = DisplayingOperandVisitor { + f: &mut f, + }; + self.visit(&mut visitor) + } +} + +impl super::Operand { + fn visit(&self, visitor: &mut V) -> Result { + match *self { + Operand::RegList(list) => { + visitor.visit_reglist(list) + } + Operand::BankedReg(bank, reg) => { + visitor.visit_banked_reg(bank, reg.number() as u16) + }, + Operand::BankedSPSR(bank) => { + visitor.visit_banked_spsr(bank) + }, + Operand::Reg(reg) => { + visitor.visit_reg(reg) + } + Operand::RegDeref(reg) => { + visitor.visit_reg_deref(reg) + } + Operand::RegShift(shift) => { + visitor.visit_reg_shift(shift) + } + Operand::RegDerefPostindexRegShift(reg, shift, add, wback) => { + visitor.visit_reg_deref_postindex_reg_shift(reg, shift, add, wback) + } + Operand::RegDerefPreindexRegShift(reg, shift, add, wback) => { + visitor.visit_reg_deref_preindex_reg_shift(reg, shift, add, wback) + } + Operand::RegDerefPostindexOffset(reg, offs, add, wback) => { + visitor.visit_reg_deref_postindex_offset(reg, offs, add, wback) + } + Operand::RegDerefPreindexOffset(reg, offs, add, wback) => { + visitor.visit_reg_deref_preindex_offset(reg, offs, add, wback) + } + Operand::RegDerefPostindexReg(reg, offsreg, add, wback) => { + visitor.visit_reg_deref_postindex_reg(reg, offsreg, add, wback) + } + Operand::RegDerefPreindexReg(reg, offsreg, add, wback) => { + visitor.visit_reg_deref_preindex_reg(reg, offsreg, add, wback) + } + Operand::Imm12(imm) => { + visitor.visit_imm12(imm) + } + Operand::Imm32(imm) => { + visitor.visit_imm32(imm) + } + Operand::BranchOffset(imm) => { + visitor.visit_branch_offset(imm) + } + Operand::BranchThumbOffset(imm) => { + visitor.visit_blx_offset(imm) + } + #[allow(deprecated)] + Operand::Coprocessor(_num) => { + unreachable!("Operand::Coprocessor is never constructed and will be removed in a future release"); + } + Operand::CoprocOption(num) => { + visitor.visit_coprocessor_option(num) + } + Operand::RegWBack(reg, wback) => { + visitor.visit_reg_wback(reg, wback) + } + Operand::CReg(creg) => { + visitor.visit_creg(creg) + } + Operand::StatusRegMask(mask) => { + visitor.visit_status_reg_mask(mask) + } + Operand::APSR => { + visitor.visit_apsr() + }, + Operand::SPSR => { + visitor.visit_spsr() + }, + Operand::CPSR => { + visitor.visit_cpsr() + }, + Operand::Nothing => { panic!("tried to print Nothing operand") }, + } + } +} + +#[cfg(all(feature="alloc", feature="fmt"))] +mod buffer_sink { + use core::fmt; + use super::{InstructionDisplayer}; + + // this exists to size `InstructionTextBuffer`'s buffer. it ideally would be somehow related to + // `Arch`, but i'm not sure how to wire that up. so instead, calculate an appropriate max size + // here. + // + // the longest instruction would be something like the longest mnemonic and four register + // lists. in reality an ARM instruction has at most one register list so this is a gross + // overestimate. but on with the show; assume 16 bytes for the longest mnemonic, then register + // lists as `{(rNN, ){16}}`. each register list, then, is 80 bytes, brackets are two more, four + // spaces for separation, and three commas for internal spacing. that's 16 + 320 + + 8 + 4 + 3. + // round again and that gets you to 32 + 320 ~~ 352 bytes. out of sheer paranoia and excessive + // padding desire, i'm going with 512 here as the next power of two. + const MAX_INSTRUCTION_LEN: usize = 512; + + /// helper to format `armv7` instructions with highest throughput and least configuration. this is + /// functionally a buffer for one armv7 instruction's text. + /// + /// ### when to use this over `fmt::Display`? + /// + /// `fmt::Display` is a fair choice in most cases. in some cases, `InstructionTextBuffer` may + /// support formatting options that may be difficult to configure for a `Display` impl. + /// additionally, `InstructionTextBuffer` may be able to specialize more effectively where + /// `fmt::Display`, writing to a generic `fmt::Write`, may not. + /// + /// if your use case for `yaxpeax-arm` involves being bounded on the speed of disassembling and + /// formatting instructions, `InstructionTextBuffer` may be a better choice. + /// + /// `InstructionTextBuffer` involves internal allocations; if your use case for `yaxpeax-arm` + /// requires allocations never occurring, it is not an appropriate tool. + /// + /// ### example + /// + /// ```text + /// use yaxpeax_arm::armv7::InstDecoder; + /// use yaxpeax_arm::armv7::InstructionTextBuffer; + /// + /// let bytes = &[0x34, 0x78, 0xbf, 0xfd]; + /// let inst = InstDecoder::default().decode_slice(bytes).expect("can decode"); + /// let mut text_buf = InstructionTextBuffer::new(); + /// assert_eq!( + /// text_buf.format_inst(&inst).expect("can format"), + /// "ldc2 p8, c7, [pc, 0xd0]" + /// ); + /// + /// // or, getting the formatted instruction with `text_str`: + /// assert_eq!( + /// text_buf.text_str(), + /// "ldc2 p8, c7, [pc, 0xd0]" + /// ); + /// ``` + pub struct InstructionTextBuffer { + content: alloc::string::String, + } + + impl InstructionTextBuffer { + /// create an `InstructionTextBuffer` with default settings. `InstructionTextBuffer`'s default + /// settings format instructions identically to their corresponding `fmt::Display`. + pub fn new() -> Self { + let mut buf = alloc::string::String::new(); + buf.reserve(MAX_INSTRUCTION_LEN); + Self { + content: buf, + } + } + + /// format `inst` into this buffer. returns a borrow of that same internal buffer for convenience. + /// + /// this clears and reuses an internal buffer; if an instruction had been previously formatted + /// through this buffer, it will be overwritten. + pub fn format_inst<'buf, 'instr>(&'buf mut self, display: &InstructionDisplayer<'instr>) -> Result<&'buf str, fmt::Error> { + // Safety: this sink is used to format exactly one instruction and then dropped. it can + // never escape `format_inst`. + let mut handle = unsafe { self.write_handle() }; + + super::visit_inst(&display.instr, &mut handle)?; + + Ok(self.text_str()) + } + + /// return a borrow of the internal buffer. if an instruction has been formatted, the + /// returned `&str` contains that instruction's buffered text. + pub fn text_str(&self) -> &str { + self.content.as_str() + } + + /// do the necessary bookkeeping and provide an `InstructionTextSink` to write an instruction + /// into. + /// + /// SAFETY: callers must print at most one instruction into this handle. + unsafe fn write_handle(&mut self) -> yaxpeax_arch::display::InstructionTextSink { + self.content.clear(); + // Safety: `content` was just cleared, so writing begins at the start of the buffer. + // `content`is large enough to hold a fully-formatted instruction (see + // `InstructionTextBuffer::new`). + yaxpeax_arch::display::InstructionTextSink::new(&mut self.content) + } + } +} + +#[cfg(all(feature="alloc", feature="fmt"))] +pub use buffer_sink::InstructionTextBuffer; -- cgit v1.1