Skip to content

Commit cb60fdf

Browse files
committed
pvh: Enable PVH boot support
Parse the ELF header looking for a PVH Note section and retrieve the encoded PVH entry point address if there is one. The entry point address is returned in KernelLoaderResult alongside the typical ELF entry point used for direct boot. A VMM implementing KernelLoader can now determine whether a PVH entry point is available and choose to configure its guests to boot using either PVH or Linux 64-bit protocol. Signed-off-by: Alejandro Jimenez <[email protected]>
1 parent 64b55fe commit cb60fdf

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed

src/loader/mod.rs

+104
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Copyright © 2020, Oracle and/or its affiliates.
2+
//
13
// Copyright (c) 2019 Intel Corporation. All rights reserved.
24
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
35
//
@@ -36,6 +38,11 @@ use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestUsize};
3638
#[allow(missing_docs)]
3739
#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))]
3840
pub mod bootparam;
41+
42+
#[allow(missing_docs)]
43+
#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))]
44+
pub mod start_info;
45+
3946
#[allow(dead_code)]
4047
#[allow(non_camel_case_types)]
4148
#[allow(non_snake_case)]
@@ -93,6 +100,10 @@ pub enum Error {
93100
SeekBzImageHeader,
94101
/// Unable to seek to bzImage compressed kernel.
95102
SeekBzImageCompressedKernel,
103+
/// Unable to seek to note header.
104+
SeekNoteHeader,
105+
/// Unable to read note header.
106+
ReadNoteHeader,
96107
}
97108

98109
/// A specialized `Result` type for the kernel loader.
@@ -125,6 +136,8 @@ impl error::Error for Error {
125136
Error::SeekBzImageEnd => "Unable to seek bzImage end",
126137
Error::SeekBzImageHeader => "Unable to seek bzImage header",
127138
Error::SeekBzImageCompressedKernel => "Unable to seek bzImage compressed kernel",
139+
Error::SeekNoteHeader => "Unable to seek to note header",
140+
Error::ReadNoteHeader => "Unable to read note header",
128141
}
129142
}
130143
}
@@ -150,6 +163,10 @@ pub struct KernelLoaderResult {
150163
/// This field is only for bzImage following https://www.kernel.org/doc/Documentation/x86/boot.txt
151164
/// VMM should make use of it to fill zero page for bzImage direct boot.
152165
pub setup_header: Option<bootparam::setup_header>,
166+
/// This field optionally holds the address of a PVH entry point, indicating that
167+
/// the kernel supports the PVH boot protocol as described in:
168+
/// https://xenbits.xen.org/docs/unstable/misc/pvh.html
169+
pub pvh_entry_addr: Option<GuestAddress>,
153170
}
154171

155172
/// A kernel image loading support must implement the KernelLoader trait.
@@ -247,6 +264,10 @@ impl KernelLoader for Elf {
247264
// Read in each section pointed to by the program headers.
248265
for phdr in &phdrs {
249266
if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 {
267+
if phdr.p_type == elf::PT_NOTE {
268+
// This segment describes a Note, check if PVH entry point is encoded.
269+
loader_result.pvh_entry_addr = parse_elf_note(phdr, kernel_image)?;
270+
}
250271
continue;
251272
}
252273

@@ -280,6 +301,73 @@ impl KernelLoader for Elf {
280301
}
281302
}
282303

304+
#[cfg(feature = "elf")]
305+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
306+
fn parse_elf_note<F>(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result<Option<GuestAddress>>
307+
where
308+
F: Read + Seek,
309+
{
310+
let n_align = phdr.p_align;
311+
312+
// Seek to the beginning of the note segment
313+
kernel_image
314+
.seek(SeekFrom::Start(phdr.p_offset))
315+
.map_err(|_| Error::SeekNoteHeader)?;
316+
317+
// Now that the segment has been found, we must locate an ELF note with the
318+
// correct type that encodes the PVH entry point if there is one.
319+
let mut nhdr: elf::Elf64_Nhdr = Default::default();
320+
let mut read_size: usize = 0;
321+
322+
while read_size < phdr.p_filesz as usize {
323+
unsafe {
324+
// read_struct is safe when reading a POD struct.
325+
// It can be used and dropped without issue.
326+
struct_util::read_struct(kernel_image, &mut nhdr).map_err(|_| Error::ReadNoteHeader)?;
327+
}
328+
// If the note header found is not the desired one, keep reading until
329+
// the end of the segment
330+
if nhdr.n_type == elf::XEN_ELFNOTE_PHYS32_ENTRY {
331+
break;
332+
}
333+
// Skip the note header plus the size of its fields (with alignment)
334+
read_size += mem::size_of::<elf::Elf64_Nhdr>()
335+
+ align_up(u64::from(nhdr.n_namesz), n_align)
336+
+ align_up(u64::from(nhdr.n_descsz), n_align);
337+
338+
kernel_image
339+
.seek(SeekFrom::Start(phdr.p_offset + read_size as u64))
340+
.map_err(|_| Error::SeekNoteHeader)?;
341+
}
342+
343+
if read_size >= phdr.p_filesz as usize {
344+
return Ok(None); // PVH ELF note not found, nothing else to do.
345+
}
346+
// Otherwise the correct note type was found.
347+
// The note header struct has already been read, so we can seek from the
348+
// current position and just skip the name field contents.
349+
kernel_image
350+
.seek(SeekFrom::Current(
351+
align_up(u64::from(nhdr.n_namesz), n_align) as i64,
352+
))
353+
.map_err(|_| Error::SeekNoteHeader)?;
354+
355+
// We support 64bit kernels only and the PVH entry point is a 32bit address
356+
// encoded in a 64 bit field, so we'll grab all 8 bytes.
357+
let mut pvh_addr_bytes = [0; 8usize];
358+
359+
if nhdr.n_descsz as usize != pvh_addr_bytes.len() {
360+
return Err(Error::ReadNoteHeader);
361+
}
362+
363+
kernel_image
364+
.read_exact(&mut pvh_addr_bytes)
365+
.map_err(|_| Error::ReadNoteHeader)
366+
.ok();
367+
368+
Ok(Some(GuestAddress(<u64>::from_le_bytes(pvh_addr_bytes))))
369+
}
370+
283371
#[cfg(feature = "bzimage")]
284372
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
285373
/// Big zImage (bzImage) kernel image support.
@@ -409,6 +497,22 @@ pub fn load_cmdline<M: GuestMemory>(
409497
Ok(())
410498
}
411499

500+
/// Align address upwards. Taken from x86_64 crate.
501+
///
502+
/// Returns the smallest x with alignment `align` so that x >= addr. The alignment must be
503+
/// a power of 2.
504+
#[cfg(feature = "elf")]
505+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
506+
fn align_up(addr: u64, align: u64) -> usize {
507+
assert!(align.is_power_of_two(), "`align` must be a power of two");
508+
let align_mask = align - 1;
509+
if addr & align_mask == 0 {
510+
addr as usize // already aligned
511+
} else {
512+
((addr | align_mask) + 1) as usize
513+
}
514+
}
515+
412516
#[cfg(test)]
413517
mod test {
414518
use super::*;

0 commit comments

Comments
 (0)