Demilade Sonuga's blog
All postsPanicking III
If you got stuck while putting down your digit descriptions in the font, you can get them from the
repo.
After getting those descriptions into the code, we need to modify the FONT
array to reflect the
new characters:
In font.rs
:
// ... Others
// DELETED: pub const FONT: [[[bool; 16]; 16]; 28] = [
pub const FONT: [[[bool; 16]; 16]; 38] = [ // NEW
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O , P, Q, R,
S, T, U, V, W, X, Y, Z, SPACE, EXCLAMATION,
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE // NEW
];
The FONT
array now contains the digits 0..=9. To be able to print digits on the screen, we need to
change our char_to_font_index
in uefi.rs
function which is in charge of mapping Unicode scalar values
(characters) to the index of the position in the FONT
array that describes that character.
This is the current state of the function:
// Takes the Unicode code point of a character in 'A'..='Z' or 'a'..='z' or "!" or " "
// and returns its index into the FONT array
fn char_to_font_index(c: u8) -> usize {
if c >= 97 {
// Small letters to big letters
char_to_font_index(c - 32)
} else if c == 32 {
// Space
26
} else if c == 33 {
// Exclamation mark
27
} else {
// FONT array index for big letters
(c - 65) as usize
}
}
The Unicode scalar values for the digits 0..=9 are 48, 49, 50, ..., 57, in that order. In the FONT
array, the
digits 0..=9 start from index 28. So, the index of 0 will be 48 - 20 == 28. The description for 1 comes
immediately after 0, so its index will be 49 - 20 == 29. The same goes for the digit 2 which is 50 - 20 == 30.
Clearly, the digits index of the digits 0..=9 in our array are the digit's Unicode scalar values minus the number
20.
/* DELETED:
fn char_to_font_index(c: u8) -> usize {
if c >= 97 {
char_to_font_index(c - 32)
} else if c == 32 {
26
} else if c == 33 {
27
} else {
(c - 65) as usize
}
}
*/
// NEW:
// Takes the Unicode code point of a character in 'A'..='Z' or 'a'..='z' or "!" or " " or '0'..='9'
// and returns its index into the FONT array
fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
48..=57 => (c - 20) as usize, // Digits
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
_ => panic!("The Unicode character {} is not representable by the font", c)
}
}
Now, let's see how the font looks on the screen.
In main.rs
:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
unsafe { SCREEN = Some(screen); }
let screen = unsafe { SCREEN.as_mut().unwrap() };
// NEW:
print_str(screen, "abcdefghijklmnopqrstuvwxyz0123456789");
loop {}
// ... Others
}
Upon running this, you should have:
And now we have digits in our font.
Let's try panicking with a digit as a format argument:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
unsafe { SCREEN = Some(screen); }
let screen = unsafe { SCREEN.as_mut().unwrap() };
/* DELETED:
print_str(screen, "abcdefghijklmnopqrstuvwxyz0123456789");
loop {}
*/
//NEW:
panic!("{}", 2);
// ... Others
}
Running:
That's much better now.
In our new char_to_font_index
, the function panics if the character has no description in the font.
Let's see what happens when this scenario takes place:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
unsafe { SCREEN = Some(screen); }
let screen = unsafe { SCREEN.as_mut().unwrap() };
// DELETED: panic!("{}", 2);
print_str(screen, "+"); // NEW
// ... Others
}
The character '+' has no description in our font, so the result of this should be a panic with the message "The Unicode character 43 is not representable by the font", since the Unicode scalar value of '+' is 43.
Running:
Only the last part of the message got printed. So, there's still something wrong somewhere.
To figure out where the problem is, we proceed by asking ourselves some relevant questions, answering those questions and moving in the direction of the logic.
We start by asking ourselves: what exactly is the problem here? Clearly, the problem is that only the last
part of the message, after the format argument, is printed. Why is this so? I don't know. All I do
know is that the point where this message gets printed is in the panic handler, where it's passed as an argument to the screen's write_fmt
function.
So, this takes us 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() {
screen.write_fmt(*panic_msg);
} else {
screen.write_str("Message returned None for some unknown reason");
}
loop {}
}
The panic_msg
holds the arguments we passed to panic!
, and write_fmt
handles everything else.
But how exactly does write_fmt
do this handling, since it appears to be doing it wrongly?
To answer this question, we go to the docs.
The default implementation of write_fmt
provided, according to the docs:
pub trait Write {
// ... Others
#[stable(feature = "rust1", since = "1.0.0")]
fn write_fmt(mut self: &mut Self, args: Arguments<'_>) -> Result {
write(&mut self, args)
}
}
Clearly, write_fmt
works by passing the work over to some function called write
. So, to understand
write_fmt
, we need to understand this function. To understand this function, we first need to see the code,
but where is that code, exactly?
A little searching reveals that the write
function is defined in the same file.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn write(output: &mut dyn Write, args: Arguments<'_>) -> Result {
let mut formatter = Formatter::new(output);
let mut idx = 0;
match args.fmt {
None => {
// We can use default formatting parameters for all arguments.
for (i, arg) in args.args.iter().enumerate() {
// SAFETY: args.args and args.pieces come from the same Arguments,
// which guarantees the indexes are always within bounds.
let piece = unsafe { args.pieces.get_unchecked(i) };
if !piece.is_empty() {
formatter.buf.write_str(*piece)?;
}
(arg.formatter)(arg.value, &mut formatter)?;
idx += 1;
}
}
Some(fmt) => {
// Every spec has a corresponding argument that is preceded by
// a string piece.
for (i, arg) in fmt.iter().enumerate() {
// SAFETY: fmt and args.pieces come from the same Arguments,
// which guarantees the indexes are always within bounds.
let piece = unsafe { args.pieces.get_unchecked(i) };
if !piece.is_empty() {
formatter.buf.write_str(*piece)?;
}
// SAFETY: arg and args.args come from the same Arguments,
// which guarantees the indexes are always within bounds.
unsafe { run(&mut formatter, arg, args.args) }?;
idx += 1;
}
}
}
// There can be only one trailing string piece left.
if let Some(piece) = args.pieces.get(idx) {
formatter.buf.write_str(*piece)?;
}
Ok(())
}
By going through this function's code, we can see how write_fmt
works.
The first argument of the function is output
, a Write
trait object. What exactly a trait object
is is something we don't need to start looking at now. All we need to know is that output
refers to
something that has implemented the Write
trait. The second argument args
is just the arguments.
From the way the function is written, we see that some variable idx
is initialized to 0. In either
branch of the match expression, a for loop is executed. In each iteration of the for loop, a piece
of the argument is retrieved and is written with write_str
. After this, the idx
variable is incremented.
At the end of the for loop, the last piece of the argument is retrieved with idx
and written with write_str
.
Now it's clear how write_fmt
works. The pieces that make up the full string are iterated over and
printed with write_str
.
In our code, panic!
is invoked with the argument
("The Unicode character {} is not representable by the font", c), so the pieces will be something like
["The Unicode character ", the value of c, " is not representable by the font"].
Since all arguments are written with write_str
, we head back there to see what went wrong:
In uefi.rs
impl Write for Screen {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
print_str(self, s);
Ok(())
}
}
print_str
is the only function called here, so the problem must be in print_str
(if there is a problem
here at all).
pub fn print_str(screen: &mut Screen, s: &str) {
let mut screen_pos = (0, 0);
for c in s.as_bytes() {
print_char(screen, &FONT[char_to_font_index(*c)], screen_pos);
screen_pos.1 += 16;
if screen_pos.1 >= NO_OF_PIXELS_IN_A_ROW {
screen_pos.0 += 16;
screen_pos.1 = 0;
}
if screen_pos.0 >= NO_OF_PIXELS_IN_A_COLUMN {
break;
}
}
}
And here we have it. The problem. The print_str
function works by printing characters across the screen
starting from screen_pos
. But screen_pos
is initialized to (0, 0) at the beginning of every call, so
every piece of the argument is written starting from the upper left corner of the screen. This is why
only the last piece shows up because it overwrites the previous piece that was printed.
To resolve this problem, we need to keep the screen_pos
out of the function, in a place where its value can
be preserved across calls to print_str
.
We can easily do this with statics:
static SCREEN_POS_ROW: AtomicUsize = AtomicUsize::new(0);
static SCREEN_POS_COL: AtomicUsize = AtomicUsize::new(0);
pub fn print_str(screen: &mut Screen, s: &str) {
// DELETED: let screen_pos = (0, 0);
let mut screen_pos = (
SCREEN_POS_ROW.load(Ordering::Relaxed),
SCREEN_POS_COL.load(Ordering::Relaxed)
); // NEW
for c in s.as_bytes() {
print_char(screen, &FONT[char_to_font_index(*c)], screen_pos);
screen_pos.1 += 16;
if screen_pos.1 >= NO_OF_PIXELS_IN_A_ROW {
screen_pos.0 += 16;
screen_pos.1 = 0;
}
if screen_pos.0 >= NO_OF_PIXELS_IN_A_COLUMN {
break;
}
}
// NEW:
// The new value of the screen position
SCREEN_POS_ROW.store(screen_pos.0, Ordering::Relaxed);
SCREEN_POS_COL.store(screen_pos.1, Ordering::Relaxed);
}
The screen position is now saved in static variables, so the variables will remain there from the start of the program to the end of it, unlike local variables which can only last during their function's execution.
In the print_str
function, the current screen positions are first loaded from the statics. After the drawing
on the screen, the statics are updated to reflect the new screen position.
AtomicUsize
s were used for the statics to avoid mutable statics. This is because mutable
statics are unsafe to handle. Handling AtomicUsize
in a static variable is safe because it can be mutated without
having to declare the static mutable.
As for the Ordering::Relaxed
used in the loads and stores of the atomic variables, these just tell the
compiler that the order of storage and loading of the variable doesn't matter. This is only relevant in
a multi-process scenario, where multiple executing programs are accessing the variable at the same time,
so we don't have to worry about it.
For the code to compile:
In uefi.rs
use crate::FONT;
use core::fmt::Write;
// NEW:
use core::sync::atomic::{AtomicUsize, Ordering};
Running:
And we've finally gotten our panicking mechanism to work properly.
To see if the block animation is still working:
#[no_mangle]
extern "efiapi" fn efi_main(
handle: *const core::ffi::c_void,
sys_table: *mut SystemTable,
) -> usize {
// ... Others
let screen = framebuffer_base as *mut Screen;
let screen = unsafe { &mut *screen };
unsafe { SCREEN = Some(screen); }
let screen = unsafe { SCREEN.as_mut().unwrap() };
// DELETED: print_str(screen, "+");
let block_bytes = include_bytes!("./block.bmp");
// ... Others
}
Running:
We have a panic message. Apparently, at some point, the character with Unicode scalar value 58 was supposed to be printed. According to the Unicode list here, the character with the value 58 is the colon ':'.
Go ahead and add a colon description to the font.
After adding it, we have to modify the FONT
array:
// DELETED: pub const FONT: [[[bool; 16]; 16]; 38] = [
pub const FONT: [[[bool; 16]; 16]; 39] = [
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O , P, Q, R,
S, T, U, V, W, X, Y, Z, SPACE, EXCLAMATION,
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
COLON // NEW
];
And in uefi.rs
, the char_to_font_index
function:
fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
48..=57 => (c - 20) as usize, // Digits
58 => 38, // Colon
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
_ => panic!("The Unicode character {} is not representable by the font", c)
}
}
The array length is 39 and the colon is the last description in the array. So, the colon's description is at index 38.
Running again, we have:
Which is the expected output from the panic!("A number: {}", 2)
in the animation while loop.
To get the animation back:
#[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 number: {}", 2);
draw_bitmap(screen, &block, block_position);
erase_bitmap(screen, &block, block_position);
// Increase block position to the right
block_position.1 += 1;
}
// ... Others
}
Upon running again, the animation plays on, but ends with another panic:
Now, we have to figure out where this error is coming from. We can do this again using our Sherlock Holmes method of tracing the error, but it will be much easier if the panicking information included the line number information.
And as a matter of fact, it does. The PanicInfo
struct includes this information.
To use it, we make use of the write!
macro:
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let screen = unsafe { SCREEN.as_mut().unwrap() };
write!(screen, "{}", panic_info);
/* DELETED:
if let Some(panic_msg) = panic_info.message() {
screen.write_fmt(*panic_msg);
} else {
screen.write_str("Message returned None for some unknown reason");
}
*/
loop {}
}
The write!
macro takes anything that implements the Write
trait as the first argument, then the format
string and arguments come after. This works because PanicInfo
implements core::fmt::Display
, the trait
that is implemented for anything that can be displayed on the screen.
The PanicInfo
's Display
implementation takes care of assembling the line number information
and message in the struct so we don't have to do it ourselves.
Running gives us yet another panic:
An infinite panic loop. This output shouldn't be surprising. Whenever a character is not representable
by our font, a panic occurs. So, if a string to be printed by the write!
macro contains any character
that we don't have a description of, a panic will occur again and again and again.
To see the actual character that we're missing, we need to devise a way to see the panic message after the first character-can't-be-represented panic occurs.
static mut panicked: bool = false;
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let screen = unsafe { SCREEN.as_mut().unwrap() };
// DELETED: write!(screen, "{}", panic_info);
// NEW:
if unsafe { !panicked } {
unsafe { panicked = true; }
write!(screen, "{}", panic_info);
} else {
if let Some(panic_msg) = panic_info.message() {
screen.write_fmt(*panic_msg);
} else {
screen.write_str("Message returned None for some unknown reason");
}
}
loop {}
}
A panicked
static is created and initialized to false. During the first panic, we attempt to use the write!
macro. The panicked
static is then set to true, so that if another panic occurs during the execution of write!
, the else block will be executed to print write!
's panic message.
Running:
The Unicode scalar value of 39 represents the apostrophe " ' ". Go ahead and add that to the font.
Adding it to the FONT
array:
// DELETED: pub const FONT: [[[bool; 16]; 16]; 39] = [
pub const FONT: [[[bool; 16]; 16]; 40] = [ // NEW
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O , P, Q, R,
S, T, U, V, W, X, Y, Z, SPACE, EXCLAMATION,
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
COLON, APOSTROPHE // NEW
];
In uefi.rs
:
fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
39 => 39, // Apostrophe
48..=57 => (c - 20) as usize, // Digits
58 => 38, // Colon
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
_ => panic!("The Unicode character {} is not representable by the font", c)
}
}
Running:
Now we have the index out-of-bounds message, but instead of the line information, we get another unrepresentable character message. The Unicode scalar value of 44 represents the comma ",". Add this one, too, to the font.
After adding the comma:
// DELETED: pub const FONT: [[[bool; 16]; 16]; 40] = [
pub const FONT: [[[bool; 16]; 16]; 41] = [
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O , P, Q, R,
S, T, U, V, W, X, Y, Z, SPACE, EXCLAMATION,
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
COLON, APOSTROPHE, COMMA // NEW
];
In uefi.rs
fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
39 => 39, // Apostrophe
44 => 40, // Comma
48..=57 => (c - 20) as usize, // Digits
58 => 38, // Colon
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
_ => panic!("The Unicode character {} is not representable by the font", c)
}
}
Running:
Forward slash, "/", is the character represented by Unicode 47. Add it to the font.
// DELETED: pub const FONT: [[[bool; 16]; 16]; 41] = [
pub const FONT: [[[bool; 16]; 16]; 42] = [ // NEW
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O , P, Q, R,
S, T, U, V, W, X, Y, Z, SPACE, EXCLAMATION,
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
COLON, APOSTROPHE, COMMA, FORWARD_SLASH // NEW
];
In uefi.rs
fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
39 => 39, // Apostrophe
44 => 40, // Comma
47 => 41, // Forward slash
48..=57 => (c - 20) as usize, // Digits
58 => 38, // Colon
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
_ => panic!("The Unicode character {} is not representable by the font", c)
}
}
Running:
Full stop, ".", is the character represented by Unicode 46. Add it to the font.
// DELETED: pub const FONT: [[[bool; 16]; 16]; 42] = [
pub const FONT: [[[bool; 16]; 16]; 43] = [ // NEW
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O , P, Q, R,
S, T, U, V, W, X, Y, Z, SPACE, EXCLAMATION,
ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
COLON, APOSTROPHE, COMMA, FORWARD_SLASH, FULL_STOP // NEW
];
In uefi.rs
fn char_to_font_index(c: u8) -> usize {
match c {
32 => 26, // Space
33 => 27, // Exclamation mark
39 => 39, // Apostrophe
44 => 40, // Comma
46 => 42, // Full stop
47 => 41, // Forward slash
48..=57 => (c - 20) as usize, // Digits
58 => 38, // Colon
65..=91 => (c - 65) as usize, // Big letters
97..=123 => char_to_font_index(c - 32), // Small letters to big letters
_ => panic!("The Unicode character {} is not representable by the font", c)
}
}
Running:
The full panic info, including the line number information, is now printed on the screen.
We can now get rid of this static:
// DELETED: static mut panicked: bool = false;
#[panic_handler]
fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! {
let screen = unsafe { SCREEN.as_mut().unwrap() };
/* DELETED:
if unsafe { !panicked } {
unsafe { panicked = true; }
write!(screen, "{}", panic_info);
} else {
if let Some(panic_msg) = panic_info.message() {
screen.write_fmt(*panic_msg);
} else {
screen.write_str("Message returned None for some unknown reason");
}
}
*/
write!(screen, "{}", panic_info); // NEW
loop {}
}
The panic info points us to the draw_bitmap
function in bitmap.rs
.
pub fn draw_bitmap(screen: &mut Screen, bitmap: &Bitmap, pos: (usize, usize)) {
for row in 0..bitmap.height() {
for col in 0..bitmap.width() {
let inverted_row = bitmap.height() - row - 1;
let color_table_index = bitmap.pixel_array[inverted_row * bitmap.width() + col];
let color = bitmap.color_table.0[color_table_index as usize];
screen.pixels[row + pos.0][col + pos.1] = Pixel {
red: color.red,
green: color.green,
blue: color.blue,
reserved: 0
};
}
}
}
Looking at this function, it's not so hard to see why it caused a panic. There is no assurance
whatsoever that row + pos.0
< NO_OF_PIXELS_IN_A_COLUMN
and col + pos.1
< NO_OF_PIXELS_IN_A_ROW
.
It's this unchecked indexing that caused the panic.
pub fn draw_bitmap(screen: &mut Screen, bitmap: &Bitmap, pos: (usize, usize)) {
for row in 0..bitmap.height() {
for col in 0..bitmap.width() {
let inverted_row = bitmap.height() - row - 1;
let color_table_index = bitmap.pixel_array[inverted_row * bitmap.width() + col];
let color = bitmap.color_table.0[color_table_index as usize];
if row + pos.0 < NO_OF_PIXELS_IN_A_COLUMN && col + pos.1 < NO_OF_PIXELS_IN_A_ROW { // NEW
screen.pixels[row + pos.0][col + pos.1] = Pixel {
red: color.red,
green: color.green,
blue: color.blue,
reserved: 0
};
}
}
}
}
The erase_bitmap
function, too, suffers from the same bug:
pub fn erase_bitmap(screen: &mut Screen, bitmap: &Bitmap, pos: (usize, usize)) {
for row in 0..bitmap.width() {
for col in 0..bitmap.height() {
if row + pos.0 < NO_OF_PIXELS_IN_A_COLUMN && col + pos.1 < NO_OF_PIXELS_IN_A_ROW { // NEW
screen.pixels[row + pos.0][col + pos.1] = Pixel {
red: 0,
green: 0,
blue: 0,
reserved: 0
};
}
}
}
}
For the code to compile:
use crate::{Screen, Pixel};
use crate::uefi::{NO_OF_PIXELS_IN_A_COLUMN, NO_OF_PIXELS_IN_A_ROW};
Upon running again, the animation plays out with no errors.
Take Away
- The
Display
trait defines behavior for something that can be displayed on the screen.
For the full code up to this point, go to the repo
In The Next Post
We'll be refactoring.
References
- https://en.wikipedia.org/wiki/List_of_Unicode_characters
Write
's defaultwrite_fmt
implementation, https://doc.rust-lang.org/src/core/fmt/mod.rs.html#191-193- The
core::fmt::write
function implementation, https://doc.rust-lang.org/src/core/fmt/mod.rs.html#1195-1237