1
Fork 0

Miri: run .CRT$XLB linker section on thread-end

This commit is contained in:
Ralf Jung 2024-04-14 18:56:31 +02:00
parent 3305e71095
commit 5934aaaa97
7 changed files with 182 additions and 210 deletions

View file

@ -276,6 +276,7 @@ unsafe fn register_dtor(key: &'static StaticKey) {
// the address of the symbol to ensure it sticks around. // the address of the symbol to ensure it sticks around.
#[link_section = ".CRT$XLB"] #[link_section = ".CRT$XLB"]
#[cfg_attr(miri, used)] // Miri only considers explicitly `#[used]` statics for `lookup_link_section`
pub static p_thread_callback: unsafe extern "system" fn(c::LPVOID, c::DWORD, c::LPVOID) = pub static p_thread_callback: unsafe extern "system" fn(c::LPVOID, c::DWORD, c::LPVOID) =
on_tls_callback; on_tls_callback;

View file

@ -137,8 +137,7 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
config.override_queries = Some(|_, local_providers| { config.override_queries = Some(|_, local_providers| {
// `exported_symbols` and `reachable_non_generics` provided by rustc always returns // `exported_symbols` and `reachable_non_generics` provided by rustc always returns
// an empty result if `tcx.sess.opts.output_types.should_codegen()` is false. // an empty result if `tcx.sess.opts.output_types.should_codegen()` is false.
// In addition we need to add #[used] symbols to exported_symbols for .init_array // In addition we need to add #[used] symbols to exported_symbols for `lookup_link_section`.
// handling.
local_providers.exported_symbols = |tcx, LocalCrate| { local_providers.exported_symbols = |tcx, LocalCrate| {
let reachable_set = tcx.with_stable_hashing_context(|hcx| { let reachable_set = tcx.with_stable_hashing_context(|hcx| {
tcx.reachable_set(()).to_sorted(&hcx, true) tcx.reachable_set(()).to_sorted(&hcx, true)

View file

@ -158,7 +158,7 @@ pub struct Thread<'mir, 'tcx> {
} }
pub type StackEmptyCallback<'mir, 'tcx> = pub type StackEmptyCallback<'mir, 'tcx> =
Box<dyn FnMut(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, Poll<()>>>; Box<dyn FnMut(&mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<'tcx, Poll<()>> + 'tcx>;
impl<'mir, 'tcx> Thread<'mir, 'tcx> { impl<'mir, 'tcx> Thread<'mir, 'tcx> {
/// Get the name of the current thread if it was set. /// Get the name of the current thread if it was set.

View file

@ -192,18 +192,18 @@ impl Default for MiriConfig {
/// The state of the main thread. Implementation detail of `on_main_stack_empty`. /// The state of the main thread. Implementation detail of `on_main_stack_empty`.
#[derive(Default, Debug)] #[derive(Default, Debug)]
enum MainThreadState { enum MainThreadState<'tcx> {
#[default] #[default]
Running, Running,
TlsDtors(tls::TlsDtorsState), TlsDtors(tls::TlsDtorsState<'tcx>),
Yield { Yield {
remaining: u32, remaining: u32,
}, },
Done, Done,
} }
impl MainThreadState { impl<'tcx> MainThreadState<'tcx> {
fn on_main_stack_empty<'tcx>( fn on_main_stack_empty(
&mut self, &mut self,
this: &mut MiriInterpCx<'_, 'tcx>, this: &mut MiriInterpCx<'_, 'tcx>,
) -> InterpResult<'tcx, Poll<()>> { ) -> InterpResult<'tcx, Poll<()>> {

View file

@ -9,16 +9,21 @@ use rand::RngCore;
use rustc_apfloat::ieee::{Double, Single}; use rustc_apfloat::ieee::{Double, Single};
use rustc_apfloat::Float; use rustc_apfloat::Float;
use rustc_hir::def::{DefKind, Namespace}; use rustc_hir::{
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; def::{DefKind, Namespace},
def_id::{CrateNum, DefId, CRATE_DEF_INDEX, LOCAL_CRATE},
};
use rustc_index::IndexVec; use rustc_index::IndexVec;
use rustc_middle::middle::dependency_format::Linkage;
use rustc_middle::middle::exported_symbols::ExportedSymbol;
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::ty::{ use rustc_middle::ty::{
self, self,
layout::{LayoutOf, TyAndLayout}, layout::{LayoutOf, TyAndLayout},
FloatTy, IntTy, Ty, TyCtxt, UintTy, FloatTy, IntTy, Ty, TyCtxt, UintTy,
}; };
use rustc_span::{def_id::CrateNum, sym, Span, Symbol}; use rustc_session::config::CrateType;
use rustc_span::{sym, Span, Symbol};
use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants}; use rustc_target::abi::{Align, FieldIdx, FieldsShape, Size, Variants};
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
@ -142,6 +147,38 @@ fn try_resolve_did(tcx: TyCtxt<'_>, path: &[&str], namespace: Option<Namespace>)
None None
} }
/// Call `f` for each exported symbol.
pub fn iter_exported_symbols<'tcx>(
tcx: TyCtxt<'tcx>,
mut f: impl FnMut(CrateNum, DefId) -> InterpResult<'tcx>,
) -> InterpResult<'tcx> {
// `dependency_formats` includes all the transitive informations needed to link a crate,
// which is what we need here since we need to dig out `exported_symbols` from all transitive
// dependencies.
let dependency_formats = tcx.dependency_formats(());
let dependency_format = dependency_formats
.iter()
.find(|(crate_type, _)| *crate_type == CrateType::Executable)
.expect("interpreting a non-executable crate");
for cnum in iter::once(LOCAL_CRATE).chain(dependency_format.1.iter().enumerate().filter_map(
|(num, &linkage)| {
// We add 1 to the number because that's what rustc also does everywhere it
// calls `CrateNum::new`...
#[allow(clippy::arithmetic_side_effects)]
(linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
},
)) {
// We can ignore `_export_info` here: we are a Rust crate, and everything is exported
// from a Rust crate.
for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
if let ExportedSymbol::NonGeneric(def_id) = symbol {
f(cnum, def_id)?;
}
}
}
Ok(())
}
/// Convert a softfloat type to its corresponding hostfloat type. /// Convert a softfloat type to its corresponding hostfloat type.
pub trait ToHost { pub trait ToHost {
type HostFloat; type HostFloat;
@ -1180,6 +1217,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} }
Ok(()) Ok(())
} }
/// Lookup an array of immediates stored as a linker section of name `name`.
fn lookup_link_section(
&mut self,
name: &str,
) -> InterpResult<'tcx, Vec<ImmTy<'tcx, Provenance>>> {
let this = self.eval_context_mut();
let tcx = this.tcx.tcx;
let mut array = vec![];
iter_exported_symbols(tcx, |_cnum, def_id| {
let attrs = tcx.codegen_fn_attrs(def_id);
let Some(link_section) = attrs.link_section else {
return Ok(());
};
if link_section.as_str() == name {
let instance = ty::Instance::mono(tcx, def_id);
let const_val = this.eval_global(instance).unwrap_or_else(|err| {
panic!(
"failed to evaluate static in required link_section: {def_id:?}\n{err:?}"
)
});
let val = this.read_immediate(&const_val)?;
array.push(val);
}
Ok(())
})?;
Ok(array)
}
} }
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> { impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {

View file

@ -2,17 +2,10 @@ use std::{collections::hash_map::Entry, io::Write, iter, path::Path};
use rustc_apfloat::Float; use rustc_apfloat::Float;
use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::expand::allocator::AllocatorKind;
use rustc_hir::{ use rustc_hir::{def::DefKind, def_id::CrateNum};
def::DefKind, use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
def_id::{CrateNum, LOCAL_CRATE},
};
use rustc_middle::middle::{
codegen_fn_attrs::CodegenFnAttrFlags, dependency_format::Linkage,
exported_symbols::ExportedSymbol,
};
use rustc_middle::mir; use rustc_middle::mir;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::config::CrateType;
use rustc_span::Symbol; use rustc_span::Symbol;
use rustc_target::{ use rustc_target::{
abi::{Align, Size}, abi::{Align, Size},
@ -158,81 +151,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Ok(()) Ok(())
} }
fn lookup_init_array(&mut self) -> InterpResult<'tcx, Vec<ty::Instance<'tcx>>> {
let this = self.eval_context_mut();
let tcx = this.tcx.tcx;
let mut init_arrays = vec![];
let dependency_formats = tcx.dependency_formats(());
let dependency_format = dependency_formats
.iter()
.find(|(crate_type, _)| *crate_type == CrateType::Executable)
.expect("interpreting a non-executable crate");
for cnum in iter::once(LOCAL_CRATE).chain(
dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| {
// We add 1 to the number because that's what rustc also does everywhere it
// calls `CrateNum::new`...
#[allow(clippy::arithmetic_side_effects)]
(linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
}),
) {
for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
if let ExportedSymbol::NonGeneric(def_id) = symbol {
let attrs = tcx.codegen_fn_attrs(def_id);
let link_section = if let Some(link_section) = attrs.link_section {
if !link_section.as_str().starts_with(".init_array") {
continue;
}
link_section
} else {
continue;
};
init_arrays.push((link_section, def_id));
}
}
}
init_arrays.sort_by(|(a, _), (b, _)| a.as_str().cmp(b.as_str()));
let endianness = tcx.data_layout.endian;
let ptr_size = tcx.data_layout.pointer_size;
let mut init_array = vec![];
for (_, def_id) in init_arrays {
let alloc = tcx.eval_static_initializer(def_id)?.inner();
let mut expected_offset = Size::ZERO;
for &(offset, prov) in alloc.provenance().ptrs().iter() {
if offset != expected_offset {
throw_ub_format!(".init_array.* may not contain any non-function pointer data");
}
expected_offset += ptr_size;
let alloc_id = prov.alloc_id();
let reloc_target_alloc = tcx.global_alloc(alloc_id);
match reloc_target_alloc {
GlobalAlloc::Function(instance) => {
let addend = {
let offset = offset.bytes() as usize;
let bytes = &alloc.inspect_with_uninit_and_ptr_outside_interpreter(
offset..offset + ptr_size.bytes() as usize,
);
read_target_uint(endianness, bytes).unwrap()
};
assert_eq!(addend, 0);
init_array.push(instance);
}
_ => throw_ub_format!(".init_array.* member is not a function pointer"),
}
}
}
Ok(init_array)
}
/// Lookup the body of a function that has `link_name` as the symbol name. /// Lookup the body of a function that has `link_name` as the symbol name.
fn lookup_exported_symbol( fn lookup_exported_symbol(
&mut self, &mut self,
@ -249,26 +167,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
Entry::Vacant(e) => { Entry::Vacant(e) => {
// Find it if it was not cached. // Find it if it was not cached.
let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None; let mut instance_and_crate: Option<(ty::Instance<'_>, CrateNum)> = None;
// `dependency_formats` includes all the transitive informations needed to link a crate, helpers::iter_exported_symbols(tcx, |cnum, def_id| {
// which is what we need here since we need to dig out `exported_symbols` from all transitive
// dependencies.
let dependency_formats = tcx.dependency_formats(());
let dependency_format = dependency_formats
.iter()
.find(|(crate_type, _)| *crate_type == CrateType::Executable)
.expect("interpreting a non-executable crate");
for cnum in iter::once(LOCAL_CRATE).chain(
dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| {
// We add 1 to the number because that's what rustc also does everywhere it
// calls `CrateNum::new`...
#[allow(clippy::arithmetic_side_effects)]
(linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
}),
) {
// We can ignore `_export_info` here: we are a Rust crate, and everything is exported
// from a Rust crate.
for &(symbol, _export_info) in tcx.exported_symbols(cnum) {
if let ExportedSymbol::NonGeneric(def_id) = symbol {
let attrs = tcx.codegen_fn_attrs(def_id); let attrs = tcx.codegen_fn_attrs(def_id);
let symbol_name = if let Some(export_name) = attrs.export_name { let symbol_name = if let Some(export_name) = attrs.export_name {
export_name export_name
@ -276,35 +175,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
tcx.item_name(def_id) tcx.item_name(def_id)
} else { } else {
// Skip over items without an explicitly defined symbol name. // Skip over items without an explicitly defined symbol name.
continue; return Ok(());
}; };
if symbol_name == link_name { if symbol_name == link_name {
if let Some((original_instance, original_cnum)) = instance_and_crate if let Some((original_instance, original_cnum)) = instance_and_crate {
{
// Make sure we are consistent wrt what is 'first' and 'second'. // Make sure we are consistent wrt what is 'first' and 'second'.
let original_span = let original_span = tcx.def_span(original_instance.def_id()).data();
tcx.def_span(original_instance.def_id()).data();
let span = tcx.def_span(def_id).data(); let span = tcx.def_span(def_id).data();
if original_span < span { if original_span < span {
throw_machine_stop!( throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions {
TerminationInfo::MultipleSymbolDefinitions {
link_name, link_name,
first: original_span, first: original_span,
first_crate: tcx.crate_name(original_cnum), first_crate: tcx.crate_name(original_cnum),
second: span, second: span,
second_crate: tcx.crate_name(cnum), second_crate: tcx.crate_name(cnum),
} });
);
} else { } else {
throw_machine_stop!( throw_machine_stop!(TerminationInfo::MultipleSymbolDefinitions {
TerminationInfo::MultipleSymbolDefinitions {
link_name, link_name,
first: span, first: span,
first_crate: tcx.crate_name(cnum), first_crate: tcx.crate_name(cnum),
second: original_span, second: original_span,
second_crate: tcx.crate_name(original_cnum), second_crate: tcx.crate_name(original_cnum),
} });
);
} }
} }
if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) {
@ -314,9 +207,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
} }
instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum)); instance_and_crate = Some((ty::Instance::mono(tcx, def_id), cnum));
} }
} Ok(())
} })?;
}
e.insert(instance_and_crate.map(|ic| ic.0)) e.insert(instance_and_crate.map(|ic| ic.0))
} }

View file

@ -219,53 +219,66 @@ impl VisitProvenance for TlsData<'_> {
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TlsDtorsState(TlsDtorsStatePriv); pub struct TlsDtorsState<'tcx>(TlsDtorsStatePriv<'tcx>);
#[derive(Debug, Default)] #[derive(Debug, Default)]
enum TlsDtorsStatePriv { enum TlsDtorsStatePriv<'tcx> {
#[default] #[default]
Init, Init,
PthreadDtors(RunningDtorState), PthreadDtors(RunningDtorState),
/// For Windows Dtors, we store the list of functions that we still have to call.
/// These are functions from the magic `.CRT$XLB` linker section.
WindowsDtors(Vec<ImmTy<'tcx, Provenance>>),
Done, Done,
} }
impl TlsDtorsState { impl<'tcx> TlsDtorsState<'tcx> {
pub fn on_stack_empty<'tcx>( pub fn on_stack_empty(
&mut self, &mut self,
this: &mut MiriInterpCx<'_, 'tcx>, this: &mut MiriInterpCx<'_, 'tcx>,
) -> InterpResult<'tcx, Poll<()>> { ) -> InterpResult<'tcx, Poll<()>> {
use TlsDtorsStatePriv::*; use TlsDtorsStatePriv::*;
let new_state = 'new_state: {
match &mut self.0 { match &mut self.0 {
Init => { Init => {
match this.tcx.sess.target.os.as_ref() { match this.tcx.sess.target.os.as_ref() {
"linux" | "freebsd" | "android" => { "linux" | "freebsd" | "android" => {
// Run the pthread dtors. // Run the pthread dtors.
self.0 = PthreadDtors(Default::default()); break 'new_state PthreadDtors(Default::default());
} }
"macos" => { "macos" => {
// The macOS thread wide destructor runs "before any TLS slots get // The macOS thread wide destructor runs "before any TLS slots get
// freed", so do that first. // freed", so do that first.
this.schedule_macos_tls_dtor()?; this.schedule_macos_tls_dtor()?;
// When the stack is empty again, go on with the pthread dtors. // When the stack is empty again, go on with the pthread dtors.
self.0 = PthreadDtors(Default::default()); break 'new_state PthreadDtors(Default::default());
} }
"windows" => { "windows" => {
// Run the special magic hook. // Determine which destructors to run.
this.schedule_windows_tls_dtors()?; let dtors = this.lookup_windows_tls_dtors()?;
// And move to the final state. // And move to the final state.
self.0 = Done; break 'new_state WindowsDtors(dtors);
} }
_ => { _ => {
// No TLS dtor support. // No TLS dtor support.
// FIXME: should we do something on wasi? // FIXME: should we do something on wasi?
self.0 = Done; break 'new_state Done;
} }
} }
} }
PthreadDtors(state) => { PthreadDtors(state) => {
match this.schedule_next_pthread_tls_dtor(state)? { match this.schedule_next_pthread_tls_dtor(state)? {
Poll::Pending => {} // just keep going Poll::Pending => return Ok(Poll::Pending), // just keep going
Poll::Ready(()) => self.0 = Done, Poll::Ready(()) => break 'new_state Done,
}
}
WindowsDtors(dtors) => {
if let Some(dtor) = dtors.pop() {
this.schedule_windows_tls_dtor(dtor)?;
return Ok(Poll::Pending); // we stay in this state (but `dtors` got shorter)
} else {
// No more destructors to run.
break 'new_state Done;
} }
} }
Done => { Done => {
@ -273,7 +286,9 @@ impl TlsDtorsState {
return Ok(Poll::Ready(())); return Ok(Poll::Ready(()));
} }
} }
};
self.0 = new_state;
Ok(Poll::Pending) Ok(Poll::Pending)
} }
} }
@ -282,22 +297,19 @@ impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for crate::MiriInterpCx<'m
trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
/// Schedule TLS destructors for Windows. /// Schedule TLS destructors for Windows.
/// On windows, TLS destructors are managed by std. /// On windows, TLS destructors are managed by std.
fn schedule_windows_tls_dtors(&mut self) -> InterpResult<'tcx> { fn lookup_windows_tls_dtors(&mut self) -> InterpResult<'tcx, Vec<ImmTy<'tcx, Provenance>>> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
// Windows has a special magic linker section that is run on certain events. // Windows has a special magic linker section that is run on certain events.
// Instead of searching for that section and supporting arbitrary hooks in there // We don't support most of that, but just enough to make thread-local dtors in `std` work.
// (that would be basically https://github.com/rust-lang/miri/issues/450), Ok(this.lookup_link_section(".CRT$XLB")?)
// we specifically look up the static in libstd that we know is placed
// in that section.
if !this.have_module(&["std"]) {
// Looks like we are running in a `no_std` crate.
// That also means no TLS dtors callback to call.
return Ok(());
} }
let thread_callback =
this.eval_windows("thread_local_key", "p_thread_callback").to_pointer(this)?; fn schedule_windows_tls_dtor(&mut self, dtor: ImmTy<'tcx, Provenance>) -> InterpResult<'tcx> {
let thread_callback = this.get_ptr_fn(thread_callback)?.as_instance()?; let this = self.eval_context_mut();
let dtor = dtor.to_scalar().to_pointer(this)?;
let thread_callback = this.get_ptr_fn(dtor)?.as_instance()?;
// FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits // FIXME: Technically, the reason should be `DLL_PROCESS_DETACH` when the main thread exits
// but std treats both the same. // but std treats both the same.
@ -305,7 +317,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`. // The signature of this function is `unsafe extern "system" fn(h: c::LPVOID, dwReason: c::DWORD, pv: c::LPVOID)`.
// FIXME: `h` should be a handle to the current module and what `pv` should be is unknown // FIXME: `h` should be a handle to the current module and what `pv` should be is unknown
// but both are ignored by std // but both are ignored by std.
this.call_function( this.call_function(
thread_callback, thread_callback,
Abi::System { unwind: false }, Abi::System { unwind: false },