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>
This commit is contained in:
Daniel Verkamp 2019-05-13 12:34:54 -07:00 committed by chrome-bot
parent 21accb31ac
commit 050af40382
2 changed files with 119 additions and 6 deletions

101
x86_64/src/bzimage.rs Normal file
View file

@ -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))
}

View file

@ -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(())
}