Rework .splitmeta, now .note.split

Uses actual ELF .note format, which is
more standard and handled better by mwld.
This commit is contained in:
Luke Street 2024-03-04 18:06:21 -07:00
parent c39795ae2c
commit 20e42a499a
3 changed files with 135 additions and 94 deletions

View File

@ -328,13 +328,12 @@ fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u64, u64>>> {
// DWARF 2+ // DWARF 2+
#[cfg(feature = "dwarf")] #[cfg(feature = "dwarf")]
{ {
use std::borrow::Cow;
let dwarf_cow = gimli::Dwarf::load(|id| { let dwarf_cow = gimli::Dwarf::load(|id| {
Ok::<_, gimli::Error>( Ok::<_, gimli::Error>(
obj_file obj_file
.section_by_name(id.name()) .section_by_name(id.name())
.and_then(|section| section.uncompressed_data().ok()) .and_then(|section| section.uncompressed_data().ok())
.unwrap_or(Cow::Borrowed(&[][..])), .unwrap_or(std::borrow::Cow::Borrowed(&[][..])),
) )
})?; })?;
let endian = match obj_file.endianness() { let endian = match obj_file.endianness() {
@ -407,13 +406,7 @@ pub fn has_function(obj_path: &Path, symbol_name: &str) -> Result<bool> {
fn split_meta(obj_file: &File<'_>) -> Result<Option<SplitMeta>> { fn split_meta(obj_file: &File<'_>) -> Result<Option<SplitMeta>> {
Ok(if let Some(section) = obj_file.section_by_name(SPLITMETA_SECTION) { Ok(if let Some(section) = obj_file.section_by_name(SPLITMETA_SECTION) {
if section.size() != 0 { Some(SplitMeta::from_section(section, obj_file.endianness(), obj_file.is_64())?)
let data = section.uncompressed_data()?;
let mut reader = data.as_ref();
Some(SplitMeta::from_reader(&mut reader, obj_file.endianness(), obj_file.is_64())?)
} else {
None
}
} else { } else {
None None
}) })

View File

@ -188,7 +188,7 @@ pub struct ObjSymbol {
pub size_known: bool, pub size_known: bool,
pub flags: ObjSymbolFlagSet, pub flags: ObjSymbolFlagSet,
pub addend: i64, pub addend: i64,
/// Original virtual address (from .splitmeta section) /// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>, pub virtual_address: Option<u64>,
// Diff // Diff
@ -215,7 +215,7 @@ pub struct ObjInfo {
pub common: Vec<ObjSymbol>, pub common: Vec<ObjSymbol>,
/// Line number info (.line or .debug_line section) /// Line number info (.line or .debug_line section)
pub line_info: Option<BTreeMap<u64, u64>>, pub line_info: Option<BTreeMap<u64, u64>>,
/// Split object metadata (.splitmeta section) /// Split object metadata (.note.split section)
pub split_meta: Option<SplitMeta>, pub split_meta: Option<SplitMeta>,
} }

View File

@ -1,13 +1,10 @@
use std::{ use std::{io, io::Write};
io,
io::{Read, Write},
};
use object::{elf::SHT_LOUSER, Endian}; use object::{elf::SHT_NOTE, Endian, ObjectSection};
pub const SPLITMETA_SECTION: &str = ".splitmeta"; pub const SPLITMETA_SECTION: &str = ".note.split";
// Use the same section type as .mwcats.* so the linker ignores it pub const SHT_SPLITMETA: u32 = SHT_NOTE;
pub const SHT_SPLITMETA: u32 = SHT_LOUSER + 0x4A2A82C2; pub const ELF_NOTE_SPLIT: &[u8] = b"Split";
/// This is used to store metadata about the source of an object file, /// 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. /// such as the original virtual addresses and the tool that wrote it.
@ -24,79 +21,50 @@ pub struct SplitMeta {
pub virtual_addresses: Option<Vec<u64>>, pub virtual_addresses: Option<Vec<u64>>,
} }
/** const NT_SPLIT_GENERATOR: u32 = u32::from_be_bytes(*b"GENR");
* .splitmeta section format: const NT_SPLIT_MODULE_NAME: u32 = u32::from_be_bytes(*b"MODN");
* - Magic: "SPMD" const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI");
* - Section: Magic: 4 bytes, Data size: 4 bytes, Data: variable const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
* Section size can be used to skip unknown sections
* - Repeat section until EOF
* Endianness matches the object file
*
* Sections:
* - Generator: Magic: "GENR", Data size: 4 bytes, Data: UTF-8 string (no null terminator)
* - Virtual addresses: Magic: "VIRT", Data size: 4 bytes, Data: array
* Data is u32 array for 32-bit objects, u64 array for 64-bit objects
* Count is size / 4 (32-bit) or size / 8 (64-bit)
*/
const SPLIT_META_MAGIC: [u8; 4] = *b"SPMD";
const GENERATOR_MAGIC: [u8; 4] = *b"GENR";
const MODULE_NAME_MAGIC: [u8; 4] = *b"MODN";
const MODULE_ID_MAGIC: [u8; 4] = *b"MODI";
const VIRTUAL_ADDRESS_MAGIC: [u8; 4] = *b"VIRT";
impl SplitMeta { impl SplitMeta {
pub fn from_reader<E, R>(reader: &mut R, e: E, is_64: bool) -> io::Result<Self> pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> io::Result<Self>
where where E: Endian {
E: Endian,
R: Read + ?Sized,
{
let mut magic = [0; 4];
reader.read_exact(&mut magic)?;
if magic != SPLIT_META_MAGIC {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid split metadata magic"));
}
let mut result = SplitMeta::default(); let mut result = SplitMeta::default();
loop { let data = section.uncompressed_data().map_err(object_io_error)?;
let mut magic = [0; 4]; let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
match reader.read_exact(&mut magic) { while let Some(note) = iter.next(e)? {
Ok(()) => {} if note.name != ELF_NOTE_SPLIT {
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break, continue;
Err(e) => return Err(e), }
}; match note.n_type {
let mut size_bytes = [0; 4]; NT_SPLIT_GENERATOR => {
reader.read_exact(&mut size_bytes)?; let string = String::from_utf8(note.desc.to_vec())
let size = e.read_u32_bytes(size_bytes);
let mut data = vec![0; size as usize];
reader.read_exact(&mut data)?;
match magic {
GENERATOR_MAGIC => {
let string = String::from_utf8(data)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
result.generator = Some(string); result.generator = Some(string);
} }
MODULE_NAME_MAGIC => { NT_SPLIT_MODULE_NAME => {
let string = String::from_utf8(data) let string = String::from_utf8(note.desc.to_vec())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
result.module_name = Some(string); result.module_name = Some(string);
} }
MODULE_ID_MAGIC => { NT_SPLIT_MODULE_ID => {
let id = e.read_u32_bytes(data.as_slice().try_into().map_err(|_| { result.module_id =
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size") io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size")
})?); })?));
result.module_id = Some(id);
} }
VIRTUAL_ADDRESS_MAGIC => { NT_SPLIT_VIRTUAL_ADDRESSES => {
let vec = if is_64 { let vec = if is_64 {
let mut vec = vec![0u64; data.len() / 8]; let mut vec = vec![0u64; note.desc.len() / 8];
for i in 0..vec.len() { for (i, v) in vec.iter_mut().enumerate() {
vec[i] = e.read_u64_bytes(data[i * 8..(i + 1) * 8].try_into().unwrap()); *v =
e.read_u64_bytes(note.desc[i * 8..(i + 1) * 8].try_into().unwrap());
} }
vec vec
} else { } else {
let mut vec = vec![0u64; data.len() / 4]; let mut vec = vec![0u64; note.desc.len() / 4];
for i in 0..vec.len() { for (i, v) in vec.iter_mut().enumerate() {
vec[i] = e.read_u32_bytes(data[i * 4..(i + 1) * 4].try_into().unwrap()) *v = e.read_u32_bytes(note.desc[i * 4..(i + 1) * 4].try_into().unwrap())
as u64; as u64;
} }
vec vec
@ -116,32 +84,29 @@ impl SplitMeta {
E: Endian, E: Endian,
W: Write + ?Sized, W: Write + ?Sized,
{ {
writer.write_all(&SPLIT_META_MAGIC)?;
if let Some(generator) = &self.generator { if let Some(generator) = &self.generator {
writer.write_all(&GENERATOR_MAGIC)?; write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
writer.write_all(&e.write_u32_bytes(generator.len() as u32))?;
writer.write_all(generator.as_bytes())?; writer.write_all(generator.as_bytes())?;
align_to_4(writer, generator.len())?;
} }
if let Some(module_name) = &self.module_name { if let Some(module_name) = &self.module_name {
writer.write_all(&MODULE_NAME_MAGIC)?; write_note_header(writer, e, NT_SPLIT_MODULE_NAME, module_name.len())?;
writer.write_all(&e.write_u32_bytes(module_name.len() as u32))?;
writer.write_all(module_name.as_bytes())?; writer.write_all(module_name.as_bytes())?;
align_to_4(writer, module_name.len())?;
} }
if let Some(module_id) = self.module_id { if let Some(module_id) = self.module_id {
writer.write_all(&MODULE_ID_MAGIC)?; write_note_header(writer, e, NT_SPLIT_MODULE_ID, 4)?;
writer.write_all(&e.write_u32_bytes(4))?;
writer.write_all(&e.write_u32_bytes(module_id))?; writer.write_all(&e.write_u32_bytes(module_id))?;
} }
if let Some(virtual_addresses) = &self.virtual_addresses { if let Some(virtual_addresses) = &self.virtual_addresses {
writer.write_all(&VIRTUAL_ADDRESS_MAGIC)?; let count = virtual_addresses.len();
let count = virtual_addresses.len() as u32; let size = if is_64 { count * 8 } else { count * 4 };
write_note_header(writer, e, NT_SPLIT_VIRTUAL_ADDRESSES, size)?;
if is_64 { if is_64 {
writer.write_all(&e.write_u32_bytes(count * 8))?;
for &addr in virtual_addresses { for &addr in virtual_addresses {
writer.write_all(&e.write_u64_bytes(addr))?; writer.write_all(&e.write_u64_bytes(addr))?;
} }
} else { } else {
writer.write_all(&e.write_u32_bytes(count * 4))?;
for &addr in virtual_addresses { for &addr in virtual_addresses {
writer.write_all(&e.write_u32_bytes(addr as u32))?; writer.write_all(&e.write_u32_bytes(addr as u32))?;
} }
@ -151,19 +116,102 @@ impl SplitMeta {
} }
pub fn write_size(&self, is_64: bool) -> usize { pub fn write_size(&self, is_64: bool) -> usize {
let mut size = 4; let mut size = 0;
if let Some(generator) = self.generator.as_deref() { if let Some(generator) = self.generator.as_deref() {
size += 8 + generator.len(); size += NOTE_HEADER_SIZE + generator.len();
} }
if let Some(module_name) = self.module_name.as_deref() { if let Some(module_name) = self.module_name.as_deref() {
size += 8 + module_name.len(); size += NOTE_HEADER_SIZE + module_name.len();
} }
if self.module_id.is_some() { if self.module_id.is_some() {
size += 12; size += NOTE_HEADER_SIZE + 4;
} }
if let Some(virtual_addresses) = self.virtual_addresses.as_deref() { if let Some(virtual_addresses) = self.virtual_addresses.as_deref() {
size += 8 + if is_64 { 8 } else { 4 } * virtual_addresses.len(); size += NOTE_HEADER_SIZE + if is_64 { 8 } else { 4 } * virtual_addresses.len();
} }
size size
} }
} }
/// Convert an object::read::Error to an io::Error.
fn object_io_error(err: object::read::Error) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, 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<E>>),
B64(object::read::elf::NoteIterator<'data, object::elf::FileHeader64<E>>),
}
impl<'data, E> NoteIterator<'data, E>
where E: Endian
{
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> io::Result<Self> {
Ok(if is_64 {
NoteIterator::B64(
object::read::elf::NoteIterator::new(e, align, data).map_err(object_io_error)?,
)
} else {
NoteIterator::B32(
object::read::elf::NoteIterator::new(e, align as u32, data)
.map_err(object_io_error)?,
)
})
}
fn next(&mut self, e: E) -> io::Result<Option<Note<'data>>> {
match self {
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_io_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_io_error)?.map(|note| Note {
n_type: note.n_type(e),
name: note.name(),
desc: note.desc(),
})),
}
}
}
fn align_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> 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);
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> io::Result<()>
where
E: Endian,
W: 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_to_4(writer, ELF_NOTE_SPLIT.len() + 1)?;
Ok(())
}