use clap::{CommandFactory, Parser};
use log::{debug, error, info};
use object::elf::{FileHeader64, PT_LOAD, SHF_ALLOC};
use object::read::elf::{ElfFile, ProgramHeader, SectionHeader};
use object::read::File;
use object::{Endianness, Object, ObjectSection};
use std::fs;
use std::process::ExitCode;
use igvm::IgvmFile;
use virtfw_igvm_tools::builder::Builder;
use virtfw_igvm_tools::inspect::inspect_igvm;
use virtfw_igvm_tools::x86regs::flat32_mode_regs;
use virtfw_libefi::hob::igvmdata::{EfiIgvmDataList, EfiIgvmDataType};
const USE_FLAT32: bool = false;
const IGVM_PARAM_MEMMAP: &str = ".igvm.memmap";
const IGVM_DATA_HOB: &str = ".igvm.datahobs";
#[derive(Parser, Debug)]
#[command(version, author, name = "igvm-elfis",
about = "convert elf images into igvm",
long_about = None)]
struct Args {
#[arg(long)]
regions: bool,
#[arg(long)]
inspect: bool,
#[arg(short, long, value_name = "ELF", required_unless_present = "manpage")]
input: Option<String>,
#[arg(short, long, value_name = "EFI")]
kernel: Option<String>,
#[arg(short, long, value_name = "IGVM")]
output: Option<String>,
#[arg(long, hide = true)]
manpage: bool,
}
struct Region<'r> {
name: &'r str,
_addr: u64,
size: u64,
data: &'r [u8],
_vaddr: u64,
_paddr: u64,
vma: Option<u64>,
lma: Option<u64>,
}
impl Region<'_> {
fn fmtaddr(addr: Option<u64>) -> String {
match addr {
Some(a) => {
format!("{a:8x}")
}
None => {
format!("{:>8}", "-")
}
}
}
fn vma(&self) -> String {
Self::fmtaddr(self.vma)
}
fn lma(&self) -> String {
Self::fmtaddr(self.lma)
}
fn is_igvm_param(&self) -> bool {
self.name == IGVM_PARAM_MEMMAP
}
fn is_igvm_data_hobs(&self) -> bool {
self.name == IGVM_DATA_HOB
}
}
fn is_rom_addr(addr: &u64) -> bool {
(0xff00_0000..0xffff_ffff).contains(addr)
}
fn parse_elf(elf: ElfFile<FileHeader64<Endianness>>) -> Option<Vec<Region>> {
let mut regions = Vec::new();
debug!(
"{:20} | {:>8} {:>8} {:>6} | {:<}",
"segment/section", "vaddr", "paddr", "size", "kind"
);
debug!("------------------------------------------------------");
for segment in elf.segments() {
let en = elf.endianness();
let ph = segment.elf_program_header();
if ph.p_type(en) != PT_LOAD {
error!("unknown elf header type: {}", ph.p_type(en));
return None;
}
let vaddr = ph.p_vaddr(en);
let paddr = ph.p_paddr(en);
let memsz = ph.p_memsz(en);
debug!(
"{:20} | {:8x} {:8x} {:6x} | {:<}",
"PT_LOAD", vaddr, paddr, memsz, "-"
);
for section in elf.sections() {
let sh = section.elf_section_header();
let flags = sh.sh_flags(en);
if flags & SHF_ALLOC as u64 == 0 {
continue;
}
let addr = section.address();
let size = section.size();
if addr < vaddr {
continue;
}
if addr + size > vaddr + memsz {
continue;
}
let name = section.name().unwrap_or("?");
debug!(
" {:16} | {:8x} {:>8} {:6x} | {:?}",
name,
addr,
"-",
size,
section.kind(),
);
let have_data = !section.data().unwrap().is_empty();
let vma = if vaddr != paddr || !have_data {
Some(addr)
} else {
None
};
let lma = if have_data {
Some(addr - vaddr + paddr)
} else {
None
};
let region = Region {
name,
_addr: addr,
size,
data: section.data().unwrap(),
_vaddr: vaddr,
_paddr: paddr,
vma,
lma,
};
regions.push(region);
}
}
Some(regions)
}
fn igvm_add_kernel(builder: &mut Builder, hobs_region: &Region, kernel_blob: Option<&[u8]>) {
let mut hoblist = EfiIgvmDataList::new(0x20000000);
if let Some(b) = kernel_blob {
info!("add efi data: kernel");
hoblist.add(b, EfiIgvmDataType::Kernel, false);
}
if hoblist.is_empty() {
return;
}
let addr = hobs_region.vma.unwrap() as usize;
info!("add efi data: hoblist at 0x{addr:x}");
let hobs_blob = hoblist.hobs();
builder.add_data_pages(addr, &hobs_blob);
for (addr, blob) in hoblist.blobs(true) {
builder.add_data_pages(addr, blob);
}
for (addr, blob) in hoblist.blobs(false) {
builder.add_data_pages_unmeasured(addr, blob);
}
}
fn process(cfg: &Args) -> Result<IgvmFile, Box<dyn std::error::Error>> {
let input = cfg.input.as_ref().unwrap();
info!("reading {}", input);
let binary = fs::read(input)?;
let object = File::parse(&*binary)?;
let File::Elf64(elf) = object else {
return Err("not an elf64 file".into());
};
info!("parsing elf");
let regions = parse_elf(elf).unwrap();
if cfg.regions {
info!("regions found");
println!("{:<16} {:>8} {:>8} {:>6}", "Name", "VMA", "LMA", "Size");
println!("--------------------------------------------");
for r in ®ions {
println!("{:<16} {} {} {:6x}", r.name, r.vma(), r.lma(), r.size);
}
}
info!("building igvm");
let mut builder = Builder::new();
builder.add_native_platform();
if USE_FLAT32 {
let regs = flat32_mode_regs();
builder.add_native_context(®s);
}
if let Some(base) = regions
.iter()
.filter_map(|r| r.lma)
.filter(is_rom_addr)
.min()
{
let size = 0x1_0000_0000 - base as usize;
info!("flash/rom at {base:x} +{size:06x}");
let mut rom = vec![0; size];
for r in regions
.iter()
.filter(|r| r.lma.is_some())
.filter(|r| is_rom_addr(&r.lma.unwrap()))
{
let offset = (r.lma.unwrap() - base) as usize;
rom[offset..offset + r.data.len()].copy_from_slice(r.data)
}
if !USE_FLAT32 {
builder.add_firmware_1m(&rom);
}
builder.add_firmware_4g(&rom);
}
for r in regions
.iter()
.filter(|r| r.vma.is_some())
.filter(|r| !r.is_igvm_param())
{
builder.add_empty_normal_pages(r.vma.unwrap() as usize, r.size as usize);
}
let params: Vec<_> = regions.iter().filter(|r| r.is_igvm_param()).collect();
if !params.is_empty() {
let s = params
.iter()
.map(|r| r.vma.unwrap())
.min()
.expect("params start");
let e = params
.iter()
.map(|r| r.vma.unwrap() + r.size)
.max()
.expect("params end")
.next_multiple_of(4096);
info!("igvm params: 0x{s:x} -> 0x{e:x}");
builder.add_igvm_param_area(0, e - s);
for p in params {
if p.name == IGVM_PARAM_MEMMAP {
builder.add_igvm_param_memmap(0, (p.vma.unwrap() - s) as u32);
}
}
builder.add_igvm_param_insert(0, s);
};
let kernel_blob = if let Some(kernel_name) = &cfg.kernel {
info!("reading {}", &kernel_name);
let data = std::fs::read(kernel_name).expect("kernel: file read error");
Some(data)
} else {
None
};
if let Some(region) = regions.iter().find(|r| r.is_igvm_data_hobs()) {
igvm_add_kernel(&mut builder, region, kernel_blob.as_deref());
}
let igvm = builder.finalize()?;
Ok(igvm)
}
fn main() -> ExitCode {
let cfg = Args::parse();
if cfg.manpage {
let man = clap_mangen::Man::new(Args::command());
man.render(&mut std::io::stdout()).expect("render manpage");
return 0.into();
}
stderrlog::new()
.module(module_path!())
.verbosity(stderrlog::LogLevelNum::Info)
.init()
.unwrap();
let igvm = match process(&cfg) {
Err(e) => {
error!("{e}");
return 1.into();
}
Ok(i) => i,
};
if cfg.inspect {
info!("inspect igvm");
inspect_igvm(&igvm);
}
if let Some(outfile) = cfg.output {
let mut blob = Vec::new();
let res = igvm.serialize(&mut blob);
if let Err(e) = res {
error!("serialize igvm: {e}");
return ExitCode::from(1);
}
info!("writing {}", &outfile);
let res = fs::write(&outfile, blob);
if let Err(e) = res {
error!("write {}: {}", &outfile, e);
return ExitCode::from(1);
}
}
ExitCode::from(0)
}