From 6aeb2d6fa24190d15525a9856e9fc580dd2322c6 Mon Sep 17 00:00:00 2001
From: oddcoder <ahmedsoliman@oddcoder.com>
Date: Sun, 19 Apr 2020 13:54:50 +0200
Subject: [PATCH] Implement code that use .init_array and .fini_array

This patch implements ld.so code that makes use of both .init_array and
.fini_array. .init_array is fully utilized and is used in the correct
manner. However .fini_array is not used yet although the function that
runs .fini_array exists
---
 src/header/dlfcn/mod.rs |  24 +++----
 src/ld_so/linker.rs     | 136 +++++++++++++++++++++++++++++++++++-----
 src/ld_so/src/lib.rs    |   1 +
 src/ld_so/start.rs      |   6 +-
 4 files changed, 138 insertions(+), 29 deletions(-)

diff --git a/src/header/dlfcn/mod.rs b/src/header/dlfcn/mod.rs
index f9c3bc81..73206830 100644
--- a/src/header/dlfcn/mod.rs
+++ b/src/header/dlfcn/mod.rs
@@ -57,22 +57,16 @@ pub unsafe extern "C" fn dlopen(filename: *const c_char, flags: c_int) -> *mut c
             eprintln!("dlopen: linker_ptr: {:p}", tcb.linker_ptr);
             let mut linker = (&*tcb.linker_ptr).lock();
 
-            match linker.load_library(filename) {
-                Ok(()) => (),
-                Err(err) => {
-                    eprintln!("dlopen: failed to load {}", filename);
-                    ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
-                    return ptr::null_mut();
-                }
+            if let Err(err) = linker.load_library(filename) {
+                eprintln!("dlopen: failed to load {}", filename);
+                ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
+                return ptr::null_mut();
             }
 
-            match linker.link(None, None) {
-                Ok(ok) => (),
-                Err(err) => {
-                    eprintln!("dlopen: failed to link '{}': {}", filename, err);
-                    ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
-                    return ptr::null_mut();
-                }
+            if let Err(err) = linker.link(None, None) {
+                eprintln!("dlopen: failed to link '{}': {}", filename, err);
+                ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
+                return ptr::null_mut();
             };
 
             // TODO
@@ -125,6 +119,8 @@ pub unsafe extern "C" fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *m
 
 #[no_mangle]
 pub extern "C" fn dlclose(handle: *mut c_void) -> c_int {
+    // TODO: Loader::fini() should be called about here
+
     0
 }
 
diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs
index b80d5d8b..fbd7dd0a 100644
--- a/src/ld_so/linker.rs
+++ b/src/ld_so/linker.rs
@@ -42,7 +42,20 @@ pub struct DSO {
     pub base_addr: usize,
     pub entry_point: usize,
 }
+#[derive(Default, Debug)]
+pub struct DepTree {
+    pub name: String,
+    pub deps: Vec<DepTree>,
+}
 
+impl DepTree {
+    fn new(name: String) -> DepTree {
+        DepTree {
+            name,
+            deps: Vec::new(),
+        }
+    }
+}
 pub struct Linker {
     // Used by load
     /// Library path to search when loading library by name
@@ -59,8 +72,10 @@ pub struct Linker {
     mmaps: BTreeMap<String, &'static mut [u8]>,
     verbose: bool,
     tls_index_offset: usize,
-    // used to detect circular dependencies in the Linker::load function
+    /// A set used to detect circular dependencies in the Linker::load function
     cir_dep: BTreeSet<String>,
+    /// Each object will have its children callec once with no repetition.
+    dep_tree: DepTree,
 }
 
 impl Linker {
@@ -74,10 +89,19 @@ impl Linker {
             verbose,
             tls_index_offset: 0,
             cir_dep: BTreeSet::new(),
+            dep_tree: Default::default(),
         }
     }
 
     pub fn load(&mut self, name: &str, path: &str) -> Result<()> {
+        self.dep_tree = self.load_recursive(name, path)?;
+        if self.verbose {
+            println!("Dep tree: {:#?}", self.dep_tree);
+        }
+        return Ok(());
+    }
+
+    fn load_recursive(&mut self, name: &str, path: &str) -> Result<DepTree> {
         if self.verbose {
             println!("load {}: {}", name, path);
         }
@@ -88,9 +112,9 @@ impl Linker {
             )));
         }
 
-        self.cir_dep.insert(name.to_string());
+        let mut deps = DepTree::new(name.to_string());
         let mut data = Vec::new();
-
+        self.cir_dep.insert(name.to_string());
         let path_c = CString::new(path)
             .map_err(|err| Error::Malformed(format!("invalid path '{}': {}", path, err)))?;
 
@@ -102,29 +126,32 @@ impl Linker {
             file.read_to_end(&mut data)
                 .map_err(|err| Error::Malformed(format!("failed to read '{}': {}", path, err)))?;
         }
-        let result = self.load_data(name, data.into_boxed_slice());
+        deps.deps = self.load_data(name, data.into_boxed_slice())?;
         self.cir_dep.remove(name);
-        result
+        Ok(deps)
     }
 
-    pub fn load_data(&mut self, name: &str, data: Box<[u8]>) -> Result<()> {
+    pub fn load_data(&mut self, name: &str, data: Box<[u8]>) -> Result<Vec<DepTree>> {
         let elf = Elf::parse(&data)?;
         //println!("{:#?}", elf);
-
+        let mut deps = Vec::new();
         for library in elf.libraries.iter() {
-            self.load_library(library)?;
+            if let Some(dep) = self.load_library(library)? {
+                deps.push(dep);
+            }
         }
 
         self.objects.insert(name.to_string(), data);
 
-        Ok(())
+        return Ok(deps);
     }
 
-    pub fn load_library(&mut self, name: &str) -> Result<()> {
+    pub fn load_library(&mut self, name: &str) -> Result<Option<DepTree>> {
         if self.objects.contains_key(name) {
-            Ok(())
+            // It should be previously resolved so we don't need to worry about it
+            Ok(None)
         } else if name.contains('/') {
-            self.load(name, name)
+            Ok(Some(self.load_recursive(name, name)?))
         } else {
             let library_path = self.library_path.clone();
             for part in library_path.split(PATH_SEP) {
@@ -146,8 +173,7 @@ impl Linker {
                 };
 
                 if access {
-                    self.load(name, &path)?;
-                    return Ok(());
+                    return Ok(Some(self.load_recursive(name, &path)?));
                 }
             }
 
@@ -212,6 +238,73 @@ impl Linker {
             None
         }
     }
+    pub fn run_init(&self) -> Result<()> {
+        self.run_init_tree(&self.dep_tree)
+    }
+
+    fn run_init_tree(&self, root: &DepTree) -> Result<()> {
+        for node in root.deps.iter() {
+            self.run_init_tree(node)?;
+        }
+        if self.verbose {
+            println!("init {}", &root.name);
+        }
+        let mmap = match self.mmaps.get(&root.name) {
+            Some(some) => some,
+            None => return Ok(()),
+        };
+        let elf = Elf::parse(self.objects.get(&root.name).unwrap())?;
+        for section in elf.section_headers {
+            let name = match elf.shdr_strtab.get(section.sh_name) {
+                Some(x) => match x {
+                    Ok(y) => y,
+                    _ => continue,
+                },
+                _ => continue,
+            };
+            if name == ".init_array" {
+                let addr = mmap.as_ptr() as usize + section.vm_range().start;
+                for i in (0..section.sh_size).step_by(8) {
+                    unsafe { call_inits_finis(addr + i as usize) };
+                }
+            }
+        }
+        return Ok(());
+    }
+
+    pub fn run_fini(&self) -> Result<()> {
+        self.run_fini_tree(&self.dep_tree)
+    }
+
+    fn run_fini_tree(&self, root: &DepTree) -> Result<()> {
+        if self.verbose {
+            println!("init {}", &root.name);
+        }
+        let mmap = match self.mmaps.get(&root.name) {
+            Some(some) => some,
+            None => return Ok(()),
+        };
+        let elf = Elf::parse(self.objects.get(&root.name).unwrap())?;
+        for section in elf.section_headers {
+            let name = match elf.shdr_strtab.get(section.sh_name) {
+                Some(x) => match x {
+                    Ok(y) => y,
+                    _ => continue,
+                },
+                _ => continue,
+            };
+            if name == ".fini_array" {
+                let addr = mmap.as_ptr() as usize + section.vm_range().start;
+                for i in (0..section.sh_size).step_by(8) {
+                    unsafe { call_inits_finis(addr + i as usize) };
+                }
+            }
+        }
+        for node in root.deps.iter() {
+            self.run_fini_tree(node)?;
+        }
+        return Ok(());
+    }
 
     pub fn link(&mut self, primary_opt: Option<&str>, dso: Option<DSO>) -> Result<Option<usize>> {
         unsafe { _r_debug.state = RTLDState::RT_ADD };
@@ -726,3 +819,18 @@ impl Linker {
         Ok(entry_opt)
     }
 }
+
+unsafe extern "C" fn call_inits_finis(addr: usize) {
+    #[cfg(target_arch = "x86_64")]
+    asm!("
+        cmp qword ptr [rdi], 0
+        je end
+        call [rdi]
+end:    nop
+        "
+        :
+        :
+        :
+        : "intel", "volatile"
+    );
+}
diff --git a/src/ld_so/src/lib.rs b/src/ld_so/src/lib.rs
index f26bc0af..e3d17c65 100644
--- a/src/ld_so/src/lib.rs
+++ b/src/ld_so/src/lib.rs
@@ -33,6 +33,7 @@ next:   pop rsi
         xor r11, r11
         fninit
         jmp rax
+        # TODO: Loader::fini() should be called about here
         "
         :
         :
diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs
index 1d60b714..7b433faa 100644
--- a/src/ld_so/start.rs
+++ b/src/ld_so/start.rs
@@ -203,7 +203,11 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) ->
             loop {}
         }
     };
-
+    if let Err(e) = linker.run_init() {
+        eprintln!("ld.so: failed to run .init_array");
+        unistd::_exit(1);
+        loop {}
+    }
     if let Some(tcb) = unsafe { Tcb::current() } {
         tcb.linker_ptr = Box::into_raw(Box::new(Mutex::new(linker)));
     }
-- 
GitLab