axbuild 0.4.15

An OS build lib toolkit used by arceos
Documentation
use std::{
    fs,
    path::{Path, PathBuf},
};

use anyhow::Context;
use object::{Object, ObjectSection};

#[derive(Clone, Copy)]
pub(super) struct AddressRange {
    pub(super) start: u64,
    pub(super) end: u64,
}

#[derive(Clone, Copy)]
pub(super) struct KernelTextRange {
    pub(super) virt: AddressRange,
    pub(super) phys: Option<AddressRange>,
}

pub(super) fn kernel_bin_path(root: &Path, target: &str, debug: bool) -> PathBuf {
    root.join("target")
        .join(target)
        .join(if debug { "debug" } else { "release" })
        .join("starryos.bin")
}

pub(super) fn detect_kernel_text_range(
    elf: &Path,
    axconfig_path: Option<&Path>,
) -> anyhow::Result<Option<KernelTextRange>> {
    if !elf.exists() {
        eprintln!(
            "qperf: kernel ELF not found at {}, skipping .text range filter",
            elf.display()
        );
        return Ok(None);
    }
    let data =
        fs::read(elf).with_context(|| format!("failed to read kernel ELF {}", elf.display()))?;
    let obj = object::File::parse(&*data)
        .map_err(|err| anyhow::anyhow!("failed to parse kernel ELF: {err}"))?;
    let mut virt: Option<AddressRange> = None;
    for section in obj.sections() {
        if !matches!(section.name().unwrap_or(""), ".head.text" | ".text") {
            continue;
        }
        let start = section.address();
        let size = section.size();
        if start == 0 || size == 0 {
            continue;
        }
        let end = start
            .checked_add(size)
            .context("kernel text section end address overflow")?;
        virt = Some(match virt {
            Some(range) => AddressRange {
                start: range.start.min(start),
                end: range.end.max(end),
            },
            None => AddressRange { start, end },
        });
    }

    let Some(virt) = virt else {
        eprintln!(
            "qperf: could not find .head.text/.text sections in kernel ELF, address filter \
             disabled"
        );
        return Ok(None);
    };
    let size = virt.end - virt.start;
    let phys = detect_physical_text_range(virt, size, axconfig_path)?
        .or_else(|| detect_low_address_text_alias(virt));
    eprintln!(
        "qperf: detected kernel text virtual range: 0x{:x}..0x{:x} ({size} bytes)",
        virt.start, virt.end
    );
    if let Some(phys) = phys {
        eprintln!(
            "qperf: detected kernel text physical alias: 0x{:x}..0x{:x}",
            phys.start, phys.end
        );
    }
    Ok(Some(KernelTextRange { virt, phys }))
}

fn detect_physical_text_range(
    virt: AddressRange,
    size: u64,
    axconfig_path: Option<&Path>,
) -> anyhow::Result<Option<AddressRange>> {
    let Some(axconfig_path) = axconfig_path else {
        return Ok(None);
    };
    let Some((kernel_vaddr, kernel_paddr)) = read_kernel_base_addresses(axconfig_path)? else {
        return Ok(None);
    };
    if virt.start < kernel_vaddr {
        return Ok(None);
    }
    let phys_start = kernel_paddr
        .checked_add(virt.start - kernel_vaddr)
        .context("kernel .text physical address overflow")?;
    let phys_end = phys_start
        .checked_add(size)
        .context("kernel .text physical end address overflow")?;
    Ok(Some(AddressRange {
        start: phys_start,
        end: phys_end,
    }))
}

fn detect_low_address_text_alias(virt: AddressRange) -> Option<AddressRange> {
    const LOW_32BIT_MASK: u64 = 0x0000_0000_ffff_ffff;

    if virt.end <= LOW_32BIT_MASK || virt.start & !LOW_32BIT_MASK == 0 {
        return None;
    }
    let size = virt.end.checked_sub(virt.start)?;
    let start = virt.start & LOW_32BIT_MASK;
    let end = start.checked_add(size)?;
    Some(AddressRange { start, end })
}

fn read_kernel_base_addresses(axconfig_path: &Path) -> anyhow::Result<Option<(u64, u64)>> {
    if !axconfig_path.exists() {
        return Ok(None);
    }
    let text = fs::read_to_string(axconfig_path)
        .with_context(|| format!("failed to read {}", axconfig_path.display()))?;
    let config: toml::Value = toml::from_str(&text)
        .with_context(|| format!("failed to parse {}", axconfig_path.display()))?;
    let Some(plat) = config.get("plat").and_then(toml::Value::as_table) else {
        return Ok(None);
    };
    let Some(kernel_vaddr) = plat.get("kernel-base-vaddr").and_then(parse_axconfig_uint) else {
        return Ok(None);
    };
    let Some(kernel_paddr) = plat.get("kernel-base-paddr").and_then(parse_axconfig_uint) else {
        return Ok(None);
    };
    Ok(Some((kernel_vaddr, kernel_paddr)))
}

fn parse_axconfig_uint(value: &toml::Value) -> Option<u64> {
    match value {
        toml::Value::Integer(value) => (*value).try_into().ok(),
        toml::Value::String(value) => parse_u64_literal(value),
        _ => None,
    }
}

fn parse_u64_literal(value: &str) -> Option<u64> {
    let compact = value.trim().replace('_', "");
    if let Some(hex) = compact
        .strip_prefix("0x")
        .or_else(|| compact.strip_prefix("0X"))
    {
        u64::from_str_radix(hex, 16).ok()
    } else {
        compact.parse().ok()
    }
}