Skip to content

Commit 0a94afd

Browse files
authored
Fix the location of the thread metadata on riscv64 (#96)
On riscv64, the thread pointer doesn't point to the first ABI-exposed field in the thread-local storage. Add an empty `thread_pointee` field to represent the place the thread pointer points to, and fix the address calculation code to point there. And add a tls testcase that creates a `#[thread_local]` variable and checks that it's initialized and can be accessed properly.
1 parent 4984e42 commit 0a94afd

8 files changed

Lines changed: 294 additions & 62 deletions

File tree

src/thread/linux_raw.rs

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,45 +114,59 @@ struct Metadata {
114114
thread: ThreadData,
115115
}
116116

117-
/// Fields which accessed by user code via known offsets from the platform
118-
/// thread-pointer register.
117+
/// Fields which accessed by user code via well-known offsets from the platform
118+
/// thread-pointer register. Specifically, the thread-pointer register points
119+
/// to the `thread_pointee` field.
119120
#[repr(C)]
120121
#[cfg_attr(target_arch = "arm", repr(align(8)))]
121122
struct Abi {
123+
/// The address the thread pointer points to.
124+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
125+
thread_pointee: [u8; 0],
126+
122127
/// The ABI-exposed `canary` field.
123128
#[cfg(any(target_arch = "aarch64", target_arch = "arm", target_arch = "riscv64"))]
124129
canary: usize,
125130

131+
/// The address the thread pointer points to.
132+
#[cfg(any(target_arch = "aarch64", target_arch = "arm",))]
133+
thread_pointee: [u8; 0],
134+
126135
/// The ABI-exposed `dtv` field (though we don't yet implement dynamic
127136
/// linking).
128137
#[cfg(any(target_arch = "aarch64", target_arch = "arm", target_arch = "riscv64"))]
129138
dtv: *const c_void,
130139

140+
/// The address the thread pointer points to.
141+
#[cfg(target_arch = "riscv64")]
142+
thread_pointee: [u8; 0],
143+
144+
/// Padding to put the TLS data which follows at its well-known offset.
145+
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
146+
_pad: [usize; 1],
147+
148+
/// Padding to put the TLS data which follows at its well-known offset.
149+
#[cfg(target_arch = "riscv64")]
150+
_pad: [usize; 0],
151+
131152
/// x86 and x86-64 put a copy of the thread-pointer register at the memory
132153
/// location pointed to by the thread-pointer register, because reading the
133154
/// thread-pointer register directly is slow.
134155
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
135-
this: *mut Abi,
156+
this: *mut c_void,
136157

137158
/// The ABI-exposed `dtv` field (though we don't yet implement dynamic
138159
/// linking).
139160
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
140161
dtv: *const c_void,
141162

163+
/// Padding to put the `canary` field at its well-known offset.
142164
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
143165
_pad: [usize; 3],
144166

145167
/// The ABI-exposed `canary` field.
146168
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
147169
canary: usize,
148-
149-
/// Padding to put the TLS data which follows at its known offset.
150-
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
151-
_pad: [usize; 1],
152-
153-
/// Padding to put the TLS data which follows at its known offset.
154-
#[cfg(target_arch = "riscv64")]
155-
_pad: [usize; 0],
156170
}
157171

158172
/// Information obtained from the `DT_TLS` segment of the executable.
@@ -270,7 +284,9 @@ pub use rustix::thread::Pid as ThreadId;
270284
///
271285
/// This function is similar to `create_thread` except that the OS thread is
272286
/// already created, and already has a stack (which we need to locate), and is
273-
/// already running.
287+
/// already running. We still need to create the thread [`Metadata`], copy in
288+
/// the TLS initializers, and point the thread pointer to it so that it follows
289+
/// the thread ABI that all the other threads follow.
274290
///
275291
/// # Safety
276292
///
@@ -346,7 +362,7 @@ pub(super) unsafe fn initialize_main_thread(mem: *mut c_void) {
346362

347363
let tls_data = new.add(tls_data_bottom);
348364
let metadata: *mut Metadata = new.add(header).cast();
349-
let newtls: *mut Abi = &mut (*metadata).abi;
365+
let newtls: *mut c_void = (*metadata).abi.thread_pointee.as_mut_ptr().cast();
350366

351367
let thread_id_ptr = (*metadata).thread.thread_id.as_ptr();
352368
let tid = rustix::runtime::set_tid_address(thread_id_ptr.cast());
@@ -364,6 +380,7 @@ pub(super) unsafe fn initialize_main_thread(mem: *mut c_void) {
364380
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
365381
this: newtls,
366382
_pad: Default::default(),
383+
thread_pointee: [],
367384
},
368385
thread: ThreadData::new(
369386
Some(tid),
@@ -390,7 +407,7 @@ pub(super) unsafe fn initialize_main_thread(mem: *mut c_void) {
390407
.fill(0);
391408

392409
// Point the platform thread-pointer register at the new thread metadata.
393-
set_thread_pointer(newtls.cast::<u8>().cast());
410+
set_thread_pointer(newtls);
394411
}
395412

396413
/// Creates a new thread.
@@ -486,7 +503,7 @@ pub unsafe fn create_thread(
486503

487504
let tls_data = map.add(tls_data_bottom);
488505
let metadata: *mut Metadata = map.add(header).cast();
489-
let newtls: *mut Abi = &mut (*metadata).abi;
506+
let newtls: *mut c_void = (*metadata).abi.thread_pointee.as_mut_ptr().cast();
490507

491508
// Copy the current thread's canary to the new thread.
492509
let canary = (*current_metadata()).abi.canary;
@@ -499,6 +516,7 @@ pub unsafe fn create_thread(
499516
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
500517
this: newtls,
501518
_pad: Default::default(),
519+
thread_pointee: [],
502520
},
503521
thread: ThreadData::new(
504522
None, // the real tid will be written by `clone`.
@@ -560,7 +578,7 @@ pub unsafe fn create_thread(
560578
stack.cast(),
561579
thread_id_ptr,
562580
thread_id_ptr,
563-
newtls.cast::<u8>().cast(),
581+
newtls,
564582
core::mem::transmute(fn_),
565583
args.len(),
566584
);
@@ -909,7 +927,7 @@ pub fn at_thread_exit(func: Box<dyn FnOnce()>) {
909927
fn current_metadata() -> *mut Metadata {
910928
thread_pointer()
911929
.cast::<u8>()
912-
.wrapping_sub(offset_of!(Metadata, abi))
930+
.wrapping_sub(offset_of!(Metadata, abi) + offset_of!(Abi, thread_pointee))
913931
.cast()
914932
}
915933

@@ -976,8 +994,8 @@ pub fn current_thread_tls_addr(offset: usize) -> *mut c_void {
976994
{
977995
thread_pointer()
978996
.cast::<u8>()
997+
.wrapping_add(size_of::<Abi>() - offset_of!(Abi, thread_pointee))
979998
.wrapping_add(TLS_OFFSET)
980-
.wrapping_add(size_of::<Abi>())
981999
.wrapping_add(offset)
9821000
.cast()
9831001
}
@@ -990,8 +1008,8 @@ pub fn current_thread_tls_addr(offset: usize) -> *mut c_void {
9901008
unsafe {
9911009
thread_pointer()
9921010
.cast::<u8>()
993-
.wrapping_add(TLS_OFFSET)
9941011
.wrapping_sub(STARTUP_TLS_INFO.mem_size)
1012+
.wrapping_add(TLS_OFFSET)
9951013
.wrapping_add(offset)
9961014
.cast()
9971015
}

test-crates/tls/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "tls"
3+
version = "0.0.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
origin = { path = "../..", default-features = false, features = ["origin-thread", "origin-start"] }
9+
atomic-dbg = { version = "0.1.8", default-features = false }
10+
rustix-dlmalloc = { version = "0.1.0", features = ["global"] }
11+
compiler_builtins = { version = "0.1.101", features = ["mem"] }
12+
13+
# This is just a test crate, and not part of the origin workspace.
14+
[workspace]

test-crates/tls/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This is similar to the `origin-start` example, but adds `#[thread_local]`
2+
variables and a bunch of asserts to ensure that they're initialized and
3+
mutated properly.

test-crates/tls/build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
println!("cargo:rustc-link-arg=-nostartfiles");
3+
}

test-crates/tls/src/main.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! Like `origin-start`, but add a `#[thread_local]` variable and asserts
2+
//! to ensure that it works.
3+
4+
#![no_std]
5+
#![no_main]
6+
#![allow(internal_features)]
7+
#![feature(lang_items)]
8+
#![feature(core_intrinsics)]
9+
#![feature(thread_local)]
10+
#![feature(strict_provenance)]
11+
#![feature(const_mut_refs)]
12+
13+
extern crate alloc;
14+
extern crate compiler_builtins;
15+
16+
use alloc::boxed::Box;
17+
use atomic_dbg::dbg;
18+
use core::arch::asm;
19+
use core::ptr::{addr_of_mut, invalid_mut};
20+
use origin::program::*;
21+
use origin::thread::*;
22+
23+
#[panic_handler]
24+
fn panic(panic: &core::panic::PanicInfo<'_>) -> ! {
25+
dbg!(panic);
26+
core::intrinsics::abort();
27+
}
28+
29+
#[lang = "eh_personality"]
30+
extern "C" fn eh_personality() {}
31+
32+
#[global_allocator]
33+
static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc;
34+
35+
#[no_mangle]
36+
unsafe fn origin_main(_argc: usize, _argv: *mut *mut u8, _envp: *mut *mut u8) -> i32 {
37+
// Assert that the main thread initialized its TLS properly.
38+
check_eq(TEST_DATA.0);
39+
40+
// Mutate one of the TLS fields.
41+
THREAD_LOCAL[1] = invalid_mut(77);
42+
43+
// Assert that the mutation happened properly.
44+
check_eq([TEST_DATA.0[0], invalid_mut(77), TEST_DATA.0[2]]);
45+
46+
at_exit(Box::new(|| {
47+
// This is the last thing to run. Assert that we see the value stored
48+
// by the `at_thread_exit` callback.
49+
check_eq([TEST_DATA.0[0], invalid_mut(79), TEST_DATA.0[2]]);
50+
51+
// Mutate one of the TLS fields.
52+
THREAD_LOCAL[1] = invalid_mut(80);
53+
}));
54+
at_thread_exit(Box::new(|| {
55+
// Assert that we see the value stored at the end of `main`.
56+
check_eq([TEST_DATA.0[0], invalid_mut(78), TEST_DATA.0[2]]);
57+
58+
// Mutate one of the TLS fields.
59+
THREAD_LOCAL[1] = invalid_mut(79);
60+
}));
61+
62+
let thread = create_thread(
63+
|_args| {
64+
// Assert that the new thread initialized its TLS properly.
65+
check_eq(TEST_DATA.0);
66+
67+
// Mutate one of the TLS fields.
68+
THREAD_LOCAL[1] = invalid_mut(175);
69+
70+
// Assert that the mutation happened properly.
71+
check_eq([TEST_DATA.0[0], invalid_mut(175), TEST_DATA.0[2]]);
72+
73+
at_thread_exit(Box::new(|| {
74+
// Assert that we still see the value stored in the thread.
75+
check_eq([TEST_DATA.0[0], invalid_mut(175), TEST_DATA.0[2]]);
76+
}));
77+
78+
None
79+
},
80+
&[],
81+
default_stack_size(),
82+
default_guard_size(),
83+
)
84+
.unwrap();
85+
86+
join_thread(thread);
87+
88+
// Assert that the main thread's TLS is still in place.
89+
check_eq([TEST_DATA.0[0], invalid_mut(77), TEST_DATA.0[2]]);
90+
91+
// Mutate one of the TLS fields.
92+
THREAD_LOCAL[1] = invalid_mut(78);
93+
94+
// Assert that the mutation happened properly.
95+
check_eq([TEST_DATA.0[0], invalid_mut(78), TEST_DATA.0[2]]);
96+
97+
exit(200);
98+
}
99+
100+
struct SyncTestData([*const u32; 3]);
101+
unsafe impl Sync for SyncTestData {}
102+
static TEST_DATA: SyncTestData = unsafe {
103+
SyncTestData([
104+
invalid_mut(0xa0b1a2b3a4b5a6b7_u64 as usize),
105+
addr_of_mut!(SOME_REGULAR_DATA),
106+
addr_of_mut!(SOME_ZERO_DATA),
107+
])
108+
};
109+
110+
#[thread_local]
111+
static mut THREAD_LOCAL: [*const u32; 3] = TEST_DATA.0;
112+
113+
// Some variables to point to.
114+
static mut SOME_REGULAR_DATA: u32 = 909;
115+
static mut SOME_ZERO_DATA: u32 = 0;
116+
117+
fn check_eq(data: [*const u32; 3]) {
118+
unsafe {
119+
// Check `THREAD_LOCAL` using a static address.
120+
asm!("# {}", in(reg) THREAD_LOCAL.as_mut_ptr(), options(nostack, preserves_flags));
121+
assert_eq!(THREAD_LOCAL, data);
122+
123+
// Check `THREAD_LOCAL` using a dynamic address.
124+
let mut thread_local_addr: *mut [*const u32; 3] = &mut THREAD_LOCAL;
125+
asm!("# {}", inout(reg) thread_local_addr, options(pure, nomem, nostack, preserves_flags));
126+
assert_eq!(*thread_local_addr, data);
127+
}
128+
}

tests/example_crates.rs

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
44
#![feature(cfg_target_abi)]
55

6+
mod utils;
7+
8+
use utils::{COMMON_STDERR, NO_ALLOC_STDERR};
9+
610
fn test_crate(
711
name: &str,
812
args: &[&str],
@@ -11,51 +15,9 @@ fn test_crate(
1115
stderr: &'static str,
1216
code: Option<i32>,
1317
) {
14-
use assert_cmd::Command;
15-
16-
#[cfg(target_arch = "x86_64")]
17-
let arch = "x86_64";
18-
#[cfg(target_arch = "aarch64")]
19-
let arch = "aarch64";
20-
#[cfg(target_arch = "riscv64")]
21-
let arch = "riscv64gc";
22-
#[cfg(target_arch = "x86")]
23-
let arch = "i686";
24-
#[cfg(target_arch = "arm")]
25-
let arch = "armv5te";
26-
#[cfg(target_env = "gnueabi")]
27-
let env = "gnueabi";
28-
#[cfg(all(target_env = "gnu", target_abi = "eabi"))]
29-
let env = "gnueabi";
30-
#[cfg(all(target_env = "gnu", not(target_abi = "eabi")))]
31-
let env = "gnu";
32-
33-
let mut command = Command::new("cargo");
34-
command.arg("run").arg("--quiet");
35-
command.arg(&format!("--target={arch}-unknown-linux-{env}"));
36-
command.args(args);
37-
command.envs(envs.iter().copied());
38-
command.current_dir(format!("example-crates/{name}"));
39-
let assert = command.assert();
40-
let assert = assert.stdout(stdout).stderr(stderr);
41-
if let Some(code) = code {
42-
assert.code(code);
43-
} else {
44-
assert.success();
45-
}
18+
utils::test_crate("example", name, args, envs, stdout, stderr, code);
4619
}
4720

48-
/// Stderr output for most of the example crates.
49-
const COMMON_STDERR: &str = "Hello from main thread\n\
50-
Hello from child thread\n\
51-
Hello from child thread's at_thread_exit handler\n\
52-
Goodbye from main\n\
53-
Hello from a main-thread at_thread_exit handler\n\
54-
Hello from an at_exit handler\n";
55-
56-
/// Stderr output for the origin-start-no-alloc crate.
57-
const NO_ALLOC_STDERR: &str = "Hello!\n";
58-
5921
#[test]
6022
fn example_crate_basic() {
6123
test_crate("basic", &[], &[], "", COMMON_STDERR, None);

0 commit comments

Comments
 (0)