From fa5fc7fe154553326a7c985a5337a47b6c26474d Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 15 May 2026 07:24:06 +0200 Subject: [PATCH 1/5] loader_gen: Expose ELF module on non-x86 architectures Signed-off-by: Aelin Reidel --- src/lib.rs | 14 ++++++++++++-- src/loader_gen/{x86_64 => }/elf.rs | 0 src/loader_gen/mod.rs | 12 +++++++++++- src/loader_gen/x86_64/mod.rs | 9 --------- 4 files changed, 23 insertions(+), 12 deletions(-) rename src/loader_gen/{x86_64 => }/elf.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 7d85390b..05909373 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,9 +127,19 @@ pub mod configurator; pub mod loader; #[allow(clippy::undocumented_unsafe_blocks)] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any( + all(target_arch = "aarch64", feature = "elf"), + all(target_arch = "riscv64", feature = "elf"), + target_arch = "x86", + target_arch = "x86_64" +))] mod loader_gen; -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(any( + all(target_arch = "aarch64", feature = "elf"), + all(target_arch = "riscv64", feature = "elf"), + target_arch = "x86", + target_arch = "x86_64" +))] pub use loader_gen::*; extern crate vm_memory; diff --git a/src/loader_gen/x86_64/elf.rs b/src/loader_gen/elf.rs similarity index 100% rename from src/loader_gen/x86_64/elf.rs rename to src/loader_gen/elf.rs diff --git a/src/loader_gen/mod.rs b/src/loader_gen/mod.rs index 31010c71..b280dce0 100644 --- a/src/loader_gen/mod.rs +++ b/src/loader_gen/mod.rs @@ -9,7 +9,17 @@ //! Bindgen autogenerated structs for boot parameters. -#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod x86_64; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use x86_64::*; + +#[cfg(feature = "elf")] +pub mod elf; diff --git a/src/loader_gen/x86_64/mod.rs b/src/loader_gen/x86_64/mod.rs index 4c71acca..967bde3f 100644 --- a/src/loader_gen/x86_64/mod.rs +++ b/src/loader_gen/x86_64/mod.rs @@ -10,17 +10,8 @@ //! Bindgen autogenerated structs for `x86_64` boot parameters. #![cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(missing_docs)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(non_upper_case_globals)] pub mod bootparam; -#[cfg(feature = "elf")] -pub mod elf; - #[cfg(feature = "elf")] pub mod start_info; From 481d7de0b901a9614486ae23854804c9132dd4d7 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Fri, 15 May 2026 07:26:20 +0200 Subject: [PATCH 2/5] loader: Add support for loading ELF on aarch64 and riscv64 These lack PVH support, so we conditionally don't compile the code related to that. Since aarch64 can technically also be big-endian, I also extended the code to not check for little endian, but rather native endian. I have however not run the testsuite on aarch64_be, only on aarch64. Signed-off-by: Aelin Reidel --- README.md | 11 ++++---- src/loader/elf/mod.rs | 64 +++++++++++++++++++++++++++++++++++++------ src/loader/mod.rs | 60 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index f8627ea2..d267fe88 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,10 @@ [![crates.io](https://img.shields.io/crates/v/linux-loader)](https://crates.io/crates/linux-loader) [![docs.rs](https://img.shields.io/docsrs/linux-loader)](https://docs.rs/linux-loader/) -The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) and -compressed big zImage (`bzImage`) format kernel images on `x86_64` and PE -(`Image`) kernel images on `aarch64` and `riscv64`. ELF support includes the +The `linux-loader` crate offers support for loading raw ELF (`vmlinux`) on +`aarch64`, `riscv64` and `x86_64`, compressed big zImage (`bzImage`) format +kernel images on `x86_64` and PE (`Image`) kernel images on `aarch64` and +`riscv64`. ELF support includes the [Linux](https://www.kernel.org/doc/Documentation/x86/boot.txt) and [PVH](https://xenbits.xen.org/docs/unstable/misc/pvh.html) boot protocols. @@ -16,8 +17,8 @@ much of the boot process remains the VMM's responsibility. See [Usage] for detai - Parsing and loading kernel images into guest memory. - `x86_64`: `vmlinux` (raw ELF image), `bzImage` - - `aarch64`: `Image` - - `riscv64`: `Image` + - `aarch64`: raw ELF images, `Image` + - `riscv64`: raw ELF images, `Image` - Parsing and building the kernel command line. - Loading device tree blobs (`aarch64` and `riscv64`). - Configuring boot parameters using the exported primitives. diff --git a/src/loader/elf/mod.rs b/src/loader/elf/mod.rs index 10d37451..af8f5248 100644 --- a/src/loader/elf/mod.rs +++ b/src/loader/elf/mod.rs @@ -10,11 +10,20 @@ //! Traits and structs for loading elf image kernels into guest memory. -#![cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +#![cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) +))] use std::fmt; use std::io::{Read, Seek, SeekFrom}; use std::mem; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use std::result; use vm_memory::{ @@ -23,6 +32,7 @@ use vm_memory::{ use crate::loader::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; use crate::loader_gen::elf; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use crate::loader_gen::start_info; // SAFETY: The layout of the structure is fixed and can be initialized by @@ -54,6 +64,8 @@ pub enum Error { InvalidProgramHeaderAddress, /// Invalid entry address. InvalidEntryAddress, + /// Loaded little endian binary on a big endian platform. + LittleEndianElfOnBig, /// Overflow occurred during an arithmetic operation. Overflow, /// Unable to read ELF header. @@ -88,6 +100,9 @@ impl fmt::Display for Error { Error::InvalidProgramHeaderOffset => "Invalid program header offset", Error::InvalidProgramHeaderAddress => "Invalid Program Header Address", Error::InvalidEntryAddress => "Invalid entry address", + Error::LittleEndianElfOnBig => { + "Trying to load little-endian binary on big-endian machine" + } Error::Overflow => "Overflow occurred during an arithmetic operation", Error::ReadElfHeader => "Unable to read elf header", Error::ReadKernelImage => "Unable to read kernel image", @@ -148,9 +163,14 @@ impl Elf { { return Err(Error::InvalidElfMagicNumber); } + #[cfg(target_endian = "little")] if ehdr.e_ident[elf::EI_DATA] != elf::ELFDATA2LSB { return Err(Error::BigEndianElfOnLittle); } + #[cfg(target_endian = "big")] + if ehdr.e_ident[elf::EI_DATA] != elf::ELFDATA2MSB { + return Err(Error::LittleEndianElfOnBig); + } if ehdr.e_phentsize as usize != mem::size_of::() { return Err(Error::InvalidProgramHeaderSize); } @@ -252,6 +272,7 @@ impl KernelLoader for Elf { // Read in each section pointed to by the program headers. for phdr in phdrs { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 { if phdr.p_type == elf::PT_NOTE { // The PVH boot protocol currently requires that the kernel is loaded at @@ -294,13 +315,17 @@ impl KernelLoader for Elf { } // elf image has no setup_header which is defined for bzImage - loader_result.setup_header = None; + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + loader_result.setup_header = None; + } Ok(loader_result) } } // Size of string "Xen", including the terminating NULL. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] const PVH_NOTE_STR_SZ: usize = 4; /// Examines a supplied elf program header of type `PT_NOTE` to determine if it contains an entry @@ -309,6 +334,7 @@ const PVH_NOTE_STR_SZ: usize = 4; /// with paging disabled, as described by the PVH boot protocol. /// Returns the encoded entry point address, or `None` if no `XEN_ELFNOTE_PHYS32_ENTRY` entries /// are found in the note header. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result where F: Read + ReadVolatile + Seek, @@ -415,6 +441,7 @@ where /// /// Returns the smallest x with alignment `align` so that x >= addr if the alignment is a power of /// 2, or an error otherwise. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn align_up(addr: u64, align: u64) -> result::Result { if !align.is_power_of_two() { return Err(Error::Align); @@ -448,22 +475,27 @@ mod tests { v } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_elfnote() -> Vec { include_bytes!("test_elfnote.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_elfnote_8byte_align() -> Vec { include_bytes!("test_elfnote_8byte_align.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_dummy_elfnote() -> Vec { include_bytes!("test_dummy_note.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_invalid_pvh_note() -> Vec { include_bytes!("test_invalid_pvh_note.bin").to_vec() } + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn make_elfnote_bad_align() -> Vec { include_bytes!("test_bad_align.bin").to_vec() } @@ -522,15 +554,27 @@ mod tests { #[test] fn test_bad_endian() { - // Only little endian is supported. + // Only native endian is supported. let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); let mut bad_image = make_elf_bin(); - bad_image[0x5] = 2; - assert_eq!( - Some(KernelLoaderError::Elf(Error::BigEndianElfOnLittle)), - Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() - ); + + #[cfg(target_endian = "little")] + { + bad_image[0x5] = elf::ELFDATA2MSB; + assert_eq!( + Some(KernelLoaderError::Elf(Error::BigEndianElfOnLittle)), + Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() + ); + } + #[cfg(target_endian = "big")] + { + bad_image[0x5] = elf::ELFDATA2LSB; + assert_eq!( + Some(KernelLoaderError::Elf(Error::LittleEndianElfOnBig)), + Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err() + ); + } } #[test] @@ -547,6 +591,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_load_pvh() { let gm = create_guest_mem(); let pvhnote_image = make_elfnote(); @@ -571,6 +616,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_dummy_elfnote() { let gm = create_guest_mem(); let dummynote_image = make_dummy_elfnote(); @@ -582,6 +628,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_bad_elfnote() { let gm = create_guest_mem(); let badnote_image = make_invalid_pvh_note(); @@ -592,6 +639,7 @@ mod tests { } #[test] + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] fn test_load_pvh_with_align() { // Alignment of ELF notes is always const value (4-bytes), ELF notes parse should not get Align // error. diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 4f1c5d21..caf98d17 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -36,9 +36,25 @@ pub mod pe; #[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))] pub use pe::*; -#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))] +#[cfg(all( + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ), + feature = "elf" +))] pub mod elf; -#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))] +#[cfg(all( + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ), + feature = "elf" +))] pub use elf::*; #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))] @@ -54,7 +70,15 @@ pub enum Error { Bzimage(bzimage::Error), /// Failed to load elf image. - #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + #[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) + ))] Elf(elf::Error), /// Failed to load PE image. @@ -84,7 +108,15 @@ impl fmt::Display for Error { match self { #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] Error::Bzimage(ref e) => write!(f, "failed to load bzImage kernel image: {e}"), - #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + #[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) + ))] Error::Elf(ref e) => write!(f, "failed to load ELF kernel image: {e}"), #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Error::Pe(ref e) => write!(f, "failed to load PE kernel image: {e}"), @@ -103,7 +135,15 @@ impl std::error::Error for Error { match self { #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] Error::Bzimage(ref e) => Some(e), - #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + #[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) + ))] Error::Elf(ref e) => Some(e), #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))] Error::Pe(ref e) => Some(e), @@ -117,7 +157,15 @@ impl std::error::Error for Error { } } -#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +#[cfg(all( + feature = "elf", + any( + target_arch = "aarch64", + target_arch = "riscv64", + target_arch = "x86", + target_arch = "x86_64" + ) +))] impl From for Error { fn from(err: elf::Error) -> Self { Error::Elf(err) From 18a835e2ad488cc267fd00b8aadd3808c50e1dc6 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Sun, 17 May 2026 00:07:15 +0200 Subject: [PATCH 3/5] loader: Move load_dtb to top-level module This method can also be used when loading ELF files, hence move it to the loader module level. Signed-off-by: Aelin Reidel --- src/loader/mod.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++ src/loader/pe/mod.rs | 40 ------------------------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/loader/mod.rs b/src/loader/mod.rs index caf98d17..1a8dd595 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -20,6 +20,8 @@ extern crate vm_memory; use std::fmt; +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +use std::io::SeekFrom; use std::io::{Read, Seek}; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -95,6 +97,19 @@ pub enum Error { InvalidKernelStartAddress, /// Memory to load kernel image is too small. MemoryOverflow, + + /// Unable to seek to DTB start. + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + SeekDtbStart, + /// Unable to seek to DTB end. + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + SeekDtbEnd, + /// Device tree binary too big. + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + DtbTooBig, + /// Unable to read DTB image + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + ReadDtbImage, } /// A specialized [`Result`] type for the kernel loader. @@ -126,6 +141,15 @@ impl fmt::Display for Error { Error::CommandLineOverflow => write!(f, "command line overflowed guest memory"), Error::InvalidKernelStartAddress => write!(f, "invalid kernel start address"), Error::MemoryOverflow => write!(f, "memory to load kernel image is not enough"), + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::SeekDtbStart => write!(f, "unable to seek DTB start"), + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::SeekDtbEnd => write!(f, "unable to seek DTB end"), + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::DtbTooBig => write!(f, "device tree image too big"), + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::ReadDtbImage => write!(f, "unable to read DTB image"), } } } @@ -153,6 +177,11 @@ impl std::error::Error for Error { Error::CommandLineOverflow => None, Error::InvalidKernelStartAddress => None, Error::MemoryOverflow => None, + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + Error::SeekDtbStart | Error::SeekDtbEnd | Error::DtbTooBig | Error::ReadDtbImage => { + None + } } } } @@ -297,6 +326,34 @@ pub fn load_cmdline( Ok(()) } +/// Writes the device tree to the given memory slice. +/// +/// # Arguments +/// +/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob. +/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob. +/// * `dtb_image` - The device tree blob. +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] +pub fn load_dtb( + guest_mem: &M, + guest_addr: GuestAddress, + dtb_image: &mut F, +) -> Result<()> +where + F: ReadVolatile + Read + Seek, +{ + let dtb_size = dtb_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekDtbEnd)? as usize; + if dtb_size > 0x200000 { + return Err(Error::DtbTooBig); + } + dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?; + guest_mem + .read_exact_volatile_from(guest_addr, dtb_image, dtb_size) + .map_err(|_| Error::ReadDtbImage) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/loader/pe/mod.rs b/src/loader/pe/mod.rs index a22dc2fa..0631a942 100644 --- a/src/loader/pe/mod.rs +++ b/src/loader/pe/mod.rs @@ -30,18 +30,10 @@ pub enum Error { SeekImageEnd, /// Unable to seek to Image header. SeekImageHeader, - /// Unable to seek to DTB start. - SeekDtbStart, - /// Unable to seek to DTB end. - SeekDtbEnd, - /// Device tree binary too big. - DtbTooBig, /// Unable to read kernel image. ReadKernelImage, /// Unable to read Image header. ReadImageHeader, - /// Unable to read DTB image - ReadDtbImage, /// Invalid Image binary. InvalidImage, /// Invalid Image magic number. @@ -56,12 +48,8 @@ impl fmt::Display for Error { Error::SeekImageEnd => "unable to seek Image end", Error::SeekImageHeader => "unable to seek Image header", Error::ReadImageHeader => "unable to read Image header", - Error::ReadDtbImage => "unable to read DTB image", - Error::SeekDtbStart => "unable to seek DTB start", - Error::SeekDtbEnd => "unable to seek DTB end", Error::InvalidImage => "invalid Image", Error::InvalidImageMagicNumber => "invalid Image magic number", - Error::DtbTooBig => "device tree image too big", Error::ReadKernelImage => "unable to read kernel image", Error::InvalidBaseAddrAlignment => "base address not aligned to 2 MB", }; @@ -207,34 +195,6 @@ impl KernelLoader for PE { } } -/// Writes the device tree to the given memory slice. -/// -/// # Arguments -/// -/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob. -/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob. -/// * `dtb_image` - The device tree blob. -#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -pub fn load_dtb( - guest_mem: &M, - guest_addr: GuestAddress, - dtb_image: &mut F, -) -> Result<()> -where - F: ReadVolatile + Read + Seek, -{ - let dtb_size = dtb_image - .seek(SeekFrom::End(0)) - .map_err(|_| Error::SeekDtbEnd)? as usize; - if dtb_size > 0x200000 { - return Err(Error::DtbTooBig.into()); - } - dtb_image.rewind().map_err(|_| Error::SeekDtbStart)?; - guest_mem - .read_exact_volatile_from(guest_addr, dtb_image, dtb_size) - .map_err(|_| Error::ReadDtbImage.into()) -} - #[cfg(test)] mod tests { use super::*; From dc07b7ee56ece0388e84542d8db0518da7dc9361 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Wed, 24 Jun 2026 02:33:36 +0200 Subject: [PATCH 4/5] loader: elf: Add a test for loading aarch64 ELF image The test file is the qemu_el1 example from aarch64-rt: https://github.com/google/aarch64-rt/blob/main/examples/qemu_el1.rs Signed-off-by: Aelin Reidel --- src/loader/elf/mod.rs | 40 ++++++++++++------ src/loader/elf/test_arm64_elf.bin | Bin 0 -> 108888 bytes .../elf/{test_elf.bin => test_x86-64_elf.bin} | Bin 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100755 src/loader/elf/test_arm64_elf.bin rename src/loader/elf/{test_elf.bin => test_x86-64_elf.bin} (100%) diff --git a/src/loader/elf/mod.rs b/src/loader/elf/mod.rs index af8f5248..32b44cf8 100644 --- a/src/loader/elf/mod.rs +++ b/src/loader/elf/mod.rs @@ -209,7 +209,7 @@ impl KernelLoader for Elf { /// let kernel_addr = GuestAddress(0x200000); /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); /// let mut kernel_image = vec![]; - /// kernel_image.extend_from_slice(include_bytes!("test_elf.bin")); + /// kernel_image.extend_from_slice(include_bytes!("test_x86-64_elf.bin")); /// elf::Elf::load( /// &gm, /// Some(kernel_addr), @@ -463,16 +463,18 @@ mod tests { use vm_memory::{Address, GuestAddress}; type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>; - const MEM_SIZE: u64 = 0x100_0000; + const MEM_SIZE: u64 = 0x8000_0000; fn create_guest_mem() -> GuestMemoryMmap { GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() } - fn make_elf_bin() -> Vec { - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("test_elf.bin")); - v + fn make_x86_64_elf_bin() -> Vec { + include_bytes!("test_x86-64_elf.bin").to_vec() + } + + fn make_aarch64_elf_bin() -> Vec { + include_bytes!("test_arm64_elf.bin").to_vec() } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -501,9 +503,9 @@ mod tests { } #[test] - fn test_load_elf() { + fn test_load_x86_64_elf() { let gm = create_guest_mem(); - let image = make_elf_bin(); + let image = make_x86_64_elf_bin(); let kernel_addr = GuestAddress(0x200000); let mut highmem_start_address = GuestAddress(0x0); let mut loader_result = Elf::load( @@ -540,11 +542,25 @@ mod tests { ); } + #[test] + fn test_load_aarch64_elf() { + let gm = create_guest_mem(); + let image = make_aarch64_elf_bin(); + + assert_eq!( + Elf::load(&gm, None, &mut Cursor::new(&image), None) + .unwrap() + .kernel_load + .raw_value(), + 0x40080000 + ); + } + #[test] fn test_bad_magic_number() { let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); - let mut bad_image = make_elf_bin(); + let mut bad_image = make_x86_64_elf_bin(); bad_image[0x1] = 0x33; assert_eq!( Some(KernelLoaderError::Elf(Error::InvalidElfMagicNumber)), @@ -557,7 +573,7 @@ mod tests { // Only native endian is supported. let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); - let mut bad_image = make_elf_bin(); + let mut bad_image = make_x86_64_elf_bin(); #[cfg(target_endian = "little")] { @@ -582,7 +598,7 @@ mod tests { // Program header has to be past the end of the elf header. let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); - let mut bad_image = make_elf_bin(); + let mut bad_image = make_x86_64_elf_bin(); bad_image[0x20] = 0x10; assert_eq!( Some(KernelLoaderError::Elf(Error::InvalidProgramHeaderOffset)), @@ -670,7 +686,7 @@ mod tests { #[test] fn test_overflow_loadaddr() { let gm = create_guest_mem(); - let image = make_elf_bin(); + let image = make_x86_64_elf_bin(); assert_eq!( Some(KernelLoaderError::Elf(Error::Overflow)), Elf::load( diff --git a/src/loader/elf/test_arm64_elf.bin b/src/loader/elf/test_arm64_elf.bin new file mode 100755 index 0000000000000000000000000000000000000000..a9015eac16515393de839f848cc339187523b4f7 GIT binary patch literal 108888 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 P63hR8xgnN8ng724V=@OI literal 0 HcmV?d00001 diff --git a/src/loader/elf/test_elf.bin b/src/loader/elf/test_x86-64_elf.bin similarity index 100% rename from src/loader/elf/test_elf.bin rename to src/loader/elf/test_x86-64_elf.bin From efb9127f7aebd84af73d78cb91aaecc7508004f7 Mon Sep 17 00:00:00 2001 From: Aelin Reidel Date: Wed, 24 Jun 2026 02:37:38 +0200 Subject: [PATCH 5/5] Add a changelog entry for ELF loading on aarch64 and riscv64 Signed-off-by: Aelin Reidel --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7715f13d..fe96d96b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Upcoming Release +## Added + +- [[#234](https://github.com/rust-vmm/linux-loader/pull/234)] Add ELF loading support for aarch64 and riscv64 + +## Fixed + - [[#224](https://github.com/rust-vmm/linux-loader/pull/224)] Fixed docs.rs build. # [v0.13.2]