Implement boot setup and loader utils
This commit is contained in:
parent
d0c84e0b6f
commit
aea8f38dc1
|
@ -662,6 +662,10 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "jinux-frame-x86-boot-setup"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"spin 0.9.8",
|
||||
"uart_16550",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinux-framebuffer"
|
||||
|
@ -1392,6 +1396,17 @@ dependencies = [
|
|||
name = "typeflags-util"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "uart_16550"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dc00444796f6c71f47c85397a35e9c4dbf9901902ac02386940d178e2b78687"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"rustversion",
|
||||
"x86",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uefi-raw"
|
||||
version = "0.3.0"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
search.fs_label grub root
|
||||
if [ -e /boot/grub/grub.cfg ]; then
|
||||
set prefix=($root)/boot/grub
|
||||
configfile /boot/grub/grub.cfg
|
||||
else
|
||||
echo "Could not find a configuration file!"
|
||||
fi
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
# AUTOMATICALLY GENERATED FILE, DO NOT EDIT IF YOU KNOW WHAT YOU ARE DOING
|
||||
|
||||
# set debug=linux,efi
|
||||
|
||||
set timeout_style=#GRUB_TIMEOUT_STYLE#
|
||||
set timeout=#GRUB_TIMEOUT#
|
||||
|
||||
menuentry 'jinux' {
|
||||
#GRUB_CMD_KERNEL# /boot/jinux #KERNEL_COMMAND_LINE#
|
||||
#GRUB_CMD_KERNEL# /boot/#KERNEL_NAME# #KERNEL_COMMAND_LINE#
|
||||
#GRUB_CMD_INITRAMFS# /boot/initramfs.cpio.gz
|
||||
boot
|
||||
}
|
||||
|
|
|
@ -42,61 +42,111 @@ pub fn create_bootdev_image(
|
|||
grub_cfg: String,
|
||||
protocol: GrubBootProtocol,
|
||||
) -> PathBuf {
|
||||
let dir = path.parent().unwrap();
|
||||
let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
let iso_path = dir.join(name + ".iso").to_str().unwrap().to_string();
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
let target_dir = path.parent().unwrap();
|
||||
let out_dir = target_dir.join("boot_device");
|
||||
|
||||
// Clean up the image directory.
|
||||
if Path::new("target/iso_root").exists() {
|
||||
fs::remove_dir_all("target/iso_root").unwrap();
|
||||
// Clear or make the out dir.
|
||||
if out_dir.exists() {
|
||||
fs::remove_dir_all(&out_dir).unwrap();
|
||||
}
|
||||
|
||||
// Copy the needed files into an ISO image.
|
||||
fs::create_dir_all("target/iso_root/boot/grub").unwrap();
|
||||
|
||||
fs::copy(
|
||||
"regression/build/initramfs.cpio.gz",
|
||||
"target/iso_root/boot/initramfs.cpio.gz",
|
||||
)
|
||||
.unwrap();
|
||||
fs::create_dir_all(&out_dir).unwrap();
|
||||
|
||||
// Find the setup header in the build script output directory.
|
||||
let out_dir = glob("target/x86_64-custom/debug/build/jinux-frame-*").unwrap();
|
||||
let header_bin = Path::new(out_dir.into_iter().next().unwrap().unwrap().as_path())
|
||||
let bs_out_dir = glob("target/x86_64-custom/debug/build/jinux-frame-*").unwrap();
|
||||
let header_bin = Path::new(bs_out_dir.into_iter().next().unwrap().unwrap().as_path())
|
||||
.join("out")
|
||||
.join("bin")
|
||||
.join("jinux-frame-x86-boot-setup.bin");
|
||||
|
||||
// Deliver the kernel image to the boot directory.
|
||||
match protocol {
|
||||
let target_path = match protocol {
|
||||
GrubBootProtocol::Linux => {
|
||||
// Make the `zimage`-compatible kernel image and place it in the boot directory.
|
||||
make_zimage(
|
||||
&Path::new("target/iso_root/boot/jinux"),
|
||||
&path.as_path(),
|
||||
&header_bin.as_path(),
|
||||
)
|
||||
.unwrap();
|
||||
let target_path = out_dir.join("jinuz");
|
||||
make_zimage(&target_path, &path.as_path(), &header_bin.as_path()).unwrap();
|
||||
target_path
|
||||
}
|
||||
GrubBootProtocol::Multiboot | GrubBootProtocol::Multiboot2 => {
|
||||
// Copy the kernel image into the boot directory.
|
||||
fs::copy(&path, "target/iso_root/boot/jinux").unwrap();
|
||||
}
|
||||
}
|
||||
GrubBootProtocol::Multiboot | GrubBootProtocol::Multiboot2 => path.clone(),
|
||||
};
|
||||
let target_name = target_path.file_name().unwrap().to_str().unwrap();
|
||||
|
||||
// Write the grub.cfg file
|
||||
fs::write("target/iso_root/boot/grub/grub.cfg", grub_cfg).unwrap();
|
||||
let grub_cfg_path = out_dir.join("grub.cfg");
|
||||
fs::write(&grub_cfg_path, grub_cfg).unwrap();
|
||||
|
||||
// Make the boot device .iso image.
|
||||
let status = std::process::Command::new("grub-mkrescue")
|
||||
// Make the boot device CDROM image.
|
||||
|
||||
// Firstly use `grub-mkrescue` to generate grub.img.
|
||||
let grub_img_path = out_dir.join("grub.img");
|
||||
let mut cmd = std::process::Command::new("grub-mkimage");
|
||||
cmd.arg("--format=i386-pc")
|
||||
.arg(format!("--prefix={}", out_dir.display()))
|
||||
.arg(format!("--output={}", grub_img_path.display()));
|
||||
// A embedded config file should be used to find the real config with menuentries.
|
||||
cmd.arg("--config=build/grub/grub.cfg.embedded");
|
||||
let grub_modules = &[
|
||||
"linux",
|
||||
"boot",
|
||||
"multiboot",
|
||||
"multiboot2",
|
||||
"elf",
|
||||
"loadenv",
|
||||
"memdisk",
|
||||
"biosdisk",
|
||||
"iso9660",
|
||||
"normal",
|
||||
"loopback",
|
||||
"chain",
|
||||
"configfile",
|
||||
"halt",
|
||||
"help",
|
||||
"ls",
|
||||
"reboot",
|
||||
"echo",
|
||||
"test",
|
||||
"sleep",
|
||||
"true",
|
||||
"vbe",
|
||||
"vga",
|
||||
"video_bochs",
|
||||
];
|
||||
for module in grub_modules {
|
||||
cmd.arg(module);
|
||||
}
|
||||
if !cmd.status().unwrap().success() {
|
||||
panic!("Failed to run `{:?}`.", cmd);
|
||||
}
|
||||
// Secondly prepend grub.img with cdboot.img.
|
||||
let cdboot_path = PathBuf::from("/usr/lib/grub/i386-pc/cdboot.img");
|
||||
let mut grub_img = fs::read(cdboot_path).unwrap();
|
||||
grub_img.append(&mut fs::read(&grub_img_path).unwrap());
|
||||
fs::write(&grub_img_path, &grub_img).unwrap();
|
||||
|
||||
// Finally use the `genisoimage` command to generate the CDROM image.
|
||||
let iso_path = out_dir.join(target_name.to_string() + ".iso");
|
||||
let mut cmd = std::process::Command::new("genisoimage");
|
||||
cmd.arg("-graft-points")
|
||||
.arg("-quiet")
|
||||
.arg("-R")
|
||||
.arg("-no-emul-boot")
|
||||
.arg("-boot-info-table")
|
||||
.arg("-boot-load-size")
|
||||
.arg("4")
|
||||
.arg("-input-charset")
|
||||
.arg("utf8")
|
||||
.arg("-A")
|
||||
.arg("jinux-grub2")
|
||||
.arg("-b")
|
||||
.arg(&grub_img_path)
|
||||
.arg("-o")
|
||||
.arg(&iso_path)
|
||||
.arg("target/iso_root")
|
||||
.status()
|
||||
.unwrap();
|
||||
|
||||
if !status.success() {
|
||||
panic!("Failed to create boot iso image.")
|
||||
.arg(format!("boot/{}={}", target_name, target_path.display()))
|
||||
.arg(format!("boot/grub/grub.cfg={}", grub_cfg_path.display()))
|
||||
.arg(format!("boot/grub/grub.img={}", grub_img_path.display()))
|
||||
.arg("boot/initramfs.cpio.gz=regression/build/initramfs.cpio.gz")
|
||||
.arg(cwd.as_os_str());
|
||||
if !cmd.status().unwrap().success() {
|
||||
panic!("Failed to run `{:?}`.", cmd);
|
||||
}
|
||||
|
||||
iso_path.into()
|
||||
|
@ -131,12 +181,15 @@ pub fn generate_grub_cfg(
|
|||
let buffer = match protocol {
|
||||
GrubBootProtocol::Multiboot => buffer
|
||||
.replace("#GRUB_CMD_KERNEL#", "multiboot")
|
||||
.replace("#KERNEL_NAME#", "jinux")
|
||||
.replace("#GRUB_CMD_INITRAMFS#", "module --nounzip"),
|
||||
GrubBootProtocol::Multiboot2 => buffer
|
||||
.replace("#GRUB_CMD_KERNEL#", "multiboot2")
|
||||
.replace("#KERNEL_NAME#", "jinux")
|
||||
.replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"),
|
||||
GrubBootProtocol::Linux => buffer
|
||||
.replace("#GRUB_CMD_KERNEL#", "linux")
|
||||
.replace("#KERNEL_NAME#", "jinuz")
|
||||
.replace("#GRUB_CMD_INITRAMFS#", "initrd"),
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ pub mod machine;
|
|||
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
@ -101,25 +102,60 @@ pub const GDB_ARGS: &[&str] = &[
|
|||
"-S",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
if args.run_gdb_client {
|
||||
let mut gdb_cmd = Command::new("gdb");
|
||||
// Adding the debug symbols from the kernel image.
|
||||
// Alternatively, use "file /usr/lib/grub/i386-pc/boot.image"
|
||||
// to load symbols from GRUB.
|
||||
gdb_cmd
|
||||
.arg("-ex")
|
||||
.arg(format!("file {}", args.path.display()));
|
||||
// Set the architecture, otherwise GDB will complain about.
|
||||
gdb_cmd.arg("-ex").arg("set arch i386:x86-64:intel");
|
||||
fn run_gdb_client(path: &PathBuf, gdb_grub: bool) {
|
||||
let path = std::fs::canonicalize(path).unwrap();
|
||||
let mut gdb_cmd = Command::new("gdb");
|
||||
// Set the architecture, otherwise GDB will complain about.
|
||||
gdb_cmd.arg("-ex").arg("set arch i386:x86-64:intel");
|
||||
let grub_script = "/tmp/jinux-gdb-grub-script";
|
||||
if gdb_grub {
|
||||
// Load symbols from GRUB using the provided grub gdb script.
|
||||
// Read the contents from /usr/lib/grub/i386-pc/gdb_grub and
|
||||
// replace the lines containing "file kernel.exec" and
|
||||
// "target remote :1234".
|
||||
gdb_cmd.current_dir("/usr/lib/grub/i386-pc/");
|
||||
let grub_script_content = include_str!("/usr/lib/grub/i386-pc/gdb_grub");
|
||||
let lines = grub_script_content.lines().collect::<Vec<_>>();
|
||||
let mut f = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(grub_script)
|
||||
.unwrap();
|
||||
for line in lines {
|
||||
if line.contains("target remote :1234") {
|
||||
// Connect to the GDB server.
|
||||
writeln!(f, "target remote /tmp/jinux-gdb-socket").unwrap();
|
||||
} else {
|
||||
writeln!(f, "{}", line).unwrap();
|
||||
}
|
||||
}
|
||||
gdb_cmd.arg("-x").arg(grub_script);
|
||||
} else {
|
||||
// Load symbols from the kernel image.
|
||||
gdb_cmd.arg("-ex").arg(format!("file {}", path.display()));
|
||||
// Connect to the GDB server.
|
||||
gdb_cmd
|
||||
.arg("-ex")
|
||||
.arg("target remote /tmp/jinux-gdb-socket");
|
||||
println!("running:{:#?}", gdb_cmd);
|
||||
gdb_cmd.status().unwrap();
|
||||
}
|
||||
// Connect to the GDB server and run.
|
||||
println!("running:{:#?}", gdb_cmd);
|
||||
gdb_cmd.status().unwrap();
|
||||
if gdb_grub {
|
||||
// Clean the temporary script file then return.
|
||||
std::fs::remove_file(grub_script).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
if args.run_gdb_client {
|
||||
let gdb_grub = args.boot_method.contains("grub");
|
||||
// You should comment out this code if you want to debug gdb instead
|
||||
// of the kernel because this argument is not exposed by runner CLI.
|
||||
// let gdb_grub = gdb_grub && false;
|
||||
run_gdb_client(&args.path, gdb_grub);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,13 @@ fn build_linux_setup_header() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||
let objcopy = std::env::var("OBJCOPY").unwrap();
|
||||
let mut cmd = std::process::Command::new(objcopy);
|
||||
cmd.arg("-O").arg("binary");
|
||||
cmd.arg("-j").arg(".boot_real_mode");
|
||||
cmd.arg("-j").arg(".header");
|
||||
cmd.arg("-j").arg(".text");
|
||||
cmd.arg("-j").arg(".rodata");
|
||||
cmd.arg("-j").arg(".data");
|
||||
cmd.arg("-j").arg(".bss");
|
||||
cmd.arg("-j").arg(".eh_frame");
|
||||
cmd.arg("-j").arg(".eh_frame_hdr");
|
||||
cmd.arg(elf_path.to_str().unwrap());
|
||||
cmd.arg(bin_path.to_str().unwrap());
|
||||
let output = cmd.output()?;
|
||||
|
|
|
@ -6,3 +6,5 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
uart_16550 = "0.3.0"
|
||||
spin = "0.9.4"
|
||||
|
|
|
@ -2,21 +2,28 @@ ENTRY(start_of_setup)
|
|||
OUTPUT_ARCH(i386:x86)
|
||||
OUTPUT_FORMAT(elf32-i386)
|
||||
|
||||
SETUP_LMA = 0x1000;
|
||||
SETUP32_LMA = 0x100000;
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = SETUP_LMA;
|
||||
. = SETUP32_LMA;
|
||||
|
||||
.boot_real_mode : AT(ADDR(.boot_real_mode) - SETUP_LMA) { KEEP(*(.boot_real_mode)) }
|
||||
.header : { KEEP(*(.header)) }
|
||||
|
||||
.text : AT(ADDR(.text) - SETUP_LMA) { *(.text .text.*) }
|
||||
.rodata : AT(ADDR(.rodata) - SETUP_LMA) { *(.rodata .rodata.*) }
|
||||
.text : { *(.text .text.*) }
|
||||
.rodata : { *(.rodata .rodata.*) }
|
||||
|
||||
.data : AT(ADDR(.data) - SETUP_LMA) { *(.data .data.*) }
|
||||
.bss : AT(ADDR(.bss) - SETUP_LMA) {
|
||||
.data : { *(.data .data.*) }
|
||||
.bss : {
|
||||
__bss = .;
|
||||
*(.bss .bss.*) *(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
.eh_frame : {
|
||||
*(.eh_frame .eh_frame.*)
|
||||
}
|
||||
.eh_frame_hdr : {
|
||||
*(.eh_frame_hdr .eh_frame_hdr.*)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
use core::fmt::{self, Write};
|
||||
|
||||
use spin::Once;
|
||||
use uart_16550::SerialPort;
|
||||
|
||||
struct Stdout {
|
||||
serial_port: SerialPort,
|
||||
}
|
||||
|
||||
static mut STDOUT: Once<Stdout> = Once::new();
|
||||
|
||||
/// safety: this function must only be called once
|
||||
pub unsafe fn init() {
|
||||
STDOUT.call_once(|| 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.get_mut().unwrap().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)+)?))
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
// The section name is used by the build script to strip and make
|
||||
// the binary file.
|
||||
.section ".boot_real_mode", "awx"
|
||||
.section ".header", "awx"
|
||||
|
||||
// The Linux x86 Boot Protocol header.
|
||||
//
|
||||
|
@ -19,7 +19,8 @@
|
|||
sentinel: .byte 0xff, 0xff
|
||||
.org 0x01f1
|
||||
hdr:
|
||||
setup_sects: .byte 0
|
||||
SETUP_SECTS = 4
|
||||
setup_sects: .byte SETUP_SECTS
|
||||
root_flags: .word 1
|
||||
syssize: .long 0
|
||||
ram_size: .word 0
|
||||
|
@ -27,7 +28,7 @@ vid_mode: .word 0xfffd
|
|||
root_dev: .word 0
|
||||
boot_flag: .word 0xAA55
|
||||
jump: .byte 0xeb
|
||||
.byte start_of_setup-jump
|
||||
jump_addr: .byte start_of_setup32-jump_addr
|
||||
magic: .ascii "HdrS"
|
||||
.word 0x020f
|
||||
realmode_swtch: .word 0, 0
|
||||
|
@ -35,7 +36,7 @@ start_sys_seg: .word 0
|
|||
.word 0
|
||||
type_of_loader: .byte 0
|
||||
loadflags: .byte (1 << 0)
|
||||
setup_move_size: .word 0x8000
|
||||
setup_move_size: .word 0
|
||||
code32_start: .long 0x100000
|
||||
ramdisk_image: .long 0
|
||||
ramdisk_size: .long 0
|
||||
|
@ -62,53 +63,9 @@ kernel_info_offset: .long 0
|
|||
|
||||
// End of header.
|
||||
|
||||
// Temporary real mode GDTR/GDT entries.
|
||||
.align 16
|
||||
real_gdtr:
|
||||
.word gdt_end - gdt - 1
|
||||
.long 0 # upper 32-bit address of GDT
|
||||
.long gdt # lower 32-bit address of GDT
|
||||
|
||||
.align 16
|
||||
gdt:
|
||||
.quad 0x0000000000000000 # 0: null descriptor
|
||||
.quad 0x00cf8a000000ffff # 8: 32-bit system segment (4k sys ex rw)
|
||||
.quad 0x00cf9a000000ffff # 16: 32-bit code/data segment (4k sys ex rw)
|
||||
gdt_end:
|
||||
|
||||
// 16-bit setup code starts here.
|
||||
.code16
|
||||
start_of_setup:
|
||||
// Enter 32-bit protected mode without paging.
|
||||
// Disable interrupts.
|
||||
cli
|
||||
|
||||
// Enable a20 gate.
|
||||
in al, 0x92
|
||||
or al, 2
|
||||
out 0x92, al
|
||||
|
||||
// Load GDT.
|
||||
lgdt [real_gdtr]
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
|
||||
// Go to protected mode.
|
||||
jmp start_of_setup32
|
||||
|
||||
// 32-bit setup code starts here.
|
||||
.code32
|
||||
start_of_setup32:
|
||||
// print to screen a debug message using out port 0x3f8.
|
||||
mov dx, 0x3f8
|
||||
mov al, 'H'
|
||||
out dx, al
|
||||
mov al, 'e'
|
||||
out dx, al
|
||||
mov al, 'l'
|
||||
out dx, al
|
||||
mov al, 'l'
|
||||
out dx, al
|
||||
mov al, 'o'
|
||||
out dx, al
|
||||
.org 0x200 * SETUP_SECTS
|
||||
.extern _rust_setup_entry
|
||||
jmp _rust_setup_entry
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
mod console;
|
||||
|
||||
use core::arch::global_asm;
|
||||
|
||||
global_asm!(include_str!("header.S"));
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn _rust_setup_entry() -> ! {
|
||||
// safety: this init function is only called once
|
||||
unsafe { console::init() };
|
||||
println!("Hello, world!");
|
||||
#[allow(clippy::empty_loop)]
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
loop {}
|
||||
|
|
Loading…
Reference in New Issue