From aea8f38dc1fd89eaad02a789adef6c5fee122140 Mon Sep 17 00:00:00 2001 From: Zhang Junyang Date: Sat, 7 Oct 2023 16:30:28 +0800 Subject: [PATCH] Implement boot setup and loader utils --- Cargo.lock | 15 ++ build/grub/grub.cfg.embedded | 7 + build/grub/grub.cfg.template | 4 +- build/src/machine/default.rs | 131 ++++++++++++------ build/src/main.rs | 66 +++++++-- framework/jinux-frame/build.rs | 8 +- .../arch/x86/boot/linux_boot/setup/Cargo.toml | 2 + .../arch/x86/boot/linux_boot/setup/linker.ld | 21 ++- .../x86/boot/linux_boot/setup/src/console.rs | 52 +++++++ .../x86/boot/linux_boot/setup/src/header.S | 59 ++------ .../x86/boot/linux_boot/setup/src/main.rs | 11 ++ 11 files changed, 262 insertions(+), 114 deletions(-) create mode 100644 build/grub/grub.cfg.embedded create mode 100644 framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/console.rs diff --git a/Cargo.lock b/Cargo.lock index ba3e2ee17..4eb1c8e64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/build/grub/grub.cfg.embedded b/build/grub/grub.cfg.embedded new file mode 100644 index 000000000..490f389ef --- /dev/null +++ b/build/grub/grub.cfg.embedded @@ -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 diff --git a/build/grub/grub.cfg.template b/build/grub/grub.cfg.template index bbbda8351..3171168f7 100644 --- a/build/grub/grub.cfg.template +++ b/build/grub/grub.cfg.template @@ -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 } diff --git a/build/src/machine/default.rs b/build/src/machine/default.rs index 81af6ccae..3d00ee911 100644 --- a/build/src/machine/default.rs +++ b/build/src/machine/default.rs @@ -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"), }; diff --git a/build/src/main.rs b/build/src/main.rs index fba55d081..c77fa6df0 100644 --- a/build/src/main.rs +++ b/build/src/main.rs @@ -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::>(); + 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; } diff --git a/framework/jinux-frame/build.rs b/framework/jinux-frame/build.rs index ab669ae64..2b87f4d80 100644 --- a/framework/jinux-frame/build.rs +++ b/framework/jinux-frame/build.rs @@ -52,7 +52,13 @@ fn build_linux_setup_header() -> Result<(), Box> { 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()?; diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml index 7eb687be1..e7a467733 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/Cargo.toml @@ -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" diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/linker.ld b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/linker.ld index bfc95aa73..aa4ff60a3 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/linker.ld +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/linker.ld @@ -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.*) + } } diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/console.rs b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/console.rs new file mode 100644 index 000000000..3091e923e --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/console.rs @@ -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 = 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)+)?)) + } +} diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/header.S b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/header.S index 69f436a96..cc70dbb62 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/header.S +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/header.S @@ -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 diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs index e38821074..5bb5146cb 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/setup/src/main.rs @@ -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 {}