Moves symbol resolver into a dedicated struct (#303)

This commit is contained in:
Putta Khunchalee 2023-08-17 01:42:14 +07:00 committed by GitHub
parent 73cb89d6a7
commit 42fbf587e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 186 additions and 43 deletions

View file

@ -13,7 +13,7 @@ You can download binaries from the latest commits [here](https://github.com/obhq
## Screenshots
![Game list](screenshots/game-list.png)
![Screenshots](screenshots.png)
Thanks to [VocalFan](https://github.com/VocalFan) for the awesome icon!

BIN
screenshots.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

View file

@ -1,11 +1,12 @@
pub use mem::*;
pub use module::*;
use self::resolver::{ResolveFlags, SymbolResolver};
use crate::errno::{Errno, EINVAL, ENOEXEC};
use crate::fs::{Fs, VPath, VPathBuf};
use crate::memory::{MmapError, MprotectError, Protections};
use bitflags::bitflags;
use elf::{DynamicFlags, Elf, FileInfo, FileType, ReadProgramError, Relocation, Symbol};
use elf::{DynamicFlags, Elf, FileType, ReadProgramError, Relocation};
use std::fs::File;
use std::num::NonZeroI32;
use std::ops::Deref;
@ -15,6 +16,7 @@ use thiserror::Error;
mod mem;
mod module;
mod resolver;
/// An implementation of
/// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/libexec/rtld-elf/rtld.c.
@ -202,8 +204,14 @@ impl<'a> RuntimeLinker<'a> {
/// No other threads may access the memory of all loaded modules.
pub unsafe fn relocate(&self) -> Result<(), RelocateError> {
// TODO: Check what the PS4 actually doing.
let list = self.list.read().unwrap();
let resolver = SymbolResolver::new(
&list,
self.app.sdk_ver() >= 0x5000000 || self.flags.contains(LinkerFlags::UNK2),
);
for m in self.list.read().unwrap().deref() {
self.relocate_single(m)?;
self.relocate_single(m, &resolver)?;
}
Ok(())
@ -213,7 +221,11 @@ impl<'a> RuntimeLinker<'a> {
///
/// # Safety
/// No other thread may access the module memory.
unsafe fn relocate_single(&self, md: &Arc<Module>) -> Result<(), RelocateError> {
unsafe fn relocate_single<'b>(
&self,
md: &'b Arc<Module>,
resolver: &SymbolResolver<'b>,
) -> Result<(), RelocateError> {
// Unprotect the memory.
let mut mem = match md.memory().unprotect() {
Ok(v) => v,
@ -226,7 +238,7 @@ impl<'a> RuntimeLinker<'a> {
self.relocate_rela(md, mem.as_mut(), &mut relocated)?;
if !md.flags().contains(ModuleFlags::UNK4) {
self.relocate_plt(md, mem.as_mut(), &mut relocated)?;
self.relocate_plt(md, mem.as_mut(), &mut relocated, resolver)?;
}
Ok(())
@ -280,11 +292,12 @@ impl<'a> RuntimeLinker<'a> {
}
/// See `reloc_jmplots` on the PS4 for a reference.
fn relocate_plt(
fn relocate_plt<'b>(
&self,
md: &Arc<Module>,
md: &'b Arc<Module>,
mem: &mut [u8],
relocated: &mut [bool],
resolver: &SymbolResolver<'b>,
) -> Result<(), RelocateError> {
// Do nothing if not a dynamic module.
let info = match md.file_info() {
@ -312,7 +325,7 @@ impl<'a> RuntimeLinker<'a> {
}
// Resolve symbol.
let sym = match self.resolve_symbol(md, reloc.symbol(), info) {
let sym = match resolver.resolve_with_local(md, reloc.symbol(), ResolveFlags::UNK1) {
Some((m, s)) => {
m.memory().addr() + m.memory().base() + m.symbol(s).unwrap().value()
}
@ -331,41 +344,6 @@ impl<'a> RuntimeLinker<'a> {
Ok(())
}
fn resolve_symbol(
&self,
md: &Arc<Module>,
index: usize,
info: &FileInfo,
) -> Option<(Arc<Module>, usize)> {
// Check if symbol index is valid.
let sym = md.symbols().get(index)?;
if index >= info.nchains() {
return None;
}
if self.app.sdk_ver() >= 0x5000000 || self.flags.contains(LinkerFlags::UNK2) {
// Get library and module.
let (li, mi) = match sym.decode_name() {
Some(v) => (
md.libraries().iter().find(|&l| l.id() == v.1),
md.modules().iter().find(|&m| m.id() == v.2),
),
None => (None, None),
};
} else {
todo!("resolve symbol with SDK version < 0x5000000");
}
// Return this symbol if the binding is local. The reason we don't check this in the
// first place is because we want to maintain the same behavior as the PS4.
if sym.binding() == Symbol::STB_LOCAL {
return Some((md.clone(), index));
}
None
}
}
bitflags! {

View file

@ -0,0 +1,165 @@
use super::Module;
use bitflags::bitflags;
use elf::{LibraryInfo, ModuleInfo, Symbol};
use std::sync::Arc;
/// An object to resolve a symbol from loaded (S)ELF.
pub struct SymbolResolver<'a> {
mods: &'a [Arc<Module>],
new_algorithm: bool,
}
impl<'a> SymbolResolver<'a> {
pub fn new(mods: &'a [Arc<Module>], new_algorithm: bool) -> Self {
Self {
mods,
new_algorithm,
}
}
/// See `find_symdef` on the PS4 for a reference.
pub fn resolve_with_local(
&self,
md: &'a Arc<Module>,
index: usize,
flags: ResolveFlags,
) -> Option<(&'a Arc<Module>, usize)> {
// Check if symbol index is valid.
let sym = md.symbols().get(index)?;
let data = md.file_info().unwrap();
if index >= data.nchains() {
return None;
}
// Get symbol information.
let (name, decoded_name, symmod, symlib, hash) = if self.new_algorithm {
// Get library and module.
let (li, mi) = match sym.decode_name() {
Some(v) => (
md.libraries().iter().find(|&l| l.id() == v.1),
md.modules().iter().find(|&m| m.id() == v.2),
),
None => (None, None),
};
// Calculate symbol hash.
(
Some(sym.name()),
None,
mi,
li,
Self::hash(Some(sym.name()), li.map(|i| i.name()), mi.map(|i| i.name())),
)
} else {
todo!("resolve symbol with SDK version < 0x5000000");
};
// Return this symbol if the binding is local. The reason we don't check this in the
// first place is because we want to maintain the same behavior as the PS4.
if sym.binding() == Symbol::STB_LOCAL {
return Some((md, index));
} else if sym.ty() == Symbol::STT_SECTION {
return None;
}
// Lookup from global list if the symbol is not local.
if let Some(v) = self.resolve(md, name, decoded_name, symmod, symlib, hash, flags) {
return Some(v);
} else if sym.binding() == Symbol::STB_WEAK {
// TODO: Return sym_zero on obj_main.
todo!("resolving weak symbol");
}
None
}
/// See `symlook_default` on the PS4 for a reference.
pub fn resolve(
&self,
refmod: &'a Arc<Module>,
name: Option<&str>,
decoded_name: Option<&str>,
symmod: Option<&ModuleInfo>,
symlib: Option<&LibraryInfo>,
hash: u64,
flags: ResolveFlags,
) -> Option<(&'a Arc<Module>, usize)> {
// TODO: Resolve from global.
// TODO: Resolve from DAGs.
None
}
pub fn hash(name: Option<&str>, libname: Option<&str>, modname: Option<&str>) -> u64 {
let mut h: u64 = 0;
let mut t: u64 = 0;
let mut l: i32 = -1;
let mut c = |b: u8| {
t = (b as u64) + (h << 4);
h = t & 0xf0000000;
h = ((h >> 24) ^ t) & !h;
};
// Hash symbol name.
if let Some(v) = name {
for b in v.bytes() {
c(b);
if b == b'#' {
break;
}
}
l = 0;
}
// Hash library name.
let v = match libname {
Some(v) => v,
None => return h,
};
if l == 0 {
// This seems like a bug in the PS4 because it hash on # two times.
c(b'#');
}
l = 0;
for b in v.bytes() {
c(b);
if b == b'#' {
l = 0x23; // #
break;
}
}
// Hash module name.
let v = match modname {
Some(v) => v,
None => return h,
};
if l == 0 {
c(b'#');
}
for b in v.bytes() {
c(b);
if b == b'#' {
break;
}
}
h
}
}
bitflags! {
/// Flags to control behavior of [`SymbolResolver`].
pub struct ResolveFlags: u32 {
const UNK1 = 0x00000001;
}
}