diff --git a/Cargo.toml b/Cargo.toml index b08eec80..daf98130 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "object" version = "0.30.0" edition = "2018" exclude = ["/.github", "/testfiles"] -keywords = ["object", "elf", "mach-o", "pe", "coff"] +keywords = ["object", "elf", "mach-o", "pe", "coff", "xcoff"] license = "Apache-2.0 OR MIT" repository = "https://github.com/gimli-rs/object" description = "A unified interface for reading and writing object file formats." @@ -38,7 +38,7 @@ write_core = ["crc32fast", "indexmap", "hashbrown"] # Core write support with libstd features. You will need to enable some file formats too. write_std = ["write_core", "std", "indexmap/std", "crc32fast/std"] # Write support for all file formats, including libstd features. -write = ["write_std", "coff", "elf", "macho", "pe"] +write = ["write_std", "coff", "elf", "macho", "pe", "xcoff"] #======================================= # Misc features. diff --git a/src/write/mod.rs b/src/write/mod.rs index aa4980b1..ee54d546 100644 --- a/src/write/mod.rs +++ b/src/write/mod.rs @@ -29,6 +29,9 @@ mod macho; #[cfg(feature = "pe")] pub mod pe; +#[cfg(feature = "xcoff")] +mod xcoff; + mod string; pub use string::StringId; @@ -218,6 +221,8 @@ impl<'a> Object<'a> { BinaryFormat::Elf => self.elf_section_info(section), #[cfg(feature = "macho")] BinaryFormat::MachO => self.macho_section_info(section), + #[cfg(feature = "xcoff")] + BinaryFormat::Xcoff => self.xcoff_section_info(section), _ => unimplemented!(), } } @@ -243,7 +248,7 @@ impl<'a> Object<'a> { fn has_subsections_via_symbols(&self) -> bool { match self.format { - BinaryFormat::Coff | BinaryFormat::Elf => false, + BinaryFormat::Coff | BinaryFormat::Elf | BinaryFormat::Xcoff => false, BinaryFormat::MachO => true, _ => unimplemented!(), } @@ -506,6 +511,8 @@ impl<'a> Object<'a> { BinaryFormat::Elf => self.elf_fixup_relocation(&mut relocation)?, #[cfg(feature = "macho")] BinaryFormat::MachO => self.macho_fixup_relocation(&mut relocation), + #[cfg(feature = "xcoff")] + BinaryFormat::Xcoff => self.xcoff_fixup_relocation(&mut relocation), _ => unimplemented!(), }; if addend != 0 { @@ -574,6 +581,8 @@ impl<'a> Object<'a> { BinaryFormat::Elf => self.elf_write(buffer), #[cfg(feature = "macho")] BinaryFormat::MachO => self.macho_write(buffer), + #[cfg(feature = "xcoff")] + BinaryFormat::Xcoff => self.xcoff_write(buffer), _ => unimplemented!(), } } @@ -894,6 +903,8 @@ pub enum Mangling { Elf, /// Mach-O symbol mangling. MachO, + /// Xcoff symbol mangling. + Xcoff, } impl Mangling { @@ -904,6 +915,7 @@ impl Mangling { (BinaryFormat::Coff, _) => Mangling::Coff, (BinaryFormat::Elf, _) => Mangling::Elf, (BinaryFormat::MachO, _) => Mangling::MachO, + (BinaryFormat::Xcoff, _) => Mangling::Xcoff, _ => Mangling::None, } } @@ -911,7 +923,7 @@ impl Mangling { /// Return the prefix to use for global symbols. pub fn global_prefix(self) -> Option { match self { - Mangling::None | Mangling::Elf | Mangling::Coff => None, + Mangling::None | Mangling::Elf | Mangling::Coff | Mangling::Xcoff => None, Mangling::CoffI386 | Mangling::MachO => Some(b'_'), } } diff --git a/src/write/xcoff.rs b/src/write/xcoff.rs new file mode 100644 index 00000000..d87eecc8 --- /dev/null +++ b/src/write/xcoff.rs @@ -0,0 +1,408 @@ +use core::mem; + +use crate::endian::{BigEndian as BE, I16, U16, U32}; +use crate::write::string::*; +use crate::write::util::*; +use crate::write::*; + +use crate::{xcoff, AddressSize}; + +#[derive(Default, Clone, Copy)] +struct SectionOffsets { + data_offset: usize, + reloc_offset: usize, +} + +#[derive(Default, Clone, Copy)] +struct SymbolOffsets { + index: usize, + str_id: Option, + aux_count: u8, +} + +impl<'a> Object<'a> { + pub(crate) fn xcoff_section_info( + &self, + section: StandardSection, + ) -> (&'static [u8], &'static [u8], SectionKind) { + match section { + StandardSection::Text => (&[], &b".text"[..], SectionKind::Text), + StandardSection::Data => (&[], &b".data"[..], SectionKind::Data), + StandardSection::ReadOnlyData + | StandardSection::ReadOnlyDataWithRel + | StandardSection::ReadOnlyString => (&[], &b".rdata"[..], SectionKind::ReadOnlyData), + StandardSection::UninitializedData => { + (&[], &b".bss"[..], SectionKind::UninitializedData) + } + // TLS sections are data sections with a special name. + StandardSection::Tls => (&[], &b".tls$"[..], SectionKind::Data), + StandardSection::UninitializedTls => { + // Unsupported section. + (&[], &[], SectionKind::UninitializedTls) + } + StandardSection::TlsVariables => { + // Unsupported section. + (&[], &[], SectionKind::TlsVariables) + } + StandardSection::Common => { + // Unsupported section. + (&[], &[], SectionKind::Common) + } + } + } + + pub(crate) fn xcoff_fixup_relocation(&mut self, mut relocation: &mut Relocation) -> i64 { + let constant = match relocation.kind { + RelocationKind::Relative => relocation.addend + 4, + _ => relocation.addend, + }; + relocation.addend -= constant; + constant + } + + pub(crate) fn xcoff_write(&self, buffer: &mut dyn WritableBuffer) -> Result<()> { + let is_64 = match self.architecture.address_size().unwrap() { + AddressSize::U8 | AddressSize::U16 | AddressSize::U32 => false, + AddressSize::U64 => true, + }; + + let (hdr_size, sechdr_size, rel_size, sym_size) = if is_64 { + ( + mem::size_of::(), + mem::size_of::(), + mem::size_of::(), + mem::size_of::(), + ) + } else { + ( + mem::size_of::(), + mem::size_of::(), + mem::size_of::(), + mem::size_of::(), + ) + }; + + // Calculate offsets and build strtab. + let mut offset = 0; + let mut strtab = StringTable::default(); + + // XCOFF file header. + offset += hdr_size; + // Section headers. + offset += self.sections.len() * sechdr_size; + + // Calculate size of section data. + let mut section_offsets = vec![SectionOffsets::default(); self.sections.len()]; + for (index, section) in self.sections.iter().enumerate() { + let len = section.data.len(); + if len != 0 { + // Set the default section alignment as 4. + offset = align(offset, 4); + section_offsets[index].data_offset = offset; + offset += len; + } else { + section_offsets[index].data_offset = 0; + } + } + + // Calculate size of relocations. + for (index, section) in self.sections.iter().enumerate() { + let count = section.relocations.len(); + if count != 0 { + section_offsets[index].reloc_offset = offset; + offset += count * rel_size; + } else { + section_offsets[index].reloc_offset = 0; + } + } + + // Calculate size of symbols. + let mut symbol_offsets = vec![SymbolOffsets::default(); self.symbols.len()]; + let mut symtab_count = 0; + for (index, symbol) in self.symbols.iter().enumerate() { + symbol_offsets[index].index = symtab_count; + symbol_offsets[index].str_id = Some(strtab.add(&symbol.name)); + symtab_count += 1; + // TODO: support the auxiliary symbols. + symbol_offsets[index].aux_count = 0; + } + let symtab_offset = offset; + let symtab_len = symtab_count * sym_size; + offset += symtab_len; + + // Calculate size of strtab. + let strtab_offset = offset; + let mut strtab_data = Vec::new(); + // First 4 bytes of strtab are the length. + strtab.write(4, &mut strtab_data); + let strtab_len = strtab_data.len() + 4; + offset += strtab_len; + + // Start writing. + buffer + .reserve(offset) + .map_err(|_| Error(String::from("Cannot allocate buffer")))?; + + // Write file header. + if is_64 { + let header = xcoff::FileHeader64 { + f_magic: U16::new(BE, xcoff::MAGIC_64), + f_nscns: U16::new(BE, self.sections.len() as u16), + f_timdat: U32::new(BE, 0), + f_symptr: U64::new(BE, symtab_offset as u64), + f_nsyms: U32::new(BE, symtab_count as u32), + f_opthdr: U16::new(BE, 0), + f_flags: match self.flags { + FileFlags::Xcoff { f_flags } => U16::new(BE, f_flags), + _ => U16::default(), + }, + }; + buffer.write(&header); + } else { + let header = xcoff::FileHeader32 { + f_magic: U16::new(BE, xcoff::MAGIC_32), + f_nscns: U16::new(BE, self.sections.len() as u16), + f_timdat: U32::new(BE, 0), + f_symptr: U32::new(BE, symtab_offset as u32), + f_nsyms: U32::new(BE, symtab_count as u32), + f_opthdr: U16::new(BE, 0), + f_flags: match self.flags { + FileFlags::Xcoff { f_flags } => U16::new(BE, f_flags), + _ => U16::default(), + }, + }; + buffer.write(&header); + } + + // Write section headers. + for (index, section) in self.sections.iter().enumerate() { + let mut sectname = [0; 8]; + sectname + .get_mut(..section.name.len()) + .ok_or_else(|| { + Error(format!( + "section name `{}` is too long", + section.name().unwrap_or(""), + )) + })? + .copy_from_slice(§ion.name); + let flags = if let SectionFlags::Xcoff { s_flags } = section.flags { + s_flags + } else { + match section.kind { + SectionKind::Text | SectionKind::ReadOnlyData | SectionKind::ReadOnlyString => { + xcoff::STYP_TEXT + } + SectionKind::Data => xcoff::STYP_DATA, + SectionKind::UninitializedData => xcoff::STYP_BSS, + SectionKind::Tls => xcoff::STYP_TDATA, + SectionKind::UninitializedTls => xcoff::STYP_TBSS, + SectionKind::OtherString => xcoff::STYP_INFO, + SectionKind::Debug => xcoff::STYP_DEBUG, + SectionKind::Other | SectionKind::Metadata => 0, + SectionKind::Note + | SectionKind::Linker + | SectionKind::Common + | SectionKind::Unknown + | SectionKind::TlsVariables + | SectionKind::Elf(_) => { + return Err(Error(format!( + "unimplemented section `{}` kind {:?}", + section.name().unwrap_or(""), + section.kind + ))); + } + } + .into() + }; + if is_64 { + let section_header = xcoff::SectionHeader64 { + s_name: sectname, + s_paddr: U64::new(BE, 0), + s_vaddr: U64::new(BE, 0), + s_size: U64::new(BE, section.data.len() as u64), + s_scnptr: U64::new(BE, section_offsets[index].data_offset as u64), + s_relptr: U64::new(BE, section_offsets[index].reloc_offset as u64), + s_lnnoptr: U64::new(BE, 0), + s_nreloc: U32::new(BE, section.relocations.len() as u32), + s_nlnno: U32::new(BE, 0), + s_flags: U32::new(BE, flags), + s_reserve: U32::new(BE, 0), + }; + buffer.write(§ion_header); + } else { + let section_header = xcoff::SectionHeader32 { + s_name: sectname, + s_paddr: U32::new(BE, 0), + s_vaddr: U32::new(BE, 0), + s_size: U32::new(BE, section.data.len() as u32), + s_scnptr: U32::new(BE, section_offsets[index].data_offset as u32), + s_relptr: U32::new(BE, section_offsets[index].reloc_offset as u32), + s_lnnoptr: U32::new(BE, 0), + // TODO: If more than 65,534 relocation entries are required, the field + // value will be 65535, and an STYP_OVRFLO section header will contain + // the actual count of relocation entries in the s_paddr field. + s_nreloc: U16::new(BE, section.relocations.len() as u16), + s_nlnno: U16::new(BE, 0), + s_flags: U32::new(BE, flags), + }; + buffer.write(§ion_header); + } + } + + // Write section data. + for (index, section) in self.sections.iter().enumerate() { + let len = section.data.len(); + if len != 0 { + write_align(buffer, 4); + debug_assert_eq!(section_offsets[index].data_offset, buffer.len()); + buffer.write_bytes(§ion.data); + } + } + + // Write relocations. + for (index, section) in self.sections.iter().enumerate() { + if !section.relocations.is_empty() { + debug_assert_eq!(section_offsets[index].reloc_offset, buffer.len()); + for reloc in §ion.relocations { + let rtype = match reloc.kind { + RelocationKind::Absolute => xcoff::R_POS, + RelocationKind::Relative => xcoff::R_REL, + RelocationKind::Got => xcoff::R_TOC, + RelocationKind::Xcoff(x) => x, + _ => { + return Err(Error(format!("unimplemented relocation {:?}", reloc))); + } + }; + if is_64 { + let xcoff_rel = xcoff::Rel64 { + r_vaddr: U64::new(BE, reloc.offset as u64), + r_symndx: U32::new(BE, symbol_offsets[reloc.symbol.0].index as u32), + r_rsize: reloc.size, + r_rtype: rtype, + }; + buffer.write(&xcoff_rel); + } else { + let xcoff_rel = xcoff::Rel32 { + r_vaddr: U32::new(BE, reloc.offset as u32), + r_symndx: U32::new(BE, symbol_offsets[reloc.symbol.0].index as u32), + r_rsize: reloc.size, + r_rtype: rtype, + }; + buffer.write(&xcoff_rel); + } + } + } + } + + // Write symbols. + debug_assert_eq!(symtab_offset, buffer.len()); + for (index, symbol) in self.symbols.iter().enumerate() { + let (section_number, section) = match symbol.section { + SymbolSection::None => { + debug_assert_eq!(symbol.kind, SymbolKind::File); + (xcoff::N_DEBUG, None) + } + SymbolSection::Undefined | SymbolSection::Common => (xcoff::N_UNDEF, None), + SymbolSection::Absolute => (xcoff::N_ABS, None), + SymbolSection::Section(id) => (id.0 as i16 + 1, Some(&self.sections[id.0])), + }; + let mut is_sym_info = false; + if section.is_some() { + if let SectionFlags::Xcoff { s_flags } = section.unwrap().flags { + is_sym_info = (s_flags as u16 & xcoff::STYP_INFO) != 0 + } + } + let storage_class = match symbol.kind { + SymbolKind::File => xcoff::C_FILE, + SymbolKind::Null => { + // C_INFO symbol is now set as a null kind symbol. + if is_sym_info { + xcoff::C_INFO + } else { + xcoff::C_NULL + } + } + SymbolKind::Label => { + if symbol.is_undefined() { + xcoff::C_ULABEL + } else { + xcoff::C_LABEL + } + } + SymbolKind::Tls => { + if symbol.is_local() { + xcoff::C_STTLS + } else { + xcoff::C_GTLS + } + } + SymbolKind::Section | SymbolKind::Data | SymbolKind::Text => { + if symbol.weak { + xcoff::C_WEAKEXT + } else if symbol.is_undefined() { + xcoff::C_HIDEXT + } else { + xcoff::C_EXT + } + } + SymbolKind::Unknown => { + return Err(Error(format!( + "unimplemented symbol `{}` kind {:?}", + symbol.name().unwrap_or(""), + symbol.kind + ))); + } + }; + let sym_type = if (symbol.scope == SymbolScope::Linkage) + && (storage_class == xcoff::C_EXT + || storage_class == xcoff::C_WEAKEXT + || storage_class == xcoff::C_HIDEXT) + { + xcoff::SYM_V_HIDDEN + } else { + 0 + }; + if is_64 { + let xcoff_sym = xcoff::Symbol64 { + n_value: U64::new(BE, symbol.value), + n_offset: U32::new( + BE, + strtab.get_offset(symbol_offsets[index].str_id.unwrap()) as u32, + ), + n_scnum: I16::new(BE, section_number), + n_type: U16::new(BE, sym_type), + n_sclass: storage_class, + n_numaux: symbol_offsets[index].aux_count, + }; + buffer.write(&xcoff_sym); + } else { + let mut sym_name = [0; 8]; + let name = &symbol.name[..]; + if name.len() <= 8 { + sym_name[..name.len()].copy_from_slice(name); + } else { + let str_offset = strtab.get_offset(symbol_offsets[index].str_id.unwrap()); + sym_name[4..8].copy_from_slice(&u32::to_le_bytes(str_offset as u32)); + } + let xcoff_sym = xcoff::Symbol32 { + n_name: sym_name, + n_value: U32::new(BE, symbol.value as u32), + n_scnum: I16::new(BE, section_number), + n_type: U16::new(BE, sym_type), + n_sclass: storage_class, + n_numaux: symbol_offsets[index].aux_count, + }; + buffer.write(&xcoff_sym); + } + } + + // Write string table. + debug_assert_eq!(strtab_offset, buffer.len()); + buffer.write_bytes(&u32::to_be_bytes(strtab_len as u32)); + buffer.write_bytes(&strtab_data); + + debug_assert_eq!(offset, buffer.len()); + Ok(()) + } +} diff --git a/tests/round_trip/mod.rs b/tests/round_trip/mod.rs index 31f7fd78..6a79fb7f 100644 --- a/tests/round_trip/mod.rs +++ b/tests/round_trip/mod.rs @@ -1,7 +1,7 @@ #![cfg(all(feature = "read", feature = "write"))] use object::read::{Object, ObjectSection, ObjectSymbol}; -use object::{read, write}; +use object::{read, write, SectionIndex}; use object::{ Architecture, BinaryFormat, Endianness, RelocationEncoding, RelocationKind, SectionKind, SymbolFlags, SymbolKind, SymbolScope, SymbolSection, @@ -447,3 +447,97 @@ fn macho_x86_64() { assert_eq!(symbol.name(), "_func1"); assert_eq!(map.get(func1_offset - 1), None); } + +#[test] +fn xcoff_powerpc() { + for arch in [Architecture::PowerPc, Architecture::PowerPc64] { + let mut object = write::Object::new(BinaryFormat::Xcoff, arch, Endianness::Big); + + object.add_file_symbol(b"file.c".to_vec()); + + let text = object.section_id(write::StandardSection::Text); + object.append_section_data(text, &[1; 30], 4); + + let func1_offset = object.append_section_data(text, &[1; 30], 4); + assert_eq!(func1_offset, 32); + let func1_symbol = object.add_symbol(write::Symbol { + name: b"func1".to_vec(), + value: func1_offset, + size: 32, + kind: SymbolKind::Text, + scope: SymbolScope::Linkage, + weak: false, + section: write::SymbolSection::Section(text), + flags: SymbolFlags::None, + }); + + object + .add_relocation( + text, + write::Relocation { + offset: 8, + size: 64, + kind: RelocationKind::Absolute, + encoding: RelocationEncoding::Generic, + symbol: func1_symbol, + addend: 0, + }, + ) + .unwrap(); + + let bytes = object.write().unwrap(); + let object = read::File::parse(&*bytes).unwrap(); + assert_eq!(object.format(), BinaryFormat::Xcoff); + assert_eq!(object.architecture(), arch); + assert_eq!(object.endianness(), Endianness::Big); + + let mut sections = object.sections(); + + let text = sections.next().unwrap(); + println!("{:?}", text); + let text_index = text.index().0 + 1; + assert_eq!(text.name(), Ok(".text")); + assert_eq!(text.kind(), SectionKind::Text); + assert_eq!(text.address(), 0); + assert_eq!(text.size(), 62); + assert_eq!(&text.data().unwrap()[..30], &[1; 30]); + assert_eq!(&text.data().unwrap()[32..62], &[1; 30]); + + let mut symbols = object.symbols(); + + let mut symbol = symbols.next().unwrap(); + println!("{:?}", symbol); + assert_eq!(symbol.name(), Ok("file.c")); + assert_eq!(symbol.address(), 0); + assert_eq!(symbol.kind(), SymbolKind::File); + assert_eq!(symbol.section_index(), None); + assert_eq!(symbol.scope(), SymbolScope::Compilation); + assert_eq!(symbol.is_weak(), false); + assert_eq!(symbol.is_undefined(), false); + + symbol = symbols.next().unwrap(); + println!("{:?}", symbol); + let func1_symbol = symbol.index(); + assert_eq!(symbol.name(), Ok("func1")); + assert_eq!(symbol.address(), func1_offset); + assert_eq!(symbol.kind(), SymbolKind::Text); + assert_eq!(symbol.section_index(), Some(SectionIndex(text_index))); + assert_eq!(symbol.scope(), SymbolScope::Linkage); + assert_eq!(symbol.is_weak(), false); + assert_eq!(symbol.is_undefined(), false); + + let mut relocations = text.relocations(); + + let (offset, relocation) = relocations.next().unwrap(); + println!("{:?}", relocation); + assert_eq!(offset, 8); + assert_eq!(relocation.kind(), RelocationKind::Absolute); + assert_eq!(relocation.encoding(), RelocationEncoding::Generic); + assert_eq!(relocation.size(), 64); + assert_eq!( + relocation.target(), + read::RelocationTarget::Symbol(func1_symbol) + ); + assert_eq!(relocation.addend(), 0); + } +}