diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index 39bedaaf8bf4875d0d3c2cf65beeb2332867f0a6..e011a6a9da0629eb104d432b81cef9a291c19fc4 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -2,7 +2,7 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::string::{String, ToString}; use alloc::vec::Vec; -use core::{ptr, slice}; +use core::{mem, ptr, slice}; use goblin::elf::{Elf, program_header, reloc, sym}; use goblin::error::{Error, Result}; @@ -20,6 +20,61 @@ const PATH_SEP: char = ';'; #[cfg(target_os = "linux")] const PATH_SEP: char = ':'; +// On Linux, a new TCB is required +#[cfg(target_os = "linux")] +unsafe fn allocate_tls(size: usize) -> Result<&'static mut [u8]> { + let ptr = sys_mman::mmap( + ptr::null_mut(), + size + PAGE_SIZE /* TLS and TCB */, + sys_mman::PROT_READ | sys_mman::PROT_WRITE, + sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE, + -1, + 0 + ); + if ptr as usize == !0 /* MAP_FAILED */ { + return Err(Error::Malformed( + format!("failed to map tls") + )); + } + + let mut tls = slice::from_raw_parts_mut(ptr as *mut u8, size); + let mut tcb = slice::from_raw_parts_mut((ptr as *mut u8).add(size), PAGE_SIZE); + *(tcb.as_mut_ptr() as *mut *mut u8) = tcb.as_mut_ptr(); + + #[cfg(target_arch = "x86_64")] + { + const ARCH_SET_FS: usize = 0x1002; + syscall!(ARCH_PRCTL, ARCH_SET_FS, tcb.as_mut_ptr()); + } + + Ok(tls) +} + +// On Redox, reuse the current TCB +// TODO: Consider adopting Linux behavior +#[cfg(target_os = "redox")] +unsafe fn allocate_tls(size: usize) -> Result<&'static [u8]> { + let ptr = sys_mman::mmap( + ptr::null_mut(), + size, + sys_mman::PROT_READ | sys_mman::PROT_WRITE, + sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE, + -1, + 0 + ); + if ptr as usize == !0 /* MAP_FAILED */ { + return Err(Error::Malformed( + format!("failed to map tls") + )); + } + + let mut tls = slice::from_raw_parts_mut(ptr as *mut u8, size); + let mut tcb = slice::from_raw_parts_mut(0xB000_0000 as *mut u8, PAGE_SIZE); + *(tcb.as_mut_ptr() as *mut *mut u8) = tcb.as_mut_ptr(); + + Ok(tls) +} + pub struct Linker { library_path: String, objects: BTreeMap<String, Box<[u8]>> @@ -119,10 +174,12 @@ impl Linker { }; // Load all ELF files into memory and find all globals + let mut tls_primary = 0; + let mut tls_size = 0; let mut mmaps = BTreeMap::new(); let mut globals = BTreeMap::new(); for (elf_name, elf) in elfs.iter() { - println!("load {}", elf_name); + println!("map {}", elf_name); let object = match self.objects.get(*elf_name) { Some(some) => some, @@ -133,12 +190,12 @@ impl Linker { let bounds = { let mut bounds_opt: Option<(usize, usize)> = None; for ph in elf.program_headers.iter() { + let voff = ph.p_vaddr as usize % PAGE_SIZE; + let vaddr = ph.p_vaddr as usize - voff; + let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; + match ph.p_type { program_header::PT_LOAD => { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; - println!(" load {:#x}, {:#x}: {:x?}", vaddr, vsize, ph); if let Some(ref mut bounds) = bounds_opt { @@ -152,6 +209,13 @@ impl Linker { bounds_opt = Some((vaddr, vaddr + vsize)); } }, + program_header::PT_TLS => { + println!(" load tls {:#x}: {:x?}", vsize, ph); + tls_size += vsize; + if *elf_name == primary { + tls_primary += vsize; + } + }, _ => () } } @@ -189,21 +253,44 @@ impl Linker { if let Some(name_res) = elf.dynstrtab.get(sym.st_name) { let name = name_res?; let value = mmap.as_ptr() as usize + sym.st_value as usize; - println!(" global {}: {:x?} = {:#x}", name, sym, value); + //println!(" global {}: {:x?} = {:#x}", name, sym, value); globals.insert(name, value); } } } + mmaps.insert(elf_name, mmap); + } + + // Allocate TLS + let mut tls = unsafe { allocate_tls(tls_size)? }; + println!("tls {:p}, {:#x}", tls.as_mut_ptr(), tls.len()); + + // Copy data + let mut tls_offset = tls_primary; + let mut tls_index = 0; + let mut tls_ranges = BTreeMap::new(); + for (elf_name, elf) in elfs.iter() { + let object = match self.objects.get(*elf_name) { + Some(some) => some, + None => continue, + }; + + let mmap = match mmaps.get_mut(elf_name) { + Some(some) => some, + None => continue + }; + + println!("load {}", elf_name); + // Copy data for ph in elf.program_headers.iter() { + let voff = ph.p_vaddr as usize % PAGE_SIZE; + let vaddr = ph.p_vaddr as usize - voff; + let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; + match ph.p_type { program_header::PT_LOAD => { - let voff = ph.p_vaddr as usize % PAGE_SIZE; - let vaddr = ph.p_vaddr as usize - voff; - let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; - - // Copy data let obj_data = { let range = ph.file_range(); match object.get(range.clone()) { @@ -228,15 +315,47 @@ impl Linker { mmap_data.copy_from_slice(obj_data); }, + program_header::PT_TLS => { + let obj_data = { + let range = ph.file_range(); + match object.get(range.clone()) { + Some(some) => some, + None => return Err(Error::Malformed( + format!("failed to read {:?}", range) + )), + } + }; + + let tls_data = { + let (index, start) = if *elf_name == primary { + (0, tls.len() - tls_primary) + } else { + tls_offset += obj_data.len(); + tls_index += 1; + (tls_index, tls.len() - tls_offset) + }; + let range = start..start + obj_data.len(); + match tls.get_mut(range.clone()) { + Some(some) => { + tls_ranges.insert(elf_name, (index, range)); + some + }, + None => return Err(Error::Malformed( + format!("failed to write tls {:?}", range) + )), + } + }; + + println!(" copy tls {:#x}, {:#x}: {:#x}, {:#x}", vaddr, vsize, voff, obj_data.len()); + + tls_data.copy_from_slice(obj_data); + }, _ => () } } - - mmaps.insert(elf_name, mmap); } - // Perform relocations and protect pages - let mut entry_opt = None; + // Perform relocations, and protect pages for (elf_name, elf) in elfs.iter() { let mmap = match mmaps.get_mut(elf_name) { Some(some) => some, @@ -245,20 +364,16 @@ impl Linker { println!("link {}", elf_name); - if *elf_name == primary { - entry_opt = Some(mmap.as_mut_ptr() as usize + elf.header.e_entry as usize); - } - // Relocate for rel in elf.dynrelas.iter().chain(elf.dynrels.iter()).chain(elf.pltrelocs.iter()) { - println!(" rel {}: {:x?}", - reloc::r_to_str(rel.r_type, elf.header.e_machine), - rel - ); + // println!(" rel {}: {:x?}", + // reloc::r_to_str(rel.r_type, elf.header.e_machine), + // rel + // ); let a = rel.r_addend.unwrap_or(0) as usize; - let b = mmap.as_ptr() as usize; + let b = mmap.as_mut_ptr() as usize; let s = if rel.r_sym > 0 { let sym = elf.dynsyms.get(rel.r_sym).ok_or(Error::Malformed( @@ -270,22 +385,28 @@ impl Linker { ))??; if let Some(value) = globals.get(name) { - println!(" sym {}: {:x?} = {:#x}", name, sym, value); + // println!(" sym {}: {:x?} = {:#x}", name, sym, value); *value } else { - println!(" sym {}: {:x?} = undefined", name, sym); + // println!(" sym {}: {:x?} = undefined", name, sym); 0 } } else { 0 }; + let t = if let Some((tls_index, tls_range)) = tls_ranges.get(elf_name) { + tls_range.start + } else { + 0 + }; + let ptr = unsafe { mmap.as_mut_ptr().add(rel.r_offset as usize) }; let set_u64 = |value| { - println!(" set_u64 {:#x}", value); + //println!(" set_u64 {:#x}", value); unsafe { *(ptr as *mut u64) = value; } }; @@ -299,8 +420,12 @@ impl Linker { reloc::R_X86_64_RELATIVE => { set_u64((b + a) as u64); }, + reloc::R_X86_64_TPOFF64 => { + set_u64((s + a).wrapping_sub(t) as u64); + }, + reloc::R_X86_64_IRELATIVE => (), // Handled below _ => { - println!(" unsupported"); + println!(" {} unsupported", reloc::r_to_str(rel.r_type, elf.header.e_machine)); } } } @@ -348,6 +473,93 @@ impl Linker { } } + // Perform indirect relocations (necessary evil), gather entry point + let mut entry_opt = None; + for (elf_name, elf) in elfs.iter() { + let mmap = match mmaps.get_mut(elf_name) { + Some(some) => some, + None => continue + }; + + println!("entry {}", elf_name); + + let is_primary = *elf_name == primary; + if is_primary { + entry_opt = Some(mmap.as_mut_ptr() as usize + elf.header.e_entry as usize); + } + + // Relocate + for rel in elf.dynrelas.iter().chain(elf.dynrels.iter()).chain(elf.pltrelocs.iter()) { + // println!(" rel {}: {:x?}", + // reloc::r_to_str(rel.r_type, elf.header.e_machine), + // rel + // ); + + let a = rel.r_addend.unwrap_or(0) as usize; + + let b = mmap.as_mut_ptr() as usize; + + let ptr = unsafe { + mmap.as_mut_ptr().add(rel.r_offset as usize) + }; + + let set_u64 = |value| { + // println!(" set_u64 {:#x}", value); + unsafe { *(ptr as *mut u64) = value; } + }; + + match rel.r_type { + reloc::R_X86_64_IRELATIVE => unsafe { + let f: unsafe extern "C" fn () -> u64 = mem::transmute(b + a); + set_u64(f()); + }, + _ => () + } + } + + // Protect pages + for ph in elf.program_headers.iter() { + match ph.p_type { + program_header::PT_LOAD => { + let voff = ph.p_vaddr as usize % PAGE_SIZE; + let vaddr = ph.p_vaddr as usize - voff; + let vsize = ((ph.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE) * PAGE_SIZE; + + let mut prot = 0; + + if ph.p_flags & program_header::PF_R == program_header::PF_R { + prot |= sys_mman::PROT_READ; + } + + // W ^ X. If it is executable, do not allow it to be writable, even if requested + if ph.p_flags & program_header::PF_X == program_header::PF_X { + prot |= sys_mman::PROT_EXEC; + } else if ph.p_flags & program_header::PF_W == program_header::PF_W { + prot |= sys_mman::PROT_WRITE; + } + + let res = unsafe { + let ptr = mmap.as_mut_ptr().add(vaddr); + println!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot); + + sys_mman::mprotect( + ptr as *mut c_void, + vsize, + prot + ) + }; + + if res < 0 { + return Err(Error::Malformed( + format!("failed to mprotect {}", elf_name) + )); + } + }, + _ => () + } + } + } + entry_opt.ok_or(Error::Malformed( format!("missing entry for {}", primary) ))