xref: /relibc/src/ld_so/dso.rs (revision 041d1604b526a0b908d4222e39baa0030a94fd8f)
1 use super::{
2     debug::{RTLDDebug, _r_debug},
3     linker::Symbol,
4     tcb::{round_up, Master},
5 };
6 use crate::{
7     header::{errno::STR_ERROR, sys_mman},
8     platform::{errno, types::c_void},
9 };
10 use alloc::{
11     collections::BTreeMap,
12     string::{String, ToString},
13     vec::Vec,
14 };
15 use core::{
16     mem::{size_of, transmute},
17     ptr, slice,
18 };
19 #[cfg(target_pointer_width = "32")]
20 use goblin::elf32::{
21     header::ET_DYN,
22     program_header,
23     r#dyn::{Dyn, DT_DEBUG, DT_RUNPATH},
24     section_header::{SHN_UNDEF, SHT_FINI_ARRAY, SHT_INIT_ARRAY},
25     sym,
26 };
27 #[cfg(target_pointer_width = "64")]
28 use goblin::elf64::{
29     header::ET_DYN,
30     program_header,
31     r#dyn::{Dyn, DT_DEBUG, DT_RUNPATH},
32     section_header::{SHN_UNDEF, SHT_FINI_ARRAY, SHT_INIT_ARRAY},
33     sym,
34 };
35 use goblin::{
36     elf::Elf,
37     error::{Error, Result},
38 };
39 
40 /// Use to represent a library as well as all the symbols that is loaded withen it.
41 #[derive(Default)]
42 pub struct DSO {
43     pub name: String,
44     pub id: usize,
45     pub dlopened: bool,
46     pub entry_point: usize,
47     pub runpath: Option<String>,
48     /// Loaded library in-memory data
49     pub mmap: &'static mut [u8],
50     pub global_syms: BTreeMap<String, Symbol>,
51     pub weak_syms: BTreeMap<String, Symbol>,
52     pub dependencies: Vec<String>,
53     /// .init_array addr and len
54     pub init_array: (usize, usize),
55     /// .fini_array addr and len
56     pub fini_array: (usize, usize),
57     pub tls_module_id: usize,
58     pub tls_offset: usize,
59     pub use_count: usize,
60 }
61 
62 impl DSO {
63     pub fn new(
64         path: &str,
65         data: &Vec<u8>,
66         base_addr: Option<usize>,
67         dlopened: bool,
68         id: usize,
69         tls_module_id: usize,
70         tls_offset: usize,
71     ) -> Result<(DSO, Option<Master>)> {
72         let elf = Elf::parse(data)?;
73         let (mmap, tcb_master) = DSO::mmap_and_copy(&path, &elf, &data, base_addr, tls_offset)?;
74         let (global_syms, weak_syms) = DSO::collect_syms(&elf, &mmap)?;
75         let (init_array, fini_array) = DSO::init_fini_arrays(&elf, mmap.as_ptr() as usize);
76 
77         let name = match elf.soname {
78             Some(soname) => soname.to_string(),
79             _ => basename(&path),
80         };
81         let tls_offset = match tcb_master {
82             Some(ref master) => master.offset,
83             _ => 0,
84         };
85         let entry_point = if is_pie_enabled(&elf) {
86             mmap.as_ptr() as usize + elf.header.e_entry as usize
87         } else {
88             elf.header.e_entry as usize
89         };
90         let dso = DSO {
91             name: name,
92             id: id,
93             use_count: 1,
94             dlopened: dlopened,
95             entry_point: entry_point,
96             runpath: DSO::get_runpath(&path, &elf)?,
97             mmap: mmap,
98             global_syms: global_syms,
99             weak_syms: weak_syms,
100             dependencies: elf.libraries.iter().map(|s| s.to_string()).collect(),
101             init_array: init_array,
102             fini_array: fini_array,
103             tls_module_id: tls_module_id,
104             tls_offset: tls_offset,
105         };
106         return Ok((dso, tcb_master));
107     }
108 
109     pub fn get_sym(&self, name: &str) -> Option<(Symbol, bool)> {
110         if let Some(value) = self.global_syms.get(name) {
111             Some((*value, true))
112         } else if let Some(value) = self.weak_syms.get(name) {
113             Some((*value, false))
114         } else {
115             None
116         }
117     }
118 
119     pub fn run_init(&self) {
120         unsafe {
121             let (addr, size) = self.init_array;
122             for i in (0..size).step_by(8) {
123                 let func = transmute::<usize, *const Option<extern "C" fn()>>(addr + i);
124                 (*func).map(|x| x());
125             }
126         }
127     }
128 
129     pub fn run_fini(&self) {
130         unsafe {
131             let (addr, size) = self.fini_array;
132             for i in (0..size).step_by(8).rev() {
133                 let func = transmute::<usize, *const Option<extern "C" fn()>>(addr + i);
134                 (*func).map(|x| x());
135             }
136         }
137     }
138 
139     fn get_runpath(path: &str, elf: &Elf) -> Result<Option<String>> {
140         if let Some(dynamic) = &elf.dynamic {
141             let entry = dynamic.dyns.iter().find(|d| d.d_tag == DT_RUNPATH);
142             match entry {
143                 Some(entry) => {
144                     let runpath = elf
145                         .dynstrtab
146                         .get(entry.d_val as usize)
147                         .ok_or(Error::Malformed("Missing RUNPATH in dynstrtab".to_string()))??;
148                     let base = dirname(path);
149                     return Ok(Some(runpath.replace("$ORIGIN", &base)));
150                 }
151                 _ => return Ok(None),
152             }
153         }
154         return Ok(None);
155     }
156 
157     fn mmap_and_copy(
158         path: &str,
159         elf: &Elf,
160         data: &Vec<u8>,
161         base_addr: Option<usize>,
162         tls_offset: usize,
163     ) -> Result<(&'static mut [u8], Option<Master>)> {
164         trace!("# {}", path);
165         // data for struct LinkMap
166         let mut l_ld = 0;
167         // Calculate virtual memory bounds
168         let bounds = {
169             let mut bounds_opt: Option<(usize, usize)> = None;
170             for ph in elf.program_headers.iter() {
171                 let voff = ph.p_vaddr % ph.p_align;
172                 let vaddr = (ph.p_vaddr - voff) as usize;
173                 let vsize = round_up((ph.p_memsz + voff) as usize, ph.p_align as usize);
174 
175                 match ph.p_type {
176                     program_header::PT_DYNAMIC => {
177                         l_ld = ph.p_vaddr;
178                     }
179                     program_header::PT_LOAD => {
180                         trace!("  load {:#x}, {:#x}: {:x?}", vaddr, vsize, ph);
181                         if let Some(ref mut bounds) = bounds_opt {
182                             if vaddr < bounds.0 {
183                                 bounds.0 = vaddr;
184                             }
185                             if vaddr + vsize > bounds.1 {
186                                 bounds.1 = vaddr + vsize;
187                             }
188                         } else {
189                             bounds_opt = Some((vaddr, vaddr + vsize));
190                         }
191                     }
192                     _ => (),
193                 }
194             }
195             bounds_opt.ok_or(Error::Malformed(
196                 "Unable to find PT_LOAD section".to_string(),
197             ))?
198         };
199         trace!("  bounds {:#x}, {:#x}", bounds.0, bounds.1);
200         // Allocate memory
201         let mmap = unsafe {
202             if let Some(addr) = base_addr {
203                 let size = if is_pie_enabled(&elf) {
204                     bounds.1
205                 } else {
206                     bounds.1 - bounds.0
207                 };
208                 _r_debug.insert_first(addr as usize, path, addr + l_ld as usize);
209                 slice::from_raw_parts_mut(addr as *mut u8, size)
210             } else {
211                 let (start, end) = bounds;
212                 let size = end - start;
213                 let mut flags = sys_mman::MAP_ANONYMOUS | sys_mman::MAP_PRIVATE;
214                 if start != 0 {
215                     flags |= sys_mman::MAP_FIXED_NOREPLACE;
216                 }
217                 trace!("  mmap({:#x}, {:x}, {:x})", start, size, flags);
218                 let ptr = sys_mman::mmap(
219                     start as *mut c_void,
220                     size,
221                     //TODO: Make it possible to not specify PROT_EXEC on Redox
222                     sys_mman::PROT_READ | sys_mman::PROT_WRITE,
223                     flags,
224                     -1,
225                     0,
226                 );
227                 if ptr as usize == !0
228                 /* MAP_FAILED */
229                 {
230                     return Err(Error::Malformed(format!(
231                         "failed to map {}. errno: {}",
232                         path, STR_ERROR[errno as usize]
233                     )));
234                 }
235                 if start as *mut c_void != ptr::null_mut() {
236                     assert_eq!(
237                         ptr, start as *mut c_void,
238                         "mmap must always map on the destination we requested"
239                     );
240                 }
241                 trace!("    = {:p}", ptr);
242                 ptr::write_bytes(ptr as *mut u8, 0, size);
243                 _r_debug.insert(ptr as usize, path, ptr as usize + l_ld as usize);
244                 slice::from_raw_parts_mut(ptr as *mut u8, size)
245             }
246         };
247 
248         let skip_load_segment_copy = base_addr.is_some();
249         let mut tcb_master = None;
250 
251         // Copy data
252         for ph in elf.program_headers.iter() {
253             let voff = ph.p_vaddr % ph.p_align;
254             let vaddr = (ph.p_vaddr - voff) as usize;
255             let vsize = round_up((ph.p_memsz + voff) as usize, ph.p_align as usize);
256 
257             match ph.p_type {
258                 program_header::PT_LOAD => {
259                     if skip_load_segment_copy {
260                         continue;
261                     }
262                     let obj_data = {
263                         let range = ph.file_range();
264                         match data.get(range.clone()) {
265                             Some(some) => some,
266                             None => {
267                                 return Err(Error::Malformed(format!(
268                                     "failed to read {:x?}",
269                                     range
270                                 )))
271                             }
272                         }
273                     };
274 
275                     let mmap_data = {
276                         let range = if is_pie_enabled(elf) {
277                             let addr = ph.p_vaddr as usize;
278                             addr..addr + obj_data.len()
279                         } else {
280                             let addr = ph.p_vaddr as usize - mmap.as_ptr() as usize;
281                             addr..addr + obj_data.len()
282                         };
283                         match mmap.get_mut(range.clone()) {
284                             Some(some) => some,
285                             None => {
286                                 return Err(Error::Malformed(format!(
287                                     "failed to write {:x?}",
288                                     range
289                                 )));
290                             }
291                         }
292                     };
293                     trace!(
294                         "  copy {:#x}, {:#x}: {:#x}, {:#x}",
295                         vaddr,
296                         vsize,
297                         voff,
298                         obj_data.len()
299                     );
300                     mmap_data.copy_from_slice(obj_data);
301                 }
302                 program_header::PT_TLS => {
303                     let ptr = unsafe {
304                         if is_pie_enabled(elf) {
305                             mmap.as_ptr().add(ph.p_vaddr as usize)
306                         } else {
307                             ph.p_vaddr as *const u8
308                         }
309                     };
310                     tcb_master = Some(Master {
311                         ptr: ptr,
312                         len: ph.p_filesz as usize,
313                         offset: tls_offset + vsize,
314                     });
315                     trace!("  tcb master {:x?}", tcb_master);
316                 }
317                 program_header::PT_DYNAMIC => {
318                     // overwrite DT_DEBUG if exist in DYNAMIC segment
319                     // first we identify the location of DYNAMIC segment
320                     let dyn_start = ph.p_vaddr as usize;
321                     let mut debug_start = None;
322                     // next we identify the location of DT_DEBUG in .dynamic section
323                     if let Some(dynamic) = elf.dynamic.as_ref() {
324                         let mut i = 0;
325                         for entry in &dynamic.dyns {
326                             if entry.d_tag == DT_DEBUG {
327                                 debug_start = Some(i as usize);
328                                 break;
329                             }
330                             i += 1;
331                         }
332                     }
333                     if let Some(i) = debug_start {
334                         let bytes: [u8; size_of::<Dyn>() / 2] =
335                             unsafe { transmute((&_r_debug) as *const RTLDDebug as usize) };
336                         let start = if is_pie_enabled(elf) {
337                             dyn_start + i * size_of::<Dyn>() + size_of::<Dyn>() / 2
338                         } else {
339                             dyn_start + i * size_of::<Dyn>() + size_of::<Dyn>() / 2
340                                 - mmap.as_mut_ptr() as usize
341                         };
342                         mmap[start..start + size_of::<Dyn>() / 2].clone_from_slice(&bytes);
343                     }
344                 }
345                 _ => (),
346             }
347         }
348         return Ok((mmap, tcb_master));
349     }
350 
351     fn collect_syms(
352         elf: &Elf,
353         mmap: &[u8],
354     ) -> Result<(BTreeMap<String, Symbol>, BTreeMap<String, Symbol>)> {
355         let mut globals = BTreeMap::new();
356         let mut weak_syms = BTreeMap::new();
357         for sym in elf.dynsyms.iter() {
358             let bind = sym.st_bind();
359             if sym.st_shndx == SHN_UNDEF as usize
360                 || ![sym::STB_GLOBAL, sym::STB_WEAK].contains(&bind)
361             {
362                 continue;
363             }
364             let name: String;
365             let value: Symbol;
366             if let Some(name_res) = elf.dynstrtab.get(sym.st_name) {
367                 name = name_res?.to_string();
368                 value = if is_pie_enabled(elf) {
369                     Symbol {
370                         base: mmap.as_ptr() as usize,
371                         value: sym.st_value as usize,
372                         size: sym.st_size as usize,
373                         sym_type: sym::st_type(sym.st_info),
374                     }
375                 } else {
376                     Symbol {
377                         base: 0,
378                         value: sym.st_value as usize,
379                         size: sym.st_size as usize,
380                         sym_type: sym::st_type(sym.st_info),
381                     }
382                 };
383             } else {
384                 continue;
385             }
386             match sym.st_bind() {
387                 sym::STB_GLOBAL => {
388                     trace!("  global {}: {:x?} = {:p}", &name, sym, value.as_ptr());
389                     globals.insert(name, value);
390                 }
391                 sym::STB_WEAK => {
392                     trace!("  weak {}: {:x?} = {:p}", &name, sym, value.as_ptr());
393                     weak_syms.insert(name, value);
394                 }
395                 _ => unreachable!(),
396             }
397         }
398         return Ok((globals, weak_syms));
399     }
400 
401     fn init_fini_arrays(elf: &Elf, mmap_addr: usize) -> ((usize, usize), (usize, usize)) {
402         let mut init_array: (usize, usize) = (0, 0);
403         let mut fini_array: (usize, usize) = (0, 0);
404         for section in elf
405             .section_headers
406             .iter()
407             .filter(|s| s.sh_type == SHT_INIT_ARRAY || s.sh_type == SHT_FINI_ARRAY)
408         {
409             let addr = if is_pie_enabled(&elf) {
410                 mmap_addr + section.vm_range().start
411             } else {
412                 section.vm_range().start
413             };
414             if section.sh_type == SHT_INIT_ARRAY {
415                 init_array = (addr, section.sh_size as usize);
416             } else {
417                 fini_array = (addr, section.sh_size as usize);
418             }
419         }
420         return (init_array, fini_array);
421     }
422 }
423 
424 impl Drop for DSO {
425     fn drop(&mut self) {
426         self.run_fini();
427         unsafe { sys_mman::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()) };
428     }
429 }
430 
431 pub fn is_pie_enabled(elf: &Elf) -> bool {
432     return elf.header.e_type == ET_DYN;
433 }
434 
435 fn basename(path: &str) -> String {
436     return path.split("/").last().unwrap_or(path).to_string();
437 }
438 
439 fn dirname(path: &str) -> String {
440     let mut parts: Vec<&str> = path.split("/").collect();
441     parts.truncate(parts.len() - 1);
442     return parts.join("/");
443 }
444