dyn-loader: implement basic dladdr()

This commit is contained in:
Mark Poliakov 2025-02-24 11:00:56 +02:00
parent 8e45e48362
commit 5d5379ac8a
8 changed files with 232 additions and 105 deletions

87
test.c
View File

@ -1,85 +1,16 @@
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
static void fmt_inaddr(char *buffer, const struct sockaddr_in *inaddr) {
uint8_t a = (uint8_t) inaddr->sin_addr.s_addr;
uint8_t b = (uint8_t) (inaddr->sin_addr.s_addr >> 8);
uint8_t c = (uint8_t) (inaddr->sin_addr.s_addr >> 16);
uint8_t d = (uint8_t) (inaddr->sin_addr.s_addr >> 24);
uint16_t port = ntohs(inaddr->sin_port);
sprintf(buffer, "%hhu.%hhu.%hhu.%hhu:%hu", a, b, c, d, port);
}
int main(int argc, char **argv) {
struct Dl_info dl0, dl1;
int main(int argc, const char **argv) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
dladdr(main, &dl0);
dladdr(printf, &dl1);
printf("%s: %p\n", dl1.dli_sname, dl1.dli_saddr);
printf("%s @ %p\n", dl1.dli_fname, dl1.dli_fbase);
if (fd < 0) {
perror("socket()");
return EXIT_FAILURE;
}
struct sockaddr_in sa;
socklen_t slen;
char buffer[256];
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(4321);
sa.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (const struct sockaddr *) &sa, sizeof(sa)) != 0) {
perror("bind()");
return EXIT_FAILURE;
}
if (listen(fd, 64) != 0) {
perror("listen()");
return EXIT_FAILURE;
}
while (1) {
int rfd;
slen = sizeof(sa);
if ((rfd = accept(fd, (struct sockaddr *) &sa, &slen)) < 0) {
perror("accept()");
return EXIT_FAILURE;
}
fmt_inaddr(buffer, &sa);
printf("Received connection from %s\n", buffer);
while (1) {
ssize_t len;
if ((len = recv(rfd, buffer, sizeof(buffer), 0)) < 0) {
perror("recv()");
break;
}
if (len == 0) {
break;
}
if (len >= 4 && !strncmp(buffer, "quit", 4)) {
break;
}
fwrite(buffer, 1, len, stdout);
if ((len = send(rfd, buffer, len, 0)) < 0) {
perror("send()");
break;
}
}
printf("Connection closed\n");
close(rfd);
}
printf("%s: %p\n", dl0.dli_sname, dl0.dli_saddr);
printf("%s @ %p\n", dl0.dli_fname, dl0.dli_fbase);
return 0;
}

View File

@ -1,3 +1,24 @@
use std::{
ffi::{c_char, c_int, c_void},
ptr,
};
use crate::OBJECTS;
pub const RTLD_LAZY: c_int = 1 << 0;
pub const RTLD_NOW: c_int = 0 << 0;
pub const RTLD_LOCAL: c_int = 1 << 1;
pub const RTLD_GLOBAL: c_int = 0 << 1;
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
#[repr(C)]
pub struct Dl_info {
pub dli_fname: *const c_char,
pub dli_fbase: *mut c_void,
pub dli_sname: *const c_char,
pub dli_saddr: *mut c_void,
}
// Has to be naked: need to prevent the function from clobbering any registers besides x0
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
@ -6,8 +27,53 @@ pub(crate) unsafe extern "C" fn tlsdesc_resolve_static(argument: *const usize) -
// x0 -- pointer to tlsdesc struct:
// [0]: this function pointer
// [1]: static offset to return
core::arch::naked_asm!(r#"
core::arch::naked_asm!(
r#"
ldr x0, [x0, #8]
ret
"#);
"#
);
}
pub(crate) unsafe extern "C" fn dlopen(_path: *const c_char, _flags: c_int) -> *mut c_void {
todo!()
}
pub(crate) unsafe extern "C" fn dlclose(_object: *mut c_void) -> c_int {
todo!()
}
pub(crate) unsafe extern "C" fn dlsym(_object: *mut c_void, _name: *const c_char) -> *mut c_void {
todo!()
}
pub(crate) unsafe extern "C" fn dlerror(_object: *mut c_void) -> c_int {
todo!()
}
pub(crate) unsafe extern "C" fn dladdr(addr: *mut c_void, info: *mut Dl_info) -> c_int {
#[allow(static_mut_refs)]
let objects = OBJECTS.assume_init_ref();
let Some(symbol) = objects.lookup_address(addr.addr()) else {
log::warn!("dladdr({addr:p}) -> NULL");
return 0;
};
if let Some(info) = info.as_mut() {
info.dli_sname = symbol.symbol_name.as_ptr();
info.dli_saddr = ptr::with_exposed_provenance_mut(symbol.symbol_base);
info.dli_fname = symbol.object_name.as_ptr();
info.dli_fbase = ptr::with_exposed_provenance_mut(symbol.object_base);
}
1
}
pub(crate) unsafe extern "C" fn dladdr1(
_addr: *mut c_void,
_info: *mut Dl_info,
_extra_info: *mut *mut c_void,
_flags: c_int,
) -> c_int {
todo!()
}

View File

@ -4,7 +4,7 @@ use std::io;
pub enum Error {
#[error("{0}")]
Io(#[from] io::Error),
#[error("ELF parse error")]
#[error("ELF parse error: {0}")]
ElfParse(#[from] elf::ParseError),
#[error("Memory mapping error: {0:?}")]
MemoryMap(yggdrasil_rt::Error),

View File

@ -8,7 +8,7 @@
)]
#![allow(clippy::new_without_default, clippy::missing_transmute_annotations)]
use std::process::ExitCode;
use std::{mem::MaybeUninit, process::ExitCode};
use error::Error;
use object::Object;
@ -25,12 +25,15 @@ pub mod search;
pub mod state;
pub mod thread_local;
static mut OBJECTS: MaybeUninit<ObjectSet> = MaybeUninit::uninit();
fn run(binary: &str, args: &[String]) -> Result<!, Error> {
// Open root and its dependencies
let root = Object::open(binary)?;
let root = Object::open(binary, true)?;
let mut objects = ObjectSet::new(root);
objects.open_root_dependencies()?;
let tls_image = objects.load_all()?;
objects.state.add_magic_symbols();
objects.resolve_symbols()?;
if let Err(undefined) = objects.state.no_undefined_symbols() {
@ -100,6 +103,9 @@ fn run(binary: &str, args: &[String]) -> Result<!, Error> {
let entry = objects.root.entry().ok_or(Error::NoEntrypoint)?;
log::info!("entry = {:p}", entry);
#[allow(static_mut_refs)]
unsafe { OBJECTS.write(objects) };
let argument = env::build_argument(args, &auxv)?;
unsafe { enter(entry, argument) };
}

View File

@ -1,11 +1,5 @@
use std::{
fs::File,
io::{BufReader, Read, Seek, SeekFrom},
mem,
ops::Range,
path::{Path, PathBuf},
ptr,
rc::Rc,
cmp, collections::HashMap, ffi::{CStr, CString}, fs::File, io::{BufReader, Read, Seek, SeekFrom}, mem, ops::Range, path::{Path, PathBuf}, ptr, rc::Rc
};
use elf::{
@ -51,6 +45,8 @@ pub struct DynamicSymbol {
pub struct Object {
pub path: PathBuf,
pub c_name: CString,
pub load_range: Option<Range<usize>>,
pub file: BufReader<File>,
pub elf: ElfStream<AnyEndian, BufReader<File>>,
@ -69,6 +65,8 @@ pub struct Object {
init_array: Option<Range<usize>>,
pre_init_array: Option<Range<usize>>,
symtab: Vec<(Symbol, Option<CString>)>,
}
impl ResolvedSymbol<'_> {
@ -83,8 +81,9 @@ impl ResolvedSymbol<'_> {
}
impl Object {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
pub fn open<P: AsRef<Path>>(path: P, need_symtab: bool) -> Result<Self, Error> {
let path = path.as_ref().to_owned();
let c_name = CString::new(path.to_str().unwrap()).unwrap();
let file = BufReader::new(File::open(&path)?);
let mut elf = ElfStream::open_stream(file)?;
let file = BufReader::new(File::open(&path)?);
@ -155,8 +154,22 @@ impl Object {
}
let tls = tls_segments.first().map(|s| **s);
let mut symtab = vec![];
if need_symtab {
if let Some((symtab_items, strtab)) = elf.symbol_table()? {
for symbol in symtab_items {
let name = strtab.get(symbol.st_name as _)?;
let c_name = CString::new(name).ok();
symtab.push((symbol, c_name));
}
}
symtab.sort_by(|(s0, _), (s1, _)| cmp::Ord::cmp(&s0.st_value, &s1.st_value));
}
Ok(Self {
path,
c_name,
load_range: None,
file,
elf,
@ -174,6 +187,7 @@ impl Object {
init_array: None,
pre_init_array: None,
symtab,
})
}
@ -200,6 +214,9 @@ impl Object {
ElfType::Static => todo!(),
};
let load_base = (mapping.base() as isize - self.vma_start as isize) as usize;
self.load_range = Some(load_base..load_base + mapping_size);
log::info!(
"Actual range: {:#x?} ({:+#x})",
mapping.range(),
@ -275,6 +292,22 @@ impl Object {
Ok(())
}
pub fn lookup_address(&self, address: usize) -> Option<(&CStr, usize)> {
const NULL_STRING: &CStr = c"";
let address = address as u64;
let base = self.load_range.as_ref().map(|r| r.start).unwrap_or(0);
for (symbol, name) in self.symtab.iter() {
if address >= symbol.st_value && address - symbol.st_value < symbol.st_size {
let name = name.as_ref().map(|c| c.as_c_str()).unwrap_or(NULL_STRING);
return Some((name, symbol.st_value as usize + base));
}
}
None
}
pub fn place_tls_copy(&mut self, dst: &mut [u8]) -> Result<(), Error> {
let tls = self.tls.as_ref().unwrap();
if tls.p_filesz > 0 {

View File

@ -12,7 +12,7 @@ pub fn find_library(name: &str) -> Result<PathBuf, Error> {
if path.exists() {
return Ok(path);
}
let path = Path::new("/tmp").join(name);
let path = Path::new("/mnt").join(name);
if path.exists() {
return Ok(path);
}

View File

@ -1,7 +1,5 @@
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
rc::Rc,
collections::{HashMap, HashSet}, ffi::{CStr, CString}, iter, path::{Path, PathBuf}, rc::Rc
};
use elf::abi::{STT_FUNC, STT_NOTYPE, STT_OBJECT, STT_TLS};
@ -13,10 +11,20 @@ use crate::{
thread_local::{self, TlsImage, TlsLayout},
};
pub struct SymbolByAddress<'a> {
pub object_name: &'a CStr,
pub object_base: usize,
pub symbol_name: &'a CStr,
pub symbol_base: usize,
}
pub struct ExportedNormalSymbol {
pub source: PathBuf,
pub value: usize,
pub size: usize,
pub weak: bool,
pub object_id: u32,
pub c_name: CString
}
pub struct ExportedTlsSymbol {
@ -75,6 +83,8 @@ impl State {
let value: usize = (isize::try_from(sym.raw.st_value).unwrap() + offset)
.try_into()
.unwrap();
let size: usize = sym.raw.st_size as usize;
let c_name = CString::new(sym.name.as_ref()).unwrap();
match self.symbol_table.get_mut(&sym.name) {
// Stronger binding exported
@ -82,7 +92,10 @@ impl State {
*export = ExportedNormalSymbol {
source,
value,
size,
weak,
c_name,
object_id,
};
}
// Do nothing, already strong or already weak
@ -93,7 +106,10 @@ impl State {
ExportedNormalSymbol {
source,
value,
size,
weak,
c_name,
object_id,
},
);
}
@ -175,6 +191,27 @@ impl State {
Err(&self.undefined_references)
}
}
fn export_magic_symbol(&mut self, name: &str, value: usize) {
let c_name = CString::new(name).unwrap();
self.symbol_table.insert(name.into(), ExportedNormalSymbol {
source: "".into(),
weak: false,
value,
size: 0,
c_name,
object_id: u32::MAX
});
}
pub fn add_magic_symbols(&mut self) {
self.export_magic_symbol("dlopen", crate::builtins::dlopen as usize);
self.export_magic_symbol("dlclose", crate::builtins::dlclose as usize);
self.export_magic_symbol("dlsym", crate::builtins::dlsym as usize);
self.export_magic_symbol("dlerror", crate::builtins::dlerror as usize);
self.export_magic_symbol("dladdr", crate::builtins::dladdr as usize);
self.export_magic_symbol("dladdr1", crate::builtins::dladdr1 as usize);
}
}
impl ObjectSet {
@ -189,23 +226,37 @@ impl ObjectSet {
}
}
pub fn open_root_dependencies(&mut self) -> Result<(), Error> {
for needed in self.root.needed() {
// TODO load needed of needed
pub fn objects(&self) -> impl Iterator<Item = &Object> {
iter::chain(iter::once(&self.root), self.libraries.values())
}
fn open_dependencies_of(&mut self, object_id: u32) -> Result<(), Error> {
let object = match object_id {
0 => &self.root,
_ => self.libraries.get(&object_id).ok_or(Error::NotLoaded)?,
};
let needed = object.needed().cloned().collect::<Vec<_>>();
for needed in needed {
let path = search::find_library(needed.as_str())?;
if self.library_path_map.contains_key(&path) {
continue;
}
let mut object = Object::open(&path)?;
let mut object = Object::open(&path, false)?;
self.last_object_id += 1;
let id = self.last_object_id;
object.id = id;
self.libraries.insert(id, object);
self.library_path_map.insert(path, id);
self.open_dependencies_of(id)?;
}
Ok(())
}
pub fn open_root_dependencies(&mut self) -> Result<(), Error> {
self.open_dependencies_of(0)
}
pub fn load_all(&mut self) -> Result<Option<TlsImage>, Error> {
// Load all objects somewhere, order doesn't matter
self.root.load()?;
@ -278,4 +329,37 @@ impl ObjectSet {
_ => self.libraries.get_mut(&id),
}
}
pub fn lookup_address(&self, address: usize) -> Option<SymbolByAddress> {
// Lookup in stuff exported from .dynsym
for (_, symbol) in self.state.symbol_table.iter() {
if address >= symbol.value && address - symbol.value < symbol.size {
let object = self.get(symbol.object_id).unwrap();
let object_base = object.load_range.as_ref().map(|r| r.start).unwrap_or(0);
return Some(SymbolByAddress {
object_name: object.c_name.as_c_str(),
object_base,
symbol_name: symbol.c_name.as_c_str(),
symbol_base: symbol.value
});
}
}
// Fall back to main object's .strtab
for object in self.objects() {
if let Some(range) = object.load_range.clone() {
if range.contains(&address) {
let address = address - range.start;
let (name, base) = object.lookup_address(address)?;
return Some(SymbolByAddress {
object_name: object.c_name.as_c_str(),
object_base: range.start,
symbol_name: name,
symbol_base: base,
});
}
}
}
None
}
}

View File

@ -14,31 +14,38 @@ pub struct Dl_info {
pub dli_saddr: *mut c_void,
}
#[linkage = "weak"]
#[no_mangle]
unsafe extern "C" fn dlclose(_dl: *mut c_void) -> c_int {
todo!()
unreachable!()
}
#[linkage = "weak"]
#[no_mangle]
unsafe extern "C" fn dlerror() -> *mut c_char {
todo!()
unreachable!()
}
#[linkage = "weak"]
#[no_mangle]
unsafe extern "C" fn dlopen(_path: *const c_char, _flags: c_int) -> *mut c_void {
todo!()
unreachable!()
}
#[linkage = "weak"]
#[no_mangle]
unsafe extern "C" fn dlsym(_dl: *mut c_void, _name: *const c_char) -> *mut c_void {
todo!()
unreachable!()
}
// Non-POSIX
#[linkage = "weak"]
#[no_mangle]
unsafe extern "C" fn dladdr(_addr: *mut c_void, _info: *mut Dl_info) -> c_int {
-1
unreachable!()
}
#[linkage = "weak"]
#[no_mangle]
unsafe extern "C" fn dladdr1(
_addr: *mut c_void,
@ -46,5 +53,5 @@ unsafe extern "C" fn dladdr1(
_extra_info: *mut *mut c_void,
_flags: c_int,
) -> c_int {
todo!()
unreachable!()
}