diff --git a/src/cmd/rel.rs b/src/cmd/rel.rs index ee642637..a8edf2f4 100644 --- a/src/cmd/rel.rs +++ b/src/cmd/rel.rs @@ -42,7 +42,8 @@ use crate::{ path::native_path, rel::{ PERMITTED_SECTIONS, RelHeader, RelReloc, RelSectionHeader, RelWriteInfo, - print_relocations, process_rel, process_rel_header, process_rel_sections, write_rel, + is_permitted_section, print_relocations, process_rel, process_rel_header, + process_rel_sections, write_rel, }, }, vfs::open_file, @@ -140,34 +141,27 @@ fn match_section_index( section_index: SectionIndex, rel_sections: &[RelSectionHeader], ) -> Result { - let (_, _) = (obj, rel_sections); - Ok(section_index.0) - // TODO - // rel_sections - // .iter() - // .enumerate() - // .filter(|(_, s)| s.size() > 0) - // .zip(obj.sections().filter(|s| s.size() > 0)) - // .find_map( - // |((rel_section_index, _), obj_section)| { - // if obj_section.index() == section_index { - // Some(rel_section_index) - // } else { - // None - // } - // }, - // ) - // .ok_or_else(|| { - // anyhow!( - // "Failed to find matching section index for {} ({}), REL section count: {}", - // obj.section_by_index(section_index) - // .ok() - // .and_then(|s| s.name().ok().map(|s| s.to_string())) - // .unwrap_or("[invalid]".to_string()), - // section_index.0, - // rel_sections.len() - // ) - // }) + rel_sections + .iter() + .enumerate() + .filter(|(_, s)| s.size() > 0) + .zip(obj.sections().filter(|s| is_permitted_section(s) && s.size() > 0)) + .find_map( + |((rel_section_index, _), obj_section)| { + if obj_section.index() == section_index { Some(rel_section_index) } else { None } + }, + ) + .ok_or_else(|| { + anyhow!( + "Failed to find matching section index for {} ({}), REL section count: {}", + obj.section_by_index(section_index) + .ok() + .and_then(|s| s.name().ok().map(|s| s.to_string())) + .unwrap_or("[invalid]".to_string()), + section_index.0, + rel_sections.len() + ) + }) } fn load_rel(module_config: &ModuleConfig, object_base: &ObjectBase) -> Result { @@ -384,6 +378,7 @@ fn make(args: MakeArgs) -> Result<()> { quiet: args.no_warn, section_align: None, section_exec: None, + section_index_map: None, }; if let Some((header, section_headers, section_defs)) = existing_headers.get(&module_info.module_id) @@ -399,6 +394,23 @@ fn make(args: MakeArgs) -> Result<()> { .map(|defs| defs.iter().map(|def| def.align).collect()) .unwrap_or_default(); info.section_exec = Some(section_headers.iter().map(|s| s.exec()).collect()); + // Where each ELF section lands in the original's section table. They + // differ whenever the original reserved a slot that mwld dropped for + // being empty, which shifts everything after it by one. + let mut map = vec![0usize; module_info.file.sections().count()]; + for ((rel_idx, _), obj_section) in + section_headers.iter().enumerate().filter(|(_, s)| s.size() > 0).zip( + module_info.file.sections().filter(|s| is_permitted_section(s) && s.size() > 0), + ) + { + map[obj_section.index().0] = rel_idx; + } + // Only carry the map when it actually differs from the ELF's own + // indices. Projects whose RELs have no dropped slot then take the + // exact path they took before. + if map.iter().enumerate().any(|(elf_idx, &rel_idx)| elf_idx != rel_idx) { + info.section_index_map = Some(map); + } } let rel_path = module_info.path.with_extension("rel"); let mut w = buf_writer(&rel_path)?; diff --git a/src/util/rel.rs b/src/util/rel.rs index bae64c89..75033e10 100644 --- a/src/util/rel.rs +++ b/src/util/rel.rs @@ -691,6 +691,11 @@ pub struct RelWriteInfo { /// This is used to match empty sections: mwld will emit them with /// NULL type, but the original REL may have them marked executable. pub section_exec: Option>, + /// Map from ELF section index to REL section index. + /// Some games reserve an empty section slot that mwld drops during the + /// partial link, because nothing lands in it. Without this the rebuilt + /// REL packs its sections one index lower than the original. + pub section_index_map: Option>, } pub const PERMITTED_SECTIONS: [&str; 7] = @@ -968,21 +973,24 @@ where header.fix_size = Some(offset); } + let map_section = |idx: usize| -> u8 { + info.section_index_map.as_ref().and_then(|m| m.get(idx).copied()).unwrap_or(idx) as u8 + }; for symbol in file.symbols().filter(|s| s.is_definition()) { let Some(symbol_section) = symbol.section_index() else { continue; }; match symbol.name() { Ok("_prolog") => { - header.prolog_section = symbol_section.0 as u8; + header.prolog_section = map_section(symbol_section.0); header.prolog_offset = symbol.address() as u32; } Ok("_epilog") => { - header.epilog_section = symbol_section.0 as u8; + header.epilog_section = map_section(symbol_section.0); header.epilog_offset = symbol.address() as u32; } Ok("_unresolved") => { - header.unresolved_section = symbol_section.0 as u8; + header.unresolved_section = map_section(symbol_section.0); header.unresolved_offset = symbol.address() as u32; } _ => {} @@ -993,9 +1001,45 @@ where ensure!(w.stream_position()? as u32 == header.section_info_offset); let mut current_data_offset = section_data_offset; let mut permitted_section_idx = 0; + // Reverse of section_index_map: for each REL slot, the ELF section that + // belongs in it. Slots with no ELF section are the placeholders mwld drops. + let rel_to_elf: Option>> = info.section_index_map.as_ref().map(|map| { + let mut rev = vec![None; num_sections as usize]; + for (elf_idx, &rel_idx) in map.iter().enumerate() { + if rel_idx < rev.len() { + rev[rel_idx] = Some(elf_idx); + } + } + rev + }); for section_index in 0..num_sections { - let Ok(section) = file.section_by_index(object::SectionIndex(section_index as usize)) - else { + let elf_index = match &rel_to_elf { + Some(rev) => match rev[section_index as usize] { + Some(i) => i, + None => { + // No ELF section belongs in this slot. Two cases: a slot the + // original reserved between real sections, which mwld drops + // for being empty and which points at where its data would + // start; or a trailing slot past the last real section, + // which is left at zero. + let last_real = rev.iter().rposition(|e| e.is_some()).unwrap_or(0); + let (offset, exec) = if (section_index as usize) < last_real { + let exec = info + .section_exec + .as_ref() + .and_then(|m| m.get(section_index as usize).copied()) + .unwrap_or(false); + (current_data_offset, exec) + } else { + (0, false) + }; + RelSectionHeader::new(offset, 0, exec).to_writer(w, Endian::Big)?; + continue; + } + }, + None => section_index as usize, + }; + let Ok(section) = file.section_by_index(object::SectionIndex(elf_index)) else { RelSectionHeader::new(0, 0, false).to_writer(w, Endian::Big)?; continue; };