Implement boot setup and loader utils

This commit is contained in:
Zhang Junyang
2023-10-07 16:30:28 +08:00
committed by Tate, Hongliang Tian
parent d0c84e0b6f
commit aea8f38dc1
11 changed files with 262 additions and 114 deletions

15
Cargo.lock generated
View File

@ -662,6 +662,10 @@ dependencies = [
[[package]] [[package]]
name = "jinux-frame-x86-boot-setup" name = "jinux-frame-x86-boot-setup"
version = "0.1.0" version = "0.1.0"
dependencies = [
"spin 0.9.8",
"uart_16550",
]
[[package]] [[package]]
name = "jinux-framebuffer" name = "jinux-framebuffer"
@ -1392,6 +1396,17 @@ dependencies = [
name = "typeflags-util" name = "typeflags-util"
version = "0.1.0" 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]] [[package]]
name = "uefi-raw" name = "uefi-raw"
version = "0.3.0" version = "0.3.0"

View File

@ -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

View File

@ -2,11 +2,13 @@
# AUTOMATICALLY GENERATED FILE, DO NOT EDIT IF YOU KNOW WHAT YOU ARE DOING # 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_style=#GRUB_TIMEOUT_STYLE#
set timeout=#GRUB_TIMEOUT# set timeout=#GRUB_TIMEOUT#
menuentry 'jinux' { 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 #GRUB_CMD_INITRAMFS# /boot/initramfs.cpio.gz
boot boot
} }

View File

@ -42,61 +42,111 @@ pub fn create_bootdev_image(
grub_cfg: String, grub_cfg: String,
protocol: GrubBootProtocol, protocol: GrubBootProtocol,
) -> PathBuf { ) -> PathBuf {
let dir = path.parent().unwrap(); let cwd = std::env::current_dir().unwrap();
let name = path.file_name().unwrap().to_str().unwrap().to_string(); let target_dir = path.parent().unwrap();
let iso_path = dir.join(name + ".iso").to_str().unwrap().to_string(); let out_dir = target_dir.join("boot_device");
// Clean up the image directory. // Clear or make the out dir.
if Path::new("target/iso_root").exists() { if out_dir.exists() {
fs::remove_dir_all("target/iso_root").unwrap(); fs::remove_dir_all(&out_dir).unwrap();
} }
fs::create_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();
// Find the setup header in the build script output directory. // Find the setup header in the build script output directory.
let out_dir = glob("target/x86_64-custom/debug/build/jinux-frame-*").unwrap(); let bs_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 header_bin = Path::new(bs_out_dir.into_iter().next().unwrap().unwrap().as_path())
.join("out") .join("out")
.join("bin") .join("bin")
.join("jinux-frame-x86-boot-setup.bin"); .join("jinux-frame-x86-boot-setup.bin");
// Deliver the kernel image to the boot directory. let target_path = match protocol {
match protocol {
GrubBootProtocol::Linux => { GrubBootProtocol::Linux => {
// Make the `zimage`-compatible kernel image and place it in the boot directory. // Make the `zimage`-compatible kernel image and place it in the boot directory.
make_zimage( let target_path = out_dir.join("jinuz");
&Path::new("target/iso_root/boot/jinux"), make_zimage(&target_path, &path.as_path(), &header_bin.as_path()).unwrap();
&path.as_path(), target_path
&header_bin.as_path(),
)
.unwrap();
}
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 // 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. // Make the boot device CDROM image.
let status = std::process::Command::new("grub-mkrescue")
// 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("-o")
.arg(&iso_path) .arg(&iso_path)
.arg("target/iso_root") .arg(format!("boot/{}={}", target_name, target_path.display()))
.status() .arg(format!("boot/grub/grub.cfg={}", grub_cfg_path.display()))
.unwrap(); .arg(format!("boot/grub/grub.img={}", grub_img_path.display()))
.arg("boot/initramfs.cpio.gz=regression/build/initramfs.cpio.gz")
if !status.success() { .arg(cwd.as_os_str());
panic!("Failed to create boot iso image.") if !cmd.status().unwrap().success() {
panic!("Failed to run `{:?}`.", cmd);
} }
iso_path.into() iso_path.into()
@ -131,12 +181,15 @@ pub fn generate_grub_cfg(
let buffer = match protocol { let buffer = match protocol {
GrubBootProtocol::Multiboot => buffer GrubBootProtocol::Multiboot => buffer
.replace("#GRUB_CMD_KERNEL#", "multiboot") .replace("#GRUB_CMD_KERNEL#", "multiboot")
.replace("#KERNEL_NAME#", "jinux")
.replace("#GRUB_CMD_INITRAMFS#", "module --nounzip"), .replace("#GRUB_CMD_INITRAMFS#", "module --nounzip"),
GrubBootProtocol::Multiboot2 => buffer GrubBootProtocol::Multiboot2 => buffer
.replace("#GRUB_CMD_KERNEL#", "multiboot2") .replace("#GRUB_CMD_KERNEL#", "multiboot2")
.replace("#KERNEL_NAME#", "jinux")
.replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"), .replace("#GRUB_CMD_INITRAMFS#", "module2 --nounzip"),
GrubBootProtocol::Linux => buffer GrubBootProtocol::Linux => buffer
.replace("#GRUB_CMD_KERNEL#", "linux") .replace("#GRUB_CMD_KERNEL#", "linux")
.replace("#KERNEL_NAME#", "jinuz")
.replace("#GRUB_CMD_INITRAMFS#", "initrd"), .replace("#GRUB_CMD_INITRAMFS#", "initrd"),
}; };

View File

@ -13,6 +13,7 @@ pub mod machine;
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions,
io::Write,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
}; };
@ -101,25 +102,60 @@ pub const GDB_ARGS: &[&str] = &[
"-S", "-S",
]; ];
fn main() { fn run_gdb_client(path: &PathBuf, gdb_grub: bool) {
let args = Args::parse(); let path = std::fs::canonicalize(path).unwrap();
if args.run_gdb_client {
let mut gdb_cmd = Command::new("gdb"); 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. // Set the architecture, otherwise GDB will complain about.
gdb_cmd.arg("-ex").arg("set arch i386:x86-64:intel"); 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. // Connect to the GDB server.
gdb_cmd gdb_cmd
.arg("-ex") .arg("-ex")
.arg("target remote /tmp/jinux-gdb-socket"); .arg("target remote /tmp/jinux-gdb-socket");
}
// Connect to the GDB server and run.
println!("running:{:#?}", gdb_cmd); println!("running:{:#?}", gdb_cmd);
gdb_cmd.status().unwrap(); 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; return;
} }

View File

@ -52,7 +52,13 @@ fn build_linux_setup_header() -> Result<(), Box<dyn Error + Send + Sync>> {
let objcopy = std::env::var("OBJCOPY").unwrap(); let objcopy = std::env::var("OBJCOPY").unwrap();
let mut cmd = std::process::Command::new(objcopy); let mut cmd = std::process::Command::new(objcopy);
cmd.arg("-O").arg("binary"); 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(elf_path.to_str().unwrap());
cmd.arg(bin_path.to_str().unwrap()); cmd.arg(bin_path.to_str().unwrap());
let output = cmd.output()?; let output = cmd.output()?;

View File

@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
uart_16550 = "0.3.0"
spin = "0.9.4"

View File

@ -2,21 +2,28 @@ ENTRY(start_of_setup)
OUTPUT_ARCH(i386:x86) OUTPUT_ARCH(i386:x86)
OUTPUT_FORMAT(elf32-i386) OUTPUT_FORMAT(elf32-i386)
SETUP_LMA = 0x1000; SETUP32_LMA = 0x100000;
SECTIONS 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.*) } .text : { *(.text .text.*) }
.rodata : AT(ADDR(.rodata) - SETUP_LMA) { *(.rodata .rodata.*) } .rodata : { *(.rodata .rodata.*) }
.data : AT(ADDR(.data) - SETUP_LMA) { *(.data .data.*) } .data : { *(.data .data.*) }
.bss : AT(ADDR(.bss) - SETUP_LMA) { .bss : {
__bss = .; __bss = .;
*(.bss .bss.*) *(COMMON) *(.bss .bss.*) *(COMMON)
__bss_end = .; __bss_end = .;
} }
.eh_frame : {
*(.eh_frame .eh_frame.*)
}
.eh_frame_hdr : {
*(.eh_frame_hdr .eh_frame_hdr.*)
}
} }

View File

@ -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)+)?))
}
}

View File

@ -4,7 +4,7 @@
// The section name is used by the build script to strip and make // The section name is used by the build script to strip and make
// the binary file. // the binary file.
.section ".boot_real_mode", "awx" .section ".header", "awx"
// The Linux x86 Boot Protocol header. // The Linux x86 Boot Protocol header.
// //
@ -19,7 +19,8 @@
sentinel: .byte 0xff, 0xff sentinel: .byte 0xff, 0xff
.org 0x01f1 .org 0x01f1
hdr: hdr:
setup_sects: .byte 0 SETUP_SECTS = 4
setup_sects: .byte SETUP_SECTS
root_flags: .word 1 root_flags: .word 1
syssize: .long 0 syssize: .long 0
ram_size: .word 0 ram_size: .word 0
@ -27,7 +28,7 @@ vid_mode: .word 0xfffd
root_dev: .word 0 root_dev: .word 0
boot_flag: .word 0xAA55 boot_flag: .word 0xAA55
jump: .byte 0xeb jump: .byte 0xeb
.byte start_of_setup-jump jump_addr: .byte start_of_setup32-jump_addr
magic: .ascii "HdrS" magic: .ascii "HdrS"
.word 0x020f .word 0x020f
realmode_swtch: .word 0, 0 realmode_swtch: .word 0, 0
@ -35,7 +36,7 @@ start_sys_seg: .word 0
.word 0 .word 0
type_of_loader: .byte 0 type_of_loader: .byte 0
loadflags: .byte (1 << 0) loadflags: .byte (1 << 0)
setup_move_size: .word 0x8000 setup_move_size: .word 0
code32_start: .long 0x100000 code32_start: .long 0x100000
ramdisk_image: .long 0 ramdisk_image: .long 0
ramdisk_size: .long 0 ramdisk_size: .long 0
@ -62,53 +63,9 @@ kernel_info_offset: .long 0
// End of header. // 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. // 32-bit setup code starts here.
.code32 .code32
start_of_setup32: start_of_setup32:
// print to screen a debug message using out port 0x3f8. .org 0x200 * SETUP_SECTS
mov dx, 0x3f8 .extern _rust_setup_entry
mov al, 'H' jmp _rust_setup_entry
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

View File

@ -1,10 +1,21 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
mod console;
use core::arch::global_asm; use core::arch::global_asm;
global_asm!(include_str!("header.S")); 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] #[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {} loop {}