BizHawk/waterbox/waterboxhost/src/elf.rs

224 lines
6.7 KiB
Rust

use goblin;
use goblin::{elf::Elf, elf64::{sym::*, section_header::*}};
use crate::*;
use crate::memory_block::Protection;
use std::collections::HashMap;
use memory_block::MemoryBlock;
/// Special system import area
const INFO_OBJECT_NAME: &str = "__wbxsysinfo";
/// Section names that are not marked as readonly, but we'll make them readonly anyway
fn section_name_is_readonly(name: &str) -> bool {
name.contains(".rel.ro")
|| name.starts_with(".got")
|| name == ".init_array"
|| name == ".fini_array"
|| name == ".tbss"
|| name == ".sealed"
}
pub struct SectionInfo {
name: String,
addr: AddressRange,
}
pub struct ElfLoader {
sections: Vec<SectionInfo>,
exports: HashMap<String, AddressRange>,
entry_point: usize,
hash: Vec<u8>,
}
impl ElfLoader {
pub fn elf_addr(wbx: &Elf) -> AddressRange {
let start = wbx.program_headers.iter()
.filter(|x| x.p_vaddr != 0)
.map(|x| x.vm_range().start)
.min()
.unwrap();
let end = wbx.program_headers.iter()
.filter(|x| x.p_vaddr != 0)
.map(|x| x.vm_range().end)
.max()
.unwrap();
return AddressRange { start, size: end - start };
}
pub fn new(wbx: &Elf, data: &[u8],
module_name: &str,
layout: &WbxSysLayout,
b: &mut MemoryBlock
) -> anyhow::Result<ElfLoader> {
println!("Mouting `{}` @{:x}", module_name, layout.elf.start);
println!(" Sections:");
let mut sections = Vec::new();
for section in wbx.section_headers.iter() {
let name = match wbx.shdr_strtab.get(section.sh_name) {
Some(Ok(s)) => s,
_ => "<anon>"
};
println!(" @{:x}:{:x} {}{}{} `{}` {} bytes",
section.sh_addr,
section.sh_addr + section.sh_size,
if section.sh_flags & (SHF_ALLOC as u64) != 0 { "R" } else { " " },
if section.sh_flags & (SHF_WRITE as u64) != 0 { "W" } else { " " },
if section.sh_flags & (SHF_EXECINSTR as u64) != 0 { "X" } else { " " },
name,
section.sh_size
);
if section.sh_type != SHT_NOBITS
&& name != "<anon>"
&& section.sh_addr != 0 {
let si = SectionInfo {
name: name.to_string(),
addr: AddressRange {
start: section.sh_addr as usize,
size: section.sh_size as usize
}
};
sections.push(si);
}
}
let mut exports = HashMap::new();
let mut info_area_opt = None;
for sym in wbx.syms.iter() {
let name = match wbx.strtab.get(sym.st_name) {
Some(Ok(s)) => s,
_ => continue
};
if sym.st_visibility() == STV_DEFAULT && sym.st_bind() == STB_GLOBAL {
exports.insert(
name.to_string(),
AddressRange { start: sym.st_value as usize, size: sym.st_size as usize }
);
}
if name == INFO_OBJECT_NAME {
info_area_opt = Some(AddressRange { start: sym.st_value as usize, size: sym.st_size as usize });
}
}
{
let invis_opt = sections.iter().find(|x| x.name == ".invis");
if let Some(invis) = invis_opt {
for s in sections.iter() {
if s.addr.align_expand().start < invis.addr.align_expand().start {
if s.addr.align_expand().end() > invis.addr.align_expand().start {
return Err(anyhow!("When aligned, {} partially overlaps .invis from below -- check linkscript.", s.name))
}
} else if s.addr.align_expand().start > invis.addr.align_expand().start {
if invis.addr.align_expand().end() > s.addr.align_expand().start {
return Err(anyhow!("When aligned, {} partially overlaps .invis from above -- check linkscript.", s.name))
}
} else {
if s.addr.align_expand().size != 0 && s.name != ".invis" {
return Err(anyhow!("When aligned, {} partially overlays .invis -- check linkscript", s.name))
}
}
}
b.mark_invisible(invis.addr.align_expand())?;
}
}
b.mark_invisible(layout.invis)?;
println!(" Segments:");
for segment in wbx.program_headers.iter().filter(|x| x.p_vaddr != 0) {
let addr = AddressRange {
start: segment.vm_range().start,
size: segment.vm_range().end - segment.vm_range().start
};
let prot_addr = addr.align_expand();
let prot = match (segment.is_read(), segment.is_write(), segment.is_executable()) {
(false, false, false) => Protection::None,
(true, false, false) => Protection::R,
(_, false, true) => Protection::RX,
(_, true, false) => Protection::RW,
(_, true, true) => Protection::RWX
};
println!(" %{:x}:{:x} {}{}{} {} bytes",
addr.start,
addr.end(),
if segment.is_read() { "R" } else { " " },
if segment.is_write() { "W" } else { " " },
if segment.is_executable() { "X" } else { " " },
addr.size
);
if prot_addr.size != 0 {
// TODO: Using no_replace false here because the linker puts eh_frame_hdr in a separate segment that overlaps the other RO segment???
b.mmap_fixed(prot_addr, Protection::RW, false)?;
let src_data = &data[segment.file_range()];
if src_data.len() != 0 {
b.copy_from_external(src_data, addr.start)?;
}
b.mprotect(prot_addr, prot)?;
}
}
match info_area_opt {
Some(i) => {
if i.size != std::mem::size_of::<WbxSysLayout>() {
return Err(anyhow!("Symbol {} is the wrong size", INFO_OBJECT_NAME))
}
unsafe {
let src = std::slice::from_raw_parts(layout as *const WbxSysLayout as *const u8, i.size);
b.copy_from_external(src, i.start)?;
}
},
// info area can legally be missing if the core calls no emulibc functions
// None => return Err(anyhow!("Symbol {} is missing", INFO_OBJECT_NAME))
None => ()
};
// Main thread area. TODO: Should this happen here?
b.mmap_fixed(layout.main_thread, Protection::RWStack, true)?;
b.mprotect(AddressRange { start: layout.main_thread.start, size: PAGESIZE * 4 }, Protection::None)?;
b.mark_invisible(layout.main_thread)?;
b.mmap_fixed(layout.alt_thread, Protection::RWStack, true)?;
b.mprotect(AddressRange { start: layout.alt_thread.start, size: PAGESIZE * 4 }, Protection::None)?;
b.mark_invisible(layout.alt_thread)?;
Ok(ElfLoader {
sections,
exports,
entry_point: wbx.entry as usize,
hash: bin::hash(data)
})
}
pub fn seal(&mut self, b: &mut MemoryBlock) {
for section in self.sections.iter() {
if section.addr.align_expand().size != 0 && section_name_is_readonly(section.name.as_str()) {
b.mprotect(section.addr.align_expand(), Protection::R).unwrap();
}
}
}
pub fn entry_point(&self) -> usize {
self.entry_point
}
pub fn get_proc_addr(&self, proc: &str) -> usize {
match self.exports.get(proc) {
Some(addr) => addr.start,
None => 0,
}
}
}
const MAGIC: &str = "ElfLoader";
impl IStateable for ElfLoader {
fn save_state(&mut self, stream: &mut dyn Write) -> anyhow::Result<()> {
bin::write_magic(stream, MAGIC)?;
bin::write_hash(stream, &self.hash[..])?;
Ok(())
}
fn load_state(&mut self, stream: &mut dyn Read) -> anyhow::Result<()> {
bin::verify_magic(stream, MAGIC)?;
bin::verify_hash(stream, &self.hash[..])?;
Ok(())
}
}