aarch64: dynamic linking, libc and TLS for aarch64

This commit is contained in:
Mark Poliakov 2024-11-18 18:43:35 +02:00
parent d198571ac7
commit 7b9788188b
26 changed files with 490 additions and 172 deletions

View File

@ -243,6 +243,10 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
__aarch64_switch_task_and_drop(self.inner.get(), thread);
}
fn set_thread_pointer(&self, _tp: usize) {
// Do nothing: tp can be set from EL0 by writing to TPIDR_EL0 directly
}
}
impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddress>> Drop

View File

@ -49,62 +49,6 @@ pub struct LoadOptions<'e, P: AsRef<Path>> {
pub disable_aslr: bool,
}
// struct BufferPlacer<'a> {
// buffer: &'a mut [u8],
// virtual_offset: usize,
// offset: usize,
// }
//
// impl<'a> BufferPlacer<'a> {
// pub fn new(virtual_offset: usize, buffer: &'a mut [u8]) -> Self {
// Self {
// buffer,
// virtual_offset,
// offset: 0,
// }
// }
//
// unsafe fn alloc_layout<T>(
// &mut self,
// layout: Layout,
// ) -> Result<(NonNull<T>, NonNull<T>), Error> {
// // TODO checks
// let aligned = (self.offset + layout.align() - 1) & !(layout.align() - 1);
// self.offset = aligned + layout.size();
// Ok((
// NonNull::new_unchecked(self.buffer.as_mut_ptr().add(aligned) as *mut T),
// NonNull::new_unchecked((self.virtual_offset + aligned) as *mut T),
// ))
// }
// }
//
// unsafe impl<'a> Placer for BufferPlacer<'a> {
// fn place_ref<T: Place>(&mut self, r: &T) -> Result<NonNull<T::Output>, Error> {
// let layout = Layout::new::<T::Output>();
// unsafe {
// let (kernel, user) = self.alloc_layout::<T::Output>(layout)?;
// kernel.as_ptr().write(r.place(self)?);
// Ok(user)
// }
// }
//
// fn place_slice<T: Place>(&mut self, r: &[T]) -> Result<NonNull<[T::Output]>, Error> {
// let layout = Layout::array::<T>(r.len()).unwrap();
// unsafe {
// let (kernel, user) = self.alloc_layout::<T::Output>(layout)?;
// let kernel = NonNull::slice_from_raw_parts(kernel, r.len());
// let user = NonNull::slice_from_raw_parts(user, r.len());
// for (i, elem) in r.iter().enumerate() {
// kernel
// .get_unchecked_mut(i)
// .as_ptr()
// .write(elem.place(self)?);
// }
// Ok(user)
// }
// }
// }
struct ArgPlacer<'a> {
buffer: &'a mut [u8],
position: usize,
@ -192,24 +136,18 @@ fn setup_program_env(
let mut arg_ptrs = vec![];
let mut env_ptrs = vec![];
log::debug!("place karg: {:#x}", virt);
for arg in args.iter() {
let ptr = placer.put_str(arg)? + virt;
log::debug!("put arg {:?} -> {:#x}", arg, ptr);
arg_ptrs.push(ptr);
}
for env in envs.iter() {
let ptr = placer.put_str(env)? + virt;
log::debug!("put env {:?} -> {:#x}", env, ptr);
env_ptrs.push(ptr);
}
let argv = placer.put_ptr_array(&arg_ptrs)? + virt;
log::debug!("put argv {} -> {:#x}", arg_ptrs.len(), argv);
let envp = placer.put_ptr_array(&env_ptrs)? + virt;
log::debug!("put envp {} -> {:#x}", env_ptrs.len(), envp);
let auxv = placer.put_aux_array(aux)? + virt;
log::debug!("put auxv {} -> {:#x}", aux.len(), auxv);
// Put ProgramArgumentInner struct
let arg_address = placer.position + virt;

View File

@ -71,7 +71,10 @@ pub fn kinit() -> Result<(), Error> {
// TODO move this to userspace so it doesn't block the init process, maybe lazy-load on first
// attempt to load a module?
load_kernel_symbol_table(&mut ioctx, "/kernel.sym")?;
#[cfg(not(target_arch = "aarch64"))]
{
load_kernel_symbol_table(&mut ioctx, "/kernel.sym")?;
}
{
let group_id = Process::create_group();

View File

@ -48,12 +48,14 @@ pub(crate) fn map_memory(
}
};
space.allocate(
let result = space.allocate(
None,
len,
backing,
MapAttributes::USER_WRITE | MapAttributes::USER_READ | MapAttributes::NON_GLOBAL,
)
);
result
})
}

View File

@ -11,5 +11,8 @@ pub fn get_thread_pointer() -> usize {
}
pub unsafe fn set_thread_pointer(value: usize) -> Result<(), Error> {
todo!()
unsafe {
core::arch::asm!("msr tpidr_el0, {0}", in(reg) value);
}
Ok(())
}

View File

@ -56,6 +56,8 @@ pub struct Dtv {
entries: Vec<*mut c_void>,
// Entries for pthread_setspecific()-like behavior
specific: Vec<*mut c_void>,
// Self-reference for the thread
thread_self: *mut c_void,
}
struct TcbHeader {
@ -124,6 +126,7 @@ impl Dtv {
Self {
entries: Vec::new(),
specific: Vec::new(),
thread_self: null_mut(),
}
}
@ -197,14 +200,13 @@ impl Dtv {
pub fn init_tls_from_auxv<'a, I: Iterator<Item = &'a AuxValue>>(
auxv: I,
force: bool,
tcb_size: usize,
) -> Result<Option<TlsImage>, Error> {
let Some(tls_image) = TlsImage::from_auxv(auxv) else {
return Ok(None);
};
if force || !tls_image.already_initialized {
let (base, tp) = clone_tls(&tls_image, tcb_size)?;
let (base, tp) = clone_tls(&tls_image)?;
unsafe { set_thread_pointer(tp) }?;
setup_dtv(&tls_image, base)?;
}
@ -214,14 +216,14 @@ pub fn init_tls_from_auxv<'a, I: Iterator<Item = &'a AuxValue>>(
/// Initializes thread-local storage for this thread from a [TlsImage]. If no image is provided,
/// a dummy (platform-specific) TLS copy will be created.
pub fn init_tls(image: Option<&TlsImage>, force: bool, tcb_size: usize) -> Result<(), Error> {
pub fn init_tls(image: Option<&TlsImage>, force: bool) -> Result<(), Error> {
let Some(image) = image else {
crate::debug_trace!("TODO: handle executables with no TLS");
return Err(Error::NotImplemented);
};
if force || !image.already_initialized {
let (base, tp) = clone_tls(image, tcb_size)?;
let (base, tp) = clone_tls(image)?;
unsafe { set_thread_pointer(tp) }?;
setup_dtv(&image, base)?;
}
@ -253,11 +255,6 @@ fn setup_dtv(image: &TlsImage, tls_base: usize) -> Result<(), Error> {
Ok(())
}
/// Returns the pointer to the TCB contents after the self-pointer and the DTV
pub fn get_tcb() -> *mut u8 {
unsafe { get_tcb_raw().add(1).cast() }
}
/// Gets or allocates a DTV for current thread.
pub fn get_dtv() -> &'static mut Dtv {
let tcb = get_tcb_mut();
@ -269,7 +266,10 @@ pub fn get_dtv() -> &'static mut Dtv {
unsafe { &mut *tcb.dtv_pointer }
}
#[cfg(any(feature = "__tls_get_addr", rust_analyzer))]
#[cfg(all(
any(target_arch = "x86", target_arch = "x86_64"),
any(feature = "__tls_get_addr", rust_analyzer)
))]
#[no_mangle]
unsafe extern "C" fn __tls_get_addr(index: *mut usize) -> *mut c_void {
let module_id = index.read();

View File

@ -1,13 +1,82 @@
use abi::error::Error;
use abi::{
error::Error,
mem::{MappingFlags, MappingSource},
};
use super::TlsImage;
// Variant I TLS layout:
//
// [ TCB ] [ pad to p_align ] [ MODULE ] [ MODULE ] [ MODULE ] ...
// | | | |
// | | | |
// tp off1 off2 off3
//
// TCB size is fixed at 2 * sizeof(usize).
/// Creates a new TLS image in the process memory, copying data from the TLS master copy (if any).
/// Returns the resulting thread pointer.
pub fn clone_tls(image: &TlsImage, tcb_size: usize) -> Result<(usize, usize), Error> {
Err(Error::NotImplemented)
pub fn clone_tls(image: &TlsImage) -> Result<(usize, usize), Error> {
const TCB_SIZE: usize = size_of::<usize>() * 2;
if !image.align.is_power_of_two() {
panic!("TLS layout not aligned to a power of two: {}", image.align)
}
if image.align > 0x1000 {
panic!("TODO: TLS alignment larger than a page size is not supported");
}
let align = image.align;
// TCB size, padded to align. Also the start of the first module
let tcb_aligned_size = (TCB_SIZE + image.align - 1) & !(image.align - 1);
let full_size = tcb_aligned_size + image.full_size;
let page_aligned_size = (full_size + 0xFFF) & !0xFFF;
let base = unsafe {
crate::sys::map_memory(
None,
page_aligned_size,
MappingFlags::WRITE,
&MappingSource::Anonymous,
)
}?;
let ptr = core::ptr::with_exposed_provenance_mut::<u8>(base);
let module_data =
unsafe { core::slice::from_raw_parts_mut(ptr.add(tcb_aligned_size), image.full_size) };
let tcb = unsafe { core::slice::from_raw_parts_mut(ptr, TCB_SIZE) };
// Clone the Master Copy, if any
let data_size = if let Some(master_copy) = image.master_copy {
let source = unsafe { master_copy.as_ref() };
module_data[..source.len()].copy_from_slice(source);
source.len()
} else {
0
};
// Zero the rest
module_data[data_size..image.full_size].fill(0);
// Place the self-pointer to point at the TP itself
let tp = base;
assert_eq!(
tp % size_of::<usize>(),
0,
"TLS self-pointer should at least be word-aligned"
);
tcb[..size_of::<usize>()].copy_from_slice(&tp.to_ne_bytes());
// Zero the TCB after the self-pointer
tcb[size_of::<usize>()..].fill(0);
crate::debug_trace!("TLS: base={:#x}, tp={:#x}", base, tp);
Ok((base, tp))
}
pub(super) fn get_tcb_raw(_tp: usize) -> *mut u8 {
todo!()
pub(super) fn get_tcb_raw(tp: usize) -> *mut u8 {
core::ptr::with_exposed_provenance_mut(tp)
}

View File

@ -14,18 +14,13 @@ use super::TlsImage;
/// Creates a new TLS image in the process memory, copying data from the TLS master copy (if any).
/// Returns the resulting thread pointer.
pub fn clone_tls(image: &TlsImage, tcb_size: usize) -> Result<(usize, usize), Error> {
pub fn clone_tls(image: &TlsImage) -> Result<(usize, usize), Error> {
// Basically, the layout is:
// * align(image.full_size) below the TP
// * tcb_size starting with the TP
// If the caller asked for no TCB to be placed, just reserve two words, the first one
// will be used to put the self-pointer
let tcb_size = if tcb_size < size_of::<usize>() * 2 {
size_of::<usize>() * 2
} else {
(tcb_size + size_of::<usize>() - 1) & !(size_of::<usize>() - 1)
};
// Reserve two words for the TCB to be consistent with Variant I
let tcb_size = size_of::<usize>() * 2;
if !image.align.is_power_of_two() {
panic!("TLS layout not aligned to a power of two: {}", image.align);

1
test.c
View File

@ -15,6 +15,7 @@ static void *function(void *arg) {
int main(int argc, const char **argv) {
pthread_t thread;
printf("[main] &x = %p\n", &x);
printf("[main] x = %d\n", x);
x = 321;
printf("[main] x = %d\n", x);

1
userspace/Cargo.lock generated
View File

@ -376,6 +376,7 @@ name = "dyn-loader"
version = "0.1.0"
dependencies = [
"bytemuck",
"cfg-if",
"elf",
"thiserror",
"yggdrasil-rt",

View File

@ -9,6 +9,7 @@ thiserror.workspace = true
elf = "0.7.4"
bytemuck = "1.19.0"
cfg-if = "1.0.0"
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }

View File

@ -0,0 +1,13 @@
// Has to be naked: need to prevent the function from clobbering any registers besides x0
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
#[naked]
pub unsafe extern "C" fn tlsdesc_resolve_static(argument: *const usize) -> usize {
// x0 -- pointer to tlsdesc struct:
// [0]: this function pointer
// [1]: static offset to return
core::arch::naked_asm!(r#"
ldr x0, [x0, #8]
ret
"#);
}

View File

@ -1,4 +1,4 @@
#![feature(yggdrasil_os, never_type, map_try_insert, slice_ptr_get, iter_chain)]
#![feature(yggdrasil_os, never_type, map_try_insert, slice_ptr_get, iter_chain, naked_functions)]
use std::process::ExitCode;
@ -81,7 +81,7 @@ fn run(binary: &str, args: &[String]) -> Result<!, Error> {
// Set up TLS for the main thread. This needs to be done here, because
// we still need to call initializers before handing off control to the executable
// TODO use an ELF note to indicate the extra size required in the TCB
yggdrasil_rt::process::thread_local::init_tls_from_auxv(auxv.iter(), true, size_of::<usize>() * 4)
yggdrasil_rt::process::thread_local::init_tls_from_auxv(auxv.iter(), true)
.expect("Could not initialize main thread TLS for the executable");
auxv.push(AuxValue {
@ -117,8 +117,14 @@ unsafe fn enter(entry: extern "C" fn(usize), argument: usize) -> ! {
);
}
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
{}
unreachable!()
{
loop {}
}
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
{
entry(argument);
unreachable!()
}
}
fn main() -> ExitCode {

View File

@ -0,0 +1,104 @@
use elf::{
abi::{
R_AARCH64_ABS64, R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_RELATIVE,
R_AARCH64_TLSDESC, R_AARCH64_TLS_TPREL,
},
relocation::{Rel, Rela},
};
use crate::{builtins, error::Error, object::ResolvedSymbol, state::State};
use super::{RelValue, RelaValue, Relocation};
// Descriptor method of TLS access:
// * For static access:
// [0]: resolver function pointer
// [1]: fixed offset of the variable with the TLS area
// * For dynamic, TODO
fn make_tlsdesc_relocation(
dynamic: bool,
state: &State,
module_id: u32,
symbol_offset: usize,
) -> RelaValue {
if dynamic {
todo!("Dynamic TLSDESC relocations are not yet supported")
}
let layout = state
.tls_layout
.as_ref()
.expect("TLSDESC relocation, but no TLS segments in linked objects");
let Some(offset) = layout.offset(module_id, symbol_offset) else {
panic!("TLSDESC relocation against unknown module_id={module_id}, offset={symbol_offset}");
};
RelaValue::DQWord(builtins::tlsdesc_resolve_static as i64, offset as i64)
}
impl Relocation for Rel {
type Value = RelValue;
fn resolve(
&self,
_state: &State,
_name: &str,
_symbol: &ResolvedSymbol,
_load_base: usize,
) -> Result<Option<Self::Value>, Error> {
unimplemented!("rel-type relocations are not implemented for aarch64")
}
}
impl Relocation for Rela {
type Value = RelaValue;
fn resolve(
&self,
state: &State,
name: &str,
symbol: &ResolvedSymbol,
load_base: usize,
) -> Result<Option<Self::Value>, Error> {
match symbol {
&ResolvedSymbol::Tls(tls) => match self.r_type {
// See make_tlsdesc_relocation()
R_AARCH64_TLSDESC => Ok(Some(make_tlsdesc_relocation(
false,
state,
tls.module_id,
tls.offset,
))),
R_AARCH64_TLS_TPREL => {
let layout = state.tls_layout.as_ref().unwrap();
let offset = layout.offset(tls.module_id, tls.offset).unwrap();
debug_trace!("{}@tprel -> {}", name, offset);
Ok(Some(RelaValue::QWord(offset as _)))
}
_ => todo!("Unsupported relocation against TLS symbol: {}", self.r_type),
},
&ResolvedSymbol::Null(object_id) => match self.r_type {
// See make_tlsdesc_relocation()
R_AARCH64_TLSDESC => Ok(Some(make_tlsdesc_relocation(false, state, object_id, 0))),
// B + A
R_AARCH64_RELATIVE => Ok(Some(RelaValue::QWord(load_base as i64 + self.r_addend))),
_ => todo!(
"Unsupported relocation against NULL symbol: {}",
self.r_type
),
},
_ => {
let s = symbol.value() as i64;
if s == 0 {
todo!()
}
match self.r_type {
// Direct 64 bit: S
R_AARCH64_ABS64 | R_AARCH64_JUMP_SLOT | R_AARCH64_GLOB_DAT => {
Ok(Some(RelaValue::QWord(s)))
}
_ => todo!("Unsupported relocation: {}", self.r_type),
}
}
}
}
}

View File

@ -2,12 +2,15 @@ use elf::{parse::ParseAt, relocation::{Rel, Rela}};
use crate::{error::Error, mapping::Mapping, object::ResolvedSymbol, state::State};
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
mod aarch64;
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
mod x86_64;
#[cfg(any(target_arch = "x86", rust_analyzer))]
mod i686;
pub enum RelaValue {
DQWord(i64, i64),
QWord(i64),
}
@ -40,7 +43,17 @@ pub trait Relocation {
impl RelocationValue for RelaValue {
fn write(&self, mapping: &mut Mapping, offset: u64) {
match *self {
Self::QWord(value) => unsafe { mapping.qword(offset).write_unaligned(value) },
Self::DQWord(v0, v1) => {
unsafe {
mapping.qword(offset).write_unaligned(v0);
mapping.qword(offset + 8).write_unaligned(v1);
}
}
Self::QWord(value) => unsafe {
let ptr = mapping.qword(offset);
// yggdrasil_rt::debug_trace!("write({:p}, {:#x})", ptr, value);
ptr.write_unaligned(value)
},
}
}
}

View File

@ -26,7 +26,7 @@ impl Relocation for Rel {
_symbol: &ResolvedSymbol,
_load_base: usize,
) -> Result<Option<Self::Value>, Error> {
unimplemented!("rel-type relocations are not implemented for i686")
unimplemented!("rel-type relocations are not implemented for x86_64")
}
}

View File

@ -1,16 +1,26 @@
#[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
mod variant2;
use std::collections::HashMap;
use elf::segment::ProgramHeader;
#[cfg(any(target_arch = "x86_64", target_arch = "x86", rust_analyzer))]
pub use variant2::TlsLayoutImpl;
use yggdrasil_rt::mem::MappingFlags;
use cfg_if::cfg_if;
use crate::{error::Error, mapping::Mapping, object::Object};
cfg_if! {
if #[cfg(rust_analyzer)] {
mod variant1;
mod variant2;
pub use variant2::TlsLayoutImpl;
} else if #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] {
mod variant2;
pub use variant2::TlsLayoutImpl;
} else if #[cfg(target_arch = "aarch64")] {
mod variant1;
pub use variant1::TlsLayoutImpl;
}
}
pub trait TlsLayoutBuilder {
fn build(align: usize, root: &Object, libraries: &HashMap<u32, Object>) -> TlsLayout;
fn build(root: &Object, libraries: &HashMap<u32, Object>) -> Option<(TlsLayout, usize)>;
}
#[derive(Debug)]
@ -18,6 +28,7 @@ pub struct TlsLayout {
pub segments: Vec<TlsSegment>,
pub total_size: usize,
pub tp_offset: usize,
pub prefix_len: usize,
}
#[derive(Debug)]
@ -46,30 +57,20 @@ pub fn build_tls_image(
root: &mut Object,
libraries: &mut HashMap<u32, Object>,
) -> Result<Option<(TlsImage, TlsLayout)>, Error> {
// Find max align
let mut align = usize::MIN;
if let Some(tls) = root.tls.as_ref() {
align = align.max(tls.p_align as usize);
}
for tls in libraries.values().filter_map(|lib| lib.tls.as_ref()) {
align = align.max(tls.p_align as usize);
}
// No TLS segment in any of the objects
if align == usize::MIN {
let Some((layout, align)) = TlsLayoutImpl::build(root, libraries) else {
return Ok(None);
}
let align = align.max(size_of::<usize>());
let layout = TlsLayoutImpl::build(align, root, libraries);
};
// Create a master image based on this layout
let mut image_data = Mapping::new(layout.total_size, MappingFlags::empty())?;
let tp_offset = layout.tp_offset;
for (i, segment) in layout.segments.iter().enumerate() {
for segment in layout.segments.iter() {
debug_trace!(
"Load TLS segment: tlsoffset_i={:#x?}, module_id={}",
"Load TLS segment: tlsoffset_i={:#x?} (-{:#x}), module_id={}",
segment.offset..segment.offset + segment.size,
layout.prefix_len,
segment.object_id
);
@ -78,7 +79,8 @@ pub fn build_tls_image(
_ => libraries.get_mut(&segment.object_id).unwrap(),
};
object.tls_offset = Some(segment.offset);
object.place_tls_copy(&mut image_data[segment.offset..segment.offset + segment.size])?;
let load_offset = segment.offset - layout.prefix_len;
object.place_tls_copy(&mut image_data[load_offset..load_offset + segment.size])?;
}
Ok(Some((

View File

@ -0,0 +1,77 @@
use std::collections::HashMap;
use crate::{object::Object, thread_local::TlsSegment};
use super::{TlsLayout, TlsLayoutBuilder};
pub struct TlsLayoutImpl;
impl TlsLayoutBuilder for TlsLayoutImpl {
fn build(root: &Object, libraries: &HashMap<u32, Object>) -> Option<(TlsLayout, usize)> {
const TCB_SIZE: usize = size_of::<usize>() * 2;
// Variant I doesn't use global align for modules. Instead, it aligns each of those
// individually
let mut segments = vec![];
let mut total_size = TCB_SIZE;
let mut first_module_align = 0;
if let Some(tls) = root.tls.as_ref() {
let align = tls.p_align as usize;
assert!(align.is_power_of_two());
if first_module_align == 0 {
first_module_align = align;
}
total_size = (total_size + align - 1) & !(align - 1);
segments.push(TlsSegment {
offset: total_size,
size: tls.p_memsz as _,
object_id: 0,
});
total_size += tls.p_memsz as usize;
}
for (&object_id, lib) in libraries {
let Some(tls) = lib.tls.as_ref() else {
continue;
};
let align = tls.p_align as usize;
assert!(align.is_power_of_two());
if first_module_align == 0 {
first_module_align = align;
}
total_size = (total_size + align - 1) & !(align - 1);
segments.push(TlsSegment {
offset: total_size,
size: tls.p_memsz as _,
object_id,
});
total_size += tls.p_memsz as usize;
}
if first_module_align == 0 {
assert!(segments.is_empty());
None
} else {
let prefix_len = (TCB_SIZE + first_module_align - 1) & !(first_module_align - 1);
Some((
TlsLayout {
segments,
total_size,
tp_offset: 0,
prefix_len,
},
first_module_align,
))
}
}
}

View File

@ -9,7 +9,22 @@ use super::{TlsLayout, TlsLayoutBuilder, TlsSegment};
pub struct TlsLayoutImpl;
impl TlsLayoutBuilder for TlsLayoutImpl {
fn build(align: usize, root: &Object, libraries: &HashMap<u32, Object>) -> TlsLayout {
fn build(root: &Object, libraries: &HashMap<u32, Object>) -> Option<(TlsLayout, usize)> {
// Find max align
let mut align = usize::MIN;
if let Some(tls) = root.tls.as_ref() {
align = align.max(tls.p_align as usize);
}
for tls in libraries.values().filter_map(|lib| lib.tls.as_ref()) {
align = align.max(tls.p_align as usize);
}
// No TLS segment in any of the objects
if align == usize::MIN {
return None;
}
let align = align.max(size_of::<usize>());
let mut total_size = 0;
// Layout:
//
@ -54,12 +69,16 @@ impl TlsLayoutBuilder for TlsLayoutImpl {
offset,
size,
object_id,
}).collect();
})
.collect();
TlsLayout {
segments,
total_size,
tp_offset
}
Some((
TlsLayout {
segments,
total_size,
tp_offset,
},
align,
))
}
}

View File

@ -0,0 +1,6 @@
extern void __ygglibc_entry(const void *, const void *);
extern int main(int, const char **, const char **);
void _start(const void *arg) {
__ygglibc_entry(main, arg);
}

View File

@ -0,0 +1,28 @@
{
"is-builtin": false,
"arch": "aarch64",
"os": "none",
"llvm-target": "aarch64-unknown-none",
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
"max-atomic-width": 128,
"target-pointer-width": "64",
"features": "+v8a,+strict-align,+neon,+fp-armv8",
"disable-redzone": true,
"executables": true,
"panic-strategy": "abort",
"dynamic-linking": true,
"relocation-model": "pic",
"position-independent-executables": true,
"crt-static-allows-dylibs": true,
"has-thread-local": true,
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"pre-link-args": {
"ld.lld": [
"--dynamic-linker=/libexec/dyn-loader"
]
}
}

View File

@ -0,0 +1,37 @@
use core::ffi::c_int;
pub type __jmp_buf = [usize; 8];
// TODO
#[no_mangle]
#[naked]
unsafe extern "C" fn setjmp(buf: *mut __jmp_buf) -> c_int {
core::arch::naked_asm!(
r#"
b .
"#
);
}
#[no_mangle]
#[naked]
unsafe extern "C" fn _setjmp(buf: *mut __jmp_buf) -> c_int {
core::arch::naked_asm!("b .");
}
#[no_mangle]
#[naked]
unsafe extern "C" fn longjmp(buf: *mut __jmp_buf, val: c_int) -> c_int {
core::arch::naked_asm!(
r#"
b .
"#
)
}
#[no_mangle]
#[naked]
unsafe extern "C" fn _longjmp(buf: *mut __jmp_buf, val: c_int) -> c_int {
core::arch::naked_asm!("b .")
}

View File

@ -8,4 +8,9 @@ pub mod i686;
#[cfg(any(target_arch = "x86", rust_analyzer))]
pub use i686::__jmp_buf;
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
pub mod aarch64;
#[cfg(any(target_arch = "aarch64", rust_analyzer))]
pub use aarch64::__jmp_buf;
pub type jmp_buf = __jmp_buf;

View File

@ -22,6 +22,9 @@ pub mod tls;
static THREADS: Mutex<BTreeMap<pthread_t, Arc<Thread>>> = Mutex::new(BTreeMap::new());
#[thread_local]
static mut SELF: Option<Arc<Thread>> = None;
enum ThreadStack {
Owned(usize, usize),
Borrowed,
@ -114,24 +117,16 @@ impl Thread {
}
pub fn this() -> EResult<Arc<Thread>> {
let tcb: *const *const Self = thread_local::get_tcb().cast();
let raw = unsafe { tcb.read() };
if !raw.is_null() {
// Arc::from_raw creates an owned Arc, which will decrement refcount when dropped,
// prevent this by incrementing the count once more
unsafe { Arc::increment_strong_count(raw) };
let arc = unsafe { Arc::from_raw(raw) };
EResult::Ok(arc)
} else {
panic!("pthread_self() is NULL")
}
let ptr = unsafe { SELF.clone() }.expect("pthread_self() is NULL");
EResult::Ok(ptr)
}
unsafe fn set_this(thread: Arc<Self>) {
let tcb: *mut usize = thread_local::get_tcb().cast();
let raw = Arc::into_raw(thread);
tcb.write(raw.addr());
unsafe { SELF = Some(thread) };
}
unsafe fn clear_this() {
unsafe { SELF = None };
}
pub fn result(&self) -> *mut c_void {
@ -142,7 +137,7 @@ impl Thread {
// Set up TLS as soon as possible. Note the `force = true` parameter, because the image
// contains "already initialized" tag, which only matters for the main thread.
if let Err(err) =
unsafe { thread_local::init_tls(tls::TLS_IMAGE.as_ref(), true, size_of::<usize>() * 4) }
unsafe { thread_local::init_tls(tls::TLS_IMAGE.as_ref(), true) }
{
yggdrasil_rt::debug_trace!("thread_entry failed: TLS init error: {err:?}");
unsafe { yggdrasil_rt::sys::exit_thread() };
@ -169,9 +164,7 @@ impl Thread {
let result = (argument.entry)(argument.argument);
argument.thread.result.store(result, Ordering::Release);
// Drop the Arc<Self> written to TLS
let this = Self::this().expect("pthread_self() is NULL");
unsafe { Arc::decrement_strong_count(Arc::as_ptr(&this)) };
unsafe { Self::clear_this() };
}
// TODO call thread-local destructors
@ -194,7 +187,7 @@ pub fn init_main_thread(arg: &ProgramArgumentInner) {
// Usually, a dynamic loader will do this for us, but this still needs to be
// done for statically-linked programs if the kernel transfers control directly to
// the program.
let tls_image = thread_local::init_tls_from_auxv(arg.auxv(), false, size_of::<usize>() * 4)
let tls_image = thread_local::init_tls_from_auxv(arg.auxv(), false)
.expect("Could not initialize TLS");
// Store the TLS image, it'll be needed when creating new threads

View File

@ -5,7 +5,7 @@ use std::{
};
use crate::{
env::{Arch, BuildEnv},
env::BuildEnv,
error::Error,
util::{self, git_clone},
};
@ -113,29 +113,29 @@ fn build_test_c_program(
install.push((target_dir.join("c-test"), "c-test".into()));
if env.arch == Arch::x86_64 {
log::info!("Building a test C program [static]");
let mut command = llvm.c_clang(env);
command
.args([
"-v",
"-static",
"-O0",
"-ggdb",
"-fstack-protector-strong",
"-lm",
])
.arg("-o")
.arg(target_dir.join("c-test-static"))
.arg(env.workspace_root.join("test.c"));
// if env.arch == Arch::x86_64 {
log::info!("Building a test C program [static]");
let mut command = llvm.c_clang(env);
command
.args([
"-v",
"-static",
"-O0",
"-ggdb",
"-fstack-protector-strong",
"-lm",
])
.arg("-o")
.arg(target_dir.join("c-test-static"))
.arg(env.workspace_root.join("test.c"));
if !command.status()?.success() {
return Err(Error::ExternalCommandFailed);
}
install.push((target_dir.join("c-test-static"), "c-test-static".into()));
if !command.status()?.success() {
return Err(Error::ExternalCommandFailed);
}
install.push((target_dir.join("c-test-static"), "c-test-static".into()));
// }
Ok(())
}
@ -188,6 +188,7 @@ fn build_openlibm(env: &BuildEnv, llvm: &Llvm) -> Result<Openlibm, Error> {
let mut command = Command::new("make");
command.env("SYSROOT", &env.llvm_sysroot);
command.env("ARCH", env.arch.name());
command.env("OS", "yggdrasil");
command.env("TRIPLE", format!("{}-unknown-yggdrasil", env.arch.name()));
command.env("USEGCC", "0");
command.env("USECLANG", "1");
@ -265,7 +266,6 @@ pub fn build_c(env: &BuildEnv, install: &mut Vec<(PathBuf, PathBuf)>) -> Result<
build_test_c_program(env, &llvm, install)?;
build_test_cpp_program(env, &llvm, install)?;
// TODO
install.push((
env.llvm_sysroot.join("lib/libc++.so"),
"lib/libc++.so".into(),

View File

@ -9,7 +9,7 @@ use walkdir::WalkDir;
use crate::{
build::{c, cargo::CargoBuilder},
check::AllOk,
env::{Arch, BuildEnv},
env::BuildEnv,
error::Error,
util,
};
@ -73,9 +73,7 @@ fn build_userspace(
CargoBuilder::Userspace(env).build(env.workspace_root.join("userspace"))?;
CargoBuilder::Userspace(env).build(env.workspace_root.join("userspace/dynload-program"))?;
// TODO other architectures
if env.arch == Arch::x86_64 || env.arch == Arch::i686 {
c::build_c(env, extra)?;
}
c::build_c(env, extra)?;
Ok(())
}