Skip to content
Draft
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand Down
14 changes: 12 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
104 changes: 84 additions & 20 deletions src/loader/elf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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::<elf::Elf64_Phdr>() {
return Err(Error::InvalidProgramHeaderSize);
}
Expand Down Expand Up @@ -189,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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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<F>(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result<PvhBootCapability>
where
F: Read + ReadVolatile + Seek,
Expand Down Expand Up @@ -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<u64, Error> {
if !align.is_power_of_two() {
return Err(Error::Align);
Expand All @@ -436,42 +463,49 @@ 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<u8> {
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("test_elf.bin"));
v
fn make_x86_64_elf_bin() -> Vec<u8> {
include_bytes!("test_x86-64_elf.bin").to_vec()
}

fn make_aarch64_elf_bin() -> Vec<u8> {
include_bytes!("test_arm64_elf.bin").to_vec()
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn make_elfnote() -> Vec<u8> {
include_bytes!("test_elfnote.bin").to_vec()
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn make_elfnote_8byte_align() -> Vec<u8> {
include_bytes!("test_elfnote_8byte_align.bin").to_vec()
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn make_dummy_elfnote() -> Vec<u8> {
include_bytes!("test_dummy_note.bin").to_vec()
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn make_invalid_pvh_note() -> Vec<u8> {
include_bytes!("test_invalid_pvh_note.bin").to_vec()
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn make_elfnote_bad_align() -> Vec<u8> {
include_bytes!("test_bad_align.bin").to_vec()
}

#[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(
Expand Down Expand Up @@ -508,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)),
Expand All @@ -522,23 +570,35 @@ 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()
);
let mut bad_image = make_x86_64_elf_bin();

#[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]
fn test_bad_phoff() {
// 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)),
Expand All @@ -547,6 +607,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();
Expand All @@ -571,6 +632,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();
Expand All @@ -582,6 +644,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();
Expand All @@ -592,6 +655,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.
Expand Down Expand Up @@ -622,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(
Expand Down
Binary file added src/loader/elf/test_arm64_elf.bin
Binary file not shown.
File renamed without changes.
Loading