-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
pthread_exit no longer works in panic=abort #101469
Comments
The RFC defines force unwind as undefined behaviour when it cross non-POF, which your example does. |
show me the live variables with a |
The RFC says:
Unwinding through |
As I understand it a POF is any frame without pending destructors or catch_unwind calls. Neither is the case in this example. |
@nbdd0121 That would make every use of |
This paragraph below makes explicit that
|
it's defined to cause an abort only for non-forced unwinds, forced unwinds are specifically specified to work despite panic=abort:
|
I think @nbdd0121 is right that the definition of POF is somewhat confusing here. But certainly this should be considered a POF. "POF-ness" should never depend on the
|
I discussed this yesterday with @bjorn3 about this. The problem is that, for a
In order to satisfy the first requirement, we have to insert landing pads to The most reasonable way would be to tweak the personality function so that all landing pads are ignored for a forced unwind. This means that the destructors will not be ran at all for a forced unwind, but that should be okay as we define unwinding across non-POF frames as undefined behaviour. This method however is not very portable because there are platforms that we don't use our own personality function, such as EMCC and MSVC. For MSVC we have #48572 which should avoid the aborting landing pad (catchpad) from being called. I am not sure about EMCC though (is forced unwind a thing for emscripten?) Overall, I think allowing forced unwind through a |
Yes, and that's exactly why we defined it that way! 😁
...even after #92845? Dang.
Maybe I'm not understanding the question, but forced unwind is, effectively, exactly like |
With longjmp you know the exact place to jump to because it is passed in as argument. With forced unwind, you unwind the stack to find the right location and depending in the platform and language destructors will still run. As for windows where longjmp is implemented using forced unwind, I believe unwind tables are mandatory. |
@bjorn3 I'm not sure that's correct. Forced unwinds can't be "caught" (by definition), so either the "exact place to jump to" is the thread boundary (in the case of
Regardless of the language, running destructors for a forced unwind is UB. This is why we require frames that can be force-unwound to be POFs. |
AFAIK the personality function can catch forced unwinds. You just aren't supposed to do this in user code. I think libc does catch it to end unwinding at the end of the thread stack, but I haven't checked this. To find this catch at the end you need to unwind the stack using unwind tables. |
Let me rewind back to @nbdd0121's final statement above:
As I mentioned, "correctly" here just means that the correct frames are deallocated. So the question is essentially "how does the runtime know where to jump to," correct? It sounds like the only platforms where we think this is an issue are the ones that use So if I understand the problem correctly, on If that's the case, I don't understand why it would matter what Rust does. It sounds like |
Or, to put it another way: in the case of a forced unwind, the runtime must solve the problem of "where to stop unwinding" somehow, and since the place to stop unwinding should never be a Rust frame (because we don't abort on forced unwind), I think it simply doesn't matter whether or not there are any Rust frames in a thread being unwound by |
Correctly means that the unwinding runtime can actually perform unwinding at all. Some unwinding runtimes require unwinding information to exist to perform unwinding at all; some unwinding runtimes try to unwind frames without any unwinding information at all by using the frame pointers alone. For the latter if force-frame-pointers is off, it can clobber the stack pointer and cause memory safety issues.
On Windows platforms unwinding info is required: https://doc.rust-lang.org/1.63.0/nightly-rustc/src/rustc_target/spec/windows_msvc_base.rs.html#19
It needs unwind tables to unwind through intermediatary frames. It can't even see the catching frame without going through all the intermediatary frames. |
Actually, just went into a deep dive into how libgcc and libunwind's unwinder was implemented, it turns out none of them are performing unwinding of frames with no unwinding information by using frame pointers. Both of them just bail out and say "this is the end of stack" when such function is found. Perhaps my memory was off recalling similar things from gdb or Linux kernel. This is good since we don't have to worry about memory safety issues. I also had a deep dive into how |
@nbdd0121 So is there not actually a problem in that case, then? |
Well, without unwind tables, it essentially makes I am not sure about other uses (are there any?) of forced unwind, though. |
@nbdd0121 I believe there are not; but in any case, it still seems to me that if there are, it's not on the Rust compiler to ensure that unwind tables are used or available. |
So we just need to ensure, somehow, that forced unwinds don't cause an automatic |
here's a c++ equivalent that works: https://gcc.godbolt.org/z/8aWxr45jP maybe have rustc mostly duplicate that? |
Interesting, I was under the false impression that clang will abort when forced unwind crosses a noexcept frame (because that's why #48572 existed, so our behaviour is different from clang's). But apparently that's only on MSVC and not on Linux. Clang generates Thanks @programmerjake for pointing this out. I was focusing on things more complicated but oblivious to the simple solution. @rustbot claim |
This aborts with |
Now this is getting even more interesting. On my machine (x86_64, GCC 10.2.1, Clang 11.0.1, Linux 5.10.74, glibc 2.31) both GCC and Clang produced binaries complete without issue. What's your environment? |
x86_64, GCC 12.2.0, clang 14.0.6, Linux 5.17.8, glibc 2.36. With I think the key difference might be in the glibc version, specifically in the way it is throwing the forced unwind. |
Actually it could also be a difference in the libgcc unwinder (it's still used even with |
Okay, I am able to replicate @Amanieu's behaviour. So there are two versions of I was attempting to try to understand the behaviour with glibc and libstdc++ by plugging in my own unwinder using So I decided to look directly at the libstdc++ personality code instead. It turns out it's sort of a deliberate decision to abort when noexcept function is encountered upon forced unwind in C++. https://github.com/gcc-mirror/gcc/blob/03381beccb52c0e2c15da3b8b8dfa3bb6eb71df9/libstdc%2B%2B-v3/libsupc%2B%2B/eh_personality.cc#L669-L673 |
For what it's worth, that phrasing seems very apt to me. I don't believe the C++ standard makes any attempt to define "forced unwinding" or characterize its behavior, so it's unsurprising that the behavior isn't consistent. @nbdd0121 Do you still feel like you know how to proceed here? |
I tried this code:
https://rust.godbolt.org/z/M5Pz1rjnd
I expected to see this happen: exits with exit code 0 as documented for
pthread_exit
Instead, this happened: aborts with double panic
Meta
rustc --version --verbose
:sorry, i can't figure out how to get a backtrace from godbolt.org, it doesn't seem to allow setting environment variables.
This bug serms to be caused by #96959
@rust-lang/wg-ffi-unwind
The text was updated successfully, but these errors were encountered: