rust-lang / miri

An interpreter for Rust's mid-level intermediate representation
Apache License 2.0
4.39k stars 340 forks source link

```no_std``` issues with implementing ```panic_handler``` #3498

Closed Necromaticon closed 5 months ago

Necromaticon commented 5 months ago

I'm trying to interpet a very simple no_std example before going for any bigger problems. Problem appears to be that none of the panic_handler crates are working with Miri.

The command I'm using to run the code is: MIRI_NO_STD=1 cargo miri run

Code:

#![no_main]
#![no_std]

//insert individual panic methods here

use cortex_m_rt;

#[cortex_m_rt::entry]
fn main() -> ! {
    loop {}
}

First off, both use panic_halt as _; and use panic_abort as _; create the following issue:

 error: unwinding panics are not supported without std
  |
  = help: using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding
  = note: since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem

That's something that seems to be an issue with cargo build in general and not just Miri specific. I don't quite understand why abort and halt wouldn't work for no_std environments though considering these panic handlers were made for this express purpose.

So I tried going for use panic_semihosting as _; and encountered an even weirder problem where the crates aren't recognized on build but only when I run cargo miri. If I run cargo run everything works without a hitch. Error:

error[E0463]: can't find crate for `panic_semihosting`
 --> embeddedMiri/src/main.rs:4:5
  |
4 | use panic_semihosting as _;
  |     ^^^^^^^^^^^^^^^^^ can't find crate
  |
  = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: `#[panic_handler]` function required, but not found

error: unwinding panics are not supported without std
  |
  = help: using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding
  = note: since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem

It seems that Miri fails to somehow link the cargo crates when building. I've also tried building with extern crate but then the problem of the panic handler being missing consists and all examples of panic handlers using the #[panic_handler] flag I found invoke the unwinding panics error.

Thank you in advance for the help!

RalfJung commented 5 months ago

That's something that seems to be an issue with cargo build in general and not just Miri specific. I don't quite understand why abort and halt wouldn't work for no_std environments though considering these panic handlers were made for this express purpose.

The error says you should set panic=abort. Have you tried that?

I doubt that cortex_m_rt supports unwinding, so panic=abort is likely what you want.

Necromaticon commented 5 months ago

The error says you should set panic=abort. Have you tried that?

I doubt that cortex_m_rt supports unwinding, so panic=abort is likely what you want.

First off thank you for the incredibly quick reply! Forgot to mention that I tried that already by adding

[profile.dev]
panic="abort"

to Cargo.toml and also tried the other variants like [profiles.test] and [profiles.release]. My Cargo.toml looks as follows:

[package]
name = "embeddedMiri"
version = "0.1.0"
edition = "2021"

[profile.dev]
panic ="abort"

[profile.release]
panic ="abort"

[profile.test]
panic ="abort"

[dependencies]
cortex-m-rt = "0.7.4"
panic-abort = "0.3.2"
panic-halt = "0.2.0"
panic-semihosting = "0.6.0"

And running both with and without the extra -Z build-std flag results in the same unwinding panics error. The "abort" also has to be in quotation marks as without cargo throws a runtime error of not recognizing the command so as far as syntax goes this should work.

RalfJung commented 5 months ago

the extra -Z build-std flag

-Zbuild-std is known to confuse Miri, so maybe that is part of the problem here. (Cc https://github.com/rust-lang/miri/issues/2705)

Forgot to mention that I tried that already by adding

Does RUSTFLAGS="-Cpanic=abort" cargo miri run help?

So I tried going for use panicsemihosting as ; and encountered an even weirder problem where the crates aren't recognized on build but only when I run cargo miri. If I run cargo run everything works without a hitch.

Strange... looks like Miri asks for the lang item in a case where rustc does not.

Is there a small example we could check out to reproduce the issue?

Necromaticon commented 5 months ago

Does RUSTFLAGS="-Cpanic=abort" cargo miri run help?

I tried that just now and found the following behavior: With panic_abort when I run RUSTFLAGS="-Cpanic=abort" cargo run there is the same same where it can't find the crate:

error[E0463]: can't find crate for `panic_abort`
 --> embeddedMiri/src/main.rs:5:5
  |
5 | use panic_abort as _;
  |     ^^^^^^^^^^^ can't find crate

With RUSTFLAGS="-Cpanic=abort" MIRI_NO_STD=1 cargo miri run it yields: error: miri can only run programs that have a main function

To which I found #2962 and edited my main.rs file to:

#![no_main]
#![no_std]
#![feature(start)]

use panic_abort as _;

use cortex_m_rt;
//#[cortex_m_rt::entry]
#[start]
fn main(){  
}

But execution returns the same error regarding missing a main function. Which is odd considering the #[start] should prevent exactly the type of error where an entry function is missing.

Strange... looks like Miri asks for the lang item in a case where rustc does not. Is there a small example we could check out to reproduce the issue?

Here is the project I've been using. I am able to execute it using cargo run but it fails when I run MIRI_NO_STD=1 cargo miri run

bjorn3 commented 5 months ago

If you use #[start] you shouldn't also use #![no_main]. Also #[start] requires fn(isize, *const *const u8) -> isize as function signature.

Necromaticon commented 5 months ago

If you use #[start] you shouldn't also use #![no_main]. Also #[start] requires fn(isize, const const u8) -> isize as function signature.

That did the trick for panic_halt and panic_abort! I'm now able to run follow code snippet by using RUSTFLAGS="-Cpanic=abort" MIRI_NO_STD=1 cargo miri run:

 #![no_std]
#![feature(start)]

use panic_semihosting as _;

use cortex_m_rt;

#[cortex_m_rt::entry]
fn main()->!{
    unreachable!()
}

#[start]
#[cfg(miri)]
fn main(_argc: isize, _argv: *const *const u8) ->isize{
    0
}

If I build without miri it's still throwing errors for not having a main, but that's just playing around with conditional compilation to add #![no_main] in when miri isn't executed. From what I've gathered there is no way to invoke the #[cortex_m_rt::entry] main is there? If there is, then I could use the #[start] main as a sort of proof harness, otherwise I could also just copy paste the #[cortex_m_rt::entry] main which is less ideal but sort of does the job.

The issue with panic_semihosting of not finding the crates consists, so I'll leave the issue open in case there is an interest to investigate.

Thank you so much for your help @RalfJung and @bjorn3 !

RalfJung commented 5 months ago

So I guess the issue with the start function is that cortex_m_rt has its own start function that Miri doesn't know about.

@Necromaticon I assume that no_main is generally required for cortex_m_rt? And that the cortex_m_rt::entry macro then generates some magic attributes or assembly that make the given function the entry point without rustc knowing anything about that? I don't know what is the best way to teach Miri about the entry points for headless systems. I don't think we want to implement support for each and every "program entry point" convention in Miri... but we could establish some suitable convention and make the cortex_m_rt::entry macro emit some attribute or whatever that could let Miri find the entry point.

The issue with panic_semihosting of not finding the crates consists, so I'll leave the issue open in case there is an interest to investigate.

With the entry point issue resolved, what exactly is the issue with these panic crates? What even is use panic_abort as _;, is that importing the panic_abort crate from the sysroot or something else?

bjorn3 commented 5 months ago

What even is use panicabort as ;, is that importing the panic_abort crate from the sysroot or something else?

That is using a crate on crates.io called panic-abort, which contains a #[panic_handler] which calls core::intrinsics::abort().

bjorn3 commented 5 months ago

And that the cortex_m_rt::entry macro then generates some magic attributes or assembly that make the given function the entry point without rustc knowing anything about that?

It seems to create an extern "C" function marked with #[export_name = "main"] which calls the user function with zero or more arguments. This generated function doesn't accept any arguments, unlike the C main function.

Necromaticon commented 5 months ago

I don't think we want to implement support for each and every "program entry point" convention in Miri...

I don't think that'd be necessary either. Since there is already a way to define the entry function in Miri using #[start] one could easily avoid all issues regarding differing starting points.

The setup I'm using now requires limited editing to the files under test:

#![no_std]
#![cfg_attr(miri, feature(start))]
#![cfg_attr(not(miri), no_main)]

use panic_abort as _;
use cortex_m_rt;

#[cfg_attr(not(miri),cortex_m_rt::entry)]
fn main()->!{
    loop{}
}

#[cfg(miri)]
#[start]
fn main1(_argc: isize, _argv: *const *const u8) ->isize{
    main();
    0
}

Only important note here is that the #[start] function can't be named main as then the compiler will complain about function redefinition despite clearly having different function signatures.

It seems to create an extern "C" function marked with #[export_name = "main"] which calls the user function with zero or more arguments. This generated function doesn't accept any arguments, unlike the C main function.

Then that makes sense why it would require the ![no_main] attribute as otherwise the library can't ensure an unambiguous function call to main().

As for the missing crates issue I found out that cortex-m-rt uses its own semihosting called cortex-m-semihosting so I assume the error comes from not using that and thus creating conflicts.

RalfJung commented 5 months ago

I filed https://github.com/rust-lang/rust/issues/124581 for the confusion around no_main. However, Miri already prints a pretty decent error when no_main is set ("miri can only run programs that have a main function") so I think the confusion here came mostly from this being mixed up with whatever is going on with the panic crates.

For the panic crates, I tried your example. I adds 3 panic runtimes as dependency, which makes little sense to me? Even cargo run fails on that example:

error: the crate `panic_abort` is not a panic runtime

error[E0152]: duplicate lang item in crate `panic_abort`: `panic_impl`.
  |
  = note: the lang item is first defined in crate `std` (which `panic_semihosting` depends on)
  = note: first definition in `std` loaded from /home/r/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-9579298f7adeed43.so, /home/r/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-9579298f7adeed43.rlib
  = note: second definition in `panic_abort` loaded from /home/r/src/rust/tmp/embeddedMiriExample/embeddedMiri/target/debug/deps/libpanic_abort-08618b6f3fb3dbb8.rlib

So it is entirely expected that cargo miri run fails in the same way.

So I reduced it to 1:

[dependencies]
cortex-m-rt = "0.7.4"
#panic-abort = "0.3.2"
#panic-halt = "0.2.0"
panic-semihosting = "0.6.0"

Now I can do cargo miri run and it works just fine. Of course it doesn't do anything.

So, currently I can't reproduce any unexpected behavior here.

Strangely, if I make that program panic it seems to use the default panic handler from std. I don't know where this comes from. But cargo run behaves the same so it's not a Miri thing...

RalfJung commented 5 months ago

I guess there is a feature request here for having Miri support the "entry point" for cortex-m-rt. Currently we only support entry points that Rust "knows", i.e. the usual main function (to be invoked by the standard library, i.e. this only works for with-std binaries) and the #[start] attribute.

However, I assume every embedded platform has its own idiosyncratic ways to define and find the start function; I don't think I want Miri to become a collection of all of those mechanisms. One approach I could consider is to say that if no main or start function is defined, Miri goes looking for a symbol named miri_start and if that exists, it gets invoked the same way that the start function would (i.e., the signature should be fn(isize, *const *const u8) ->isize). Then embedded runtimes could implement support for Miri by declaring such a function when cfg(miri) is set, and having that function invoke whatever the actual start function is.

If there's interest in pursuing that from the cortex-m folks, then someone please file a separate Miri issue for that so it can be tracked properly.

RalfJung commented 5 months ago

Strangely, if I make that program panic it seems to use the default panic handler from std. I don't know where this comes from. But cargo run behaves the same so it's not a Miri thing...

Ah, lol, figured it out...

#![cfg(all(target_arch = "arm", target_os = "none"))]

That crate is empty when built for any other target. Which means it does not have the no_std attribute. Which means it pulls in std, and therefore the std panic handler.

RalfJung commented 5 months ago

That also explains why you got a "missing crate" error, @Necromaticon: by setting MIRI_NO_STD=1, i.e. by building your host's Miri sysroot (which usually has std!) without std, you got a partial sysroot, but panic_semihosting actually requires std on all targets that do not satisfy all(target_arch = "arm", target_os = "none").

MIRI_NO_STD should not usually be used; these days Miri generally recognizes no-std targets by itself (though that is somewhat heuristic, so we keep the env var for cases where the heuristic fails).

So, I'll close this issue, as there is no Miri bug: you added a crate as dependency that depends on std, and built a sysroot without std, and then that fails to build. No surprise here.

RalfJung commented 5 months ago

Filed https://github.com/rust-lang/miri/issues/3529 for the poor diagnostic when MIRI_NO_STD is used and a crate actually requires std.