implement memory allocation in boot setup

This commit is contained in:
Alex Zenla
2024-01-10 16:07:57 -08:00
parent d46d0cf0c3
commit 153619a02c
11 changed files with 619 additions and 73 deletions

View File

@ -5,10 +5,12 @@ edition = "2021"
resolver = "2"
[dependencies]
libc = "0.2"
elf = "0.7.4"
flate2 = "1.0"
xz2 = "0.1"
memchr = "2"
slice-copy = "0.3.0"
[dependencies.xencall]
path = "../xencall"

View File

@ -1,9 +1,9 @@
use std::alloc::Layout;
use std::{env, process};
use xencall::domctl::DomainControl;
use xencall::memory::MemoryControl;
use xencall::sys::CreateDomain;
use xencall::XenCall;
use xenclient::boot::BootImageLoader;
use xenclient::boot::{BootImageLoader, BootSetup};
use xenclient::elfloader::ElfImageLoader;
use xenclient::XenClientError;
@ -19,15 +19,12 @@ fn main() -> Result<(), XenClientError> {
let domid = domctl.create_domain(CreateDomain::default())?;
let domain = domctl.get_domain_info(domid)?;
println!("domain created: {:?}", domain);
let boot = ElfImageLoader::load_file_kernel(kernel_image_path.as_str())?;
let ptr = unsafe { std::alloc::alloc(Layout::from_size_align(128 * 1024 * 1024, 16).unwrap()) };
let info = boot.load(ptr)?;
println!("loaded kernel image into memory: {:?}", info);
// The address calculations don't make sense here and I am certain something
// is wrong up the stack.
// if info.virt_hypercall != XEN_UNSET_ADDR {
// domctl.hypercall_init(domid, info.virt_hypercall)?;
// }
let image_loader = ElfImageLoader::load_file_kernel(kernel_image_path.as_str())?;
let image_info = image_loader.parse()?;
println!("loaded kernel image into memory: {:?}", image_info);
let memctl = MemoryControl::new(&call);
let mut boot = BootSetup::new(&call, &domctl, &memctl, domid, 512 * 1024);
boot.initialize(image_info)?;
domctl.destroy_domain(domid)?;
println!("domain destroyed: {}", domid);
Ok(())

View File

@ -1,7 +1,17 @@
use crate::mem::PhysicalPages;
use crate::sys::{
SUPERPAGE_2MB_NR_PFNS, SUPERPAGE_2MB_SHIFT, SUPERPAGE_BATCH_SIZE, XEN_PAGE_SHIFT,
};
use crate::XenClientError;
use libc::memset;
use std::ffi::c_void;
use xencall::domctl::DomainControl;
use xencall::memory::MemoryControl;
use xencall::XenCall;
pub trait BootImageLoader {
fn load(&self, dst: *mut u8) -> Result<BootImageInfo, XenClientError>;
fn parse(&self) -> Result<BootImageInfo, XenClientError>;
fn load(&self, image_info: BootImageInfo, dst: &mut [u8]) -> Result<(), XenClientError>;
}
pub const XEN_UNSET_ADDR: u64 = -1i64 as u64;
@ -11,6 +21,174 @@ pub struct BootImageInfo {
pub virt_kstart: u64,
pub virt_kend: u64,
pub virt_hypercall: u64,
pub entry: u64,
pub hv_start_low: u64,
pub virt_entry: u64,
pub init_p2m: u64,
}
pub struct BootSetup<'a> {
domctl: &'a DomainControl<'a>,
memctl: &'a MemoryControl<'a>,
phys: PhysicalPages<'a>,
domid: u32,
memkb: u64,
virt_alloc_end: u64,
pfn_alloc_end: u64,
}
struct DomainSegment {
_vstart: u64,
_vend: u64,
pfn: u64,
_pages: u64,
}
struct VmemRange {
start: u64,
end: u64,
_flags: u32,
_nid: u32,
}
impl BootSetup<'_> {
pub fn new<'a>(
call: &'a XenCall,
domctl: &'a DomainControl<'a>,
memctl: &'a MemoryControl<'a>,
domid: u32,
memkb: u64,
) -> BootSetup<'a> {
BootSetup {
domctl,
memctl,
phys: PhysicalPages::new(call, domid),
domid,
memkb,
virt_alloc_end: 0,
pfn_alloc_end: 0,
}
}
fn initialize_memory(&mut self) -> Result<(), XenClientError> {
let mem_mb: u64 = self.memkb / 1024;
let page_count: u64 = mem_mb << (20 - XEN_PAGE_SHIFT);
let mut pfn_base_idx: u64 = 0;
let mut vmemranges: Vec<VmemRange> = Vec::new();
let stub = VmemRange {
start: 0,
end: page_count << XEN_PAGE_SHIFT,
_flags: 0,
_nid: 0,
};
vmemranges.push(stub);
let mut p2m_size: u64 = 0;
let mut total: u64 = 0;
for range in &vmemranges {
total += (range.end - range.start) >> XEN_PAGE_SHIFT;
p2m_size = p2m_size.max(range.end >> XEN_PAGE_SHIFT);
}
if total != page_count {
return Err(XenClientError::new(
"Page count mismatch while calculating pages.",
));
}
let mut p2m = vec![-1i64 as u64; p2m_size as usize];
for range in &vmemranges {
let mut extents = vec![0u64; SUPERPAGE_BATCH_SIZE as usize];
let pages = (range.end - range.start) >> XEN_PAGE_SHIFT;
let pfn_base = range.start >> XEN_PAGE_SHIFT;
for pfn in pfn_base..pfn_base + pages {
p2m[pfn as usize] = pfn;
}
let mut super_pages = pages >> SUPERPAGE_2MB_SHIFT;
while super_pages > 0 {
let count = super_pages.min(SUPERPAGE_BATCH_SIZE);
super_pages -= count;
for (i, pfn) in (pfn_base_idx..(count << SUPERPAGE_2MB_SHIFT))
.step_by(SUPERPAGE_2MB_NR_PFNS as usize)
.enumerate()
{
extents[i] = p2m[pfn as usize];
}
let starts = self.memctl.populate_physmap(
self.domid,
count,
SUPERPAGE_2MB_SHIFT as u32,
0,
extents.as_slice(),
)?;
let pfn = pfn_base;
for mfn in starts {
for k in 0..SUPERPAGE_2MB_NR_PFNS {
p2m[pfn as usize] = mfn + k;
}
}
pfn_base_idx = pfn;
}
let mut j = pfn_base_idx - pfn_base;
loop {
if j >= pages {
break;
}
let allocsz = (pages - j).min(1024 * 1024);
let result = self.memctl.populate_physmap(
self.domid,
allocsz,
0,
0,
&[p2m[(pfn_base + j) as usize]],
)?;
p2m[(pfn_base + j) as usize] = result[0];
j += allocsz;
}
}
self.phys.load_p2m(p2m);
Ok(())
}
fn initialize_hypercall(&mut self, image_info: BootImageInfo) -> Result<(), XenClientError> {
if image_info.virt_hypercall != XEN_UNSET_ADDR {
self.domctl
.hypercall_init(self.domid, image_info.virt_hypercall)?;
}
Ok(())
}
pub fn initialize(&mut self, image_info: BootImageInfo) -> Result<(), XenClientError> {
self.initialize_memory()?;
let _kernel_segment = self.alloc_segment(image_info.virt_kend - image_info.virt_kstart)?;
self.initialize_hypercall(image_info)?;
Ok(())
}
fn alloc_segment(&mut self, size: u64) -> Result<DomainSegment, XenClientError> {
let page_size = 1u64 << XEN_PAGE_SHIFT;
let pages = (size + page_size - 1) / page_size;
let start = self.virt_alloc_end;
let mut segment = DomainSegment {
_vstart: start,
_vend: 0,
pfn: self.pfn_alloc_end,
_pages: pages,
};
let ptr = self.phys.pfn_to_ptr(segment.pfn, pages)?;
unsafe {
memset(ptr as *mut c_void, 0, (pages * page_size) as usize);
}
self.virt_alloc_end += pages * page_size;
segment._vend = self.virt_alloc_end;
self.pfn_alloc_end += 1;
Ok(segment)
}
}

View File

@ -1,6 +1,7 @@
use crate::boot::{BootImageInfo, BootImageLoader, XEN_UNSET_ADDR};
use crate::sys::{
XEN_ELFNOTE_ENTRY, XEN_ELFNOTE_HV_START_LOW, XEN_ELFNOTE_HYPERCALL_PAGE, XEN_ELFNOTE_VIRT_BASE,
XEN_ELFNOTE_ENTRY, XEN_ELFNOTE_HYPERCALL_PAGE, XEN_ELFNOTE_INIT_P2M, XEN_ELFNOTE_PADDR_OFFSET,
XEN_ELFNOTE_TYPES, XEN_ELFNOTE_VIRT_BASE,
};
use crate::XenClientError;
use elf::abi::{PF_R, PF_W, PF_X, PT_LOAD, SHT_NOTE};
@ -9,7 +10,9 @@ use elf::note::Note;
use elf::{ElfBytes, ParseError};
use flate2::bufread::GzDecoder;
use memchr::memmem::find_iter;
use slice_copy::copy;
use std::collections::HashMap;
use std::ffi::{FromVecWithNulError, IntoStringError};
use std::io::{BufReader, Read};
use std::mem::size_of;
use xz2::bufread::XzDecoder;
@ -20,40 +23,48 @@ impl From<ParseError> for XenClientError {
}
}
impl From<FromVecWithNulError> for XenClientError {
fn from(value: FromVecWithNulError) -> Self {
XenClientError::new(value.to_string().as_str())
}
}
impl From<IntoStringError> for XenClientError {
fn from(value: IntoStringError) -> Self {
XenClientError::new(value.to_string().as_str())
}
}
pub struct ElfImageLoader {
data: Vec<u8>,
}
fn xen_note_value_as_u64(
endian: AnyEndian,
notes: &HashMap<u64, Vec<u8>>,
key: u64,
) -> Option<u64> {
let value = notes.get(&key)?;
fn xen_note_value_as_u64(endian: AnyEndian, value: &[u8]) -> Option<u64> {
let bytes = value.to_vec();
match value.len() {
1 => {
let bytes: Option<[u8; size_of::<u8>()]> = value.clone().try_into().ok();
let bytes: Option<[u8; size_of::<u8>()]> = bytes.try_into().ok();
Some(match endian {
AnyEndian::Little => u8::from_le_bytes(bytes?),
AnyEndian::Big => u8::from_be_bytes(bytes?),
} as u64)
}
2 => {
let bytes: Option<[u8; size_of::<u16>()]> = value.clone().try_into().ok();
let bytes: Option<[u8; size_of::<u16>()]> = bytes.try_into().ok();
Some(match endian {
AnyEndian::Little => u16::from_le_bytes(bytes?),
AnyEndian::Big => u16::from_be_bytes(bytes?),
} as u64)
}
4 => {
let bytes: Option<[u8; size_of::<u32>()]> = value.clone().try_into().ok();
let bytes: Option<[u8; size_of::<u32>()]> = bytes.try_into().ok();
Some(match endian {
AnyEndian::Little => u32::from_le_bytes(bytes?),
AnyEndian::Big => u32::from_be_bytes(bytes?),
} as u64)
}
8 => {
let bytes: Option<[u8; size_of::<u64>()]> = value.clone().try_into().ok();
let bytes: Option<[u8; size_of::<u64>()]> = bytes.try_into().ok();
Some(match endian {
AnyEndian::Little => u64::from_le_bytes(bytes?),
AnyEndian::Big => u64::from_be_bytes(bytes?),
@ -139,14 +150,18 @@ impl ElfImageLoader {
}
}
struct ElfNoteValue {
value: u64,
}
impl BootImageLoader for ElfImageLoader {
fn load(&self, dst: *mut u8) -> Result<BootImageInfo, XenClientError> {
fn parse(&self) -> Result<BootImageInfo, XenClientError> {
let elf = ElfBytes::<AnyEndian>::minimal_parse(self.data.as_slice())?;
let headers = elf.section_headers().ok_or(XenClientError::new(
"Unable to parse kernel image: section headers not found.",
))?;
let mut linux_notes: HashMap<u64, Vec<u8>> = HashMap::new();
let mut xen_notes: HashMap<u64, Vec<u8>> = HashMap::new();
let mut xen_notes: HashMap<u64, ElfNoteValue> = HashMap::new();
for header in headers {
if header.sh_type != SHT_NOTE {
@ -161,7 +176,19 @@ impl BootImageLoader for ElfImageLoader {
}
if note.name == "Xen" {
xen_notes.insert(note.n_type, note.desc.to_vec());
for typ in XEN_ELFNOTE_TYPES {
if typ.id != note.n_type {
continue;
}
let value = if !typ.is_string {
xen_note_value_as_u64(elf.ehdr.endianness, note.desc).unwrap_or(0)
} else {
0
};
xen_notes.insert(typ.id, ElfNoteValue { value });
}
continue;
}
}
@ -180,23 +207,34 @@ impl BootImageLoader for ElfImageLoader {
));
}
let virt_base =
xen_note_value_as_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_VIRT_BASE).ok_or(
XenClientError::new("Unable to find virt_base note in kernel."),
)?;
let entry = xen_note_value_as_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_ENTRY)
.ok_or(XenClientError::new("Unable to find entry note in kernel."))?;
let hv_start_low =
xen_note_value_as_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_HV_START_LOW)
.ok_or(XenClientError::new(
"Unable to find hv_start_low note in kernel.",
))?;
let hypercall_page =
xen_note_value_as_u64(elf.ehdr.endianness, &xen_notes, XEN_ELFNOTE_HYPERCALL_PAGE)
.ok_or(XenClientError::new(
"Unable to find hypercall_page note in kernel.",
))?;
let paddr_offset = xen_notes
.get(&XEN_ELFNOTE_PADDR_OFFSET)
.ok_or(XenClientError::new(
"Unable to find paddr_offset note in kernel.",
))?
.value;
let virt_base = xen_notes
.get(&XEN_ELFNOTE_VIRT_BASE)
.ok_or(XenClientError::new(
"Unable to find virt_base note in kernel.",
))?
.value;
let entry = xen_notes
.get(&XEN_ELFNOTE_ENTRY)
.ok_or(XenClientError::new("Unable to find entry note in kernel."))?
.value;
let virt_hypercall = xen_notes
.get(&XEN_ELFNOTE_HYPERCALL_PAGE)
.ok_or(XenClientError::new(
"Unable to find hypercall_page note in kernel.",
))?
.value;
let init_p2m = xen_notes
.get(&XEN_ELFNOTE_INIT_P2M)
.ok_or(XenClientError::new(
"Unable to find init_p2m note in kernel.",
))?
.value;
let mut start: u64 = u64::MAX;
let mut end: u64 = 0;
@ -204,12 +242,13 @@ impl BootImageLoader for ElfImageLoader {
let segments = elf.segments().ok_or(XenClientError::new(
"Unable to parse kernel image: segments not found.",
))?;
for segment in segments {
if (segment.p_type != PT_LOAD) || (segment.p_flags & (PF_R | PF_W | PF_X)) == 0 {
for header in segments {
if (header.p_type != PT_LOAD) || (header.p_flags & (PF_R | PF_W | PF_X)) == 0 {
continue;
}
let paddr = segment.p_paddr;
let memsz = segment.p_memsz;
let paddr = header.p_paddr;
let memsz = header.p_memsz;
if start > paddr {
start = paddr;
}
@ -219,34 +258,61 @@ impl BootImageLoader for ElfImageLoader {
}
}
let base_dst_addr = dst as u64;
for header in segments {
let paddr = header.p_paddr;
let filesz = header.p_filesz;
let memsz = header.p_memsz;
let dest = base_dst_addr + paddr - start;
let data = elf.segment_data(&header)?;
unsafe {
std::ptr::copy(data.as_ptr(), dest as *mut u8, filesz as usize);
std::ptr::write_bytes((dest + filesz) as *mut u8, 0, (memsz - filesz) as usize);
}
if paddr_offset != XEN_UNSET_ADDR && virt_base == XEN_UNSET_ADDR {
return Err(XenClientError::new(
"Unable to load kernel image: paddr_offset set but virt_base is unset.",
));
}
let virt_base = if virt_base == XEN_UNSET_ADDR {
0
} else {
virt_base
};
let virt_kstart = start.wrapping_add(virt_base);
let virt_kend = end.wrapping_add(virt_base);
let virt_hypercall = hypercall_page.wrapping_add(virt_base);
let paddr_offset = if paddr_offset == XEN_UNSET_ADDR {
0
} else {
paddr_offset
};
let virt_offset = virt_base - paddr_offset;
let virt_kstart = start + virt_offset;
let virt_kend = end + virt_offset;
let virt_entry = if entry == XEN_UNSET_ADDR {
elf.ehdr.e_entry
} else {
entry
};
Ok(BootImageInfo {
virt_kstart,
virt_kend,
virt_hypercall,
entry,
hv_start_low,
virt_entry,
init_p2m,
})
}
fn load(&self, image_info: BootImageInfo, dst: &mut [u8]) -> Result<(), XenClientError> {
let elf = ElfBytes::<AnyEndian>::minimal_parse(self.data.as_slice())?;
let segments = elf.segments().ok_or(XenClientError::new(
"Unable to parse kernel image: segments not found.",
))?;
for header in segments {
let paddr = header.p_paddr;
let filesz = header.p_filesz;
let memsz = header.p_memsz;
let base_offset = paddr - image_info.virt_kstart;
let data = elf.segment_data(&header)?;
let segment_dst = &mut dst[base_offset as usize..];
copy(segment_dst, &data[0..filesz as usize]);
if memsz - filesz > 0 {
let remaining = &mut segment_dst[filesz as usize..(memsz - filesz) as usize];
remaining.fill(0);
}
}
Ok(())
}
}

View File

@ -1,6 +1,7 @@
pub mod boot;
pub mod create;
pub mod elfloader;
pub mod mem;
pub mod sys;
use crate::create::DomainConfig;

99
xenclient/src/mem.rs Normal file
View File

@ -0,0 +1,99 @@
use crate::sys::XEN_PAGE_SHIFT;
use crate::XenClientError;
use xencall::sys::MmapEntry;
use xencall::XenCall;
pub struct PhysicalPage {
pfn: u64,
ptr: u64,
size: u64,
}
pub struct PhysicalPages<'a> {
domid: u32,
p2m: Vec<u64>,
call: &'a XenCall,
pages: Vec<PhysicalPage>,
}
impl PhysicalPages<'_> {
pub fn new(call: &XenCall, domid: u32) -> PhysicalPages {
PhysicalPages {
domid,
p2m: Vec::new(),
call,
pages: Vec::new(),
}
}
pub fn load_p2m(&mut self, p2m: Vec<u64>) {
self.p2m = p2m;
}
pub fn pfn_to_ptr(&mut self, pfn: u64, count: u64) -> Result<u64, XenClientError> {
for page in &self.pages {
if pfn >= page.pfn + page.size {
continue;
}
if count > 0 {
if (pfn + count) <= page.pfn {
continue;
}
if pfn < page.pfn || (pfn + count) > page.pfn + page.size {
return Err(XenClientError::new("request overlaps allocated block"));
}
} else {
if pfn < page.pfn {
continue;
}
if pfn >= page.pfn + page.size {
continue;
}
}
return Ok(page.ptr + ((pfn - page.pfn) << XEN_PAGE_SHIFT));
}
if count == 0 {
return Err(XenClientError::new(
"allocation is only allowed when a size is given",
));
}
self.pfn_alloc(pfn, count)
}
fn pfn_alloc(&mut self, pfn: u64, count: u64) -> Result<u64, XenClientError> {
let mut entries = vec![MmapEntry::default(); count as usize];
for (i, entry) in (0_u64..).zip(entries.iter_mut()) {
entry.mfn = self.p2m[(pfn + i) as usize];
}
let chunk_size = 1 << XEN_PAGE_SHIFT;
let num_per_entry = chunk_size >> XEN_PAGE_SHIFT;
let num = num_per_entry * entries.len();
let mut pfns = vec![0u64; num];
for i in 0..entries.len() {
for j in 0..num_per_entry {
pfns[i * num_per_entry + j] = entries[i].mfn + j as u64;
}
}
let size = (num as u64) << XEN_PAGE_SHIFT;
let addr = self
.call
.mmap(0, size)
.ok_or(XenClientError::new("failed to mmap address"))?;
self.call.mmap_batch(self.domid, num as u64, addr, pfns)?;
let page = PhysicalPage {
pfn,
ptr: addr,
size,
};
self.pages.push(page);
Ok(addr)
}
}

View File

@ -17,3 +17,105 @@ pub const XEN_ELFNOTE_INIT_P2M: u64 = 15;
pub const XEN_ELFNOTE_MOD_START_PFN: u64 = 16;
pub const XEN_ELFNOTE_SUPPORTED_FEATURES: u64 = 17;
pub const XEN_ELFNOTE_PHYS32_ENTRY: u64 = 18;
#[derive(Copy, Clone)]
pub struct ElfNoteXenType {
pub id: u64,
pub name: &'static str,
pub is_string: bool,
}
pub const XEN_ELFNOTE_TYPES: &[ElfNoteXenType] = &[
ElfNoteXenType {
id: XEN_ELFNOTE_ENTRY,
name: "ENTRY",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_HYPERCALL_PAGE,
name: "HYPERCALL_PAGE",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_VIRT_BASE,
name: "VIRT_BASE",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_INIT_P2M,
name: "INIT_P2M",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_PADDR_OFFSET,
name: "PADDR_OFFSET",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_HV_START_LOW,
name: "HV_START_LOW",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_XEN_VERSION,
name: "XEN_VERSION",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_GUEST_OS,
name: "GUEST_OS",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_GUEST_VERSION,
name: "GUEST_VERSION",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_LOADER,
name: "LOADER",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_PAE_MODE,
name: "PAE_MODE",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_FEATURES,
name: "FEATURES",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_SUPPORTED_FEATURES,
name: "SUPPORTED_FEATURES",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_BSD_SYMTAB,
name: "BSD_SYMTAB",
is_string: true,
},
ElfNoteXenType {
id: XEN_ELFNOTE_SUSPEND_CANCEL,
name: "SUSPEND_CANCEL",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_MOD_START_PFN,
name: "MOD_START_PFN",
is_string: false,
},
ElfNoteXenType {
id: XEN_ELFNOTE_PHYS32_ENTRY,
name: "PHYS32_ENTRY",
is_string: false,
},
];
pub const XEN_PAGE_SHIFT: u64 = 12;
pub const XEN_PAGE_SIZE: u64 = 1 << XEN_PAGE_SHIFT;
pub const XEN_PAGE_MASK: u64 = !(XEN_PAGE_SIZE - 1);
pub const SUPERPAGE_BATCH_SIZE: u64 = 512;
pub const SUPERPAGE_2MB_SHIFT: u64 = 9;
pub const SUPERPAGE_2MB_NR_PFNS: u64 = 1u64 << SUPERPAGE_2MB_SHIFT;