Introduce the boot trojan

This commit is contained in:
Zhang Junyang
2023-11-12 15:49:35 +08:00
committed by Tate, Hongliang Tian
parent ddca4fb2fc
commit 953ff66fcc
22 changed files with 767 additions and 209 deletions

View File

@ -0,0 +1,12 @@
[package]
name = "aster-boot-trojan-builder"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytemuck = { version = "1.14.0", features = ["derive"] }
bitflags = "1.3"
serde = { version = "1.0.192", features = ["derive"] }
xmas-elf = "0.9.1"

View File

@ -0,0 +1,165 @@
mod mapping;
mod pe_header;
use std::{
error::Error,
fs::File,
io::{Read, Seek, Write},
path::Path,
};
use xmas_elf::program::{ProgramHeader, SegmentData};
use mapping::{TrojanFileOffset, TrojanVA};
/// We need a flat binary which satisfies PA delta == File delta, and objcopy
/// does not satisfy us well, so we should parse the ELF and do our own
/// objcopy job.
///
/// Interstingly, the resulting binary should be the same as the memory
/// dump of the kernel setup header when it's loaded by the bootloader.
fn trojan_to_flat_binary(elf_file: &[u8]) -> Vec<u8> {
let elf = xmas_elf::ElfFile::new(&elf_file).unwrap();
let mut bin = Vec::<u8>::new();
for ph in elf.program_iter() {
let ProgramHeader::Ph32(program) = ph else {
panic!("Unexpected program header type");
};
if program.get_type().unwrap() == xmas_elf::program::Type::Load {
let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else {
panic!("Unexpected segment data type");
};
let dst_file_offset = usize::from(TrojanFileOffset::from(TrojanVA::from(
program.virtual_addr as usize,
)));
let dst_file_length = program.file_size as usize;
if bin.len() < dst_file_offset + dst_file_length {
bin.resize(dst_file_offset + dst_file_length, 0);
}
let dest_slice = bin[dst_file_offset..dst_file_offset + dst_file_length].as_mut();
dest_slice.copy_from_slice(header_data);
}
}
bin
}
/// This function sould be used when generating the Linux x86 Boot setup header.
/// Some fields in the Linux x86 Boot setup header should be filled after assembled.
/// And the filled fields must have the bytes with values of 0xAB. See
/// `framework/aster-frame/src/arch/x86/boot/linux_boot/setup/src/header.S` for more
/// info on this mechanism.
fn fill_header_field(header: &mut [u8], offset: usize, value: &[u8]) {
let size = value.len();
assert_eq!(
&header[offset..offset + size],
vec![0xABu8; size].as_slice(),
"The field {:#x} to be filled must be marked with 0xAB",
offset
);
header[offset..offset + size].copy_from_slice(value);
}
fn fill_legacy_header_fields(
header: &mut [u8],
kernel_len: usize,
header_len: usize,
payload_offset: TrojanVA,
) {
fill_header_field(
header,
0x248, /* payload_offset */
&(usize::from(payload_offset) as u32).to_le_bytes(),
);
fill_header_field(
header,
0x24C, /* payload_length */
&(kernel_len as u32).to_le_bytes(),
);
fill_header_field(
header,
0x260, /* init_size */
&((header_len + kernel_len) as u32).to_le_bytes(),
);
}
pub fn make_bzimage(path: &Path, kernel_path: &Path, header_path: &Path) -> std::io::Result<()> {
let mut header_elf_file = Vec::new();
File::open(header_path)?.read_to_end(&mut header_elf_file)?;
let mut header = trojan_to_flat_binary(&header_elf_file);
// Pad the Linux boot header to let the payload starts with 8-byte alignment.
header.resize((header.len() + 7) & !7, 0x00);
let mut kernel = Vec::new();
File::open(kernel_path)?.read_to_end(&mut kernel)?;
let payload = kernel;
let header_len = header.len();
let payload_len = payload.len();
let payload_offset = TrojanFileOffset::from(header_len);
fill_legacy_header_fields(&mut header, payload_len, header_len, payload_offset.into());
let mut kernel_image = File::create(path)?;
kernel_image.write_all(&header)?;
kernel_image.write_all(&payload)?;
let image_size = header_len + payload_len;
// Since the Linux boot header starts at 0x1f1, we can write the PE/COFF header directly to the
// start of the file without overwriting the Linux boot header.
let pe_header = pe_header::make_pe_coff_header(&header_elf_file, image_size);
assert!(
pe_header.header_at_zero.len() <= 0x1f1,
"PE/COFF header is too large"
);
// FIXME: Oops, EFI hanover stucks, so I removed the pe header to let grub go through the legacy path.
kernel_image.seek(std::io::SeekFrom::Start(0))?;
// kernel_image.write_all(&pe_header.header_at_zero)?;
kernel_image.seek(std::io::SeekFrom::Start(
usize::from(pe_header.relocs.0) as u64
))?;
// kernel_image.write_all(&pe_header.relocs.1)?;
Ok(())
}
pub fn build_linux_setup_header_from_trojan(
source_dir: &Path,
out_dir: &Path,
) -> Result<(), Box<dyn Error + Send + Sync>> {
// Build the setup header to ELF.
let target_json = source_dir.join("x86_64-i386_protected_mode.json");
let cargo = std::env::var("CARGO").unwrap();
let mut cmd = std::process::Command::new(cargo);
cmd.arg("install").arg("aster-boot-trojan");
cmd.arg("--debug");
cmd.arg("--locked");
cmd.arg("--path").arg(source_dir.to_str().unwrap());
cmd.arg("--target").arg(target_json.as_os_str());
cmd.arg("-Zbuild-std=core,compiler_builtins");
cmd.arg("-Zbuild-std-features=compiler-builtins-mem");
// Specify the installation root.
cmd.arg("--root").arg(out_dir.as_os_str());
// Specify the build target directory to avoid cargo running
// into a deadlock reading the workspace files.
cmd.arg("--target-dir").arg(out_dir.as_os_str());
cmd.env_remove("RUSTFLAGS");
cmd.env_remove("CARGO_ENCODED_RUSTFLAGS");
let output = cmd.output()?;
if !output.status.success() {
std::io::stdout().write_all(&output.stdout).unwrap();
std::io::stderr().write_all(&output.stderr).unwrap();
return Err(format!(
"Failed to build linux x86 setup header:\n\tcommand `{:?}`\n\treturned {}",
cmd, output.status
)
.into());
}
Ok(())
}

View File

@ -0,0 +1,76 @@
//! In the trojan, VA - SETUP32_LMA == FileOffset - LEGACY_SETUP_SEC_SIZE.
//! And the addresses are specified in the ELF file.
use std::{cmp::PartialOrd, convert::From, ops::Sub};
// We chose the legacy setup sections to be 7 so that the setup header
// is page-aligned and the legacy setup section size would be 0x1000.
pub const LEGACY_SETUP_SECS: usize = 7;
pub const LEGACY_SETUP_SEC_SIZE: usize = 0x200 * (LEGACY_SETUP_SECS + 1);
pub const SETUP32_LMA: usize = 0x100000;
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct TrojanVA {
addr: usize,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct TrojanFileOffset {
offset: usize,
}
impl From<usize> for TrojanVA {
fn from(addr: usize) -> Self {
Self { addr }
}
}
impl From<TrojanVA> for usize {
fn from(va: TrojanVA) -> Self {
va.addr
}
}
impl Sub for TrojanVA {
type Output = usize;
fn sub(self, rhs: Self) -> Self::Output {
self.addr - rhs.addr
}
}
impl From<usize> for TrojanFileOffset {
fn from(offset: usize) -> Self {
Self { offset }
}
}
impl From<TrojanFileOffset> for usize {
fn from(offset: TrojanFileOffset) -> Self {
offset.offset
}
}
impl Sub for TrojanFileOffset {
type Output = usize;
fn sub(self, rhs: Self) -> Self::Output {
self.offset - rhs.offset
}
}
impl From<TrojanVA> for TrojanFileOffset {
fn from(va: TrojanVA) -> Self {
Self {
offset: va.addr + LEGACY_SETUP_SEC_SIZE - SETUP32_LMA,
}
}
}
impl From<TrojanFileOffset> for TrojanVA {
fn from(offset: TrojanFileOffset) -> Self {
Self {
addr: offset.offset + SETUP32_LMA - LEGACY_SETUP_SEC_SIZE,
}
}
}

View File

@ -0,0 +1,353 @@
//! Big zImage PE/COFF header generation.
//!
//! The definition of the PE/COFF header is in the Microsoft PE/COFF specification:
//! https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
//!
//! The reference to the Linux PE header definition:
//! https://github.com/torvalds/linux/blob/master/include/linux/pe.h
use bytemuck::{Pod, Zeroable};
use serde::Serialize;
use std::{mem::size_of, ops::Range};
use crate::mapping::{TrojanFileOffset, TrojanVA, LEGACY_SETUP_SEC_SIZE, SETUP32_LMA};
// The MS-DOS header.
const MZ_MAGIC: u16 = 0x5a4d; // "MZ"
// The `magic` field in PE header.
const PE_MAGIC: u32 = 0x00004550;
// The `machine` field choices in PE header. Not exhaustive.
#[derive(Serialize, Clone, Copy)]
#[repr(u16)]
enum PeMachineType {
Amd64 = 0x8664,
}
// The `flags` field choices in PE header.
bitflags::bitflags! {
struct PeFlags: u16 {
const RELOCS_STRIPPED = 1;
const EXECUTABLE_IMAGE = 1 << 1;
const LINE_NUMS_STRIPPED = 1 << 2;
const LOCAL_SYMS_STRIPPED = 1 << 3;
const AGGRESIVE_WS_TRIM = 1 << 4;
const LARGE_ADDRESS_AWARE = 1 << 5;
const SIXTEEN_BIT_MACHINE = 1 << 6;
const BYTES_REVERSED_LO = 1 << 7;
const THIRTY_TWO_BIT_MACHINE = 1 << 8;
const DEBUG_STRIPPED = 1 << 9;
const REMOVABLE_RUN_FROM_SWAP = 1 << 10;
const NET_RUN_FROM_SWAP = 1 << 11;
const SYSTEM = 1 << 12;
const DLL = 1 << 13;
const UP_SYSTEM_ONLY = 1 << 14;
}
}
#[derive(Zeroable, Pod, Serialize, Clone, Copy)]
#[repr(C, packed)]
struct PeHdr {
magic: u32, // PE magic
machine: u16, // machine type
sections: u16, // number of sections
timestamp: u32, // time_t
symbol_table: u32, // symbol table offset
symbols: u32, // number of symbols
opt_hdr_size: u16, // size of optional header
flags: u16, // flags
}
// The `magic` field in the PE32+ optional header.
const PE32PLUS_OPT_HDR_MAGIC: u16 = 0x020b;
// The `subsys` field choices in the PE32+ optional header. Not exhaustive.
#[derive(Serialize, Clone, Copy)]
#[repr(u16)]
enum PeImageSubsystem {
EfiApplication = 10,
}
#[derive(Zeroable, Pod, Serialize, Clone, Copy)]
#[repr(C, packed)]
struct Pe32PlusOptHdr {
magic: u16, // file type
ld_major: u8, // linker major version
ld_minor: u8, // linker minor version
text_size: u32, // size of text section(s)
data_size: u32, // size of data section(s)
bss_size: u32, // size of bss section(s)
entry_point: u32, // file offset of entry point
code_base: u32, // relative code addr in ram
image_base: u64, // preferred load address
section_align: u32, // alignment in bytes
file_align: u32, // file alignment in bytes
os_major: u16, // major OS version
os_minor: u16, // minor OS version
image_major: u16, // major image version
image_minor: u16, // minor image version
subsys_major: u16, // major subsystem version
subsys_minor: u16, // minor subsystem version
win32_version: u32, // reserved, must be 0
image_size: u32, // image size
header_size: u32, // header size rounded up to file_align
csum: u32, // checksum
subsys: u16, // subsystem
dll_flags: u16, // more flags!
stack_size_req: u64, // amt of stack requested
stack_size: u64, // amt of stack required
heap_size_req: u64, // amt of heap requested
heap_size: u64, // amt of heap required
loader_flags: u32, // reserved, must be 0
data_dirs: u32, // number of data dir entries
}
// The `flags` field choices in the PE section header.
// Excluding the alignment flags, which is not bitflags.
bitflags::bitflags! {
struct PeSectionHdrFlags: u32 {
const CNT_CODE = 1 << 5;
const CNT_INITIALIZED_DATA = 1 << 6;
const CNT_UNINITIALIZED_DATA = 1 << 7;
const LNK_INFO = 1 << 9;
const LNK_REMOVE = 1 << 11;
const LNK_COMDAT = 1 << 12;
const GPREL = 1 << 15;
const MEM_PURGEABLE = 1 << 16;
const LNK_NRELOC_OVFL = 1 << 24;
const MEM_DISCARDABLE = 1 << 25;
const MEM_NOT_CACHED = 1 << 26;
const MEM_NOT_PAGED = 1 << 27;
const MEM_SHARED = 1 << 28;
const MEM_EXECUTE = 1 << 29;
const MEM_READ = 1 << 30;
const MEM_WRITE = 1 << 31;
}
}
// The `flags` field choices in the PE section header.
#[derive(Serialize, Clone, Copy)]
#[repr(u32)]
enum PeSectionHdrFlagsAlign {
_1Bytes = 0x00100000,
_2Bytes = 0x00200000,
_4Bytes = 0x00300000,
_8Bytes = 0x00400000,
_16Bytes = 0x00500000,
_32Bytes = 0x00600000,
_64Bytes = 0x00700000,
_128Bytes = 0x00800000,
_256Bytes = 0x00900000,
_512Bytes = 0x00A00000,
_1024Bytes = 0x00B00000,
_2048Bytes = 0x00C00000,
_4096Bytes = 0x00D00000,
_8192Bytes = 0x00E00000,
}
#[derive(Zeroable, Pod, Serialize, Clone, Copy)]
#[repr(C, packed)]
struct PeSectionHdr {
name: [u8; 8], // name or "/12\0" string tbl offset
virtual_size: u32, // size of loaded section in RAM
virtual_address: u32, // relative virtual address
raw_data_size: u32, // size of the section
data_addr: u32, // file pointer to first page of sec
relocs: u32, // file pointer to relocation entries
line_numbers: u32, // line numbers!
num_relocs: u16, // number of relocations
num_lin_numbers: u16, // srsly.
flags: u32,
}
struct TrojanSectionAddrInfo {
pub setup: Range<TrojanVA>,
pub text: Range<TrojanVA>,
}
impl TrojanSectionAddrInfo {
fn from(elf: &xmas_elf::ElfFile) -> Self {
let mut setup_start = None;
let mut setup_end = None;
let mut text_start = None;
let mut text_end = None;
for section in elf.section_iter() {
match elf.get_shstr(section.name()) {
Ok(s) => {
if s == ".setup" {
setup_start = Some(section.address() as usize);
setup_end = Some(section.address() as usize + section.size() as usize);
} else if s == ".text" {
text_start = Some(section.address() as usize);
text_end = Some(section.address() as usize + section.size() as usize);
}
}
Err(e) => {
panic!("Error: {:#?}", e);
}
}
}
assert!(
matches!(setup_start, Some(SETUP32_LMA)),
"setup_start: {:#x?}",
setup_start
);
Self {
setup: TrojanVA::from(setup_start.unwrap())..TrojanVA::from(setup_end.unwrap()),
text: TrojanVA::from(text_start.unwrap())..TrojanVA::from(text_end.unwrap()),
}
}
fn setup_virt_size(&self) -> usize {
self.setup.end - self.setup.start
}
fn setup_file_size(&self) -> usize {
TrojanFileOffset::from(self.setup.end) - TrojanFileOffset::from(self.setup.start)
}
fn text_virt_size(&self) -> usize {
self.text.end - self.text.start
}
fn text_file_size(&self) -> usize {
TrojanFileOffset::from(self.text.end) - TrojanFileOffset::from(self.text.start)
}
}
pub struct TrojanPeCoffHeaderBuf {
pub header_at_zero: Vec<u8>,
pub relocs: (TrojanFileOffset, Vec<u8>),
}
pub(crate) fn make_pe_coff_header(setup_elf: &[u8], image_size: usize) -> TrojanPeCoffHeaderBuf {
let elf = xmas_elf::ElfFile::new(setup_elf).unwrap();
let mut bin = Vec::<u8>::new();
// PE header
let pe_hdr = PeHdr {
magic: PE_MAGIC,
machine: PeMachineType::Amd64 as u16,
sections: 3, // please set this field according to the number of sections added in the pe header
timestamp: 0,
symbol_table: 0,
symbols: 1, // I don't know why, Linux header.S says it's 1
opt_hdr_size: size_of::<Pe32PlusOptHdr>() as u16,
flags: (PeFlags::EXECUTABLE_IMAGE | PeFlags::DEBUG_STRIPPED | PeFlags::LINE_NUMS_STRIPPED)
.bits,
};
let elf_text_hdr = elf.find_section_by_name(".text").unwrap();
// PE32+ optional header
let pe_opt_hdr = Pe32PlusOptHdr {
magic: PE32PLUS_OPT_HDR_MAGIC,
ld_major: 0x02, // there's no linker to this extent, we do linking by ourselves
ld_minor: 0x14,
text_size: elf_text_hdr.size() as u32,
data_size: 0, // data size is irrelevant
bss_size: 0, // bss size is irrelevant
entry_point: elf.header.pt2.entry_point() as u32,
code_base: elf_text_hdr.address() as u32,
image_base: SETUP32_LMA as u64 - LEGACY_SETUP_SEC_SIZE as u64,
section_align: 0x20,
file_align: 0x20,
os_major: 0,
os_minor: 0,
image_major: 0x3, // see linux/pe.h for more info
image_minor: 0,
subsys_major: 0,
subsys_minor: 0,
win32_version: 0,
image_size: image_size as u32,
header_size: LEGACY_SETUP_SEC_SIZE as u32,
csum: 0,
subsys: PeImageSubsystem::EfiApplication as u16,
dll_flags: 0,
stack_size_req: 0,
stack_size: 0,
heap_size_req: 0,
heap_size: 0,
loader_flags: 0,
data_dirs: 0,
};
let addr_info = TrojanSectionAddrInfo::from(&elf);
// PE section headers
let pe_setup_hdr = PeSectionHdr {
name: [b'.', b's', b'e', b't', b'u', b'p', 0, 0],
virtual_size: addr_info.setup_virt_size() as u32,
virtual_address: usize::from(addr_info.setup.start) as u32,
raw_data_size: addr_info.setup_file_size() as u32,
data_addr: usize::from(TrojanFileOffset::from(addr_info.setup.start)) as u32,
relocs: 0,
line_numbers: 0,
num_relocs: 0,
num_lin_numbers: 0,
flags: (PeSectionHdrFlags::CNT_CODE
| PeSectionHdrFlags::MEM_READ
| PeSectionHdrFlags::MEM_EXECUTE
| PeSectionHdrFlags::MEM_DISCARDABLE)
.bits
| PeSectionHdrFlagsAlign::_16Bytes as u32,
};
// The EFI application loader requires a relocation section
let reloc_offset = TrojanFileOffset::from(0x500); // must be after the legacy header and before 0x1000
let relocs = vec![];
let pe_reloc_hdr = PeSectionHdr {
name: [b'.', b'r', b'e', b'l', b'o', b'c', 0, 0],
virtual_size: relocs.len() as u32,
virtual_address: usize::from(TrojanVA::from(reloc_offset)) as u32,
raw_data_size: relocs.len() as u32,
data_addr: usize::from(reloc_offset) as u32,
relocs: 0,
line_numbers: 0,
num_relocs: 0,
num_lin_numbers: 0,
flags: (PeSectionHdrFlags::CNT_INITIALIZED_DATA
| PeSectionHdrFlags::MEM_READ
| PeSectionHdrFlags::MEM_DISCARDABLE)
.bits
| PeSectionHdrFlagsAlign::_1Bytes as u32,
};
let pe_text_hdr = PeSectionHdr {
name: [b'.', b't', b'e', b'x', b't', 0, 0, 0],
virtual_size: addr_info.text_virt_size() as u32,
virtual_address: usize::from(addr_info.text.start) as u32,
raw_data_size: addr_info.text_file_size() as u32,
data_addr: usize::from(TrojanFileOffset::from(addr_info.text.start)) as u32,
relocs: 0,
line_numbers: 0,
num_relocs: 0,
num_lin_numbers: 0,
flags: (PeSectionHdrFlags::CNT_CODE
| PeSectionHdrFlags::MEM_READ
| PeSectionHdrFlags::MEM_EXECUTE)
.bits
| PeSectionHdrFlagsAlign::_16Bytes as u32,
};
// Write the MS-DOS header
bin.extend_from_slice(&MZ_MAGIC.to_le_bytes());
// Write the MS-DOS stub at 0x3c
bin.extend_from_slice(&[0x0; 0x3c - 0x2]);
// Write the PE header offset, the header is right after the offset field
bin.extend_from_slice(&(0x3cu32 + size_of::<u32>() as u32).to_le_bytes());
// Write the PE header
bin.extend_from_slice(bytemuck::bytes_of(&pe_hdr));
// Write the PE32+ optional header
bin.extend_from_slice(bytemuck::bytes_of(&pe_opt_hdr));
// Write the PE section headers
bin.extend_from_slice(bytemuck::bytes_of(&pe_setup_hdr));
bin.extend_from_slice(bytemuck::bytes_of(&pe_reloc_hdr));
bin.extend_from_slice(bytemuck::bytes_of(&pe_text_hdr));
TrojanPeCoffHeaderBuf {
header_at_zero: bin,
relocs: (reloc_offset, relocs),
}
}

View File

@ -0,0 +1,10 @@
[package]
name = "aster-boot-trojan"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
uart_16550 = "0.3.0"
xmas-elf = "0.8.0"

View File

@ -0,0 +1,9 @@
use std::path::PathBuf;
fn main() {
let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
println!(
"cargo:rustc-link-arg-bins=--script={}",
source_dir.join("linker.ld").display()
)
}

View File

@ -0,0 +1,52 @@
ENTRY(start_of_setup64)
OUTPUT_ARCH(i386:x86)
OUTPUT_FORMAT(elf32-i386)
SETUP32_LMA = 0x100000;
BOOTSECT_SIZE = 0x1000;
BOOTSECT_START = SETUP32_LMA - BOOTSECT_SIZE;
SECTIONS
{
. = BOOTSECT_START;
.header : { KEEP(*(.header)) }
. = SETUP32_LMA;
.setup : {
PROVIDE(__setup_start = .);
KEEP(*(.header))
PROVIDE(__setup_end = .);
}
.stack : { KEEP(*(.stack)) }
.text : {
PROVIDE(__text_start = .);
*(.text .text.*)
PROVIDE(__text_end = .);
}
.rodata : { *(.rodata .rodata.*) }
.data : { *(.data .data.*) }
.bss : {
PROVIDE(__bss_start = .);
*(.bss .bss.*) *(COMMON)
PROVIDE(__bss_end = .);
}
.eh_frame : {
*(.eh_frame .eh_frame.*)
}
.eh_frame_hdr : {
*(.eh_frame_hdr .eh_frame_hdr.*)
}
.symtab : {
*(.symtab .symtab.*)
}
.strtab : {
*(.strtab .strtab.*)
}
.shstrtab : {
*(.shstrtab .shstrtab.*)
}
}

View File

@ -0,0 +1,12 @@
const FIELD_PAYLOAD_OFFSET: u32 = 0x248;
const FIELD_PAYLOAD_LENGTH: u32 = 0x24c;
/// Safty: user must ensure that the boot_params_ptr is valid
pub unsafe fn get_payload_offset(boot_params_ptr: u32) -> u32 {
*((boot_params_ptr + FIELD_PAYLOAD_OFFSET) as *const u32)
}
/// Safty: user must ensure that the boot_params_ptr is valid
pub unsafe fn get_payload_length(boot_params_ptr: u32) -> u32 {
*((boot_params_ptr + FIELD_PAYLOAD_LENGTH) as *const u32)
}

View File

@ -0,0 +1,53 @@
use core::fmt::{self, Write};
use uart_16550::SerialPort;
struct Stdout {
serial_port: SerialPort,
}
static mut STDOUT: Stdout = Stdout {
serial_port: unsafe { SerialPort::new(0x0) },
};
/// safety: this function must only be called once
pub unsafe fn init() {
STDOUT = Stdout::init();
}
impl Stdout {
/// safety: this function must only be called once
pub unsafe fn init() -> Self {
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
serial_port.init();
Self { serial_port }
}
}
impl Write for Stdout {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.serial_port.write_str(s).unwrap();
Ok(())
}
}
pub fn print(args: fmt::Arguments) {
// safety: init() must be called before print() and there is no race condition
unsafe {
STDOUT.write_fmt(args).unwrap();
}
}
#[macro_export]
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!($fmt $(, $($arg)+)?))
}
}
#[macro_export]
macro_rules! println {
($fmt: literal $(, $($arg: tt)+)?) => {
$crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?))
}
}

View File

@ -0,0 +1,57 @@
// The compatibility file for the Linux x86 Boot Protocol.
// See https://www.kernel.org/doc/html/v5.6/x86/boot.html for
// more information on the Linux x86 Boot Protocol.
// Some of the fields filled with a 0xab* values should be filled
// by the runner, which is the only tool after building and can
// access the info of the payload.
// Asterinas will use only a few of these fields, and some of them
// are filled by the loader and will be read by Asterinas.
.section ".header", "a"
CODE32_START = 0x100000
SETUP_SECTS = 7 # so that the legacy setup could occupy a page
.code16
.org 0x01f1
hdr_start:
setup_sects: .byte SETUP_SECTS
root_flags: .word 1
syssize: .long 0
ram_size: .word 0
vid_mode: .word 0xfffd
root_dev: .word 0
boot_flag: .word 0xAA55
jump: .byte 0xeb
jump_addr: .byte hdr_end-jump_addr
magic: .ascii "HdrS"
.word 0x020f
realmode_swtch: .word 0, 0
start_sys_seg: .word 0
.word 0
type_of_loader: .byte 0
loadflags: .byte (1 << 0)
setup_move_size: .word 0
code32_start: .long CODE32_START
ramdisk_image: .long 0
ramdisk_size: .long 0
bootsect_kludge: .long 0
heap_end_ptr: .word 65535
ext_loader_ver: .byte 0
ext_loader_type: .byte 0
cmd_line_ptr: .long 0
initrd_addr_max: .long 0x7fffffff
kernel_alignment: .long 0x1000000
relocatable_kernel: .byte 0
min_alignment: .byte 0x10
xloadflags: .word 0b01111 # all handover protocols except kexec
cmdline_size: .long 4096-1
hardware_subarch: .long 0
hardware_subarch_data: .quad 0
payload_offset: .long 0xabababab # at 0x248/4, to be filled by the runner
payload_length: .long 0xabababab # at 0x24c/4, to be filled by the runner
setup_data: .quad 0
pref_address: .quad CODE32_START - 0x200 * (SETUP_SECTS + 1);
init_size: .long 0xabababab # at 0x260/4, to be filled by the runner
handover_offset: .long start_of_setup32
kernel_info_offset: .long 0
hdr_end:

View File

@ -0,0 +1,29 @@
use xmas_elf::program::{ProgramHeader, SegmentData};
pub fn load_elf(file: &[u8]) -> u32 {
let elf = xmas_elf::ElfFile::new(file).unwrap();
for ph in elf.program_iter() {
let ProgramHeader::Ph64(program) = ph else {
panic!("[setup] Unexpected program header type!");
};
if program.get_type().unwrap() == xmas_elf::program::Type::Load {
let SegmentData::Undefined(header_data) = program.get_data(&elf).unwrap() else {
panic!("[setup] Unexpected segment data type!");
};
// Safety: the physical address from the ELF file is valid
let dst_slice = unsafe {
core::slice::from_raw_parts_mut(
program.physical_addr as *mut u8,
program.mem_size as usize,
)
};
dst_slice[..program.file_size as usize].copy_from_slice(header_data);
let zero_slice = &mut dst_slice[program.file_size as usize..];
zero_slice.fill(0);
}
}
// Return the Linux 32-bit Boot Protocol entry point defined by Asterinas.
0x8001000
}

View File

@ -0,0 +1,46 @@
#![no_std]
#![no_main]
mod boot_params;
mod console;
mod loader;
use core::arch::{asm, global_asm};
global_asm!(include_str!("header.S"));
global_asm!(include_str!("setup.S"));
unsafe fn call_aster_entrypoint(entrypoint: u32, boot_params_ptr: u32) -> ! {
asm!("mov esi, {}", in(reg) boot_params_ptr);
asm!("mov eax, {}", in(reg) entrypoint);
asm!("jmp eax");
unreachable!();
}
#[no_mangle]
pub extern "cdecl" fn _rust_setup_entry(boot_params_ptr: u32) -> ! {
// Safety: this init function is only called once.
unsafe { console::init() };
println!("[setup] boot_params_ptr: {:#x}", boot_params_ptr);
let payload_offset = unsafe { boot_params::get_payload_offset(boot_params_ptr) };
let payload_length = unsafe { boot_params::get_payload_length(boot_params_ptr) };
let payload = unsafe {
core::slice::from_raw_parts_mut(payload_offset as *mut u8, payload_length as usize)
};
println!("[setup] loading ELF payload...");
let entrypoint = loader::load_elf(payload);
println!("[setup] entrypoint: {:#x}", entrypoint);
// Safety: the entrypoint and the ptr is valid.
unsafe { call_aster_entrypoint(entrypoint, boot_params_ptr) };
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("panic: {:?}", info);
loop {}
}

View File

@ -0,0 +1,39 @@
// 32-bit setup code starts here, and will be loaded at CODE32_START.
.section ".setup", "ax"
.code32
.global start_of_setup32
start_of_setup32:
mov eax, offset stack_bottom
mov esp, eax
mov eax, offset halt
push eax # the return address
mov ebp, esp
add ebp, -4
push ebp
mov ebp, esp
.extern _rust_setup_entry
push esi # the boot_params pointer
call _rust_setup_entry
// Unreachable here.
halt:
hlt
jmp halt
.code64
.global start_of_setup64
.org 0x200
start_of_setup64:
// Unreachable here.
halt64:
hlt
jmp halt64
// A small stack for the legacy setup code.
.section ".stack", "aw"
SETUP_STACK_SIZE = 0x1000
.align 16
stack_top:
.skip SETUP_STACK_SIZE
stack_bottom:

View File

@ -0,0 +1,20 @@
{
"llvm-target": "i386-unknown-none",
"data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32",
"cpu": "i386",
"arch": "x86",
"dynamic-linking": false,
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"max-atomic-width": 64,
"position-independent-executables": false,
"disable-redzone": true,
"target-c-int-width": "32",
"target-pointer-width": "32",
"target-endian": "little",
"panic-strategy": "abort",
"os": "none",
"relocation-model": "static",
"features": "+soft-float,-sse,-mmx"
}