use alloc::{string::String, vec, vec::Vec}; use anyhow::{Result, anyhow}; use object::{Endian, ObjectSection, elf::SHT_NOTE}; pub const SPLITMETA_SECTION: &str = ".note.split"; pub const SHT_SPLITMETA: u32 = SHT_NOTE; pub const ELF_NOTE_SPLIT: &[u8] = b"Split"; /// This is used to store metadata about the source of an object file, /// such as the original virtual addresses and the tool that wrote it. #[derive(Debug, Default, Clone)] pub struct SplitMeta { /// The tool that generated the object. Informational only. pub generator: Option, /// The name of the source module. (e.g. the DOL or REL name) pub module_name: Option, /// The ID of the source module. (e.g. the DOL or REL ID) pub module_id: Option, /// Original virtual addresses of each symbol in the object. /// Index 0 is the ELF null symbol. pub virtual_addresses: Option>, } const NT_SPLIT_GENERATOR: u32 = u32::from_be_bytes(*b"GENR"); const NT_SPLIT_MODULE_NAME: u32 = u32::from_be_bytes(*b"MODN"); const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI"); const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT"); impl SplitMeta { pub fn from_section(section: object::Section, e: E, is_64: bool) -> Result where E: Endian { let mut result = SplitMeta::default(); let data = section.uncompressed_data().map_err(object_error)?; let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?; while let Some(note) = iter.next(e)? { if note.name != ELF_NOTE_SPLIT { continue; } match note.n_type { NT_SPLIT_GENERATOR => { let string = String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?; result.generator = Some(string); } NT_SPLIT_MODULE_NAME => { let string = String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?; result.module_name = Some(string); } NT_SPLIT_MODULE_ID => { result.module_id = Some(e.read_u32_bytes( note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?, )); } NT_SPLIT_VIRTUAL_ADDRESSES => { let vec = if is_64 { let mut vec = vec![0u64; note.desc.len() / 8]; for (i, v) in vec.iter_mut().enumerate() { *v = e.read_u64_bytes(note.desc[i * 8..(i + 1) * 8].try_into().unwrap()); } vec } else { let mut vec = vec![0u64; note.desc.len() / 4]; for (i, v) in vec.iter_mut().enumerate() { *v = e.read_u32_bytes(note.desc[i * 4..(i + 1) * 4].try_into().unwrap()) as u64; } vec }; result.virtual_addresses = Some(vec); } _ => { // Ignore unknown sections } } } Ok(result) } #[cfg(feature = "std")] pub fn to_writer(&self, writer: &mut W, e: E, is_64: bool) -> std::io::Result<()> where E: Endian, W: std::io::Write + ?Sized, { if let Some(generator) = &self.generator { write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?; writer.write_all(generator.as_bytes())?; align_data_to_4(writer, generator.len())?; } if let Some(module_name) = &self.module_name { write_note_header(writer, e, NT_SPLIT_MODULE_NAME, module_name.len())?; writer.write_all(module_name.as_bytes())?; align_data_to_4(writer, module_name.len())?; } if let Some(module_id) = self.module_id { write_note_header(writer, e, NT_SPLIT_MODULE_ID, 4)?; writer.write_all(&e.write_u32_bytes(module_id))?; } if let Some(virtual_addresses) = &self.virtual_addresses { let count = virtual_addresses.len(); let size = if is_64 { count * 8 } else { count * 4 }; write_note_header(writer, e, NT_SPLIT_VIRTUAL_ADDRESSES, size)?; if is_64 { for &addr in virtual_addresses { writer.write_all(&e.write_u64_bytes(addr))?; } } else { for &addr in virtual_addresses { writer.write_all(&e.write_u32_bytes(addr as u32))?; } } } Ok(()) } pub fn write_size(&self, is_64: bool) -> usize { let mut size = 0; if let Some(generator) = self.generator.as_deref() { size += NOTE_HEADER_SIZE + generator.len(); size = align_size_to_4(size); } if let Some(module_name) = self.module_name.as_deref() { size += NOTE_HEADER_SIZE + module_name.len(); size = align_size_to_4(size); } if self.module_id.is_some() { size += NOTE_HEADER_SIZE + 4; size = align_size_to_4(size); } if let Some(virtual_addresses) = self.virtual_addresses.as_deref() { size += NOTE_HEADER_SIZE + if is_64 { 8 } else { 4 } * virtual_addresses.len(); size = align_size_to_4(size); } size } } /// Convert an object::read::Error to a String. #[inline] fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) } /// An ELF note entry. struct Note<'data> { n_type: u32, name: &'data [u8], desc: &'data [u8], } /// object::read::elf::NoteIterator is awkward to use generically, /// so wrap it in our own iterator. enum NoteIterator<'data, E> where E: Endian { B32(object::read::elf::NoteIterator<'data, object::elf::FileHeader32>), B64(object::read::elf::NoteIterator<'data, object::elf::FileHeader64>), } impl<'data, E> NoteIterator<'data, E> where E: Endian { fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> Result { Ok(if is_64 { NoteIterator::B64( object::read::elf::NoteIterator::new(e, align, data).map_err(object_error)?, ) } else { NoteIterator::B32( object::read::elf::NoteIterator::new(e, align as u32, data) .map_err(object_error)?, ) }) } fn next(&mut self, e: E) -> Result>> { match self { NoteIterator::B32(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note { n_type: note.n_type(e), name: note.name(), desc: note.desc(), })), NoteIterator::B64(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note { n_type: note.n_type(e), name: note.name(), desc: note.desc(), })), } } } fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 } #[cfg(feature = "std")] fn align_data_to_4(writer: &mut W, len: usize) -> std::io::Result<()> { const ALIGN_BYTES: &[u8] = &[0; 4]; if len % 4 != 0 { writer.write_all(&ALIGN_BYTES[..4 - len % 4])?; } Ok(()) } // ELF note format: // Name Size | 4 bytes (integer) // Desc Size | 4 bytes (integer) // Type | 4 bytes (usually interpreted as an integer) // Name | variable size, padded to a 4 byte boundary // Desc | variable size, padded to a 4 byte boundary const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3); #[cfg(feature = "std")] fn write_note_header(writer: &mut W, e: E, kind: u32, desc_len: usize) -> std::io::Result<()> where E: Endian, W: std::io::Write + ?Sized, { writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size writer.write_all(&e.write_u32_bytes(kind))?; // Type writer.write_all(ELF_NOTE_SPLIT)?; // Name writer.write_all(&[0; 1])?; // Null terminator align_data_to_4(writer, ELF_NOTE_SPLIT.len() + 1)?; Ok(()) }