blob: 8603d3a0b441db3dc654464c3a510d73961d7dc6 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*!
* Functions to convert a given ELF formatted binary into a format suitable for
* loading in CantripOS.
*
* The format of a CantripOS binary consists of a series of sections each prefixed
* by the following SectionHeader:
* magic: u64 Magic number
* vaddr: u64 Virtual address of section (bytes)
* entry: u64 Entry point; valid only when SECTION_ENTRY is set in flags
* flags: u32 See below
* fsize: u32 Length of data that follows (bytes)
* msize: u32 Size of memory region (bytes)
* align: u32 Section data alignment (bytes)
* ftype: u32 File type (see below)
* crc32: u32 CRC32 of the data that follows
*
* Section header flags (mostly from ELF program section):
* SECTION_READ: u32 = 0x1; // Data are readable
* SECTION_WRITE: u32 = 0x2; // Data are writeable
* SECTION_EXEC: u32 = 0x4; // Data are executable
* SECTION_ENTRY: u32 = 0x8; // Entry point valid
*
* All values are written big-endian.
*
* To ingest this data:
* 1. Read a page
* 2. Interpret the section header to find where the data should land
* 3. Copy fsize bytes of data to vaddr
* 4. (optionally) zero-pad fsize - msize bytes
* 5. Repeat until EOD or an invalid section header (typically the MAGIC
* number check).
*/
use core::mem::size_of;
use core::ptr;
use core::slice;
use crc::crc32;
use crc::Hasher32;
use log::*;
use std::fs::File;
use std::io::Seek;
use std::io::Write;
use xmas_elf::program::ProgramHeader;
use xmas_elf::program::SegmentData;
use xmas_elf::program::Type;
use xmas_elf::ElfFile;
/// Predicate to determine if an ELF program header is a loadble type.
fn is_load_type(seg: &ProgramHeader) -> bool {
if let Ok(t) = seg.get_type() {
t == Type::Load
} else {
false
}
}
// TODO(sleffler): use runtime defs
const MAGIC: u64 = 0x0405_1957_1014_1955;
const SECTION_READ: u32 = 0x1; // Data are readable
const SECTION_WRITE: u32 = 0x2; // Data are writeable
const SECTION_EXEC: u32 = 0x4; // Data are executable
const SECTION_ENTRY: u32 = 0x8; // Entry point valid
const FTYPE_APPLICATION: u32 = 0x0405_1957; // CantripOS application
const FTYPE_SPRINGBOK: u32 = 0x1014_1955; // Springbok model
const FTYPE_KELVIN: u32 = 0x0124_1998; // Kelvin model
/// Given a set of ELF flags, convert into a set of flags for a section
/// recognizable by the CantripOS ProcessManager's loader.
fn to_section_flags(elf_flags: xmas_elf::program::Flags) -> u32 {
let mut flags: u32 = 0;
if elf_flags.is_read() {
flags |= SECTION_READ as u32
}
if elf_flags.is_write() {
flags |= SECTION_WRITE as u32
}
if elf_flags.is_execute() {
flags |= SECTION_EXEC as u32
}
flags
}
#[repr(packed)]
#[allow(dead_code)]
struct SectionHeader {
magic: u64,
vaddr: u64,
entry: u64, // Entry point when SECTION_ENTRY set
flags: u32, // Derived from ELF program section flags
fsize: u32,
msize: u32,
align: u32, // Section data alignment (bytes)
ftype: u32, // File type
crc32: u32,
}
impl SectionHeader {
fn new(
ftype: u32,
vaddr: u64,
flags: u32,
entry: u64,
align: usize,
crc: u32,
fsize: usize,
msize: usize,
) -> Self {
SectionHeader {
magic: MAGIC.to_be(),
vaddr: vaddr.to_be(),
entry: if (flags & (SECTION_ENTRY as u32)) != 0 {
(entry as u64).to_be()
} else {
0
},
flags: (flags as u32).to_be(),
fsize: (fsize as u32).to_be(),
msize: (msize as u32).to_be(),
align: (align as u32).to_be(),
ftype: ftype.to_be(),
crc32: crc.to_be(),
}
}
fn as_slice(&self) -> &[u8] {
unsafe {
// NB: this depends on repr(packed)
slice::from_raw_parts(ptr::addr_of!(self.magic) as _, size_of::<SectionHeader>())
}
}
/// Writes section header followed by |bytes|.
fn write(&self, out: &mut File, bytes: &[u8]) -> Result<usize, std::io::Error> {
assert_eq!(u32::from_be(self.fsize) as usize, bytes.len());
out.write(self.as_slice()).and_then(|_| out.write(bytes))
}
}
#[derive(Debug)]
pub enum ConversionError {
SegmentOutsideTCM(u64),
SectionTooBig(u64, usize),
IOError(std::io::Error),
OtherError(&'static str),
}
impl ConversionError {
#[allow(dead_code)]
pub fn as_str(&self) -> String {
match &*self {
ConversionError::SegmentOutsideTCM(vaddr) =>
format!("segment at {:#x} outside TCM", vaddr),
ConversionError::SectionTooBig(vaddr, size) =>
format!("section at {:#x} too big for TCM (size was {})", vaddr, size),
ConversionError::IOError(err) => err.to_string(),
ConversionError::OtherError(str) => String::from(*str),
}
}
}
impl From<&'static str> for ConversionError {
fn from(s: &'static str) -> ConversionError {
ConversionError::OtherError(s)
}
}
impl From<std::io::Error> for ConversionError {
fn from(e: std::io::Error) -> ConversionError {
ConversionError::IOError(e)
}
}
pub mod springbok {
/// Springbok support.
use super::*;
/// Maximum memory size for TCM, used for model loading
const TCM_SIZE: usize = 0x1000000;
/// The virtualized address of each loadable WMMU section (go/shodan-vc-memory).
const TEXT_VADDR: u64 = 0x80000000;
const CONST_DATA_VADDR: u64 = 0x81000000;
const MODEL_OUTPUT_VADDR: u64 = 0x82000000;
const STATIC_DATA_VADDR: u64 = 0x83000000;
const TEMP_DATA_VADDR: u64 = 0x85000000;
/// Helpful conversion from a ModelSection to a string name of that address
fn vaddr_as_str(vaddr: u64) -> &'static str {
match vaddr {
TEXT_VADDR => ".text",
CONST_DATA_VADDR => ".data",
MODEL_OUTPUT_VADDR => ".model_output",
STATIC_DATA_VADDR => ".static",
TEMP_DATA_VADDR => ".bss",
_ => "<unknown>",
}
}
/// Predicate to determine if a given vaddr is loadable in the TCM
fn is_vaddr_in_tcm(vaddr: u64) -> bool {
matches!(vaddr, TEXT_VADDR | CONST_DATA_VADDR | MODEL_OUTPUT_VADDR | STATIC_DATA_VADDR
| TEMP_DATA_VADDR)
}
/// Converts an ELF-format ML (Springbok) model into CantripOS' loadable format.
///
/// Returns the number of bytes written.
pub fn model(elf: &ElfFile, output_file: &mut File) -> Result<u64, ConversionError> {
let entry = elf.header.pt2.entry_point();
info!("ELF entry point is {:#x}", entry);
for seg in elf.program_iter().filter(super::is_load_type) {
let fsize = seg.file_size() as usize;
let msize = seg.mem_size() as usize;
let align = seg.align() as usize;
let mut flags = to_section_flags(seg.flags());
let vaddr = seg.virtual_addr();
let segment_name = vaddr_as_str(vaddr);
debug!("Processing new section [vaddr={:#x}, fsize={:#x}, msize={:#x}, align={:#x}, flags={:#b}]",
vaddr, fsize, msize, align, flags);
if fsize > TCM_SIZE {
return Err(ConversionError::SectionTooBig(vaddr, fsize));
}
if let SegmentData::Undefined(bytes) = seg.get_data(elf)? {
if !is_vaddr_in_tcm(vaddr) {
return Err(ConversionError::SegmentOutsideTCM(vaddr));
}
if seg.virtual_addr() == TEXT_VADDR {
debug!(
"Marking segment {} as entrypoint [vaddr={:#x}, entry={:#x}]",
segment_name, vaddr, entry
);
flags |= SECTION_ENTRY;
}
debug!(
"Processing {} segment [len={}, addr={:#x}, msize={}]",
segment_name,
bytes.len(),
vaddr,
msize
);
let mut digest = crc32::Digest::new(crc32::IEEE);
digest.write(bytes);
let section = SectionHeader::new(
FTYPE_SPRINGBOK,
seg.virtual_addr(),
flags,
entry,
align,
digest.sum32(),
fsize,
msize,
);
section.write(output_file, bytes)?;
info!(
"Wrote {} segment of {} bytes at {:#x} msize {}",
segment_name,
bytes.len(),
seg.virtual_addr(),
msize
);
}
}
Ok(output_file.stream_position()?)
}
} // springbok
pub mod kelvin {
/// Kelvin support.
use super::*;
/// Maximum memory size for TCM, used for model loading
const TCM_SIZE: usize = 0x400000;
/// Converts an ELF-format Kelvin workload into CantripOS' loadable format.
///
/// Returns the number of bytes written.
pub fn model(elf: &ElfFile, output_file: &mut File) -> Result<u64, ConversionError> {
let entry = elf.header.pt2.entry_point() as usize;
info!("ELF entry point is {:#x}", entry);
for seg in elf.program_iter().filter(super::is_load_type) {
let fsize = seg.file_size() as usize;
let msize = seg.mem_size() as usize;
let align = seg.align() as usize;
let mut flags = to_section_flags(seg.flags());
let vaddr = seg.virtual_addr() as usize;
let segment_name = "LOAD"; // XXX
debug!("Processing new section [vaddr={:#x}, fsize={:#x}, msize={:#x}, align={:#x}, flags={:#b}]",
vaddr, fsize, msize, align, flags);
if let SegmentData::Undefined(bytes) = seg.get_data(elf)? {
if vaddr + msize >= TCM_SIZE {
return Err(ConversionError::SegmentOutsideTCM(vaddr as u64));
}
if vaddr <= entry && entry < vaddr + msize {
debug!(
"Marking segment {} as entrypoint [vaddr={:#x}, entry={:#x}]",
segment_name, vaddr, entry
);
flags |= SECTION_ENTRY;
}
debug!(
"Processing {} segment [len={}, addr={:#x}, msize={}]",
segment_name,
bytes.len(),
vaddr,
msize
);
let mut digest = crc32::Digest::new(crc32::IEEE);
digest.write(bytes);
let section = SectionHeader::new(
FTYPE_KELVIN,
seg.virtual_addr(),
flags,
entry as u64,
align,
digest.sum32(),
fsize,
msize,
);
section.write(output_file, bytes)?;
info!(
"Wrote {} segment of {} bytes at {:#x} msize {}",
segment_name,
bytes.len(),
seg.virtual_addr(),
msize
);
}
}
Ok(output_file.stream_position()?)
}
} // kelvin
/// Converts an ELF-format application binary into CantripOS' loadable format.
///
/// Returns the number of bytes written.
pub fn application(elf: &ElfFile, output_file: &mut File) -> Result<u64, ConversionError> {
let entry = elf.header.pt2.entry_point();
info!("ELF entry point is {:#x}", entry);
// Iterate through all ELF sections, filtering out anything that isn't
// loadable.
for seg in elf.program_iter().filter(is_load_type) {
let vaddr = seg.virtual_addr();
let fsize = seg.file_size() as usize;
let msize = seg.mem_size() as usize;
let align = seg.align() as usize;
let mut flags = to_section_flags(seg.flags());
debug!("Processing new section [vaddr={:#x}, fsize={:#x}, msize={:#x}, align={:#x}, flags={:#b}]",
vaddr, fsize, msize, align, flags);
// If the entry point for this application is in this section, ensure we
// mark as such in the flags.
if vaddr <= entry && entry < vaddr + (msize as u64) {
debug!("Marking as entrypoint [vaddr={:#x}, entry={:#x}]", vaddr, entry);
flags |= SECTION_ENTRY;
}
if let SegmentData::Undefined(bytes) = seg.get_data(elf)? {
let mut digest = crc32::Digest::new(crc32::IEEE);
digest.write(bytes);
let header = SectionHeader::new(
FTYPE_APPLICATION,
seg.virtual_addr(),
flags,
entry,
align,
digest.sum32(),
fsize,
msize,
);
header.write(output_file, bytes)?;
info!(
"Wrote segment of {} bytes at {:#x} msize {}",
fsize,
seg.virtual_addr(),
msize
);
}
}
Ok(output_file.stream_position()?)
}