From 98ea9696753173b325af1f7fc01e5fbf3451c743 Mon Sep 17 00:00:00 2001
From: Mark Poliakov <mark@alnyan.me>
Date: Fri, 15 Nov 2024 11:13:41 +0200
Subject: [PATCH] ld: call global constructors

---
 userspace/dyn-loader/src/main.rs              |  6 ++
 userspace/dyn-loader/src/object.rs            | 61 +++++++++++++++++--
 userspace/dyn-loader/src/relocation/x86_64.rs |  2 +-
 3 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/userspace/dyn-loader/src/main.rs b/userspace/dyn-loader/src/main.rs
index 866735f3..e05d88a3 100644
--- a/userspace/dyn-loader/src/main.rs
+++ b/userspace/dyn-loader/src/main.rs
@@ -53,6 +53,12 @@ fn run(binary: &str, args: &[String]) -> Result<!, Error> {
         lib.relocate(&mut state)?;
     }
 
+    // Call global constructors before handing off control to the program
+    for (_, lib) in libraries.iter_mut() {
+        lib.call_constructors();
+    }
+    root.call_constructors();
+
     let entry = root.entry().ok_or(Error::NoEntrypoint)?;
     debug_trace!("entry = {:p}", entry);
 
diff --git a/userspace/dyn-loader/src/object.rs b/userspace/dyn-loader/src/object.rs
index 3c0b1a14..2268e1ce 100644
--- a/userspace/dyn-loader/src/object.rs
+++ b/userspace/dyn-loader/src/object.rs
@@ -1,15 +1,14 @@
 use std::{
     fs::File,
     io::{BufReader, Read, Seek, SeekFrom},
+    ops::Range,
     path::{Path, PathBuf},
     rc::Rc,
 };
 
 use elf::{
     abi::{
-        DF_1_PIE, DT_FLAGS_1, DT_NEEDED, ET_DYN, ET_EXEC, PT_DYNAMIC, PT_GNU_EH_FRAME,
-        PT_GNU_RELRO, PT_GNU_STACK, PT_INTERP, PT_LOAD, PT_NOTE, PT_NULL, PT_PHDR, SHN_UNDEF,
-        SHT_REL, SHT_RELA, STB_GLOBAL, STB_LOCAL, STB_WEAK,
+        DF_1_PIE, DT_FINI, DT_FINI_ARRAY, DT_FINI_ARRAYSZ, DT_FLAGS_1, DT_INIT, DT_INIT_ARRAY, DT_INIT_ARRAYSZ, DT_NEEDED, DT_PREINIT_ARRAY, DT_PREINIT_ARRAYSZ, ET_DYN, ET_EXEC, PT_DYNAMIC, PT_GNU_EH_FRAME, PT_GNU_RELRO, PT_GNU_STACK, PT_INTERP, PT_LOAD, PT_NOTE, PT_NULL, PT_PHDR, SHN_UNDEF, SHT_REL, SHT_RELA, STB_GLOBAL, STB_LOCAL, STB_WEAK
     },
     endian::AnyEndian,
     symbol::Symbol,
@@ -55,6 +54,8 @@ pub struct Object {
 
     mapping: Option<Mapping>,
     dynamic_symbol_array: Vec<DynamicSymbol>,
+
+    init_array: Option<Range<usize>>,
 }
 
 impl ResolvedSymbol<'_> {
@@ -142,6 +143,8 @@ impl Object {
 
             mapping: None,
             dynamic_symbol_array,
+
+            init_array: None
         })
     }
 
@@ -157,7 +160,9 @@ impl Object {
         // TODO segment granularity mappings
         let mapping_size = self.vma_end - self.vma_start;
         let mut mapping = match self.ty {
-            ElfType::Relocatable(_) => Mapping::new(mapping_size, MappingFlags::WRITE | MappingFlags::EXEC)?,
+            ElfType::Relocatable(_) => {
+                Mapping::new(mapping_size, MappingFlags::WRITE | MappingFlags::EXEC)?
+            }
             // TODO fixed mapping for this one
             ElfType::Static => todo!(),
         };
@@ -168,6 +173,34 @@ impl Object {
             mapping.base() as i64 - self.vma_start as i64
         );
 
+        // Parse .dynamic section again to extract extra info
+        if let Some(dynamic) = self.elf.dynamic()? {
+            let mut dt_init_array = None;
+            let mut dt_init_array_sz = None;
+
+            for dynamic in dynamic {
+                match dynamic.d_tag {
+                    DT_INIT_ARRAY => dt_init_array = Some(dynamic.d_ptr()),
+                    DT_INIT_ARRAYSZ => dt_init_array_sz = Some(dynamic.d_val()),
+                    DT_FINI_ARRAY => todo!(),
+                    DT_FINI_ARRAYSZ => todo!(),
+                    DT_INIT => todo!(),
+                    DT_FINI => todo!(),
+                    DT_PREINIT_ARRAY => todo!(),
+                    DT_PREINIT_ARRAYSZ => todo!(),
+                    _ => ()
+                }
+            }
+
+            if let (Some(ptr), Some(size)) = (dt_init_array, dt_init_array_sz) {
+                // This address is subject to relocation
+                debug_trace!("{:?}: DT_INIT_ARRAY: {:#x?}", self.path, ptr..ptr + size);
+                let ptr = ptr as i64 + mapping.base() as i64 - self.vma_start as i64;
+                let ptr = ptr as usize;
+                self.init_array = Some(ptr..ptr + size as usize);
+            }
+        }
+
         // Load segments
         for segment in self.elf.segments() {
             let mem_size = segment.p_memsz as usize;
@@ -220,6 +253,24 @@ impl Object {
         Ok(())
     }
 
+    pub fn call_constructors(&mut self) {
+        type Constructor = extern "C" fn();
+
+        if let Some(init_array) = self.init_array.as_ref() {
+            for slot in init_array.clone().step_by(size_of::<usize>()) {
+                let func = unsafe { core::ptr::with_exposed_provenance::<usize>(slot).read_unaligned() };
+
+                if func == 0 || func == usize::MAX {
+                    continue;
+                }
+
+                let func = unsafe { core::mem::transmute::<_, Constructor>(func) };
+
+                func();
+            }
+        }
+    }
+
     pub fn entry(&self) -> Option<extern "C" fn(usize)> {
         let entry = self.elf.ehdr.e_entry as i64;
         let offset = match &self.ty {
@@ -273,7 +324,7 @@ impl Object {
             for rela in rela_section {
                 let dynsym = &self.dynamic_symbol_array[rela.r_sym as usize];
                 let sym = match dynsym.raw.st_bind() {
-                    STB_GLOBAL => ResolvedSymbol::Global(state.lookup(dynsym).unwrap()),
+                    STB_GLOBAL | STB_WEAK => ResolvedSymbol::Global(state.lookup(dynsym).unwrap()),
                     STB_LOCAL => {
                         if dynsym.name.is_empty() {
                             ResolvedSymbol::Null
diff --git a/userspace/dyn-loader/src/relocation/x86_64.rs b/userspace/dyn-loader/src/relocation/x86_64.rs
index 28d4d758..8ccf2bc4 100644
--- a/userspace/dyn-loader/src/relocation/x86_64.rs
+++ b/userspace/dyn-loader/src/relocation/x86_64.rs
@@ -28,7 +28,7 @@ impl Relocation for Rela {
             // S
             R_X86_64_JUMP_SLOT | R_X86_64_GLOB_DAT => Ok(Some(RelaValue::QWord(s))),
             // S + A
-            R_X86_64_64 => todo!(),
+            R_X86_64_64 => Ok(Some(RelaValue::QWord(s + self.r_addend))),
             // B + A
             R_X86_64_RELATIVE => Ok(Some(RelaValue::QWord(load_base as i64 + self.r_addend))),
             // TLS