Hi
I'm writing an UEFI application for learning and fun. What has me a little concerned is that the EFI entry point provide the SystemTable that include many more pointers as shown below.
typedef struct {
EFI_TABLE_HEADER Hdr;
CHAR16 *FirmwareVendor;
UINT32 FirmwareRevision;
EFI_HANDLE ConsoleInHandle;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
EFI_HANDLE ConsoleOutHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
EFI_HANDLE StandardErrorHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
EFI_RUNTIME_SERVICES *RuntimeServices;
EFI_BOOT_SERVICES *BootServices;
UINTN NumberOfTableEntries;
EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;
Reading the specification, it makes the statement for some pointers such as ConIn
:
If there is no active console, these protocols must still be present.
However many do not have such statements and I can't find anything else that explicitly offers guarantees. I have noticed that rust state to use Option<T>
when the pointer can be null
but since I have no control over the structure, I do not want to modify it in manner that would break the layout. (I assume that since Option<T>
is an enum, wrapping the pointers in Option<T>
will change the layout of the structure?)
My understanding is that rust do not provide a mechanism for checking these embedded raw pointers? To that effect, I'm transmuting
the pointers and checking if they are null
before calling them. I'm not sure if this is right or if I have missed something obvious. The code below shows and example of what I'm doing.
use core::mem::transmute;
use crate::uefi::protocols::InputKey;
use crate::uefi::{Boolean, Event, Status, EFI_ABORTED};
macro_rules! ptr_is_valid {
($ptr: expr, $type: ty) => {
if transmute::<$type, usize>($ptr) == 0 { false } else { true }
};
}
macro_rules! self_to_mut_ptr {
($self: expr) => {
core::ptr::addr_of!(*$self) as *mut Self
};
}
#[repr(C)]
pub struct SimpleTextIntputProtocol {
reset: InputReset,
read_key_stroke: InputReadKey,
wait_for_key: Event,
}
impl SimpleTextIntputProtocol {
pub fn reset(&mut self, extended_verification: Boolean) -> Status {
unsafe {
if ptr_is_valid!(self.reset, InputReset) {
(self.reset)(self_to_mut_ptr!(self), extended_verification)
} else {
panic!("Null-pointer exception!")
}
}
}
}
type InputReset =
unsafe extern "C" fn(this: *mut SimpleTextIntputProtocol, extended_verification: Boolean) -> Status;
pub type InputReadKey =
unsafe extern "C" fn(this: *mut SimpleTextIntputProtocol, key: *mut InputKey) -> Status;
pub type InputResetEx =
unsafe extern "C" fn(this: *mut SimpleTextIntputProtocol, extended_verification: Boolean) -> Status;
From my engineering background, I've learned the hard way that when you design electronics, the hardware should not trust the software / firmware and vice versa. Noting rust's focus on safety, it feels like I have missed something here and doing something wrong.
Any advice or obvious mistakes?