-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Tracking issue for the x86-interrupt
calling convention
#40180
Comments
Tracking issue: rust-lang#40180 This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit x86 targets. The compiler then uses `iret` instead of `ret` for returning and ensures that all registers are restored to their original values. Usage: ``` extern "x86-interrupt" fn handler(stack_frame: &ExceptionStackFrame) {…} ``` for interrupts and exceptions without error code and ``` extern "x86-interrupt" fn page_fault_handler(stack_frame: &ExceptionStackFrame, error_code: u64) {…} ``` for exceptions that push an error code (e.g., page faults or general protection faults). The programmer must ensure that the correct version is used for each interrupt. For more details see the [LLVM PR][1] and the corresponding [proposal][2]. [1]: https://reviews.llvm.org/D15567 [2]: http://lists.llvm.org/pipermail/cfe-dev/2015-September/045171.html
…ention, r=nagisa Add support for the x86-interrupt calling convention This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit x86 targets. The compiler then uses `iret` instead of `ret` for returning and ensures that all registers are restored to their original values. Usage: ```rust extern "x86-interrupt" fn handler(stack_frame: &ExceptionStackFrame) {…} ``` for interrupts and exceptions without error code and ```rust extern "x86-interrupt" fn handler_with_err_code(stack_frame: &ExceptionStackFrame, error_code: u64) {…} ``` for exceptions that push an error code (e.g., page faults or general protection faults). The programmer must ensure that the correct version is used for each interrupt. For more details see the [LLVM PR][1] and the corresponding [proposal][2]. [1]: https://reviews.llvm.org/D15567 [2]: http://lists.llvm.org/pipermail/cfe-dev/2015-September/045171.html It is also possible to implement interrupt handlers on x86 through [naked functions](https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md). In fact, almost all existing Rust OS projects for x86 use naked functions for this, including [Redox](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147), [IntermezzOS](https://github.com/intermezzOS/kernel/blob/f959cc18c78b1ba153f3ff7039d9ecc07f397628/interrupts/src/lib.rs#L28-L72), and [blog_os](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L49-L64). So support for the `x86-interrupt` calling convention isn't absolutely needed. However, it has a number of benefits to naked functions: - **No inline assembly needed**: [Inline assembly](https://doc.rust-lang.org/book/inline-assembly.html) is highly unstable and dangerous. It's pretty easy to mess things up. Also, it uses an arcane syntax and requires that the programmer knows x86 assembly. - **Higher performance**: A naked wrapper function always saves _all_ registers before calling the Rust function. This isn't needed for a compiler supported calling convention, since the compiler knows which registers are clobbered by the interrupt handler. Thus, only these registers need to be saved and restored. - **Safer interfaces**: We can write a `set_handler` function that takes a `extern "x86-interrupt" fn(&ExceptionStackFrame)` and the compiler ensures that we always use the right function type for all handler functions. This isn't possible with the `#[naked]` attribute. - **More convenient**: Instead of writing [tons of assembly boilerplate](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147) and desperately trying to improve things [through macros](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L17-L92), we can just write [code like this](https://github.com/phil-opp/blog_os/blob/e6a61f9507a4c4fef6fb4e3474bc596391bc97d2/src/interrupts/mod.rs#L85-L89). - **Naked functions are unreliable**: It is allowed to use Rust code inside a naked function, which sometimes works and sometimes not. For example, [calling a function](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L132) through Rust code seems to work fine without function prologue, but [code declaring a variable](https://is.gd/NQYXqE) silently adds a prologue even though the function is naked (look at the generated assembly, there is a `movl` instruction before the `nop`). **Edit**: See the [tracking issue](rust-lang#40180) for an updated list of issues. Unfortunately, the implementation of the `x86-interrupt` calling convention in LLVM has some issues that make it unsuitable for 64-bit kernels at the moment: - LLVM always tries to backup the `xmm` registers on 64-bit platforms even if the target doesn't support SSE. This leads to invalid opcode exceptions whenever an interrupt handler is invoked. I submitted a fix to LLVM in [D29959](https://reviews.llvm.org/D29959). The fix is really small (<10 lines), so maybe we could backport it to [Rust's LLVM fork](https://github.com/rust-lang/llvm)?. **Edit**: The fix was merged to LLVM trunk in [rL295347](https://reviews.llvm.org/rL295347). Backported in rust-lang/llvm#63. - On targets with SSE support, LLVM uses the `movaps` instruction for saving the `xmm` registers, which requires an alignment of 16. For handlers with error codes, however, the stack alignment is only 8, so a alignment exception occurs. This issue is tracked in [bug 26413](https://bugs.llvm.org/show_bug.cgi?id=26413). ~~Unfortunately, I don't know enough about LLVM to fix this.~~ **Edit**: Fix submitted in [D30049](https://reviews.llvm.org/D30049). This PR adds experimental support for this calling convention under the `abi_x86_interrupt` feature gate. The implementation is very similar to rust-lang#38465 and was surprisingly simple :). There is no accepted RFC for this change. In fact, the [RFC for interrupt calling convention](rust-lang/rfcs#1275) from 2015 was closed in favor of naked functions. However, the reactions to the recent [PR](rust-lang#38465) for a MSP430 interrupt calling convention were [in favor of experimental interrupt ABIs](rust-lang#38465 (comment)). - [x] Add compile-fail tests for the feature gate. - [x] Create tracking issue for the `abi_x86_interrupt` feature (and link it in code). **Edit**: Tracking issue: rust-lang#40180 - [x] Backport [rL295347](https://reviews.llvm.org/rL295347) to Rust's LLVM fork. **Edit**: Done in rust-lang/llvm#63 @tari @steveklabnik @jackpot51 @ticki @hawkw @thepowersgang, you might be interested in this.
…ention, r=nagisa Add support for the x86-interrupt calling convention This calling convention can be used for definining interrupt handlers on 32-bit and 64-bit x86 targets. The compiler then uses `iret` instead of `ret` for returning and ensures that all registers are restored to their original values. Usage: ```rust extern "x86-interrupt" fn handler(stack_frame: &ExceptionStackFrame) {…} ``` for interrupts and exceptions without error code and ```rust extern "x86-interrupt" fn handler_with_err_code(stack_frame: &ExceptionStackFrame, error_code: u64) {…} ``` for exceptions that push an error code (e.g., page faults or general protection faults). The programmer must ensure that the correct version is used for each interrupt. For more details see the [LLVM PR][1] and the corresponding [proposal][2]. [1]: https://reviews.llvm.org/D15567 [2]: http://lists.llvm.org/pipermail/cfe-dev/2015-September/045171.html It is also possible to implement interrupt handlers on x86 through [naked functions](https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md). In fact, almost all existing Rust OS projects for x86 use naked functions for this, including [Redox](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147), [IntermezzOS](https://github.com/intermezzOS/kernel/blob/f959cc18c78b1ba153f3ff7039d9ecc07f397628/interrupts/src/lib.rs#L28-L72), and [blog_os](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L49-L64). So support for the `x86-interrupt` calling convention isn't absolutely needed. However, it has a number of benefits to naked functions: - **No inline assembly needed**: [Inline assembly](https://doc.rust-lang.org/book/inline-assembly.html) is highly unstable and dangerous. It's pretty easy to mess things up. Also, it uses an arcane syntax and requires that the programmer knows x86 assembly. - **Higher performance**: A naked wrapper function always saves _all_ registers before calling the Rust function. This isn't needed for a compiler supported calling convention, since the compiler knows which registers are clobbered by the interrupt handler. Thus, only these registers need to be saved and restored. - **Safer interfaces**: We can write a `set_handler` function that takes a `extern "x86-interrupt" fn(&ExceptionStackFrame)` and the compiler ensures that we always use the right function type for all handler functions. This isn't possible with the `#[naked]` attribute. - **More convenient**: Instead of writing [tons of assembly boilerplate](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L109-L147) and desperately trying to improve things [through macros](https://github.com/phil-opp/blog_os/blob/844d739379ffdea6a7ede88365ec6e21a725bbf5/src/interrupts/mod.rs#L17-L92), we can just write [code like this](https://github.com/phil-opp/blog_os/blob/e6a61f9507a4c4fef6fb4e3474bc596391bc97d2/src/interrupts/mod.rs#L85-L89). - **Naked functions are unreliable**: It is allowed to use Rust code inside a naked function, which sometimes works and sometimes not. For example, [calling a function](https://github.com/redox-os/kernel/blob/b9793deb59c7650f0805dea96adb6b773ad99336/arch/x86_64/src/lib.rs#L132) through Rust code seems to work fine without function prologue, but [code declaring a variable](https://is.gd/NQYXqE) silently adds a prologue even though the function is naked (look at the generated assembly, there is a `movl` instruction before the `nop`). **Edit**: See the [tracking issue](rust-lang#40180) for an updated list of issues. Unfortunately, the implementation of the `x86-interrupt` calling convention in LLVM has some issues that make it unsuitable for 64-bit kernels at the moment: - LLVM always tries to backup the `xmm` registers on 64-bit platforms even if the target doesn't support SSE. This leads to invalid opcode exceptions whenever an interrupt handler is invoked. I submitted a fix to LLVM in [D29959](https://reviews.llvm.org/D29959). The fix is really small (<10 lines), so maybe we could backport it to [Rust's LLVM fork](https://github.com/rust-lang/llvm)?. **Edit**: The fix was merged to LLVM trunk in [rL295347](https://reviews.llvm.org/rL295347). Backported in rust-lang/llvm#63. - On targets with SSE support, LLVM uses the `movaps` instruction for saving the `xmm` registers, which requires an alignment of 16. For handlers with error codes, however, the stack alignment is only 8, so a alignment exception occurs. This issue is tracked in [bug 26413](https://bugs.llvm.org/show_bug.cgi?id=26413). ~~Unfortunately, I don't know enough about LLVM to fix this.~~ **Edit**: Fix submitted in [D30049](https://reviews.llvm.org/D30049). This PR adds experimental support for this calling convention under the `abi_x86_interrupt` feature gate. The implementation is very similar to rust-lang#38465 and was surprisingly simple :). There is no accepted RFC for this change. In fact, the [RFC for interrupt calling convention](rust-lang/rfcs#1275) from 2015 was closed in favor of naked functions. However, the reactions to the recent [PR](rust-lang#38465) for a MSP430 interrupt calling convention were [in favor of experimental interrupt ABIs](rust-lang#38465 (comment)). - [x] Add compile-fail tests for the feature gate. - [x] Create tracking issue for the `abi_x86_interrupt` feature (and link it in code). **Edit**: Tracking issue: rust-lang#40180 - [x] Backport [rL295347](https://reviews.llvm.org/rL295347) to Rust's LLVM fork. **Edit**: Done in rust-lang/llvm#63 @tari @steveklabnik @jackpot51 @ticki @hawkw @thepowersgang, you might be interested in this.
If we're going to have an x86-interrupt abi, would it also make sense to have an x86-syscall? Or x86-sysenter? |
(D30049 was merged to LLVM trunk on the 3rd of April, for the record.) |
@kyrias Thanks for the hint, I updated the issue text. I'll try to create a backport PR in the next few days. |
Triage: not aware of any movement stabilizing this. Personal note: this is one of my favorite rust features :) |
Both of the 64-bit issues have long since been fixed, and Rust's minimum LLVM is newer than the fix, so it's just the MMX/x87 floating point and 32-bit issues remaining, which appear to have been untouched since 2017. |
@phil-opp I think this issue can be updated to cross off #57270 On the x87/MMX issue, we should just mandate that to use the interrupt ABI, you have to build the code with We may also want to similarly disallow all SSE code in interrupt handlers (GCC bans SSE code, Clang allows it), as that might make it easier to make sure we are doing the right thing. |
Just came across the following from a conference paper, and thought I would quote here as a data point:
(section 4.1 of https://www.usenix.org/system/files/osdi20-narayanan_vikram.pdf) |
After discussing this on the Rust Community Server #os-dev channel with some folks (cc @Soveu @asquared31415), and checking out the LLVM patch that includes the ABI, it seems like taking the struct by-reference is incorrect and leads to undefined behavior (as seen in rust-osdev/x86_64#240). This seems to have worked previously, but I suspect the upgrade to LLVM 12 may have "fixed" the behavior. (edit: asquared tested both 2021-03-03 and 2021-03-05 nightly releases, both by-value and by-ref work on the former, but by-ref breaks on the latter, which I think pretty much confirms the LLVM 12 upgrade fixed the behavior) The patch to LLVM includes the following code, suggesting that the very first parameter should always be by-value and never by-reference. if (F.getCallingConv() == CallingConv::X86_INTR) {
// IA Interrupt passes frame (1st parameter) by value in the stack.
if (Idx == 1)
Flags.setByVal();
} |
By the way, if the function signature contains more than 2 arguments LLVM crashes with
|
@repnop Thanks a lot for investigating this! I'll look into it and update the description of this issue if I can confirm that it works with a by-value parameter. Given that the Rust implementation just sets the LLVM calling convention, this shouldn't require any changes on the Rust side to fix it. We should adjust the To prevent issues like this (and wrong number of arguments errors), we should probably introduce some kind of lint that checks the function signature when the |
Also, we have found with @asquared31415 that using the second argument can cause stack corruption, because LLVM does not check if there is an error code and just pops unconditionally |
Shouldn't 0x0 segment be null descriptor? (which iirc is invalid, stack segment should be a 64bit data segment) |
Since LLVM 12 (rust-lang/rust#84230) ExceptionStackFrame has to be taken by value. See rust-lang/rust#40180 (comment).
Since LLVM 12 (rust-lang/rust#84230) ExceptionStackFrame has to be taken by value. See rust-lang/rust#40180 (comment).
Since LLVM 12 (rust-lang/rust#84230) ExceptionStackFrame has to be taken by value. See rust-lang/rust#40180 (comment).
Since LLVM 12 (rust-lang/rust#84230) ExceptionStackFrame has to be taken by value. See rust-lang/rust#40180 (comment).
Since LLVM 12 (rust-lang/rust#84230) ExceptionStackFrame has to be taken by value. See rust-lang/rust#40180 (comment).
We discussed this in today's @rust-lang/lang meeting. We agree that this needs an RFC. Not specifically for x86-interrupt, but an "target-specific interrupt calling conventions" RFC. Once that RFC goes in, new target-specific interrupt calling conventions would just need a patch to the ABI section of the reference, ideally documenting the calling convention. Would someone involved in this thread be up for writing that RFC? @phil-opp Would you be willing to write that RFC? We'd also appreciate an update to the API evolution RFC, to note that target-specific calling conventions are only as stable as the targets they're supported on; if we remove a target (for which we have guidelines and process in the target tier policy), removing its associated target-specific calling conventions is not a breaking change. |
This comment has been minimized.
This comment has been minimized.
The problem described here is exactly why you must have
I don't think this is true. It's the calling code's responsibility to make sure that each interrupt handler invocation matches its function signature, including:
While the calling convention doesn't provide these protections itself (as its just abound declaring functions), things like the In short, I don't think any of the above issues should block stabilization. Of course, the LLVM crashes, error messages, and known issues probably need to be addressed before stabilization. |
@joshtriplett Yes, I'm happy to work on this! I will create a rough draft and then open a pre-rfc thread on internals to discuss the details. |
Thank you for the correction.
Given that my concern about user-level code corrupting the kernel stack was incorrect, I agree. |
Should we change the bug references above to point to:
As the LLVM project has moved to GitHub Issues. |
Thanks for the suggestion! I updated/added the references you mentioned. |
I created a Pre-RFC for initial feedback at https://internals.rust-lang.org/t/pre-rfc-interrupt-calling-conventions/16182. Please let me know what you think! |
Note: This is unsound on 32-bit x86 (Protected mode) if llvm expects the When the processor is in legacy mode, |
If these are supposed to work like normal Rust functions they probably shouldn't ICE on ZST arguments? #126418 |
Overview
Tracking issue for the
x86-interrupt
calling convention, which was added in PR #39832. The feature gate name isabi_x86_interrupt
. This feature will not be considered for stabilization without an RFC.The
x86-interrupt
calling convention can be used for defining interrupt handlers on 32-bit and 64-bit x86 targets. The compiler then usesiret
instead ofret
for returning and ensures that all registers are restored to their original values.Usage
for interrupts and exceptions without error code and
for exceptions that push an error code (e.g., page faults or general protection faults). The programmer must ensure that the correct version is used for each interrupt.
For more details see the LLVM PR, and the corresponding proposal.
Known issues
ExceptionStackFrame
is passed by reference (instead of by value). This used to work on older LLVM version, but no longer works on LLVM 12. See Tracking issue for thex86-interrupt
calling convention #40180 (comment) for more details.used byval ABI for unsized layout
#124806Tried to make Ignore indirect
#126418"x86-interrupt" fn
should be invalid #132834extern "x86-interrupt" fn
allows absurd signatures #132835()
or!
as return types #13284164-bit
The x86_64 automatically aligns the stack on a 16-byte boundary when an interrupts occurs in 64-bit mode. However, the CPU pushes an 8-byte error code for some exceptions, which destroys the 16-byte alignment. At the moment, LLVM doesn't handle this case correctly and always assumes a 16-byte alignment. This leads to alignment issues on targets with SSE support, since LLVM uses misaligned
movaps
instructions for saving thexmm
registers. This issue is tracked as bug 26413.A fix for this problem was submitted in D30049 and merged in rL299383.
LLVM always tries to backup the
xmm
registers on 64-bit platforms even if the target doesn't support SSE. This leads to invalid opcode exceptions whenever an interrupt handler is invoked.The fix was merged to LLVM trunk in rL295347. Backported in x86 interrupt calling convention: only save xmm registers if the target supports SSE llvm#63.
__attribute__((interrupt)) Handlers Dangerously Out of Spec on x86-64 llvm/llvm-project#41189
32-bit
and esp, 16
). However, LLVM doesn't do that at the moment, which might lead to alignment errors, especially for targets with SSE support. This issue is tracked in interrupt handler calls functions with misaligned stack llvm/llvm-project#26851.The text was updated successfully, but these errors were encountered: