mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-08 21:06:48 +00:00
Support PE/COFF entry point
This commit is contained in:
parent
2a7cdb0802
commit
78a9ec9e96
4
.github/workflows/test_asterinas.yml
vendored
4
.github/workflows/test_asterinas.yml
vendored
@ -99,10 +99,10 @@ jobs:
|
||||
if: ${{ matrix.test_id == 'boot_test_linux_legacy32' }}
|
||||
run: make run AUTO_TEST=boot ENABLE_KVM=1 BOOT_PROTOCOL=linux-legacy32 RELEASE=1 SMP=4 NETDEV=tap
|
||||
|
||||
- name: Syscall Test (Linux EFI Handover Boot Protocol) (Debug Build)
|
||||
- name: Syscall Test (Linux EFI PE/COFF Boot Protocol) (Debug Build)
|
||||
id: syscall_test
|
||||
if: ${{ matrix.test_id == 'syscall_test' }}
|
||||
run: make run AUTO_TEST=syscall ENABLE_KVM=1 BOOT_PROTOCOL=linux-efi-handover64 RELEASE=0 NETDEV=tap
|
||||
run: make run AUTO_TEST=syscall ENABLE_KVM=1 BOOT_PROTOCOL=linux-efi-pe64 RELEASE=0 NETDEV=tap
|
||||
|
||||
- name: Syscall Test at Ext2 (MicroVM)
|
||||
id: syscall_test_at_ext2_microvm
|
||||
|
3
Makefile
3
Makefile
@ -105,6 +105,9 @@ CARGO_OSDK_ARGS += --grub-mkrescue=/usr/bin/grub-mkrescue
|
||||
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
|
||||
# FIXME: GZIP self-decompression (--encoding gzip) triggers CPU faults
|
||||
CARGO_OSDK_ARGS += --encoding raw
|
||||
else ifeq ($(BOOT_PROTOCOL), linux-efi-pe64)
|
||||
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
|
||||
CARGO_OSDK_ARGS += --encoding raw
|
||||
else ifeq ($(BOOT_PROTOCOL), linux-legacy32)
|
||||
CARGO_OSDK_ARGS += --linux-x86-legacy-boot
|
||||
CARGO_OSDK_ARGS += --grub-boot-protocol="linux"
|
||||
|
@ -22,6 +22,7 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(maybe_uninit_fill)]
|
||||
#![feature(maybe_uninit_slice)]
|
||||
#![feature(maybe_uninit_write_slice)]
|
||||
|
||||
mod console;
|
||||
|
@ -2,33 +2,44 @@
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use uefi::boot::AllocateType;
|
||||
|
||||
pub fn alloc_at(addr: usize, size: usize) -> &'static mut [MaybeUninit<u8>] {
|
||||
assert_ne!(addr, 0, "the address to allocate is zero");
|
||||
assert!(
|
||||
addr.checked_add(size).is_some(),
|
||||
"the range to allocate overflows"
|
||||
);
|
||||
|
||||
let allocated = alloc_pages(AllocateType::Address(addr as u64), size);
|
||||
assert_eq!(
|
||||
allocated.as_ptr().addr(),
|
||||
addr,
|
||||
"the allocated address is not the request address"
|
||||
);
|
||||
|
||||
allocated
|
||||
}
|
||||
|
||||
pub(super) fn alloc_pages(ty: AllocateType, size: usize) -> &'static mut [MaybeUninit<u8>] {
|
||||
assert!(
|
||||
size <= isize::MAX as usize,
|
||||
"the size to allocate exceeds `isize::MAX`"
|
||||
);
|
||||
|
||||
addr.checked_add(size)
|
||||
.expect("the range to allocate overflows");
|
||||
|
||||
let allocated = uefi::boot::allocate_pages(
|
||||
uefi::boot::AllocateType::Address(addr as u64),
|
||||
ty,
|
||||
uefi::boot::MemoryType::LOADER_DATA,
|
||||
size.div_ceil(super::efi::PAGE_SIZE as usize),
|
||||
)
|
||||
.expect("the UEFI allocation fails");
|
||||
assert_eq!(
|
||||
allocated.as_ptr() as usize,
|
||||
addr,
|
||||
"the allocated address is not the request address"
|
||||
);
|
||||
|
||||
// SAFETY:
|
||||
// 1. The address is not zero and the size is reasonable (there are less the `isize::MAX` bytes
|
||||
// and the range won't overflow the address space), as asserted above.
|
||||
// and the range won't overflow the address space), as asserted above or guaranteed by the
|
||||
// implementation of `allocate_pages`.
|
||||
// 2. The memory region is allocated via the UEFI firmware, so it is valid for reading and
|
||||
// writing. We will not deallocate it, so it live for `'static`.
|
||||
// 3. The type alignment is 1 and the type can contain uninitialized data.
|
||||
unsafe { core::slice::from_raw_parts_mut(addr as *mut MaybeUninit<u8>, size) }
|
||||
unsafe { core::slice::from_raw_parts_mut(allocated.as_ptr().cast(), size) }
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::{ffi::CStr, mem::MaybeUninit};
|
||||
|
||||
use boot::{open_protocol_exclusive, AllocateType};
|
||||
use linux_boot_params::BootParams;
|
||||
use uefi::{boot::exit_boot_services, mem::memory_map::MemoryMap, prelude::*};
|
||||
use uefi_raw::table::system::SystemTable;
|
||||
|
||||
use super::decoder::decode_payload;
|
||||
use crate::x86::amd64_efi::alloc::alloc_pages;
|
||||
|
||||
pub(super) const PAGE_SIZE: u64 = 4096;
|
||||
|
||||
#[export_name = "main_efi_handover64"]
|
||||
extern "sysv64" fn main_efi_handover64(
|
||||
#[export_name = "main_efi_common64"]
|
||||
extern "sysv64" fn main_efi_common64(
|
||||
handle: Handle,
|
||||
system_table: *const SystemTable,
|
||||
boot_params_ptr: *mut BootParams,
|
||||
@ -23,30 +27,71 @@ extern "sysv64" fn main_efi_handover64(
|
||||
|
||||
uefi::helpers::init().unwrap();
|
||||
|
||||
// SAFETY: We get boot parameters from the boot loader, so by contract the pointer is valid and
|
||||
// the underlying memory is initialized. We are an exclusive owner of the memory region, so we
|
||||
// can create a mutable reference of the plain-old-data type.
|
||||
let boot_params = unsafe { &mut *boot_params_ptr };
|
||||
let boot_params = if boot_params_ptr.is_null() {
|
||||
allocate_boot_params()
|
||||
} else {
|
||||
// SAFETY: We get boot parameters from the boot loader, so by contract the pointer is valid and
|
||||
// the underlying memory is initialized. We are an exclusive owner of the memory region, so we
|
||||
// can create a mutable reference of the plain-old-data type.
|
||||
unsafe { &mut *boot_params_ptr }
|
||||
};
|
||||
|
||||
efi_phase_boot(boot_params);
|
||||
|
||||
// SAFETY: We do not open boot service protocols or maintain references to boot service code
|
||||
// and data.
|
||||
// SAFETY: All previously opened boot service protocols have been closed. At this time, we have
|
||||
// no references to the code and data of the boot services.
|
||||
unsafe { efi_phase_runtime(boot_params) };
|
||||
}
|
||||
|
||||
fn allocate_boot_params() -> &'static mut BootParams {
|
||||
let boot_params = {
|
||||
let bytes = alloc_pages(AllocateType::AnyPages, core::mem::size_of::<BootParams>());
|
||||
MaybeUninit::fill(bytes, 0);
|
||||
// SAFETY: Zero initialization gives a valid representation for `BootParams`.
|
||||
unsafe { &mut *bytes.as_mut_ptr().cast::<BootParams>() }
|
||||
};
|
||||
|
||||
boot_params.hdr.header = linux_boot_params::LINUX_BOOT_HEADER_MAGIC;
|
||||
|
||||
boot_params
|
||||
}
|
||||
|
||||
fn efi_phase_boot(boot_params: &mut BootParams) {
|
||||
uefi::println!(
|
||||
"[EFI stub] Loaded with offset {:#x}",
|
||||
crate::x86::image_load_offset(),
|
||||
);
|
||||
|
||||
// Load the command line if it is not loaded.
|
||||
if boot_params.hdr.cmd_line_ptr == 0 && boot_params.ext_cmd_line_ptr == 0 {
|
||||
if let Some(cmdline) = load_cmdline() {
|
||||
boot_params.hdr.cmd_line_ptr = cmdline.as_ptr().addr().try_into().unwrap();
|
||||
boot_params.ext_cmd_line_ptr = 0;
|
||||
boot_params.hdr.cmdline_size = (cmdline.count_bytes() + 1).try_into().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Load the init ramdisk if it is not loaded.
|
||||
if boot_params.hdr.ramdisk_image == 0 && boot_params.ext_ramdisk_image == 0 {
|
||||
if let Some(initrd) = load_initrd() {
|
||||
boot_params.hdr.ramdisk_image = initrd.as_ptr().addr().try_into().unwrap();
|
||||
boot_params.ext_ramdisk_image = 0;
|
||||
boot_params.hdr.ramdisk_size = initrd.len().try_into().unwrap();
|
||||
boot_params.ext_ramdisk_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the boot params with the RSDP address if it is not provided.
|
||||
if boot_params.acpi_rsdp_addr == 0 {
|
||||
boot_params.acpi_rsdp_addr =
|
||||
find_rsdp_addr().expect("ACPI RSDP address is not available") as usize as u64;
|
||||
}
|
||||
|
||||
// Fill the boot params with the screen info if it is not provided.
|
||||
if boot_params.screen_info.lfb_base == 0 && boot_params.screen_info.ext_lfb_base == 0 {
|
||||
fill_screen_info(&mut boot_params.screen_info);
|
||||
}
|
||||
|
||||
// Decode the payload and load it as an ELF file.
|
||||
uefi::println!("[EFI stub] Decoding the kernel payload");
|
||||
let kernel = decode_payload(crate::x86::payload());
|
||||
@ -54,6 +99,130 @@ fn efi_phase_boot(boot_params: &mut BootParams) {
|
||||
crate::loader::load_elf(&kernel);
|
||||
}
|
||||
|
||||
fn load_cmdline() -> Option<&'static CStr> {
|
||||
uefi::println!("[EFI stub] Loading the cmdline");
|
||||
|
||||
let loaded_image = open_protocol_exclusive::<uefi::proto::loaded_image::LoadedImage>(
|
||||
uefi::boot::image_handle(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Some(load_options) = loaded_image.load_options_as_bytes() else {
|
||||
uefi::println!("[EFI stub] Warning: No cmdline is available!");
|
||||
return None;
|
||||
};
|
||||
|
||||
if load_options.len() % 2 != 0 || load_options.iter().skip(1).step_by(2).any(|c| *c != 0) {
|
||||
uefi::println!("[EFI stub] Warning: The cmdline contains non-ASCII characters!");
|
||||
return None;
|
||||
}
|
||||
|
||||
// The load options are a `Char16` sequence. We should convert it to a `Char8` sequence.
|
||||
let cmdline_bytes = alloc_pages(
|
||||
AllocateType::MaxAddress(u32::MAX as u64),
|
||||
load_options.len() / 2 + 1,
|
||||
);
|
||||
for i in 0..load_options.len() / 2 {
|
||||
cmdline_bytes[i].write(load_options[i * 2]);
|
||||
}
|
||||
cmdline_bytes[load_options.len() / 2].write(0);
|
||||
|
||||
// SAFETY: We've initialized all the bytes above.
|
||||
let cmdline_str =
|
||||
CStr::from_bytes_until_nul(unsafe { cmdline_bytes.assume_init_ref() }).unwrap();
|
||||
|
||||
uefi::println!("[EFI stub] Loaded the cmdline: {:?}", cmdline_str);
|
||||
|
||||
Some(cmdline_str)
|
||||
}
|
||||
|
||||
// Linux loads the initrd either using a special protocol `LINUX_EFI_INITRD_MEDIA_GUID` or using
|
||||
// the file path specified on the command line (e.g., `initrd=/initrd.img`). We now only support
|
||||
// the former approach, as it is more "modern" and easier to implement. Note that this approach
|
||||
// requires the boot loader (e.g., GRUB, systemd-boot) to implement the protocol, while the latter
|
||||
// approach does not.
|
||||
fn load_initrd() -> Option<&'static [u8]> {
|
||||
uefi::println!("[EFI stub] Loading the initrd");
|
||||
|
||||
// Note that we should switch to `uefi::proto::media::load_file::LoadFile2` once it provides a
|
||||
// more ergonomic API. Its current API requires `alloc` and cannot load files on pages (i.e.,
|
||||
// ensure that the initrd is aligned to the page size).
|
||||
#[repr(transparent)]
|
||||
// SAFETY: The protocol GUID matches the protocol itself.
|
||||
#[uefi::proto::unsafe_protocol(uefi_raw::protocol::media::LoadFile2Protocol::GUID)]
|
||||
struct LoadFile2(uefi_raw::protocol::media::LoadFile2Protocol);
|
||||
|
||||
let mut device_path_buf = [MaybeUninit::uninit(); 20 /* vendor */ + 4 /* end */];
|
||||
let mut device_path = {
|
||||
use uefi::proto::device_path::build;
|
||||
build::DevicePathBuilder::with_buf(&mut device_path_buf)
|
||||
.push(&build::media::Vendor {
|
||||
// LINUX_EFI_INITRD_MEDIA_GUID
|
||||
vendor_guid: uefi::guid!("5568e427-68fc-4f3d-ac74-ca555231cc68"),
|
||||
vendor_defined_data: &[],
|
||||
})
|
||||
.unwrap()
|
||||
.finalize()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let Ok(handle) = uefi::boot::locate_device_path::<LoadFile2>(&mut device_path) else {
|
||||
uefi::println!("[EFI stub] Warning: Failed to locate the initrd device!");
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(mut load_file2) = uefi::boot::open_protocol_exclusive::<LoadFile2>(handle) else {
|
||||
uefi::println!("[EFI stub] Warning: Failed to open the initrd protocol!");
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut size = 0;
|
||||
// SAFETY: The arguments are correctly specified according to the UEFI specification.
|
||||
let status = unsafe {
|
||||
(load_file2.0.load_file)(
|
||||
&mut load_file2.0,
|
||||
device_path.as_ffi_ptr().cast(),
|
||||
false, /* boot_policy */
|
||||
&mut size,
|
||||
core::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if status != uefi::Status::BUFFER_TOO_SMALL {
|
||||
uefi::println!("[EFI stub] Warning: Failed to get the initrd size!");
|
||||
return None;
|
||||
}
|
||||
|
||||
let initrd = alloc_pages(AllocateType::MaxAddress(u32::MAX as u64), size);
|
||||
// SAFETY: The arguments are correctly specified according to the UEFI specification.
|
||||
let status = unsafe {
|
||||
(load_file2.0.load_file)(
|
||||
&mut load_file2.0,
|
||||
device_path.as_ffi_ptr().cast(),
|
||||
false, /* boot_policy */
|
||||
&mut size,
|
||||
initrd.as_mut_ptr().cast(),
|
||||
)
|
||||
};
|
||||
if status.is_error() {
|
||||
uefi::println!("[EFI stub] Warning: Failed to load the initrd!");
|
||||
return None;
|
||||
}
|
||||
assert_eq!(
|
||||
size,
|
||||
initrd.len(),
|
||||
"the initrd size has changed between two EFI calls"
|
||||
);
|
||||
|
||||
uefi::println!(
|
||||
"[EFI stub] Loaded the initrd: addr={:#x}, size={:#x}",
|
||||
initrd.as_ptr().addr(),
|
||||
initrd.len()
|
||||
);
|
||||
|
||||
// SAFETY: We've initialized all the bytes in `load_file`.
|
||||
Some(unsafe { initrd.assume_init_ref() })
|
||||
}
|
||||
|
||||
fn find_rsdp_addr() -> Option<*const ()> {
|
||||
use uefi::table::cfg::{ACPI2_GUID, ACPI_GUID};
|
||||
|
||||
@ -65,13 +234,58 @@ fn find_rsdp_addr() -> Option<*const ()> {
|
||||
.find(|entry| entry.guid == acpi_guid)
|
||||
.map(|entry| entry.address.cast::<()>())
|
||||
}) {
|
||||
uefi::println!("[EFI stub] Found the ACPI RSDP at {:p}", rsdp_addr);
|
||||
return Some(rsdp_addr);
|
||||
}
|
||||
}
|
||||
|
||||
uefi::println!("[EFI stub] Warning: Failed to find the ACPI RSDP address!");
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn fill_screen_info(screen_info: &mut linux_boot_params::ScreenInfo) {
|
||||
use uefi::proto::console::gop::{GraphicsOutput, PixelFormat};
|
||||
|
||||
let Ok(handle) = uefi::boot::get_handle_for_protocol::<GraphicsOutput>() else {
|
||||
uefi::println!("[EFI stub] Warning: Failed to locate the graphics handle!");
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(mut protocol) = open_protocol_exclusive::<GraphicsOutput>(handle) else {
|
||||
uefi::println!("[EFI stub] Warning: Failed to open the graphics protocol!");
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
protocol.current_mode_info().pixel_format(),
|
||||
PixelFormat::Rgb | PixelFormat::Bgr
|
||||
) {
|
||||
uefi::println!(
|
||||
"[EFI stub] Warning: Ignored the framebuffer as the pixel format is not supported!"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let addr = protocol.frame_buffer().as_mut_ptr().addr();
|
||||
let (width, height) = protocol.current_mode_info().resolution();
|
||||
|
||||
// TODO: We are only filling in fields that will be accessed later in the kernel. We should
|
||||
// fill in other important information such as the pixel format.
|
||||
screen_info.lfb_base = addr as u32;
|
||||
screen_info.ext_lfb_base = (addr >> 32) as u32;
|
||||
screen_info.lfb_width = width.try_into().unwrap();
|
||||
screen_info.lfb_height = height.try_into().unwrap();
|
||||
screen_info.lfb_depth = 32; // We've checked the pixel format above.
|
||||
|
||||
uefi::println!(
|
||||
"[EFI stub] Found the framebuffer at {:#x} with {}x{} pixels",
|
||||
addr,
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn efi_phase_runtime(boot_params: &mut BootParams) -> ! {
|
||||
uefi::println!("[EFI stub] Exiting EFI boot services");
|
||||
// SAFETY: The safety is upheld by the caller.
|
||||
|
@ -50,6 +50,22 @@ entry_efi_handover64:
|
||||
// RSI: efi_system_table_t *table
|
||||
// RDX: struct boot_params *bp
|
||||
|
||||
jmp efi_common64
|
||||
|
||||
.global entry_efi_pe64
|
||||
entry_efi_pe64:
|
||||
// This is the 64-bit EFI PE/COFF entry point.
|
||||
//
|
||||
// Arguments:
|
||||
// RCX: void *handle
|
||||
// RDX: efi_system_table_t *table
|
||||
|
||||
mov rdi, rcx
|
||||
mov rsi, rdx
|
||||
xor rdx, rdx
|
||||
jmp efi_common64
|
||||
|
||||
efi_common64:
|
||||
// We can reuse the stack provided by the UEFI firmware until a short time
|
||||
// after exiting the UEFI boot services. So we don't build our own stack.
|
||||
//
|
||||
@ -83,19 +99,12 @@ reloc_iter:
|
||||
reloc_done:
|
||||
|
||||
// Call the Rust main routine.
|
||||
call main_efi_handover64
|
||||
call main_efi_common64
|
||||
|
||||
// The main routine should not return. If it does, there is nothing we can
|
||||
// do but stop the machine.
|
||||
jmp halt
|
||||
|
||||
.global entry_efi_pe64
|
||||
entry_efi_pe64:
|
||||
// This is the 64-bit EFI PE/COFF entry point.
|
||||
|
||||
// Not supported yet. Just stop the machine.
|
||||
jmp halt
|
||||
|
||||
halt:
|
||||
hlt
|
||||
jmp halt
|
||||
|
Loading…
x
Reference in New Issue
Block a user