Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 41 additions & 29 deletions src/cmd/rel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -140,34 +141,27 @@ fn match_section_index(
section_index: SectionIndex,
rel_sections: &[RelSectionHeader],
) -> Result<usize> {
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<RelInfo> {
Expand Down Expand Up @@ -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)
Expand All @@ -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)?;
Expand Down
54 changes: 49 additions & 5 deletions src/util/rel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<bool>>,
/// 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<Vec<usize>>,
}

pub const PERMITTED_SECTIONS: [&str; 7] =
Expand Down Expand Up @@ -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;
}
_ => {}
Expand All @@ -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<Vec<Option<usize>>> = 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;
};
Expand Down