summary refs log tree commit diff
path: root/x86_64
diff options
context:
space:
mode:
authorDaniel Verkamp <dverkamp@chromium.org>2019-05-13 12:34:54 -0700
committerchrome-bot <chrome-bot@chromium.org>2019-05-14 23:57:41 -0700
commit050af40382a5619278fc08eef7dfbde5591abd76 (patch)
treec8f4284dbe5b59e91b0f66b9466519cfeeccad24 /x86_64
parent21accb31ac9056ab28930689ddd9dce185956258 (diff)
downloadcrosvm-050af40382a5619278fc08eef7dfbde5591abd76.tar
crosvm-050af40382a5619278fc08eef7dfbde5591abd76.tar.gz
crosvm-050af40382a5619278fc08eef7dfbde5591abd76.tar.bz2
crosvm-050af40382a5619278fc08eef7dfbde5591abd76.tar.lz
crosvm-050af40382a5619278fc08eef7dfbde5591abd76.tar.xz
crosvm-050af40382a5619278fc08eef7dfbde5591abd76.tar.zst
crosvm-050af40382a5619278fc08eef7dfbde5591abd76.zip
x86_64: support loading bzImage kernels
The current kernel loader expects an extracted ELF kernel; this adds
complexity to the build and test process for the guest kernel, since the
normal output of a Linux kernel build is a bzImage-format kernel.

bzImage also supports compressed kernels, which are smaller on disk and
potentially quicker to load, depending on disk and CPU speed.

Add support for loading of bzImage-format kernels, and use the 64-bit
boot protocol as described in the official Linux/x86 boot protocol:
https://www.kernel.org/doc/Documentation/x86/boot.txt

The existing ELF loader is kept for compatibility with shipping kernel
images; if a kernel image doesn't have the ELF signature, it is passed
to the bzImage loader as a fallback.

BUG=None
TEST=Boot bzImage and extracted ELF kernels on x86-64

Change-Id: I90be4cd597d15bc89e63f0f6cbc781c5c8c2eaeb
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1609969
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'x86_64')
-rw-r--r--x86_64/src/bzimage.rs101
-rw-r--r--x86_64/src/lib.rs24
2 files changed, 119 insertions, 6 deletions
diff --git a/x86_64/src/bzimage.rs b/x86_64/src/bzimage.rs
new file mode 100644
index 0000000..16a7338
--- /dev/null
+++ b/x86_64/src/bzimage.rs
@@ -0,0 +1,101 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Loader for bzImage-format Linux kernels as described in
+// https://www.kernel.org/doc/Documentation/x86/boot.txt
+
+use std::fmt::{self, Display};
+use std::io::{Read, Seek, SeekFrom};
+
+use sys_util::{GuestAddress, GuestMemory};
+
+use crate::bootparam::boot_params;
+
+#[derive(Debug, PartialEq)]
+pub enum Error {
+    BadSignature,
+    InvalidSetupSects,
+    InvalidSysSize,
+    ReadBootParams,
+    ReadKernelImage,
+    SeekBootParams,
+    SeekKernelStart,
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl std::error::Error for Error {}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::Error::*;
+
+        let description = match self {
+            BadSignature => "bad kernel header signature",
+            InvalidSetupSects => "invalid setup_sects value",
+            InvalidSysSize => "invalid syssize value",
+            ReadBootParams => "unable to read boot_params",
+            ReadKernelImage => "unable to read kernel image",
+            SeekBootParams => "unable to seek to boot_params",
+            SeekKernelStart => "unable to seek to kernel start",
+        };
+
+        write!(f, "bzImage loader: {}", description)
+    }
+}
+
+/// Loads a kernel from a bzImage to a slice
+///
+/// # Arguments
+///
+/// * `guest_mem` - The guest memory region the kernel is written to.
+/// * `kernel_start` - The offset into `guest_mem` at which to load the kernel.
+/// * `kernel_image` - Input bzImage.
+pub fn load_bzimage<F>(
+    guest_mem: &GuestMemory,
+    kernel_start: GuestAddress,
+    kernel_image: &mut F,
+) -> Result<(boot_params, u64)>
+where
+    F: Read + Seek,
+{
+    let mut params: boot_params = Default::default();
+    kernel_image
+        .seek(SeekFrom::Start(0))
+        .map_err(|_| Error::SeekBootParams)?;
+    unsafe {
+        // read_struct is safe when reading a POD struct.  It can be used and dropped without issue.
+        sys_util::read_struct(kernel_image, &mut params).map_err(|_| Error::ReadBootParams)?;
+    }
+
+    // bzImage header signature "HdrS"
+    if params.hdr.header != 0x53726448 {
+        return Err(Error::BadSignature);
+    }
+
+    let setup_sects = if params.hdr.setup_sects == 0 {
+        4u64
+    } else {
+        params.hdr.setup_sects as u64
+    };
+
+    let kernel_offset = setup_sects
+        .checked_add(1)
+        .ok_or(Error::InvalidSetupSects)?
+        .checked_mul(512)
+        .ok_or(Error::InvalidSetupSects)?;
+    let kernel_size = (params.hdr.syssize as usize)
+        .checked_mul(16)
+        .ok_or(Error::InvalidSysSize)?;
+
+    kernel_image
+        .seek(SeekFrom::Start(kernel_offset))
+        .map_err(|_| Error::SeekKernelStart)?;
+
+    // Load the whole kernel image to kernel_start
+    guest_mem
+        .read_to_memory(kernel_start, kernel_image, kernel_size)
+        .map_err(|_| Error::ReadKernelImage)?;
+
+    Ok((params, kernel_start.offset() + kernel_size as u64))
+}
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 455e0ea..23df5ea 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -46,6 +46,7 @@ unsafe impl data_model::DataInit for mpspec::mpc_table {}
 unsafe impl data_model::DataInit for mpspec::mpc_lintsrc {}
 unsafe impl data_model::DataInit for mpspec::mpf_intel {}
 
+mod bzimage;
 mod cpuid;
 mod gdt;
 mod interrupts;
@@ -91,6 +92,7 @@ pub enum Error {
     CreateVm(sys_util::Error),
     E820Configuration,
     KernelOffsetPastEnd,
+    LoadBzImage(bzimage::Error),
     LoadCmdline(kernel_loader::Error),
     LoadInitrd(arch::LoadImageError),
     LoadKernel(kernel_loader::Error),
@@ -133,6 +135,7 @@ impl Display for Error {
             CreateVm(e) => write!(f, "failed to create VM: {}", e),
             E820Configuration => write!(f, "invalid e820 setup params"),
             KernelOffsetPastEnd => write!(f, "the kernel extends past the end of RAM"),
+            LoadBzImage(e) => write!(f, "error loading kernel bzImage: {}", e),
             LoadCmdline(e) => write!(f, "error loading command line: {}", e),
             LoadInitrd(e) => write!(f, "error loading initrd: {}", e),
             LoadKernel(e) => write!(f, "error loading Kernel: {}", e),
@@ -181,6 +184,7 @@ fn configure_system(
     pci_irqs: Vec<(u32, PciInterruptPin)>,
     setup_data: Option<GuestAddress>,
     initrd: Option<(GuestAddress, usize)>,
+    mut params: boot_params,
 ) -> Result<()> {
     const EBDA_START: u64 = 0x0009fc00;
     const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
@@ -195,8 +199,6 @@ fn configure_system(
 
     smbios::setup_smbios(guest_mem).map_err(Error::SetupSmbios)?;
 
-    let mut params: boot_params = Default::default();
-
     params.hdr.type_of_loader = KERNEL_LOADER_OTHER;
     params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
     params.hdr.header = KERNEL_HDR_MAGIC;
@@ -353,7 +355,7 @@ impl arch::LinuxArch for X8664arch {
 
         // separate out load_kernel from other setup to get a specific error for
         // kernel loading
-        let kernel_end = Self::load_kernel(&mem, &mut components.kernel_image)?;
+        let (params, kernel_end) = Self::load_kernel(&mem, &mut components.kernel_image)?;
 
         Self::setup_system_memory(
             &mem,
@@ -364,6 +366,7 @@ impl arch::LinuxArch for X8664arch {
             pci_irqs,
             components.android_fstab,
             kernel_end,
+            params,
         )?;
 
         Ok(RunnableLinuxVm {
@@ -389,9 +392,16 @@ impl X8664arch {
     ///
     /// * `mem` - The memory to be used by the guest.
     /// * `kernel_image` - the File object for the specified kernel.
-    fn load_kernel(mem: &GuestMemory, mut kernel_image: &mut File) -> Result<u64> {
-        kernel_loader::load_kernel(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image)
-            .map_err(Error::LoadKernel)
+    fn load_kernel(mem: &GuestMemory, mut kernel_image: &mut File) -> Result<(boot_params, u64)> {
+        let elf_result =
+            kernel_loader::load_kernel(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image);
+        if elf_result == Err(kernel_loader::Error::InvalidElfMagicNumber) {
+            bzimage::load_bzimage(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image)
+                .map_err(Error::LoadBzImage)
+        } else {
+            let kernel_end = elf_result.map_err(Error::LoadKernel)?;
+            Ok((Default::default(), kernel_end))
+        }
     }
 
     /// Configures the system memory space should be called once per vm before
@@ -412,6 +422,7 @@ impl X8664arch {
         pci_irqs: Vec<(u32, PciInterruptPin)>,
         android_fstab: Option<File>,
         kernel_end: u64,
+        params: boot_params,
     ) -> Result<()> {
         kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline)
             .map_err(Error::LoadCmdline)?;
@@ -462,6 +473,7 @@ impl X8664arch {
             pci_irqs,
             setup_data,
             initrd,
+            params,
         )?;
         Ok(())
     }