Demilade Sonuga's blog
All postsPanicking I
We've gone a long way in this project. So far, we've been able to animate a bitmap on screen. We could just keep going on right now to the next thing but that's not really a good idea because there's a major thing we're missing out: that is sensible panic behavior. When a panic occurs, the computer goes into an eternal loop, without giving any information on why the panic occurred:
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
loop {}
}
If an unexpected error is encountered during execution and a panic occurs, tracing the cause of the error will be unnecessarily hard.
To get a better idea of what I'm talking about:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
while block_position.1 < NO_OF_PIXELS_IN_A_ROW {
panic!("A panic occurred here"); // NEW
draw_bitmap(screen, &block, block_position);
erase_bitmap(screen, &block, block_position);
block_position.1 += 1;
}
0
}
If you run the game now, you'll get a blank screen. This is because when the panic occurs, the computer goes into an infinite loop without giving any information about why the panic occurred.
As we proceed in the process of building this game, we will definitely need something to tell us what went wrong in the case of an unexpected error.
Our new task now, before we get on with the game, is to figure out how to get that information about why a panic occurred on screen.
A look at the signature of the panic handler tells us that the function takes one argument of type
PanicInfo
. It's this struct, unsurprisingly, that holds information about the panic.
A briefing through the
docs shows that PanicInfo
has some methods that can be used to retrieve the information.
The location
function returns a struct that contains information about where the panic occurred in the
file. The message
function returns a struct representing the arguments that panic!
was called with.
Putting 1 and 2 together, we can print panic info:
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
// NEW:
if let Some(panic_msg) = panic_info.message() {
print_str(no screen, panic_msg.as_str().unwrap()); // No access to the screen
} else {
print_str(no screen, "Message returned None for some unknown reason"); // No access to the screen
}
loop {}
}
Now, we've encountered a problem that is stopping us from making progress.
We no longer have access to the screen reference that we were using to print. This means we can no longer print. So, how can we get the information on the screen if we can't even access the screen anymore?
To solve this problem, we need to rethink our code.
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let framebuffer_base = mode.framebuffer_base;
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
// ... Others
}
This is the point where we retrieve the address of the screen. This address is saved in a local variable. Local variables are saved on the stack. The problem here is that the addresses of values saved on the stack will not be known until runtime when the program is executing.
But for the screen to be accessed in the panic handler, the reference has to be saved somewhere known
beforehand to both efi_main
and the handler so they can both access it when they need to.
One way of resolving this is to create a static variable. The address of a static variable is known at
compile time because the variable is saved in a location reserved by the compiler in the output binary.
Using this idea, we now modify main.rs
:
static mut SCREEN: Option<&mut Screen> = None;
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let framebuffer_base = mode.framebuffer_base;
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
// Initializing the screen static
unsafe { SCREEN = Some(screen); } // NEW
// The SCREEN static now holds the mutable reference to screen
// Since there can't be more than one mutable reference, a reference
// to the screen reference is used instead
let screen = unsafe { SCREEN.as_mut().unwrap() };
// ... Others
}
Since the address of the screen is not known at compile time, we set the SCREEN
static to None
.
When the address of the screen is finally known, we initialize the static with the appropriate reference.
After this initialization, for the rest of our code to keep working, we have to create a new variable screen
which is a reference to the screen reference now in the SCREEN
static. We have to do this because there
can't be more than one mutable reference to a value at once. Saving the mutable reference in the static
SCREEN
means that the reference is going to be active for the rest of the program's execution. From this
point on, there can't be another mutable reference.
We now have access to the screen reference in the panic handler:
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let screen = unsafe { SCREEN.as_mut().unwrap() };
if let Some(panic_msg) = panic_info.message() {
// DELETED: print_str(no screen, panic_msg.as_str().unwrap());
print_str(screen, panic_msg.as_str().unwrap()); // NEW
} else {
// DELETED: print_str(no screen, "Message returned None for some unknown reason");
print_str(screen, "Message returned None for some unknown reason"); // NEW
}
loop {}
}
There's just one more thing we need to add for this to work:
In main.rs
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
#![feature(panic_info_message)] // NEW
The PanicInfo
's message
function is an experimental feature and to use it, we need to add the feature
gate, which is this #![feature(panic_info_message)]
.
Running the code now will give this output:
Our panic function can now print a simple message but the problem now is that it's too limited. To see what I'm talking about:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
while block_position.1 < NO_OF_PIXELS_IN_A_ROW {
// DELETED: panic!("A panic occured here");
panic!("A panic occurred {}", "here"); // NEW
draw_bitmap(screen, &block, block_position);
erase_bitmap(screen, &block, block_position);
block_position.1 += 1;
}
0
}
The expected output should be "A panic occurred here". But instead, this is what we have:
To understand why we're having this output, we return to the panic handler:
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let screen = unsafe { SCREEN.as_mut().unwrap() };
if let Some(panic_msg) = panic_info.message() {
print_str(screen, panic_msg.as_str().unwrap());
} else {
print_str(screen, "Message returned None for some unknown reason"); // NEW
}
loop {}
}
The PanicInfo
's message
function returns an instance of core::fmt::Arguments
.
The Arguments
struct is what is used to represent format strings and their arguments. Whenever you call
println!("I am {} years old", 19)
, the combination of the string slices "I am ", " years old" and argument
19 is fully represented as an instance of the Arguments
struct.
The same thing goes for our panic!
macro. When you passed "A panic occurred here" as the to the panic!
macro, it was represented as an Arguments
instance with no arguments. When you passed
"A panic occurred {}", "here" to panic!
, it was represented as an Arguments
instance with a single argument
"here".
The Arguments
's as_str
function returns a string slice only when the struct instance has no arguments.
If it has arguments, None
is returned. This is why the first time we panicked, the message was printed.
There were no arguments, so as_str
simply returned Some("A panic occurred here")
.
The second time, there is an argument, so as_str
returns a None
, which is then unwrapped, causing another
panic. The error message that ought to be outputted was: "called Option::unwrap()
on a None
value", but
only "called" gets printed.
This means that in our handler's current state, we can't panic with format arguments, which renders the handler useless, because those arguments are needed to get useful information on the screen.
Figure out a way to resolve this.
Take Away
- The
core::fmt::Arguments
is the representation of a format string and its arguments.
For the full code, go to the repo
In The Next Post
We'll continue setting up mechanisms to print panic messages
References
- https://doc.rust-lang.org/core/panic/struct.PanicInfo.html
- https://doc.rust-lang.org/core/fmt/struct.Arguments.html