mod arch;
mod loader_core;
use arch::sys_mmap;
use loader_core::*;
use std::env;
use std::ffi::CString;
use std::fs;
use std::os::unix::io::AsRawFd;
use std::process;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("usage: loader <elf-binary> [args...]");
process::exit(1);
}
if let Err(e) = load_and_run(&args[1], &args[1..]) {
eprintln!("loader: {e}");
process::exit(1);
}
}
fn read_sysinfo_ehdr() -> u64 {
let Ok(data) = fs::read("/proc/self/auxv") else {
return 0;
};
let mut i = 0;
while i + 16 <= data.len() {
let tag = u64::from_ne_bytes(data[i..i + 8].try_into().unwrap());
let val = u64::from_ne_bytes(data[i + 8..i + 16].try_into().unwrap());
if tag == AT_NULL {
break;
}
if tag == AT_SYSINFO_EHDR {
return val;
}
i += 16;
}
0
}
fn load_and_run(path: &str, argv: &[String]) -> Result<(), String> {
let file = fs::File::open(path).map_err(|e| format!("open {path}: {e}"))?;
let data = fs::read(path).map_err(|e| format!("read {path}: {e}"))?;
let ehdr = parse_ehdr(&data).map_err(|e| e.to_string())?;
let phdrs = parse_phdrs(&data, &ehdr).map_err(|e| e.to_string())?;
let load_bias =
map_segments(phdrs, file.as_raw_fd(), ehdr.e_type).map_err(|e| e.to_string())?;
apply_relocations(&data, phdrs, load_bias).map_err(|e| e.to_string())?;
let entry = ehdr.e_entry + load_bias;
transfer(
entry,
argv,
phdr_runtime_addr(&ehdr, phdrs, load_bias),
phdrs.len(),
read_sysinfo_ehdr(),
);
}
fn phdr_runtime_addr(ehdr: &Ehdr, phdrs: &[Phdr], load_bias: u64) -> u64 {
for ph in phdrs {
if ph.p_type == PT_PHDR {
return load_bias + ph.p_vaddr;
}
}
for ph in phdrs.iter().filter(|p| p.p_type == PT_LOAD) {
if ehdr.e_phoff >= ph.p_offset && ehdr.e_phoff < ph.p_offset + ph.p_filesz {
return load_bias + ph.p_vaddr + (ehdr.e_phoff - ph.p_offset);
}
}
load_bias + ehdr.e_phoff
}
fn transfer(entry: u64, argv: &[String], phdr_addr: u64, phnum: usize, sysinfo_ehdr: u64) -> ! {
let c_argv: Vec<CString> = argv
.iter()
.map(|a| CString::new(a.as_str()).unwrap())
.collect();
let stack_size = 64 * 1024;
let stack_base = unsafe {
sys_mmap(
core::ptr::null_mut(),
stack_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0,
)
};
if stack_base == MAP_FAILED {
panic!("mmap stack failed");
}
let mut sp = stack_base as usize + stack_size;
let mut str_ptrs = Vec::new();
for c in &c_argv {
let bytes = c.as_bytes_with_nul();
sp -= bytes.len();
unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), sp as *mut u8, bytes.len()) };
str_ptrs.push(sp);
}
sp &= !7usize;
let auxv_entries: [(u64, u64); 7] = [
(AT_PHDR, phdr_addr),
(AT_PHENT, 56),
(AT_PHNUM, phnum as u64),
(AT_PAGESZ, PAGE_SIZE as u64),
(AT_ENTRY, entry),
(AT_SYSINFO_EHDR, sysinfo_ehdr),
(AT_NULL, 0),
];
sp &= !15usize;
let auxv_bytes = auxv_entries.len() * 16;
let words = 1 + 1 + str_ptrs.len() + 1 ;
if (auxv_bytes + words * 8) % 16 != 0 {
sp -= 8;
unsafe { *(sp as *mut u64) = 0 };
}
sp -= auxv_bytes;
let auxv_ptr = sp as *mut (u64, u64);
for (i, &(tag, val)) in auxv_entries.iter().enumerate() {
unsafe { *auxv_ptr.add(i) = (tag, val) };
}
sp -= 8;
unsafe { *(sp as *mut u64) = 0 };
sp -= 8;
unsafe { *(sp as *mut u64) = 0 };
for &ptr in str_ptrs.iter().rev() {
sp -= 8;
unsafe { *(sp as *mut u64) = ptr as u64 };
}
sp -= 8;
unsafe { *(sp as *mut u64) = argv.len() as u64 };
unsafe { arch::clear_thread_pointer() };
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!(
"mov rsp, {sp}",
"jmp {entry}",
sp = in(reg) sp,
entry = in(reg) entry,
in("rdi") argv.len(),
in("rsi") sp + 8,
options(noreturn)
);
}
#[cfg(target_arch = "aarch64")]
unsafe {
core::arch::asm!(
"mov sp, {sp}",
"mov x0, {argc}",
"mov x1, {argv}",
"br {entry}",
sp = in(reg) sp,
argc = in(reg) argv.len(),
argv = in(reg) sp + 8,
entry = in(reg) entry,
options(noreturn)
);
}
#[cfg(target_arch = "riscv64")]
unsafe {
core::arch::asm!(
"mv sp, {sp}",
"mv a0, {argc}",
"mv a1, {argv}",
"jr {entry}",
sp = in(reg) sp,
argc = in(reg) argv.len(),
argv = in(reg) sp + 8,
entry = in(reg) entry,
options(noreturn)
);
}
}