diff --git a/README.md b/README.md index b1be596c00..4892f3824d 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,19 @@ Miri provides some `extern` functions that programs can import to access Miri-specific functionality. They are declared in [/tests/utils/miri\_extern.rs](/tests/utils/miri_extern.rs). +## Entry point for no-std binaries + +Binaries that do not use the standard library are expected to declare a function like this so that +Miri knows where it is supposed to start execution: + +```rust +#[cfg(miri)] +#[no_mangle] +fn miri_start(argc: isize, argv: *const *const u8) -> isize { + // Call the actual start function that your project implements, based on your target's conventions. +} +``` + ## Contributing and getting help If you want to contribute to Miri, great! Please check out our diff --git a/src/bin/miri.rs b/src/bin/miri.rs index 25b154a820..42929bcb28 100644 --- a/src/bin/miri.rs +++ b/src/bin/miri.rs @@ -14,11 +14,14 @@ extern crate tracing; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_hir; +extern crate rustc_hir_analysis; extern crate rustc_interface; extern crate rustc_log; extern crate rustc_metadata; extern crate rustc_middle; extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; use std::env::{self, VarError}; use std::num::NonZero; @@ -27,9 +30,12 @@ use std::str::FromStr; use tracing::debug; +use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields}; use rustc_data_structures::sync::Lrc; use rustc_driver::Compilation; +use rustc_hir::def_id::LOCAL_CRATE; use rustc_hir::{self as hir, Node}; +use rustc_hir_analysis::check::check_function_signature; use rustc_interface::interface::Config; use rustc_middle::{ middle::{ @@ -37,14 +43,15 @@ use rustc_middle::{ exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel}, }, query::LocalCrate, - ty::TyCtxt, + traits::{ObligationCause, ObligationCauseCode}, + ty::{self, Ty, TyCtxt}, util::Providers, }; -use rustc_session::config::{CrateType, ErrorOutputType, OptLevel}; +use rustc_session::config::{CrateType, EntryFnType, ErrorOutputType, OptLevel}; use rustc_session::search_paths::PathKind; use rustc_session::{CtfeBacktrace, EarlyDiagCtxt}; - -use miri::{BacktraceStyle, BorrowTrackerMethod, ProvenanceMode, RetagFields}; +use rustc_span::def_id::DefId; +use rustc_target::spec::abi::Abi; struct MiriCompilerCalls { miri_config: miri::MiriConfig, @@ -82,11 +89,7 @@ impl rustc_driver::Callbacks for MiriCompilerCalls { tcx.dcx().fatal("miri only makes sense on bin crates"); } - let (entry_def_id, entry_type) = if let Some(entry_def) = tcx.entry_fn(()) { - entry_def - } else { - tcx.dcx().fatal("miri can only run programs that have a main function"); - }; + let (entry_def_id, entry_type) = entry_fn(tcx); let mut config = self.miri_config.clone(); // Add filename to `miri` arguments. @@ -351,6 +354,56 @@ fn jemalloc_magic() { } } +fn entry_fn(tcx: TyCtxt<'_>) -> (DefId, EntryFnType) { + if let Some(entry_def) = tcx.entry_fn(()) { + return entry_def; + } + // Look for a symbol in the local crate named `miri_start`, and treat that as the entry point. + let sym = tcx.exported_symbols(LOCAL_CRATE).iter().find_map(|(sym, _)| { + if sym.symbol_name_for_local_instance(tcx).name == "miri_start" { Some(sym) } else { None } + }); + if let Some(ExportedSymbol::NonGeneric(id)) = sym { + let start_def_id = id.expect_local(); + let start_span = tcx.def_span(start_def_id); + + let expected_sig = ty::Binder::dummy(tcx.mk_fn_sig( + [tcx.types.isize, Ty::new_imm_ptr(tcx, Ty::new_imm_ptr(tcx, tcx.types.u8))], + tcx.types.isize, + false, + hir::Safety::Safe, + Abi::Rust, + )); + + let correct_func_sig = check_function_signature( + tcx, + ObligationCause::new(start_span, start_def_id, ObligationCauseCode::Misc), + *id, + expected_sig, + ) + .is_ok(); + + if correct_func_sig { + (*id, EntryFnType::Start) + } else { + tcx.dcx().fatal( + "`miri_start` must have the following signature:\n\ + fn miri_start(argc: isize, argv: *const *const u8) -> isize", + ); + } + } else { + tcx.dcx().fatal( + "Miri can only run programs that have a main function.\n\ + Alternatively, you can export a `miri_start` function:\n\ + \n\ + #[cfg(miri)]\n\ + #[no_mangle]\n\ + fn miri_start(argc: isize, argv: *const *const u8) -> isize {\ + \n // Call the actual start function that your project implements, based on your target's conventions.\n\ + }" + ); + } +} + fn main() { #[cfg(any(target_os = "linux", target_os = "macos"))] jemalloc_magic(); diff --git a/tests/fail/miri_start_wrong_sig.rs b/tests/fail/miri_start_wrong_sig.rs new file mode 100644 index 0000000000..dac83d817b --- /dev/null +++ b/tests/fail/miri_start_wrong_sig.rs @@ -0,0 +1,21 @@ +//@compile-flags: -Cpanic=abort +//@error-in-other-file: `miri_start` must have the following signature: +#![no_main] +#![no_std] + +use core::fmt::Write; + +#[path = "../utils/mod.no_std.rs"] +mod utils; + +#[no_mangle] +fn miri_start() -> isize { + //~^ ERROR: mismatched types + writeln!(utils::MiriStdout, "Hello from miri_start!").unwrap(); + 0 +} + +#[panic_handler] +fn panic_handler(_: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/tests/fail/miri_start_wrong_sig.stderr b/tests/fail/miri_start_wrong_sig.stderr new file mode 100644 index 0000000000..6217191711 --- /dev/null +++ b/tests/fail/miri_start_wrong_sig.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> $DIR/miri_start_wrong_sig.rs:LL:CC + | +LL | fn miri_start() -> isize { + | ^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of function parameters + | + = note: expected signature `fn(isize, *const *const u8) -> _` + found signature `fn() -> _` + +error: `miri_start` must have the following signature: + fn miri_start(argc: isize, argv: *const *const u8) -> isize + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/fail/no_main.rs b/tests/fail/no_main.rs index 01b8c7bd66..4bbd820a63 100644 --- a/tests/fail/no_main.rs +++ b/tests/fail/no_main.rs @@ -1,2 +1,2 @@ -//@error-in-other-file: miri can only run programs that have a main function +//@error-in-other-file: Miri can only run programs that have a main function. #![no_main] diff --git a/tests/fail/no_main.stderr b/tests/fail/no_main.stderr index 1c4fc88989..e9b9e5d65b 100644 --- a/tests/fail/no_main.stderr +++ b/tests/fail/no_main.stderr @@ -1,4 +1,11 @@ -error: miri can only run programs that have a main function +error: Miri can only run programs that have a main function. + Alternatively, you can export a `miri_start` function: + + #[cfg(miri)] + #[no_mangle] + fn miri_start(argc: isize, argv: *const *const u8) -> isize { + // Call the actual start function that your project implements, based on your target's conventions. + } error: aborting due to 1 previous error diff --git a/tests/pass/miri_start.rs b/tests/pass/miri_start.rs new file mode 100644 index 0000000000..756a1f60be --- /dev/null +++ b/tests/pass/miri_start.rs @@ -0,0 +1,19 @@ +//@compile-flags: -Cpanic=abort +#![no_main] +#![no_std] + +use core::fmt::Write; + +#[path = "../utils/mod.no_std.rs"] +mod utils; + +#[no_mangle] +fn miri_start(_argc: isize, _argv: *const *const u8) -> isize { + writeln!(utils::MiriStdout, "Hello from miri_start!").unwrap(); + 0 +} + +#[panic_handler] +fn panic_handler(_: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/tests/pass/miri_start.stdout b/tests/pass/miri_start.stdout new file mode 100644 index 0000000000..1c9e8489b5 --- /dev/null +++ b/tests/pass/miri_start.stdout @@ -0,0 +1 @@ +Hello from miri_start!