Skip to content
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
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,22 @@ jobs:
container_image: ""
limit_to_owner: rcore-os
main_pr_only: false
- name: Test axvisor self-hosted board asus-nuc15crh-linux
use_container: false
runs_on: '["self-hosted","linux","board"]'
timeout_minutes: 20
command: |
mkdir -p tmp
Comment thread
ZR233 marked this conversation as resolved.
cargo xtask image pull qemu-x86_64 --output-dir tmp
cargo xtask image pull initramfs-x86_64-busybox.cpio.gz --output-dir tmp
gzip -dc \
tmp/initramfs-x86_64-busybox.cpio.gz/initramfs-x86_64-busybox.cpio.gz \
> tmp/initramfs-x86_64-busybox.cpio
cargo xtask axvisor test board --board asus-nuc15crh-linux
cache_key: ""
container_image: ""
limit_to_owner: rcore-os
main_pr_only: false
- name: Test starry self-hosted board orangepi-5-plus
use_container: false
runs_on: '["self-hosted","linux","board"]'
Expand Down
1 change: 0 additions & 1 deletion apps/starry/nix/build-aarch64-unknown-none-softfloat.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ features = [
"ax-driver/virtio-blk",
"ax-driver/virtio-net",
]
plat_dyn = true
1 change: 0 additions & 1 deletion apps/starry/nix/build-x86_64-unknown-none.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ features = [
"ax-driver/virtio-blk",
"ax-driver/virtio-net",
]
plat_dyn = true
33 changes: 30 additions & 3 deletions bootloader/axloader/src/loader/control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,28 @@ pub fn fetch_boot_offer() -> Result<BootOffer, ControlError> {
crate::logln!("serial_control_wait: waiting for AXLOADER BOOT");
announce_ready();
loop {
let line = read_boot_line()?;
let line = match read_boot_line() {
Ok(line) => line,
Err(ControlError::Timeout) => return Err(ControlError::Timeout),
Err(err) => {
crate::logln!("serial_control_ignored: read_error={err:?}");
announce_ready();
continue;
}
};
match parse_boot_offer(&line) {
Ok(offer) if valid_kernel_url(&offer.kernel_url) => return Ok(offer),
Ok(offer) => {
crate::logln!(
"serial_control_ignored: invalid kernel_url {}",
offer.kernel_url
);
announce_ready();
}
Err(err) => {
crate::logln!("serial_control_ignored: parse_error={err:?}");
announce_ready();
}
Err(err) => return Err(err),
}
}
}
Expand Down Expand Up @@ -133,7 +145,22 @@ fn parse_boot_offer(input: &str) -> Result<BootOffer, ControlError> {
}

fn valid_kernel_url(url: &str) -> bool {
url.starts_with("http://") && url.contains("/kernel.elf")
let Some(rest) = url.strip_prefix("http://") else {
return false;
};
if rest
.bytes()
.any(|byte| matches!(byte, b'\0' | b'\r' | b'\n' | b' ' | b'\t'))
{
return false;
}

let Some(path_start) = rest.find('/') else {
return false;
};
let authority = &rest[..path_start];
let path = &rest[path_start..];
!authority.is_empty() && path.ends_with("/kernel.elf")
}

fn json_string_field<'a>(input: &'a str, key: &str) -> Option<&'a str> {
Expand Down
91 changes: 77 additions & 14 deletions bootloader/axloader/src/loader/elf_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ pub struct LoadedElf {
pub load_addr: u64,
pub load_end: u64,
pub page_count: usize,
pub handoff: EntryHandoff,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryHandoff {
BootInfo,
Uefi,
}

#[repr(C)]
Expand Down Expand Up @@ -126,12 +133,7 @@ fn load_elf(image: &[u8], entry_symbol: Option<&str>) -> Result<LoadedElf, ElfLo
.ok_or(ElfLoadError::SegmentAddressOverflow)?;
let page_count = usize::try_from((load_end - load_addr) / UEFI_PAGE_SIZE)
.map_err(|_| ElfLoadError::SegmentAddressOverflow)?;
let target = boot::allocate_pages(
AllocateType::Address(load_addr),
MemoryType::LOADER_DATA,
page_count,
)
.map_err(|_| ElfLoadError::AllocateFailed)?;
let (target, actual_load_addr) = allocate_load_region(load_addr, page_count)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

【阻塞/正确性】这里在首选物理地址分配失败后会把整个 ELF 搬到 AnyPages 返回的地址,但后续只对入口地址做 load bias。普通内核/Hypervisor ELF 通常不是可任意重定位的 PIE,代码和数据里的绝对地址、页表/启动汇编引用仍按原始 p_paddr/链接地址生成;这种情况下跳到 biased entry 会让早期启动访问错误地址,表现为实板早期 hang 或内存破坏。请要么保持原来的语义:首选地址不可用就返回 AllocateFailed;要么实现并验证真正的 ELF relocation,或只接受已验证可重定位的镜像,并为 fallback 路径补充能失败的回归或启动测试。


if let Err(err) = copy_segments(image, &segments, load_addr, target) {
unsafe {
Expand All @@ -140,23 +142,84 @@ fn load_elf(image: &[u8], entry_symbol: Option<&str>) -> Result<LoadedElf, ElfLo
return Err(err);
}

let entry = match entry_symbol {
Some("httpboot_entry") => find_symbol(image, &header, "httpboot_entry")
.and_then(|symbol| virtual_to_physical(symbol, &segments))
.ok_or(ElfLoadError::EntryNotInLoadSegment)?,
let (entry, handoff) = match entry_symbol {
Some("httpboot_entry") => {
if let Some(entry) = find_symbol(image, &header, "httpboot_entry")
.and_then(|symbol| virtual_to_physical(symbol, &segments))
{
(entry, EntryHandoff::BootInfo)
} else if let Some(entry) = find_symbol(image, &header, "__x86_64_efi_pe_entry")
.and_then(|symbol| virtual_to_physical(symbol, &segments))
{
(entry, EntryHandoff::Uefi)
} else {
(
virtual_to_physical(header.e_entry, &segments)
.ok_or(ElfLoadError::EntryNotInLoadSegment)?,
EntryHandoff::Uefi,
)
}
}
Some(_) => return Err(ElfLoadError::UnsupportedEntrySymbol),
None => virtual_to_physical(header.e_entry, &segments)
.ok_or(ElfLoadError::EntryNotInLoadSegment)?,
None => (
virtual_to_physical(header.e_entry, &segments)
.ok_or(ElfLoadError::EntryNotInLoadSegment)?,
EntryHandoff::BootInfo,
),
};
let entry = biased_addr(entry, load_addr, actual_load_addr)?;
let actual_load_end = actual_load_addr
.checked_add(page_count as u64 * UEFI_PAGE_SIZE)
.ok_or(ElfLoadError::SegmentAddressOverflow)?;

Ok(LoadedElf {
entry_point: entry,
load_addr,
load_end,
load_addr: actual_load_addr,
load_end: actual_load_end,
page_count,
handoff,
})
}

fn biased_addr(addr: u64, original_base: u64, actual_base: u64) -> Result<u64, ElfLoadError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

【非阻塞/设计约束】allocate_load_regionAnyPages fallback 将整个 ELF 搬迁到 UEFI 分配的任意地址,但仅通过 biased_addr 调整入口地址,不处理 ELF 内部的绝对地址引用(如代码段中的立即数、页表基址等)。对于非 PIE (ET_EXEC) 的内核镜像,这可能导致早期启动访问错误物理地址。建议在函数或 EntryHandoff 上增加注释说明此 fallback 仅适用于已验证可重定位的镜像类型,并记录此已知设计约束以便后续维护者理解风险边界。

let addr = addr as i128 + actual_base as i128 - original_base as i128;
if !(0..=u64::MAX as i128).contains(&addr) {
return Err(ElfLoadError::SegmentAddressOverflow);
}
Ok(addr as u64)
}

fn allocate_load_region(
preferred_load_addr: u64,
page_count: usize,
) -> Result<(NonNull<u8>, u64), ElfLoadError> {
match boot::allocate_pages(
AllocateType::Address(preferred_load_addr),
MemoryType::LOADER_DATA,
page_count,
) {
Ok(target) => Ok((target, preferred_load_addr)),
Err(_) => {
// This is only a load-bias fallback: axloader moves PT_LOAD segments as a
// whole and adjusts the handoff entry point, but it does not process ELF
// relocation records or rewrite absolute references embedded in the image.
// It is therefore valid only for images that are already known to tolerate
// this placement model, such as the current AxVisor UEFI stub. A generic
// non-PIE ET_EXEC kernel may still dereference its original physical
// addresses and fail early after being loaded at AnyPages.
crate::logln!(
"elf_load_relocate: preferred={:#x} pages={}",
preferred_load_addr,
page_count
);
let target =
boot::allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, page_count)
.map_err(|_| ElfLoadError::AllocateFailed)?;
Ok((target, target.as_ptr() as u64))
}
}
}

fn validate_header(header: &Elf64Header) -> Result<(), ElfLoadError> {
if &header.ident[..4] != ELF_MAGIC {
return Err(ElfLoadError::BadMagic);
Expand Down
39 changes: 38 additions & 1 deletion bootloader/axloader/src/loader/entry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::{mem, ptr::NonNull};

use uefi::{
boot,
Status, boot,
mem::memory_map::{MemoryMap, MemoryType},
};

Expand All @@ -14,6 +14,7 @@ const OSTOOL_BOOT_INFO_MAX_RAM_REGIONS: usize = 32;
pub enum JumpError {
EntryAddressTooLarge,
BootInfoAllocateFailed,
SystemTableUnavailable,
}

#[repr(C)]
Expand Down Expand Up @@ -71,6 +72,27 @@ pub fn exit_boot_services_and_jump(entry_point: u64) -> Result<(), JumpError> {
unsafe { call_entry_point(entry_point, boot_info_ptr) }
}

#[cfg(target_arch = "x86_64")]
pub fn jump_to_uefi_entry(entry_point: u64) -> Result<(), JumpError> {
let entry_point = usize::try_from(entry_point).map_err(|_| JumpError::EntryAddressTooLarge)?;
let system_table = uefi::table::system_table_raw().ok_or(JumpError::SystemTableUnavailable)?;
// This handoff intentionally reuses axloader's image handle. It is only
// suitable for loaded entries that use the UEFI system table but do not
// query LoadedImage or other image-handle-specific protocols.
unsafe {
call_uefi_entry_point(
entry_point,
boot::image_handle(),
system_table.as_ptr().cast(),
)
}
}

#[cfg(not(target_arch = "x86_64"))]
pub fn jump_to_uefi_entry(_entry_point: u64) -> Result<(), JumpError> {
Err(JumpError::SystemTableUnavailable)
}

fn allocate_boot_info() -> Result<NonNull<OstoolBootInfo>, JumpError> {
let ptr = boot::allocate_pool(MemoryType::LOADER_DATA, mem::size_of::<OstoolBootInfo>())
.map_err(|_| JumpError::BootInfoAllocateFailed)?;
Expand Down Expand Up @@ -102,3 +124,18 @@ unsafe fn call_entry_point(entry_point: usize, boot_info: usize) -> ! {
let entry: extern "C" fn(usize) -> ! = unsafe { core::mem::transmute(entry_point) };
entry(boot_info)
}

#[cfg(target_arch = "x86_64")]
unsafe fn call_uefi_entry_point(
entry_point: usize,
image_handle: uefi::Handle,
system_table: *const core::ffi::c_void,
) -> Result<(), JumpError> {
// SAFETY: the address comes from the loaded kernel ELF symbol
// `__x86_64_efi_pe_entry`, whose ABI matches a UEFI PE entry point.
let entry: extern "efiapi" fn(uefi::Handle, *const core::ffi::c_void) -> Status =
unsafe { core::mem::transmute(entry_point) };
let status = entry(image_handle, system_table);
crate::logln!("uefi_entry_returned: {status:?}");
Ok(())
}
15 changes: 12 additions & 3 deletions bootloader/axloader/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,22 @@ fn fetch_control_offer() -> bool {
) {
Ok(elf) => {
logln!(
"elf_loaded: load={:#x} end={:#x} pages={} entry={:#x}",
"elf_loaded: load={:#x} end={:#x} pages={} entry={:#x} handoff={:?}",
elf.load_addr,
elf.load_end,
elf.page_count,
elf.entry_point
elf.entry_point,
elf.handoff
);
match entry::exit_boot_services_and_jump(elf.entry_point) {
let jump_result = match elf.handoff {
elf_loader::EntryHandoff::BootInfo => {
entry::exit_boot_services_and_jump(elf.entry_point)
}
elf_loader::EntryHandoff::Uefi => {
entry::jump_to_uefi_entry(elf.entry_point)
}
};
match jump_result {
Ok(()) => logln!("jump_error: entry returned unexpectedly"),
Err(err) => logln!("jump_error: {err:?}"),
}
Expand Down
8 changes: 7 additions & 1 deletion drivers/rdrive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub enum Platform {
Static,
Fdt { addr: NonNull<u8> },
Acpi(probe::acpi::AcpiRoot),
AcpiWithoutAml(probe::acpi::AcpiRoot),
}

unsafe impl Send for Platform {}
Expand All @@ -52,6 +53,7 @@ pub enum PlatformSource {
Static,
Fdt(NonNull<u8>),
Acpi(probe::acpi::AcpiRoot),
AcpiWithoutAml(probe::acpi::AcpiRoot),
}

unsafe impl Send for PlatformSource {}
Expand All @@ -69,6 +71,7 @@ pub fn init(platform: Platform) -> Result<(), DriverError> {
Platform::Static => init_sources(&[PlatformSource::Static])?,
Platform::Fdt { addr } => init_sources(&[PlatformSource::Fdt(addr)])?,
Platform::Acpi(root) => init_sources(&[PlatformSource::Acpi(root)])?,
Platform::AcpiWithoutAml(root) => init_sources(&[PlatformSource::AcpiWithoutAml(root)])?,
}
Ok(())
}
Expand All @@ -78,7 +81,9 @@ pub fn init_sources(sources: &[PlatformSource]) -> Result<(), DriverError> {
match source {
PlatformSource::Static => {}
PlatformSource::Fdt(addr) => probe::fdt::check_addr(*addr)?,
PlatformSource::Acpi(root) => probe::acpi::check_root(*root)?,
PlatformSource::Acpi(root) | PlatformSource::AcpiWithoutAml(root) => {
probe::acpi::check_root(*root)?
}
}
}

Expand All @@ -87,6 +92,7 @@ pub fn init_sources(sources: &[PlatformSource]) -> Result<(), DriverError> {
PlatformSource::Static => probe::static_::init()?,
PlatformSource::Fdt(addr) => probe::fdt::init(*addr)?,
PlatformSource::Acpi(root) => probe::acpi::init(*root)?,
PlatformSource::AcpiWithoutAml(root) => probe::acpi::init_without_aml(*root)?,
}
}

Expand Down
Loading