Skip to content

Integration test for boot service function load_image #826

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

Merged
merged 3 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `DevicePathNode::data`

### Changed
- Renamed `LoadImageSource::FromFilePath` to `LoadImageSource::FromDevicePath`

### Removed

Expand Down Expand Up @@ -44,7 +45,6 @@
`BUFFER_TOO_SMALL` error can only occur when reading a directory, not a file.
- `RegularFile::read` now reads in 1 MiB chunks to avoid a bug in some
firmware. This fix also applies to `fs::FileSystem::read`.

## uefi-services - 0.19.0 (2023-06-01)

### Changed
Expand Down
4 changes: 2 additions & 2 deletions uefi-test-runner/src/bin/shell_launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ fn efi_main(image: Handle, mut st: SystemTable<Boot>) -> Status {
let shell_image_handle = boot_services
.load_image(
image,
LoadImageSource::FromFilePath {
file_path: shell_image_path,
LoadImageSource::FromDevicePath {
device_path: shell_image_path,
from_boot_manager: false,
},
)
Expand Down
78 changes: 73 additions & 5 deletions uefi-test-runner/src/boot/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
use alloc::string::ToString;
use uefi::proto::console::text::Output;
use uefi::table::boot::{BootServices, SearchType};
use uefi::proto::device_path::media::FilePath;
use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath};
use uefi::table::boot::{BootServices, LoadImageSource, SearchType};
use uefi::table::{Boot, SystemTable};
use uefi::Identify;
use uefi::{CString16, Identify};

mod memory;
mod misc;

pub fn test(st: &SystemTable<Boot>) {
let bt = st.boot_services();
info!("Testing boot services");
memory::test(bt);
misc::test(st);
test_locate_handle_buffer(bt);
test_load_image(bt);
}

mod memory;
mod misc;

fn test_locate_handle_buffer(bt: &BootServices) {
info!("Testing the `locate_handle_buffer` function");

Expand All @@ -36,3 +40,67 @@ fn test_locate_handle_buffer(bt: &BootServices) {
);
}
}

/// This test loads the "self image" again into memory using the `load_image`
/// boot service function. The image is not started but just loaded into memory.
///
/// It transitively tests the protocol [`LoadedImageDevicePath`] which is
/// required as helper.
fn test_load_image(bt: &BootServices) {
/// The path of the loaded image executing this integration test.
const LOADED_IMAGE_PATH: &str = r"\EFI\BOOT\TEST_RUNNER.EFI";

info!("Testing the `load_image` function");

let image_device_path_protocol = bt
.open_protocol_exclusive::<LoadedImageDevicePath>(bt.image_handle())
.expect("should open LoadedImage protocol");

// Note: This is the full device path. The LoadedImage protocol would only
// provide us with the file-path portion of the device path.
let image_device_path: &DevicePath = &image_device_path_protocol;

// Get the file-path portion of the device path which is typically behind
// device path node (0x4, 0x4). The string is in upper case.

let image_device_path_file_path = image_device_path
.node_iter()
.find_map(|node| {
let node: &FilePath = node.try_into().ok()?;
let path = node.path_name().to_cstring16().ok()?;
Some(path.to_string().to_uppercase())
})
.expect("should have file-path portion in device path");

assert_eq!(image_device_path_file_path.as_str(), LOADED_IMAGE_PATH);

// Variant A: FromBuffer
{
let mut fs = bt
.get_image_file_system(bt.image_handle())
.expect("should open file system");
let path = CString16::try_from(image_device_path_file_path.as_str()).unwrap();
let image_data = fs.read(&*path).expect("should read file content");
let load_source = LoadImageSource::FromBuffer {
buffer: image_data.as_slice(),
file_path: None,
};
let _ = bt
.load_image(bt.image_handle(), load_source)
.expect("should load image");

log::debug!("load_image with FromBuffer strategy works");
}
// Variant B: FromDevicePath
{
let load_source = LoadImageSource::FromDevicePath {
device_path: image_device_path,
from_boot_manager: false,
};
let _ = bt
.load_image(bt.image_handle(), load_source)
.expect("should load image");

log::debug!("load_image with FromFilePath strategy works");
}
}
14 changes: 7 additions & 7 deletions uefi/src/data_types/strs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,8 @@ impl CStr16 {
Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1))
}

/// Creates a C string wrapper from a u16 slice
///
/// Since not every u16 value is a valid UCS-2 code point, this function
/// must do a bit more validity checking than CStr::from_bytes_with_nul
/// Creates a `&CStr16` from a u16 slice, if the slice contains exactly
/// one terminating null-byte and all chars are valid UCS-2 chars.
pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> {
for (pos, &code) in codes.iter().enumerate() {
match code.try_into() {
Expand All @@ -234,7 +232,7 @@ impl CStr16 {
Err(FromSliceWithNulError::NotNulTerminated)
}

/// Unsafely creates a C string wrapper from a u16 slice.
/// Unsafely creates a `&CStr16` from a u16 slice.
///
/// # Safety
///
Expand Down Expand Up @@ -287,11 +285,13 @@ impl CStr16 {
Self::from_u16_with_nul(&buf[..index + 1]).map_err(|err| match err {
FromSliceWithNulError::InvalidChar(p) => FromStrWithBufError::InvalidChar(p),
FromSliceWithNulError::InteriorNul(p) => FromStrWithBufError::InteriorNul(p),
FromSliceWithNulError::NotNulTerminated => unreachable!(),
FromSliceWithNulError::NotNulTerminated => {
unreachable!()
}
})
}

/// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned
/// Create a `&CStr16` from an [`UnalignedSlice`] using an aligned
/// buffer for storage. The lifetime of the output is tied to `buf`,
/// not `src`.
pub fn from_unaligned_slice<'buf>(
Expand Down
11 changes: 8 additions & 3 deletions uefi/src/proto/loaded_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@ impl LoadedImage {
self.device_handle
}

/// Get a reference to the `file_path`.
/// Get a reference to the `file_path` portion of the DeviceHandle that the
/// EFI image was loaded from.
///
/// Return `None` if the pointer to the file path portion specific to
/// DeviceHandle that the EFI Image was loaded from is null.
/// For a full device path, consider using the [`LoadedImageDevicePath`]
/// protocol.
///
/// Returns `None` if `file_path` is null.
///
/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath
#[must_use]
pub fn file_path(&self) -> Option<&DevicePath> {
if self.file_path.is_null() {
Expand Down
24 changes: 17 additions & 7 deletions uefi/src/table/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,8 +1026,8 @@ impl BootServices {
source_buffer = buffer.as_ptr();
source_size = buffer.len();
}
LoadImageSource::FromFilePath {
file_path,
LoadImageSource::FromDevicePath {
device_path: file_path,
from_boot_manager,
} => {
boot_policy = u8::from(from_boot_manager);
Expand Down Expand Up @@ -1697,11 +1697,21 @@ pub enum LoadImageSource<'a> {
/// behavior depends on `from_boot_manager`. If `true`, attempt to
/// load via the `LoadFile` protocol. If `false`, attempt to load
/// via the `LoadFile2` protocol, then fall back to `LoadFile`.
FromFilePath {
/// Device path from which to load the image.
file_path: &'a DevicePath,

/// Whether the request originates from the boot manager.
FromDevicePath {
/// The full device path from which to load the image.
///
/// The provided path should be a full device path and not just the
/// file path portion of it. So for example, it must be (the binary
/// representation)
/// `PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\\EFI\\BOOT\\BOOTX64.EFI`
/// and not just `\\EFI\\BOOT\\BOOTX64.EFI`.
device_path: &'a DevicePath,

/// If there is no instance of [`SimpleFileSystem`] protocol associated
/// with the given device path, then this function will attempt to use
/// `LoadFileProtocol` (`from_boot_manager` is `true`) or
/// `LoadFile2Protocol`, and then `LoadFileProtocol`
/// (`from_boot_manager` is `false`).
from_boot_manager: bool,
},
}
Expand Down