use std::{ cmp::{min, Ordering}, collections::{btree_map, BTreeMap}, io::Write, }; use anyhow::{anyhow, bail, ensure, Context, Result}; use itertools::Itertools; use ppc750cl::{Argument, Ins, InsIter, Opcode}; use crate::{ obj::{ ObjDataKind, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolKind, SymbolIndex, }, util::nested::NestedVec, }; #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum SymbolEntryKind { Start, End, Label, } #[derive(Debug, Copy, Clone, Eq, PartialEq)] struct SymbolEntry { index: SymbolIndex, kind: SymbolEntryKind, } pub fn write_asm(w: &mut W, obj: &ObjInfo) -> Result<()> where W: Write + ?Sized { writeln!(w, ".include \"macros.inc\"")?; if !obj.name.is_empty() { let name = obj .name .rsplit_once('/') .or_else(|| obj.name.rsplit_once('\\')) .or_else(|| obj.name.rsplit_once(' ')) .map(|(_, b)| b) .unwrap_or(&obj.name); writeln!(w, ".file \"{}\"", name.replace('\\', "\\\\"))?; } // We'll append generated symbols to the end let mut symbols: Vec = obj.symbols.iter().map(|(_, s)| s.clone()).collect(); let mut section_entries: Vec>> = vec![]; let mut section_relocations: Vec> = vec![]; for (section_idx, section) in obj.sections.iter() { // Build symbol start/end entries let mut entries = BTreeMap::>::new(); for (symbol_index, symbol) in obj.symbols.for_section(section_idx) { entries.nested_push(symbol.address as u32, SymbolEntry { index: symbol_index, kind: SymbolEntryKind::Start, }); if symbol.size > 0 { entries.nested_push((symbol.address + symbol.size) as u32, SymbolEntry { index: symbol_index, kind: SymbolEntryKind::End, }); } } let mut relocations = section.relocations.clone_map(); // Generate local jump labels if section.kind == ObjSectionKind::Code { for (addr, ins) in InsIter::new(§ion.data, section.address as u32) { if let Some(address) = ins.branch_dest(addr) { if ins.field_aa() || !section.contains(address) { continue; } // Replace section-relative jump relocations (generated by GCC) // These aren't always possible to express accurately in GNU assembler if matches!(relocations.get(&addr), Some(reloc) if reloc.addend == 0) { continue; } let vec = match entries.entry(address) { btree_map::Entry::Occupied(e) => e.into_mut(), btree_map::Entry::Vacant(e) => e.insert(vec![]), }; let mut target_symbol_idx = vec .iter() .find(|e| e.kind == SymbolEntryKind::Label) .or_else(|| vec.iter().find(|e| e.kind == SymbolEntryKind::Start)) .map(|e| e.index); if target_symbol_idx.is_none() { let display_address = address as u64 + section.virtual_address.unwrap_or(0); let symbol_idx = symbols.len() as SymbolIndex; symbols.push(ObjSymbol { name: format!(".L_{display_address:08X}"), address: display_address, section: Some(section_idx), size_known: true, ..Default::default() }); vec.push(SymbolEntry { index: symbol_idx, kind: SymbolEntryKind::Label }); target_symbol_idx = Some(symbol_idx); } if let Some(symbol_idx) = target_symbol_idx { relocations.insert(addr, ObjReloc { kind: match ins.op { Opcode::B => ObjRelocKind::PpcRel24, Opcode::Bc => ObjRelocKind::PpcRel14, _ => unreachable!(), }, target_symbol: symbol_idx, addend: 0, module: None, }); } } } } section_entries.push(entries); section_relocations.push(relocations); } // Generate labels for jump tables & relative data relocations for (_section_index, section) in obj .sections .iter() .filter(|(_, s)| matches!(s.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData)) { for (reloc_address, reloc) in section.relocations.iter() { if reloc.addend == 0 { continue; } let target = &symbols[reloc.target_symbol as usize]; let target_section_idx = match target.section { Some(v) => v, None => continue, }; let target_section = obj.sections.get(target_section_idx).ok_or_else(|| { anyhow!("Invalid relocation target section: {:#010X} {:?}", reloc_address, target) })?; let address = (target.address as i64 + reloc.addend) as u64; let vec = match section_entries[target_section_idx as usize].entry(address as u32) { btree_map::Entry::Occupied(e) => e.into_mut(), btree_map::Entry::Vacant(e) => e.insert(vec![]), }; if !vec .iter() .any(|e| e.kind == SymbolEntryKind::Label || e.kind == SymbolEntryKind::Start) { let display_address = address + target_section.virtual_address.unwrap_or(0); let symbol_idx = symbols.len() as SymbolIndex; symbols.push(ObjSymbol { name: format!(".L_{display_address:08X}"), address: display_address, section: Some(target_section_idx), size_known: true, ..Default::default() }); vec.push(SymbolEntry { index: symbol_idx, kind: SymbolEntryKind::Label }); } } } // Write common symbols let mut common_symbols = Vec::new(); for symbol in symbols.iter().filter(|s| s.flags.is_common()) { ensure!(symbol.section.is_none(), "Invalid: common symbol with section {:?}", symbol); common_symbols.push(symbol); } if !common_symbols.is_empty() { writeln!(w)?; for symbol in common_symbols { if let Some(name) = &symbol.demangled_name { writeln!(w, "# {name}")?; } write!(w, ".comm ")?; write_symbol_name(w, &symbol.name)?; writeln!(w, ", {:#X}, {}", symbol.size, symbol.align.unwrap_or(4))?; } } for (section_index, section) in obj.sections.iter() { let entries = §ion_entries[section_index as usize]; let relocations = §ion_relocations[section_index as usize]; let mut current_address = section.address as u32; let section_end = (section.address + section.size) as u32; let subsection = obj .sections .iter() .take(section_index as usize) .filter(|(_, s)| s.name == section.name) .count(); loop { if current_address >= section_end { break; } write_section_header(w, section, subsection, current_address, section_end)?; match section.kind { ObjSectionKind::Code | ObjSectionKind::Data | ObjSectionKind::ReadOnlyData => { write_data( w, &symbols, entries, relocations, section, current_address, section_end, §ion_entries, )?; } ObjSectionKind::Bss => { write_bss(w, &symbols, entries, section, current_address, section_end)?; } } // Write end of symbols if let Some(entries) = entries.get(§ion_end) { for entry in entries { if entry.kind != SymbolEntryKind::End { continue; } write_symbol_entry(w, &symbols, entry, section)?; } } current_address = section_end; } } w.flush()?; Ok(()) } fn write_code_chunk( w: &mut W, symbols: &[ObjSymbol], _entries: &BTreeMap>, relocations: &BTreeMap, section: &ObjSection, address: u32, data: &[u8], ) -> Result<()> where W: Write + ?Sized, { for (addr, ins) in InsIter::new(data, address) { let reloc = relocations.get(&addr); let file_offset = section.file_offset + (addr as u64 - section.address); write_ins(w, symbols, addr, ins, reloc, file_offset, section.virtual_address)?; } Ok(()) } fn write_ins( w: &mut W, symbols: &[ObjSymbol], addr: u32, mut ins: Ins, reloc: Option<&ObjReloc>, file_offset: u64, section_vaddr: Option, ) -> Result<()> where W: Write + ?Sized, { write!( w, "/* {:08X} {:08X} {:02X} {:02X} {:02X} {:02X} */\t", addr as u64 + section_vaddr.unwrap_or(0), file_offset, (ins.code >> 24) & 0xFF, (ins.code >> 16) & 0xFF, (ins.code >> 8) & 0xFF, ins.code & 0xFF )?; if let Some(reloc) = reloc { // Zero out relocations ins.code = match reloc.kind { ObjRelocKind::Absolute => 0, ObjRelocKind::PpcEmbSda21 => ins.code & !0x1FFFFF, ObjRelocKind::PpcRel24 => ins.code & !0x3FFFFFC, ObjRelocKind::PpcRel14 => ins.code & !0xFFFC, ObjRelocKind::PpcAddr16Hi | ObjRelocKind::PpcAddr16Ha | ObjRelocKind::PpcAddr16Lo => { ins.code & !0xFFFF } }; } if ins.op == Opcode::Illegal { write!(w, ".4byte {:#010X} /* invalid */", ins.code)?; } else if is_illegal_instruction(ins.code) { let sins = ins.simplified(); write!(w, ".4byte {:#010X} /* illegal: {} */", ins.code, sins)?; } else { let sins = ins.simplified(); write!(w, "{}", sins.mnemonic)?; let mut writing_offset = false; for (i, arg) in sins.args_iter().enumerate() { if !writing_offset { if i == 0 { write!(w, " ")?; } else { write!(w, ", ")?; } } match arg { Argument::Uimm(_) | Argument::Simm(_) | Argument::BranchDest(_) => { if let Some(reloc) = reloc { write_reloc(w, symbols, reloc)?; } else { write!(w, "{arg}")?; } } Argument::Offset(_) => { if let Some(reloc) = reloc { write_reloc(w, symbols, reloc)?; } else { write!(w, "{arg}")?; } write!(w, "(")?; writing_offset = true; continue; } _ => { write!(w, "{arg}")?; } } if writing_offset { write!(w, ")")?; writing_offset = false; } } } writeln!(w)?; Ok(()) } fn write_reloc(w: &mut W, symbols: &[ObjSymbol], reloc: &ObjReloc) -> Result<()> where W: Write + ?Sized { write_reloc_symbol(w, symbols, reloc)?; match reloc.kind { ObjRelocKind::Absolute | ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 => { // pass } ObjRelocKind::PpcAddr16Hi => { write!(w, "@h")?; } ObjRelocKind::PpcAddr16Ha => { write!(w, "@ha")?; } ObjRelocKind::PpcAddr16Lo => { write!(w, "@l")?; } ObjRelocKind::PpcEmbSda21 => { write!(w, "@sda21")?; } } Ok(()) } fn write_symbol_entry( w: &mut W, symbols: &[ObjSymbol], entry: &SymbolEntry, section: &ObjSection, ) -> Result<()> where W: Write + ?Sized, { let symbol = &symbols[entry.index as usize]; // Skip writing certain symbols if symbol.kind == ObjSymbolKind::Section { return Ok(()); } let symbol_kind = match symbol.kind { ObjSymbolKind::Function => "fn", ObjSymbolKind::Object => "obj", ObjSymbolKind::Unknown => "sym", ObjSymbolKind::Section => bail!("Attempted to write section symbol: {symbol:?}"), }; let scope = if symbol.flags.is_weak() { "weak" } else if symbol.flags.is_local() { "local" } else { // Default to global "global" }; match entry.kind { SymbolEntryKind::Label => { if symbol.name.starts_with(".L") { write_symbol_name(w, &symbol.name)?; writeln!(w, ":")?; } else { write!(w, ".sym ")?; write_symbol_name(w, &symbol.name)?; writeln!(w, ", {scope}")?; } } SymbolEntryKind::Start => { if symbol.kind != ObjSymbolKind::Unknown { writeln!(w)?; } write!(w, "# {}:{:#X}", section.name, symbol.address)?; if let Some(section_address) = section.virtual_address { write!(w, " | {:#X}", section_address + symbol.address)?; } writeln!(w, " | size: {:#X}", symbol.size)?; if let Some(name) = &symbol.demangled_name { writeln!(w, "# {name}")?; } write!(w, ".{symbol_kind} ")?; write_symbol_name(w, &symbol.name)?; writeln!(w, ", {scope}")?; } SymbolEntryKind::End => { write!(w, ".end{symbol_kind} ")?; write_symbol_name(w, &symbol.name)?; writeln!(w)?; } } if matches!(entry.kind, SymbolEntryKind::Start | SymbolEntryKind::Label) && symbol.flags.is_hidden() { write!(w, ".hidden ")?; write_symbol_name(w, &symbol.name)?; writeln!(w)?; } if entry.kind == SymbolEntryKind::Start && section.name == "extab" { writeln!(w, "/*")?; match parse_extab(symbols, entry, section) { Ok(s) => { for line in s.trim_end().lines() { writeln!(w, " * {}", line)?; } } Err(e) => { log::warn!("Failed to decode extab entry {}: {}", symbol.name, e); writeln!(w, " * Failed to decode extab entry: {}", e)?; } } writeln!(w, " */")?; } Ok(()) } fn parse_extab(symbols: &[ObjSymbol], entry: &SymbolEntry, section: &ObjSection) -> Result { let symbol = &symbols[entry.index as usize]; let data = section.symbol_data(symbol)?; let decoded = cwextab::decode_extab(data)?; let function_names = section .relocations .range(symbol.address as u32..(symbol.address + symbol.size) as u32) .map(|(_, reloc)| symbols[reloc.target_symbol as usize].name.clone()) .collect_vec(); decoded.to_string(function_names).ok_or_else(|| anyhow!("Failed to print extab entry")) } #[allow(clippy::too_many_arguments)] fn write_data( w: &mut W, symbols: &[ObjSymbol], entries: &BTreeMap>, relocations: &BTreeMap, section: &ObjSection, start: u32, end: u32, section_entries: &[BTreeMap>], ) -> Result<()> where W: Write + ?Sized, { let mut entry_iter = entries.range(start..end); let mut reloc_iter = relocations.range(start..end); let mut current_address = start; let mut current_symbol_kind = ObjSymbolKind::Unknown; let mut current_data_kind = ObjDataKind::Unknown; let mut entry = entry_iter.next(); let mut reloc = reloc_iter.next(); let mut begin = true; loop { if current_address == end { break; } if let Some((&sym_addr, vec)) = entry { #[allow(clippy::comparison_chain)] if current_address == sym_addr { for entry in vec { if entry.kind == SymbolEntryKind::End && begin { continue; } write_symbol_entry(w, symbols, entry, section)?; } current_symbol_kind = find_symbol_kind(current_symbol_kind, symbols, vec)?; current_data_kind = find_data_kind(current_data_kind, symbols, vec) .with_context(|| format!("At address {:#010X}", sym_addr))?; entry = entry_iter.next(); } else if current_address > sym_addr { let dbg_symbols = vec.iter().map(|e| &symbols[e.index as usize]).collect_vec(); bail!( "Unaligned symbol entry @ {:#010X}:\n\t{:?}", section.virtual_address.unwrap_or(0) as u32 + sym_addr, dbg_symbols ); } } begin = false; let symbol_kind = if current_symbol_kind == ObjSymbolKind::Unknown { match section.kind { ObjSectionKind::Code => ObjSymbolKind::Function, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData | ObjSectionKind::Bss => { ObjSymbolKind::Object } } } else { current_symbol_kind }; if let Some((&reloc_addr, r)) = reloc { if current_address == reloc_addr { reloc = reloc_iter.next(); match symbol_kind { ObjSymbolKind::Object => { current_address = write_data_reloc(w, symbols, entries, reloc_addr, r, section_entries)?; continue; } ObjSymbolKind::Function => { // handled in write_code_chunk } ObjSymbolKind::Unknown | ObjSymbolKind::Section => unreachable!(), } } } let until = match (entry, reloc) { (Some((sym_addr, _)), Some((reloc_addr, _))) => min(*reloc_addr, *sym_addr), (Some((addr, _)), None) | (None, Some((addr, _))) => *addr, (None, None) => end, }; ensure!( until > current_address, "Invalid address range: {}..{}\n\tNext entry: {:?}\n\tNext reloc: {:?}", current_address, until, entries, reloc ); let data = §ion.data[(current_address - section.address as u32) as usize ..(until - section.address as u32) as usize]; if symbol_kind == ObjSymbolKind::Function { ensure!( current_address & 3 == 0 && data.len() & 3 == 0, "Unaligned code write @ {} {:#010X} size {:#X}\n\tNext entry: {:?}\n\tNext reloc: {:?}", section.name, current_address, data.len(), entry, reloc, ); write_code_chunk(w, symbols, entries, relocations, section, current_address, data)?; } else { write_data_chunk(w, data, current_data_kind)?; } current_address = until; } Ok(()) } fn find_symbol_kind( current: ObjSymbolKind, symbols: &[ObjSymbol], entries: &Vec, ) -> Result { let mut kind = current; let mut found = false; for entry in entries { match entry.kind { SymbolEntryKind::Start => { let new_kind = symbols[entry.index as usize].kind; if !matches!(new_kind, ObjSymbolKind::Unknown | ObjSymbolKind::Section) { ensure!( !found || new_kind == kind, "Conflicting symbol kinds found: {kind:?} and {new_kind:?}" ); kind = new_kind; found = true; } } _ => continue, } } Ok(kind) } fn find_data_kind( current_data_kind: ObjDataKind, symbols: &[ObjSymbol], entries: &Vec, ) -> Result { let mut kind = ObjDataKind::Unknown; let mut found = false; for entry in entries { match entry.kind { SymbolEntryKind::Start => { let new_kind = symbols[entry.index as usize].data_kind; if !matches!(new_kind, ObjDataKind::Unknown) { if found && new_kind != kind { for entry in entries { log::error!("Symbol {:?}", symbols[entry.index as usize]); } bail!( "Conflicting data kinds found: {kind:?} and {new_kind:?}", kind = kind, new_kind = new_kind ); } found = true; kind = new_kind; } } SymbolEntryKind::Label => { // If type is a local label, don't change data types if !found { kind = current_data_kind; } } _ => continue, } } Ok(kind) } fn write_string(w: &mut W, data: &[u8]) -> Result<()> where W: Write + ?Sized { let terminated = matches!(data.last(), Some(&b) if b == 0); if terminated { write!(w, "\t.string \"")?; } else { write!(w, "\t.ascii \"")?; } for &b in &data[..data.len() - if terminated { 1 } else { 0 }] { match b as char { '\x08' => write!(w, "\\b")?, '\x09' => write!(w, "\\t")?, '\x0A' => write!(w, "\\n")?, '\x0C' => write!(w, "\\f")?, '\x0D' => write!(w, "\\r")?, '\\' => write!(w, "\\\\")?, '"' => write!(w, "\\\"")?, c if c.is_ascii_graphic() || c.is_ascii_whitespace() => write!(w, "{}", c)?, _ => write!(w, "\\{:03o}", b)?, } } writeln!(w, "\"")?; Ok(()) } fn write_string16(w: &mut W, data: &[u16]) -> Result<()> where W: Write + ?Sized { if matches!(data.last(), Some(&b) if b == 0) { write!(w, "\t.string16 \"")?; } else { bail!("Non-terminated UTF-16 string"); } if data.len() > 1 { for result in std::char::decode_utf16(data[..data.len() - 1].iter().cloned()) { let c = match result { Ok(c) => c, Err(_) => bail!("Failed to decode UTF-16"), }; match c { '\x08' => write!(w, "\\b")?, '\x09' => write!(w, "\\t")?, '\x0A' => write!(w, "\\n")?, '\x0C' => write!(w, "\\f")?, '\x0D' => write!(w, "\\r")?, '\\' => write!(w, "\\\\")?, '"' => write!(w, "\\\"")?, c if c.is_ascii_graphic() || c.is_ascii_whitespace() => write!(w, "{}", c)?, _ => write!(w, "\\{:#X}", c as u32)?, } } } writeln!(w, "\"")?; Ok(()) } fn write_data_chunk(w: &mut W, data: &[u8], data_kind: ObjDataKind) -> Result<()> where W: Write + ?Sized { let remain = data; match data_kind { ObjDataKind::String => { return write_string(w, data); } ObjDataKind::String16 => { if data.len() % 2 != 0 { bail!("Attempted to write wstring with length {:#X}", data.len()); } let data = data .chunks_exact(2) .map(|c| u16::from_be_bytes(c.try_into().unwrap())) .collect::>(); return write_string16(w, &data); } ObjDataKind::StringTable => { for slice in data.split_inclusive(|&b| b == 0) { write_string(w, slice)?; } return Ok(()); } ObjDataKind::String16Table => { if data.len() % 2 != 0 { bail!("Attempted to write wstring_table with length {:#X}", data.len()); } let data = data .chunks_exact(2) .map(|c| u16::from_be_bytes(c.try_into().unwrap())) .collect::>(); for slice in data.split_inclusive(|&b| b == 0) { write_string16(w, slice)?; } return Ok(()); } _ => {} } let chunk_size = match data_kind { ObjDataKind::Byte2 | ObjDataKind::Short => 2, ObjDataKind::Unknown | ObjDataKind::Byte4 | ObjDataKind::Float | ObjDataKind::Int => 4, ObjDataKind::Byte | ObjDataKind::Byte8 | ObjDataKind::Double => 8, ObjDataKind::String | ObjDataKind::String16 | ObjDataKind::StringTable | ObjDataKind::String16Table => unreachable!(), }; for chunk in remain.chunks(chunk_size) { if data_kind == ObjDataKind::Byte || matches!(chunk.len(), 1 | 3 | 5..=7) { let bytes = chunk.iter().map(|c| format!("{:#04X}", c)).collect::>(); writeln!(w, "\t.byte {}", bytes.join(", "))?; } else { match chunk.len() { 8 if data_kind == ObjDataKind::Double => { let data = f64::from_be_bytes(chunk.try_into().unwrap()); if data.is_nan() { let int_data = u64::from_be_bytes(chunk.try_into().unwrap()); writeln!(w, "\t.8byte {int_data:#018X} # {data}")?; } else { writeln!(w, "\t.double {data}")?; } } 8 => { let data = u64::from_be_bytes(chunk.try_into().unwrap()); writeln!(w, "\t.8byte {data:#018X}")?; } 4 if data_kind == ObjDataKind::Float => { let data = f32::from_be_bytes(chunk.try_into().unwrap()); if data.is_nan() { let int_data = u32::from_be_bytes(chunk.try_into().unwrap()); writeln!(w, "\t.4byte {int_data:#010X} # {data}")?; } else { writeln!(w, "\t.float {data}")?; } } 4 if data_kind == ObjDataKind::Int => { let data = i32::from_be_bytes(chunk.try_into().unwrap()); writeln!(w, "\t.int {data}")?; } 4 => { let data = u32::from_be_bytes(chunk.try_into().unwrap()); writeln!(w, "\t.4byte {data:#010X}")?; } 2 if data_kind == ObjDataKind::Short => { let data = i16::from_be_bytes(chunk.try_into().unwrap()); writeln!(w, "\t.short {data}")?; } 2 => { writeln!(w, "\t.2byte {:#06X}", u16::from_be_bytes(chunk.try_into().unwrap()))?; } _ => unreachable!(), } } } Ok(()) } fn write_data_reloc( w: &mut W, symbols: &[ObjSymbol], _entries: &BTreeMap>, reloc_address: u32, reloc: &ObjReloc, section_entries: &[BTreeMap>], ) -> Result where W: Write + ?Sized, { match reloc.kind { ObjRelocKind::Absolute => { // Attempt to use .rel macro for relative relocations if reloc.addend != 0 { let target = &symbols[reloc.target_symbol as usize]; let target_addr = (target.address as i64 + reloc.addend) as u32; if let Some(entry) = target .section .and_then(|section_idx| section_entries[section_idx as usize].get(&target_addr)) .and_then(|entries| entries.iter().find(|e| e.kind == SymbolEntryKind::Label)) { let symbol = &symbols[entry.index as usize]; write!(w, "\t.rel ")?; write_symbol_name(w, &target.name)?; write!(w, ", ")?; write_symbol_name(w, &symbol.name)?; writeln!(w)?; return Ok(reloc_address + 4); } } write!(w, "\t.4byte ")?; write_reloc_symbol(w, symbols, reloc)?; writeln!(w)?; Ok(reloc_address + 4) } _ => Err(anyhow!( "Unsupported data relocation type {:?} @ {:#010X}", reloc.kind, reloc_address )), } } fn write_bss( w: &mut W, symbols: &[ObjSymbol], entries: &BTreeMap>, section: &ObjSection, start: u32, end: u32, ) -> Result<()> where W: Write + ?Sized, { let mut entry_iter = entries.range(start..end); let mut current_address = start; let mut entry = entry_iter.next(); let mut begin = true; loop { if current_address == end { break; } if let Some((sym_addr, vec)) = entry { if current_address == *sym_addr { for entry in vec { if entry.kind == SymbolEntryKind::End && begin { continue; } write_symbol_entry(w, symbols, entry, section)?; } entry = entry_iter.next(); } } begin = false; let until = entry.map(|(addr, _)| *addr).unwrap_or(end); let size = until - current_address; if size > 0 { writeln!(w, "\t.skip {size:#X}")?; } current_address = until; } Ok(()) } fn write_section_header( w: &mut W, section: &ObjSection, subsection: usize, start: u32, end: u32, ) -> Result<()> where W: Write + ?Sized, { let section_virtual_address = section.virtual_address.unwrap_or(0); writeln!( w, "\n# {:#010X}..{:#010X} | size: {:#X}", start as u64 + section_virtual_address, end as u64 + section_virtual_address, end - start )?; match section.name.as_str() { ".text" if subsection == 0 => { write!(w, "{}", section.name)?; } // .bss excluded to support < r40 devkitPro ".data" | ".rodata" if subsection == 0 => { write!(w, "{}", section.name)?; } ".text" | ".init" => { write!(w, ".section {}", section.name)?; write!(w, ", \"ax\"")?; } ".data" | ".sdata" => { write!(w, ".section {}", section.name)?; write!(w, ", \"wa\"")?; } ".rodata" | ".sdata2" => { write!(w, ".section {}", section.name)?; write!(w, ", \"a\"")?; } ".bss" | ".sbss" => { write!(w, ".section {}", section.name)?; write!(w, ", \"wa\", @nobits")?; } ".sbss2" => { write!(w, ".section {}", section.name)?; write!(w, ", \"a\", @nobits")?; } ".ctors" | ".dtors" | ".ctors$10" | ".dtors$10" | ".dtors$15" | "extab" | "extabindex" | ".BINARY" => { write!(w, ".section {}", section.name)?; write!(w, ", \"a\"")?; } ".comment" => { write!(w, ".section {}", section.name)?; write!(w, ", \"\"")?; } name => { log::warn!("Unknown section {name}"); write!(w, ".section {}", section.name)?; if section.kind == ObjSectionKind::Bss { write!(w, ", \"\", @nobits")?; } } }; if subsection != 0 { write!(w, ", unique, {subsection}")?; } writeln!(w)?; if section.align != 0 { writeln!(w, ".balign {}", section.align)?; } Ok(()) } fn write_reloc_symbol( w: &mut W, symbols: &[ObjSymbol], reloc: &ObjReloc, ) -> std::io::Result<()> where W: Write + ?Sized, { write_symbol_name(w, &symbols[reloc.target_symbol as usize].name)?; match reloc.addend.cmp(&0i64) { Ordering::Greater => write!(w, "+{:#X}", reloc.addend), Ordering::Less => write!(w, "-{:#X}", -reloc.addend), Ordering::Equal => Ok(()), } } fn write_symbol_name(w: &mut W, name: &str) -> std::io::Result<()> where W: Write + ?Sized { if name.contains('@') || name.contains('<') || name.contains('\\') || name.contains('-') || name.contains('+') { write!(w, "\"{name}\"")?; } else { write!(w, "{name}")?; } Ok(()) } #[inline] fn is_illegal_instruction(code: u32) -> bool { matches!(code, 0x43000000 /* bc 24, lt, 0x0 */ | 0xB8030000 /* lmw r0, 0(r3) */) }