-
-
Notifications
You must be signed in to change notification settings - Fork 14.2k
core::intrinsics::abort: Terminate with __fastfail on Windows #149780
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
base: main
Are you sure you want to change the base?
Conversation
|
rustbot has assigned @Mark-Simulacrum. Use |
This is what we do on all targets. It seems odd to do something special here for Windows only. If we want the intrinsic to do something OS-specific, we should make it so that all OSes benefit from that. It is also rather unprecedented to do anything OS-specific in Cc @rust-lang/libs |
|
Well if you ignore |
This comment has been minimized.
This comment has been minimized.
We have an intrinsic that declares it does something OS-specific by being described as "Aborts the execution of the process.", which is an inherently OS-specific concept and not something the compiler has to or should implement. Here I'd argue that
|
|
For better or worse, aborting execution is an OS-independent capability in MIR. The intrinsic is just one of the ways it gets invoked; there is also am Abort terminator, and there might be other places in codegen that invoke the same operation.
If LLVM cannot generate the right instructions for us, then at the very least we should do something consistent on our side.
Is this worth bringing up with LLVM, to fix whatever intrinsic we are using for abort() so that it behaves as intended on Windows?
|
|
The intrinsic is currently implemented in terms of llvm's I don't think llvm has an abort. At least I couldn't find one. |
|
So you would argue that what LLVM does on Windows is a reasonable implementation for "trap", it's just not what we want for "abort"?
|
|
Indeed. |
|
Yes. I would expect an abort to immediately abort the process rather than raising an exception (unless there's no reasonable alternative). On the other hand raising an exception is a reasonable implementation of trap. Somewhat related is #149708 but there the exception can only be caught outside of the rust program. |
|
Okay. But then we should use that consistently for all implementations of "abort", not just the intrinsic. We should probably rename the
Then I would suggest you entirely undo the changes in
|
|
I think there may be some unfortunate choices of terminology here, but it's not super obvious to me that this is really a desirable/necessary change in terms of how the abort intrinsic is used. If we take Also worth noting that this also sends a SIGILL signal on other OSes, so I'm not sure why Windows specifically needs divergent behavior. |
|
The And personally I would expect, from a libs pov, for |
| /// this sequence of instructions will be treated as an access violation, which | ||
| /// will still terminate the process but might run some exception handlers. | ||
| /// | ||
| /// The precise behavior is not guaranteed and not stable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entire doc comment only applies to the Windows version of this function. So doc.rust-lang.org would no longer show it.
SIGILL doesn't unwind the stack, while Windows exceptions do unwind the stack through SEH. As such SIGILL only runs an explicitly registered signal handler, while Windows exceptions as I understand it run all |
I guess in that case I'd wonder whether this difference in behavior would be a compelling reason to change what llvm.trap does on Windows? |
|
Trapping is much more generic in usage than specifically aborting the process as fast as possible, which is what we want and already do in |
That's not the only thing Does __fastfail do that? |
Most direct uses of |
Yes, see the documentation for
|
|
That seems to make it a reasonable call for LLVM to use it as an implementation of |
|
What target do the Windows kernel folks use? I'm guessing some external target spec, but if that still sets I also feel pretty strongly that this should be an implementation detail of the intrinsic (not lib |
I at least can't think of a reason, as
|
|
One thing libs asked about is how libs-api feels more generally about the core/std split and whether they would consider this to be breaking it. So I'll nominate it for that reason. |
|
We discussed this in the @rust-lang/libs-api meeting. We're happy to add this for Windows since both user mode and kernel mode have special handling for this specific instruction sequence which triggers an exception. We're however not yet committing to a more general policy which would allow things like the Linux version of |
de08931 to
abe8d6a
Compare
What's the binary size consequence of using If it's just 2 instructions instead of one (no call, no linking implications, etc) then sure, let's do this. If it's more, then maybe it should be part of |
| ) | ||
| ))] | ||
| #[rustc_nounwind] | ||
| pub fn abort() -> ! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not obvious that this is the right way to change the intrinsic behaviour.
For example, if the goal is to change what abort does, should https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_llvm/builder/struct.GenericBuilder.html#method.abort be the thing that changes instead so that any uses of abort in cg_ssa also do a __fastfail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I already mentioned that above -- this needs to become a lang item so the compiler can invoke it to implement the Abort MIR terminator and other "native" aborts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's categorically different. The compiler inserts a single byte trap in some places where a code path is unreachable. That seems a bit unnecessary but fine as a debugging aid. It's only one byte.
core::intrinsic::abort is different. It's used in core where we would use std::panic::abort in std, except that obviously that's unavailable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be very strange if some aborts behave different from others. I am not talking about unreachable code. "Native" aborts occur, for instance, when code unwinds out of a drop, or out of an extern "C".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have different aborts with the core and std difference I mentioned. Should the compiler insert calls to abort() on unix platforms?
I do think the compiler misnames what should be called trap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤷 I don't think it is much complexity, and I don't think it is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, if the goal is to change what abort does, should https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_llvm/builder/struct.GenericBuilder.html#method.abort be the thing that changes instead so that any uses of abort in cg_ssa also do a __fastfail?
Doing that would replace every single ud2 instruction of two bytes with a seven byte instruction sequence, and I doubt LLVM would be allowed / smart enough to optimize out the lang item call like it does with unneeded llvm.trap instructions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The truly unreachable ones anyway shouldn't trap, they should tell LLVM that this is unreachable... (#59793)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but increasing the size now for until that six-year-old issue is resolved does not sound like a good tradeoff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
core::intrinsic::abortis different. It's used incorewhere we would usestd::panic::abortinstd, except that obviously that's unavailable.
Can you elaborate on how the library considers them different between abort and panic_nounwind, which is available in core?
|
It is just two instructions (no call, no linking implications, etc). It just calls an interrupt handler mov ecx, FAST_FAIL_FATAL_APP_EXIT
int 0x29 |
| unsafe { | ||
| cfg_select! { | ||
| any(target_arch = "x86", target_arch = "x86_64") => { | ||
| core::arch::asm!("int $$0x29", in("ecx") FAST_FAIL_FATAL_APP_EXIT, options(noreturn, nostack)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| core::arch::asm!("int $$0x29", in("ecx") FAST_FAIL_FATAL_APP_EXIT, options(noreturn, nostack)); | |
| core::arch::asm!("int 0x29", in("ecx") FAST_FAIL_FATAL_APP_EXIT, options(noreturn, nostack)); |
This has been bugging me for years but not enough to PR it. The $$ is entirely redundant (left over from AT&T syntax, I assume).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also mind removing the workaround at
rust/compiler/rustc_codegen_cranelift/src/inline_asm.rs
Lines 47 to 53 in f794a08
| // Used by panic_abort on Windows, but uses a syntax which only happens to work with | |
| // asm!() by accident and breaks with the GNU assembler as well as global_asm!() for | |
| // the LLVM backend. | |
| if template.len() == 1 && template[0] == InlineAsmTemplatePiece::String("int $$0x29".into()) { | |
| fx.bcx.ins().trap(TrapCode::user(2).unwrap()); | |
| return; | |
| } |
Currently,
std::process::abortaborts the process via__fastfailon Windows, which is the preferred way of terminating processes since Windows 8 as it doesn't go through exception handlers. However,core::intrinsics::abortdoes not, and terminates by executing an illegal instruction, which causesSTATUS_ILLEGAL_INSTRUCTIONto be thrown. This PR removes that discrepancy and makescore::intrinsics::abortuse__fastfailon Windows.Implementation-wise, this is currently done by having
core::intrinsics::abortcall a function incore::osif on Windows and on one of the supported architectures. Unfortunately, this means there is now a function that, depending on the target, isn't an intrinsic inside theintrinsicsmodule, which isn't great. I've chosen to implement it like that anyway to gather feedback on better approaches - my initial idea was to lower it to a inline asm block withinrustc_codegen_ssa, as none of the backends has an intrinsic for fastfail and we thus have to manually spell out the assembly anyway, but there didn't seem to be a good reason for manually generating an inline assembly block in MIR intrinsic handling as opposed to just writing it as an asm block in the first place, as has been done insidestd.