Skip to content

Commit 2ca1049

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 580bb93 commit 2ca1049

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

src/loader/mod.rs

+105
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,13 @@ 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+
#[cfg(feature = "elf")]
43+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
44+
#[allow(missing_docs)]
45+
#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))]
46+
pub mod start_info;
47+
3948
#[allow(dead_code)]
4049
#[allow(non_camel_case_types)]
4150
#[allow(non_snake_case)]
@@ -93,6 +102,10 @@ pub enum Error {
93102
SeekBzImageHeader,
94103
/// Unable to seek to bzImage compressed kernel.
95104
SeekBzImageCompressedKernel,
105+
/// Unable to seek to note header.
106+
SeekNoteHeader,
107+
/// Unable to read note header.
108+
ReadNoteHeader,
96109
}
97110

98111
/// A specialized `Result` type for the kernel loader.
@@ -125,6 +138,8 @@ impl error::Error for Error {
125138
Error::SeekBzImageEnd => "Unable to seek bzImage end",
126139
Error::SeekBzImageHeader => "Unable to seek bzImage header",
127140
Error::SeekBzImageCompressedKernel => "Unable to seek bzImage compressed kernel",
141+
Error::SeekNoteHeader => "Unable to seek to note header",
142+
Error::ReadNoteHeader => "Unable to read note header",
128143
}
129144
}
130145
}
@@ -150,6 +165,10 @@ pub struct KernelLoaderResult {
150165
/// This field is only for bzImage following https://www.kernel.org/doc/Documentation/x86/boot.txt
151166
/// VMM should make use of it to fill zero page for bzImage direct boot.
152167
pub setup_header: Option<bootparam::setup_header>,
168+
/// This field optionally holds the address of a PVH entry point, indicating that
169+
/// the kernel supports the PVH boot protocol as described in:
170+
/// https://xenbits.xen.org/docs/unstable/misc/pvh.html
171+
pub pvh_entry_addr: Option<GuestAddress>,
153172
}
154173

155174
/// A kernel image loading support must implement the KernelLoader trait.
@@ -247,6 +266,10 @@ impl KernelLoader for Elf {
247266
// Read in each section pointed to by the program headers.
248267
for phdr in &phdrs {
249268
if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 {
269+
if phdr.p_type == elf::PT_NOTE {
270+
// This segment describes a Note, check if PVH entry point is encoded.
271+
loader_result.pvh_entry_addr = parse_elf_note(phdr, kernel_image)?;
272+
}
250273
continue;
251274
}
252275

@@ -280,6 +303,72 @@ impl KernelLoader for Elf {
280303
}
281304
}
282305

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

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

0 commit comments

Comments
 (0)