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

Add error applet support + error applet panic hook #162

Merged
merged 17 commits into from
Feb 27, 2024

Conversation

FenrirWolf
Copy link
Member

@FenrirWolf FenrirWolf commented Feb 19, 2024

Adds support for the error applet, which is a simple applet that lets you display error text in a pop-up window on the bottom screen.

The applet is also capable of launching the EULA registration dialogue, as well as "infrastructure communications-related error messages" via error codes of a completely unspecified nature, but I've opted not to support either of these functions. The applet also supports changing the language of the "An error has occurred" header text at the top of the applet, but I don't think it's worth including that functionality either since it defaults to your system's preferred language anyway.

The real fun part about this PR was discovering that Bindgen was generating the errorConf struct with the wrong size and field offsets because the struct contains enum values that were themselves generated with the wrong sizes. Hopefully that's a problem we won't run into very often again, but it's one that's worth watching out for in the future.

@adryzz
Copy link
Contributor

adryzz commented Feb 19, 2024

this is super cool

Copy link
Member

@Meziu Meziu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, simple and powerful. As with all applets, it depends on Apt and Gfx, but I wonder how cool a panic hook using this would be!

ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
@adryzz
Copy link
Contributor

adryzz commented Feb 23, 2024

Great, simple and powerful. As with all applets, it depends on Apt and Gfx, but I wonder how cool a panic hook using this would be!

tried it a few days ago, it's super super cool, but yeah, without memory crimes you can't easily do such a thing unfortunately.

@FenrirWolf
Copy link
Member Author

tried it a few days ago, it's super super cool, but yeah, without memory crimes you can't easily do such a thing unfortunately.

Setting up a custom panic hook isn't too bad actually. For example, I could easily add a method like this to the PR if we want:

/// Sets a custom panic hook that uses the error applet to display panic messages.
///
/// SAFETY: The error applet requires that both the [`Apt`] and [`Gfx`] services are active when it launches.
/// By calling this function, you promise that you will keep those services alive until either the program ends
/// or you manually unregister this hook with [`std::panic::take_hook`].
pub unsafe fn set_panic_hook() {
    std::panic::set_hook(Box::new(|panic_info| {
        let mut popup = PopUp::new(Kind::Top);

        let thread = std::thread::current();

        let name = thread.name().unwrap_or("<unnamed>");

        let payload = format!("thread '{name}' {panic_info}");

        popup.set_text(&payload);

        unsafe {
            ctru_sys::errorDisp(popup.state.as_mut());
        }
    }))
}

@FenrirWolf
Copy link
Member Author

FenrirWolf commented Feb 24, 2024

Okay, we've got ourselves a panic hook now. It works pretty well!

2024-02-23_17-35-39 701_to 1

@Meziu
Copy link
Member

Meziu commented Feb 24, 2024

@AzureMarker I’d like to hear your opinion on this addition since it directly continues this discussion.

With the use of the error applet, we can cut down the necessity of the Console functionality and just stand on Gfx and Apt. Still, the only way I can see this working is as an unsafe function, but it might still be a worthy alternative to stderr redirection with 3dslink.

@FenrirWolf
Copy link
Member Author

FenrirWolf commented Feb 24, 2024

The way I see it, providing this hook is an overall win because:

  1. since launch_unchecked is private, the user cannot write their own equivalent to this code at all and they would have to start from scratch with raw ctru-sys calls. The error applet isn't very complicated to call, but it would still be a bit annoying for the user to create and audit multiple unsafe blocks to accomplish what we can provide here with just one, I guess we could instead expose launch_unchecked, but the only practical use for that fn being public would be to implement a hook like this one anyway.

  2. the preconditions for set_panic_hook are pretty "easy" for most users to fulfill: just init Apt and Gfx at the top of your program and everything will Just Work. And coincidentally enough, that's what the vast majority of programs using ctru-rs are already doing anyway.

  3. There might be room to further improve the safety situation. For example, the majority of libctru services are atomically reference-counted on each init and exit, meaning it should be safe to create and distribute multiple handles to those services, and some services like Apt are pre-initialized in every libctru program even if the user never does it anyway. So technically it should be perfectly fine for us to just Apt::new right inside the panic hook to guarantee access to an Apt handle. I believe that Gfx is one of the few handles that doesn't internally reference count in this way, but I did notice that we have a static GFX_ACTIVE value that the panic hook could check against before doing its thing.

@FenrirWolf
Copy link
Member Author

FenrirWolf commented Feb 25, 2024

I pushed a change that makes set_panic_hook a safe function. Are there any other sources of unsafety when calling the applet though, or is this change enough to cover all our bases?

I'm also not sure if this method of checking for Gfx access is good enough or if it's technically vulnerable to time-of-check-to-time-of-use problems. I might have to see if there's some way to smuggle a proper Gfx handle into the closure instead.

Copy link
Member

@ian-h-chamberlain ian-h-chamberlain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
popup.set_text(&payload);

unsafe {
popup.launch_unchecked();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check my understanding, this will block the app until the popup is closed, and then the app will quit (since there's no loop or anything)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, you've got that right. Though the app can technically continue if the panic is stopped by a thread boundary or catch_unwind or whatever.

ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
Comment on lines +21 to +23
// Libctru's `errorConf` struct contains two enums that depend on this narrowing property for size and alignment purposes,
// and since `bindgen` generates all enums with `c_int` sizing, we have to blocklist those types and manually define them
// here with the proper size.
Copy link
Member

@ian-h-chamberlain ian-h-chamberlain Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really interesting! Similar to what I mentioned in #166 , maybe we can just force these constants to generate using errorReturnCode as the type instead of c_int?

To catch the struct size/alignment issues, I wonder if we could have caught that using bindgen's layout tests. I think they were omitted before because we didn't have any good way to run them, but with test-runner now we might be able to actually do it in CI. I'll file an issue (edit: #167)

Edit: bindgen also has a .fit_macro_constants(true) we could try to use for this purpose, but I'm not sure if it would help with the actual struct size.

@FenrirWolf FenrirWolf changed the title Add basic error applet support Add error applet support + error applet panic hook Feb 26, 2024
ctru-rs/src/applets/error.rs Show resolved Hide resolved

/// Configuration struct to set up the Error applet.
#[doc(alias = "errorConf")]
pub struct PopUp {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I kind of liked the ErrorApplet name from before, but this works I guess. If not the original name, maybe it could be clearer that it's a pop up message. PopUpMessage?

ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
ctru-rs/src/applets/error.rs Show resolved Hide resolved
ctru-rs/src/applets/error.rs Show resolved Hide resolved
ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
ctru-rs/src/applets/error.rs Show resolved Hide resolved
ctru-rs/src/applets/error.rs Outdated Show resolved Hide resolved
@AzureMarker
Copy link
Member

Thanks for adding this!

@FenrirWolf
Copy link
Member Author

FenrirWolf commented Feb 27, 2024

By the way, the error applet actually does do scrolling for long panic messages. I'm just a dummy and had tested things wrong earlier:

2024-02-27_00-13-16 346_to 1

So let's get this thing merged.

@FenrirWolf FenrirWolf merged commit 4b1c2e0 into rust3ds:master Feb 27, 2024
4 checks passed
@FenrirWolf FenrirWolf deleted the impl_error_applet branch February 27, 2024 07:20
@FenrirWolf FenrirWolf restored the impl_error_applet branch February 27, 2024 08:59
@FenrirWolf FenrirWolf deleted the impl_error_applet branch February 27, 2024 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants