Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

noreturn on diverging functions makes LLVM trash the link register on thumb targets #69231

Closed
jonas-schievink opened this issue Feb 17, 2020 · 11 comments
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. O-Arm Target: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-embedded Working group: Embedded systems

Comments

@jonas-schievink
Copy link
Contributor

jonas-schievink commented Feb 17, 2020

Downstream issue: rust-embedded/cortex-m-rt#139

It looks like LLVM is extremely eager to overwrite the Link Register without saving it as soon as noreturn is put on a function. Since rustc does that for any -> ! function, which includes many parts of the panic machinery, this effectively makes obtaining a backtrace on panic on embedded systems impossible.

This is somewhat analogous to omission of frame pointers on x86, so maybe -Cforce-frame-pointers can be repurposed to prevent this behavior on Thumb/ARM targets? I'm not sure there's even an LLVM feature to control this specifically, but I suppose rustc could always omit the noreturn.

Since this is only important in debug builds (that may be optimized, however), it might make sense to couple this behavior to whether debug assertions are enabled, but I'm not sure about that. What do you think? Would a patch doing something of this sort be accepted?

@jonas-schievink jonas-schievink added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. O-Arm Target: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. C-bug Category: This is a bug. WG-embedded Working group: Embedded systems labels Feb 17, 2020
@hanna-kruppe
Copy link
Contributor

Note that LLVM can also infer noreturn on its own, not in all cases where rustc can add it, but certainly in many simple cases such as panic-abort's panic handler.

Have you tried -Cforce-frame-pointer and found it didn't help? At a glance it seems that the ARM backend respects the usual way to request the frame pointer to be preserved (the frame-pointer function attribute), so I would expect -Cforce-frame-pointer to work already.

@jonas-schievink
Copy link
Contributor Author

Thanks, that really does look like it'd fix this, but according to rust-embedded/cortex-m-rt#139 (comment) it doesn't help.

@hanna-kruppe
Copy link
Contributor

Oh, right. A pre-built libcore, at least the mononorphic non-inline parts such as core::panicking::panic_fmt, won't usually be affected by the -C flag. Does rebuilding libcore (via Xargo or cargo -Z build-std=...) help?

@MabezDev
Copy link
Contributor

Oh, right. A pre-built libcore, at least the mononorphic non-inline parts such as core::panicking::panic_fmt, won't usually be affected by the -C flag.

Ah, I always for get about that!

Running the minimal example with cargo +nightly run -Z build-std=core and "-C", "force-frame-pointers=yes" does indeed fix the corrupted backtrace! 🎉

Breakpoint 3, rust_begin_unwind (_info=0x2000feb8)
    at /home/mabez/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs:32
32          loop {
(gdb) bt
#0  rust_begin_unwind (_info=0x2000feb8)
    at /home/mabez/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs:32
#1  0x080008de in core::panicking::panic_fmt (fmt=..., 
    location=0x8003a40 <.Lanon.b0bb6e8da484f667ddcf6a6acf4928ab.9>)
    at /home/mabez/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/panicking.rs:85
#2  0x08002972 in core::slice::slice_index_len_fail (index=1024, len=1)
    at /home/mabez/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/slice/mod.rs:2674
#3  0x08000800 in <core::ops::range::Range<usize> as core::slice::SliceIndex<[T]>>::index (self=..., slice=...)
    at /home/mabez/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/slice/mod.rs:2838
#4  0x08000830 in <core::ops::range::RangeTo<usize> as core::slice::SliceIndex<[T]>>::index (self=..., slice=...)
    at /home/mabez/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/slice/mod.rs:2880
#5  0x08000516 in core::slice::<impl core::ops::index::Index<I> for [T]>::index (
    self=..., index=...)
    at /home/mabez/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore/slice/mod.rs:2656
#6  0x0800043c in backtrace::__cortex_m_rt_main () at src/main.rs:19

What are the performance implications of forcing framepointers? Would forcing frame pointers for thumb* targets (Does this affect all baremetal targets?) be such a bad idea?

@jonas-schievink
Copy link
Contributor Author

Is it possible to use -Cforce-frame-pointers=off to forcefully disable them again? If so, I think there's not much harm in enabling them by default for these targets. At most, we'll pay for a bit of stack space since LR is saved slightly more often, as well as slightly higher register pressure, I think?

@MabezDev
Copy link
Contributor

Is it possible to use -Cforce-frame-pointers=off to forcefully disable them again?

Not sure I follow; do you mean for downstream crates? I think if the core crate is built with framepointers, the downstream crates shouldn't have to have it enabled. I'm not sure how we could test that theory though.

@jonas-schievink
Copy link
Contributor Author

I want to know if it would be possible to opt out of frame pointers again if we decide to enable them by default

@hanna-kruppe
Copy link
Contributor

I am reasonably sure that -Cforce-frame-pointers will always override the target's default, so it should work as an opt-out (except of course for pre-built machine code in sysroot crates).

Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Feb 19, 2020
Don't eliminate frame pointers on thumb targets

This should hopefully fix issues rust-lang#69231 and rust-embedded/cortex-m-rt#139.

~~I couldn't test this locally as the rustc I produced does not create binaries (no idea why).~~ Resolved.
Dylan-DPC-zz pushed a commit to Dylan-DPC-zz/rust that referenced this issue Feb 19, 2020
Don't eliminate frame pointers on thumb targets

This should hopefully fix issues rust-lang#69231 and rust-embedded/cortex-m-rt#139.

~~I couldn't test this locally as the rustc I produced does not create binaries (no idea why).~~ Resolved.
@Amanieu
Copy link
Member

Amanieu commented Feb 25, 2020

Have you tried enabling requires-uwtable in the target spec json? This should force LLVM to keep the return address around for generating dwarf backtraces.

@jonas-schievink
Copy link
Contributor Author

I have not, but #69248 seems to fix this perfectly (with a cost in binary and stack size, of course), so closing this as fixed.

@Amanieu
Copy link
Member

Amanieu commented Feb 25, 2020

I would expect uwtable to have less impact on performance/code size since it doesn't require setting up the frame pointer at the start of every function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. O-Arm Target: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-embedded Working group: Embedded systems
Projects
None yet
Development

No branches or pull requests

4 participants