fix(ld.so): do not depend on TLS at all

In the next big refactor (next PR), all of the platform functionality
used by both relibc and ld.so will be moved into a `platform`/`sysdeps`
crate and then ld.so would be moved out of relibc and not link with it.

I think doing it in a seperate PR would make it more managable, as when
I did half of it, the diff was pretty huge and that way it would be
easier to review too :)

Signed-off-by: Anhad Singh <andypython@protonmail.com>
This commit is contained in:
Anhad Singh
2024-12-05 23:03:52 +11:00
parent a527cc53ea
commit c286ad2868
16 changed files with 143 additions and 102 deletions
+2 -2
View File
@@ -200,7 +200,7 @@ $(BUILD)/debug/ld_so.o: $(SRC)
touch $@
$(BUILD)/debug/ld_so: $(BUILD)/debug/ld_so.o $(BUILD)/debug/crti.o $(BUILD)/debug/libc.a $(BUILD)/debug/crtn.o
$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections --gc-keep-exported $^ -o $@
$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@
# Release targets
@@ -240,7 +240,7 @@ $(BUILD)/release/ld_so.o: $(SRC)
touch $@
$(BUILD)/release/ld_so: $(BUILD)/release/ld_so.o $(BUILD)/release/crti.o $(BUILD)/release/libc.a $(BUILD)/release/crtn.o
$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections --gc-keep-exported $^ -o $@
$(LD) --no-relax -T ld_so/ld_script/$(TARGET).ld --allow-multiple-definition --gc-sections $^ -o $@
# Other targets
+16 -5
View File
@@ -98,25 +98,25 @@ SECTIONS
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections */
.tdata :
/* .tdata :
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
/* .init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
} */
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
@@ -241,5 +241,16 @@ SECTIONS
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
/DISCARD/ : {
*(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*)
/*
* XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
* ld.so will be moved out of relibc. So, till that time, we have to discard any sections
* that may reference use thread local storage.
*
* .init_array also depends on TLS and is discarded as we don't need it.
*/
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
*(.init_array)
}
}
+16 -5
View File
@@ -98,25 +98,25 @@ SECTIONS
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections */
.tdata :
/* .tdata :
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
/* .init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
} */
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
@@ -241,5 +241,16 @@ SECTIONS
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
/DISCARD/ : {
*(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*)
/*
* XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
* ld.so will be moved out of relibc. So, till that time, we have to discard any sections
* that may reference use thread local storage.
*
* .init_array also depends on TLS and is discarded as we don't need it.
*/
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
*(.init_array)
}
}
+16 -5
View File
@@ -89,25 +89,25 @@ SECTIONS
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections */
.tdata :
/* .tdata :
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
/* .init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
} */
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
@@ -232,5 +232,16 @@ SECTIONS
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
/DISCARD/ : {
*(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*)
/*
* XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
* ld.so will be moved out of relibc. So, till that time, we have to discard any sections
* that may reference use thread local storage.
*
* .init_array also depends on TLS and is discarded as we don't need it.
*/
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
*(.init_array)
}
}
+16 -7
View File
@@ -101,27 +101,25 @@ SECTIONS
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections */
__relibc_ldso_tls_start = .;
.tdata : ALIGN(4K)
/* .tdata : ALIGN(4K)
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
__relibc_ldso_tls_end = .;
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
/* .init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
} */
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
@@ -246,5 +244,16 @@ SECTIONS
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
/DISCARD/ : {
*(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*)
/*
* XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
* ld.so will be moved out of relibc. So, till that time, we have to discard any sections
* that may reference use thread local storage.
*
* .init_array also depends on TLS and is discarded as we don't need it.
*/
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
*(.init_array)
}
}
+16 -7
View File
@@ -98,27 +98,25 @@ SECTIONS
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges*) }
/* Thread Local Storage sections */
__relibc_ldso_tls_start = .;
.tdata : ALIGN(4K)
/* .tdata : ALIGN(4K)
{
PROVIDE_HIDDEN (__tdata_start = .);
*(.tdata .tdata.* .gnu.linkonce.td.*)
}
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
__relibc_ldso_tls_end = .;
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } */
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
/* .init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
} */
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
@@ -243,5 +241,16 @@ SECTIONS
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
/DISCARD/ : {
*(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*)
/*
* XXX: As of now, ld.so links with relibc which has the main functionality. In the next refactor,
* ld.so will be moved out of relibc. So, till that time, we have to discard any sections
* that may reference use thread local storage.
*
* .init_array also depends on TLS and is discarded as we don't need it.
*/
*(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon)
*(.init_array)
}
}
+1 -3
View File
@@ -45,8 +45,6 @@ _start:
# Call ld_so_start(stack, entry)
mov rdi, rbp
sub rsi, 5
lea rdx, __relibc_ldso_tls_start
lea rcx, __relibc_ldso_tls_end
call relibc_ld_so_start
# Restore original stack, clear registers, and jump to new start function
@@ -83,7 +81,7 @@ pub unsafe extern "C" fn main(_argc: isize, _argv: *const *const i8) -> usize {
#[linkage = "weak"]
#[no_mangle]
extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! {
extern "C" fn relibc_panic(_pi: &::core::panic::PanicInfo) -> ! {
loop {}
}
+1 -1
View File
@@ -989,7 +989,7 @@ pub extern "C" fn herror(prefix: *const c_char) {
str::from_utf8_unchecked(msg_cstr.to_bytes())
};
let mut writer = platform::FileWriter(2);
let mut writer = platform::FileWriter::new(2);
// Prefix is optional
match unsafe { CStr::from_nullable_ptr(prefix) }
.and_then(|prefix| str::from_utf8(prefix.to_bytes()).ok())
+1 -1
View File
@@ -852,7 +852,7 @@ pub unsafe extern "C" fn perror(s: *const c_char) {
} else {
"Unknown error"
};
let mut w = platform::FileWriter(2);
let mut w = platform::FileWriter::new(2);
// The prefix, `s`, is optional (empty or NULL) according to the spec
match CStr::from_nullable_ptr(s).and_then(|s_cstr| str::from_utf8(s_cstr.to_bytes()).ok()) {
+6 -13
View File
@@ -5,7 +5,7 @@ use super::{
};
use crate::{
header::{errno::STR_ERROR, sys_mman},
platform::{types::c_void, ERRNO},
platform::{types::c_void, Pal, Sys, ERRNO},
};
use alloc::{
collections::BTreeMap,
@@ -215,7 +215,7 @@ impl DSO {
flags |= sys_mman::MAP_FIXED_NOREPLACE;
}
trace!(" mmap({:#x}, {:x}, {:x})", start, size, flags);
let ptr = sys_mman::mmap(
let ptr = Sys::mmap(
start as *mut c_void,
size,
//TODO: Make it possible to not specify PROT_EXEC on Redox
@@ -223,16 +223,9 @@ impl DSO {
flags,
-1,
0,
);
if ptr as usize == !0
/* MAP_FAILED */
{
return Err(Error::Malformed(format!(
"failed to map {}. errno: {}",
path,
STR_ERROR[ERRNO.get() as usize]
)));
}
)
.map_err(|e| Error::Malformed(format!("failed to map {}. errno: {}", path, e.0)))?;
if start as *mut c_void != ptr::null_mut() {
assert_eq!(
ptr, start as *mut c_void,
@@ -425,7 +418,7 @@ impl DSO {
impl Drop for DSO {
fn drop(&mut self) {
self.run_fini();
unsafe { sys_mman::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()) };
unsafe { Sys::munmap(self.mmap.as_mut_ptr() as *mut c_void, self.mmap.len()) };
}
}
+29 -12
View File
@@ -12,14 +12,17 @@ use goblin::{
use crate::{
c_str::{CStr, CString},
fs::File,
error::Errno,
header::{
dl_tls::{__tls_get_addr, dl_tls_index},
fcntl, sys_mman,
unistd::F_OK,
},
io::Read,
platform::types::c_void,
io::{self, Read},
platform::{
types::{c_int, c_void},
Pal, Sys,
},
};
use super::{
@@ -31,6 +34,26 @@ use super::{
ExpectTlsFree, PATH_SEP,
};
/// Same as [`crate::fs::File`], but does not touch [`crate::platform::ERRNO`] as the dynamic
/// linker does not have thread-local storage.
struct File {
fd: c_int,
}
impl File {
pub fn open(path: CStr, oflag: c_int) -> core::result::Result<Self, Errno> {
Ok(Self {
fd: Sys::open(path, oflag, 0)?,
})
}
}
impl io::Read for File {
fn read(&mut self, buf: &mut [u8]) -> core::result::Result<usize, io::Error> {
Sys::read(self.fd, buf).map_err(|err| io::Error::from_raw_os_error(err.0))
}
}
#[derive(Clone, Copy, Debug)]
pub struct Symbol {
pub value: usize,
@@ -70,7 +93,6 @@ impl Linker {
}
}
/// **Warning**: Switches to the program's TCB. Thread locals should not be accessed after this.
pub fn load_program(&mut self, path: &str, base_addr: Option<usize>) -> Result<usize> {
self.load_object(path, &None, base_addr, false)?;
return Ok(self.objects.get(&root_id).unwrap().entry_point);
@@ -193,10 +215,7 @@ impl Linker {
tcb.append_masters(tcb_masters);
// Copy the master data into the static TLS block.
tcb.copy_masters()?;
tcb.activate();
// XXX: Beyond this point, any thread local's for ld.so should not be accessed. Though, the
// TCB can still be accessed.
} else {
let tcb = Tcb::current().expect("failed to get current tcb");
@@ -502,12 +521,10 @@ impl Linker {
vaddr as *const u8
};
trace!(" prot {:#x}, {:#x}: {:p}, {:#x}", vaddr, vsize, ptr, prot);
sys_mman::mprotect(ptr as *mut c_void, vsize, prot)
Sys::mprotect(ptr as *mut c_void, vsize, prot).map_err(|_| {
Error::Malformed(format!("failed to mprotect {}", obj.name))
})?;
};
if res < 0 {
return Err(Error::Malformed(format!("failed to mprotect {}", obj.name)));
}
}
}
+2 -23
View File
@@ -144,12 +144,6 @@ fn resolve_path_name(
None
}
static mut LDSO_MASTER: Master = Master {
ptr: core::ptr::null_mut(),
len: 0,
offset: 0,
};
// TODO: Make unsafe
#[no_mangle]
pub extern "C" fn relibc_ld_so_start(
@@ -158,24 +152,9 @@ pub extern "C" fn relibc_ld_so_start(
self_tls_start: usize,
self_tls_end: usize,
) -> usize {
// Setup TLS and TCB for ourselves.
//
// On Redox, we need the TCB to setup in order to do anything. Also, thread local's like
// ERRNO may be accessed by the dynamic linker, so we need static TLS to be setup.
// Setup TCB for ourselves.
unsafe {
let tls_size = (self_tls_end - self_tls_start).next_multiple_of(4096); // alignment set in linker script
let tcb = Tcb::new(tls_size).expect_notls("ld.so: failed to allocate bootstrap TCB");
LDSO_MASTER.ptr = self_tls_start as *mut u8;
LDSO_MASTER.len = self_tls_end - self_tls_start;
LDSO_MASTER.offset = tls_size;
tcb.masters_ptr = &mut LDSO_MASTER;
tcb.masters_len = core::mem::size_of::<Master>();
tcb.copy_masters()
.expect_notls("ld.so: failed to copy TLS master data");
let tcb = Tcb::new(0).expect_notls("ld.so: failed to allocate bootstrap TCB");
tcb.activate();
#[cfg(target_os = "redox")]
+4 -7
View File
@@ -241,19 +241,16 @@ impl Tcb {
/// Mapping with correct flags for TCB and TLS
unsafe fn map(size: usize) -> Result<&'static mut [u8]> {
let ptr = sys_mman::mmap(
let ptr = Sys::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")));
}
)
.map_err(|_| Error::Malformed(format!("failed to map tls")))?;
ptr::write_bytes(ptr as *mut u8, 0, size);
Ok(slice::from_raw_parts_mut(ptr as *mut u8, size))
}
+3 -3
View File
@@ -78,7 +78,7 @@ static ALLOCATOR: Allocator = NEWALLOCATOR;
pub extern "C" fn relibc_panic(pi: &::core::panic::PanicInfo) -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter(2);
let mut w = platform::FileWriter::new(2);
let _ = w.write_fmt(format_args!("RELIBC PANIC: {}\n", pi));
Sys::exit(1);
@@ -105,7 +105,7 @@ pub extern "C" fn rust_eh_personality() {}
pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter(2);
let mut w = platform::FileWriter::new(2);
let _ = w.write_fmt(format_args!(
"RELIBC OOM: {} bytes aligned to {} bytes\n",
layout.size(),
@@ -122,7 +122,7 @@ pub extern "C" fn rust_oom(layout: ::core::alloc::Layout) -> ! {
pub extern "C" fn _Unwind_Resume() -> ! {
use core::fmt::Write;
let mut w = platform::FileWriter(2);
let mut w = platform::FileWriter::new(2);
let _ = w.write_str("_Unwind_Resume\n");
Sys::exit(1);
+2 -2
View File
@@ -13,7 +13,7 @@ macro_rules! c_str {
macro_rules! print {
($($arg:tt)*) => ({
use core::fmt::Write;
let _ = write!($crate::platform::FileWriter(1), $($arg)*);
let _ = write!($crate::platform::FileWriter::new(1), $($arg)*);
});
}
@@ -30,7 +30,7 @@ macro_rules! println {
macro_rules! eprint {
($($arg:tt)*) => ({
use core::fmt::Write;
let _ = write!($crate::platform::FileWriter(2), $($arg)*);
let _ = write!($crate::platform::FileWriter::new(2), $($arg)*);
});
}
+12 -6
View File
@@ -1,7 +1,7 @@
//! Platform abstractions and environment.
use crate::{
error::ResultExt,
error::{Errno, ResultExt},
io::{self, Read, Write},
};
use alloc::{boxed::Box, vec::Vec};
@@ -86,13 +86,19 @@ impl<'a, W: WriteByte> WriteByte for &'a mut W {
}
}
pub struct FileWriter(pub c_int);
pub struct FileWriter(pub c_int, Option<Errno>);
impl FileWriter {
pub fn write(&mut self, buf: &[u8]) -> isize {
Sys::write(self.0, buf)
.map(|u| u as isize)
.or_minus_one_errno()
pub fn new(fd: c_int) -> Self {
Self(fd, None)
}
pub fn write(&mut self, buf: &[u8]) -> fmt::Result {
let _ = Sys::write(self.0, buf).map_err(|err| {
self.1 = Some(err);
fmt::Error
})?;
Ok(())
}
}