Open38

ゼロからOS自作入門 4章のメモ

ackyacky

前回、Bootとカーネルを分離させたからここではカーネルを修正かな

ackyacky

あっMakefileでkernel.elfが削除されない。追記しよう

ackyacky

BootLoderの方はKernelに合わせて次のように修正かな

  struct FrameBufferConfig config = {
    (UINT8*)gop->Mode->FrameBufferBase,
    gop->Mode->Info->PixelsPerScanLine,
    gop->Mode->Info->HorizontalResolution,
    gop->Mode->Info->VerticalResolution,
    0
  };
  switch (gop->Mode->Info->PixelFormat) {
    case PixelRedGreenBlueReserved8BitPerColor:
      config.pixel_format = kPixelRGBResv8BitPerColor;
      break;
    case PixelBlueGreenRedReserved8BitPerColor:
      config.pixel_format = kPixelBGRResv8BitPerColor;
      break;
    default:
      Print(L"Unimplemented pixel format: %d\n", gop->Mode->Info->PixelFormat);
      Halt();
  }

  typedef void EntryPointType(const struct FrameBufferConfig*);
  EntryPointType* entry_point = (EntryPointType*)entry_addr;
  entry_point(&config);
ackyacky

最後に実行コマンド

bash /mnt/30DayOS/osbook/devenv/run_qemu.sh /mnt/30DayOS/edk2/Build
/MikanLoaderX64/DEBUG_CLANG38/X64/Loader.efi /mnt/30DayOS/workspace/ch04/kernel/kernel.elf
ackyacky

4.3 C++の機能を使って書き直す(osbook_day04c)

継承を使うやつか

ackyacky

4.5 ローダを改良する(osbook_day04d)

# readelf -l kernel.elf

Elf file type is EXEC (Executable file)
Entry point 0x101070
There are 5 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000100040 0x0000000000100040
                 0x0000000000000118 0x0000000000000118  R      0x8
  LOAD           0x0000000000000000 0x0000000000100000 0x0000000000100000
                 0x00000000000001a8 0x00000000000001a8  R      0x1000
  LOAD           0x0000000000001000 0x0000000000101000 0x0000000000101000
                 0x0000000000000219 0x0000000000000219  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000102000 0x0000000000102000
                 0x0000000000000000 0x0000000000000020  RW     0x1000
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x0

 Section to Segment mapping:
  Segment Sections...
   00
   01     .rodata
   02     .text
   03     .bss
   04
ackyacky
  • gBS->AllocatePool
  • gBS->AllocatePages

https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/5_uefi_services/51_services_that_uefi_drivers_commonly_use/511_memory_allocation_services

The AllocatePool() and FreePool() boot services are used by UEFI drivers to allocate and free small buffers that are guaranteed to be aligned on an 8-byte boundary. These services are ideal for allocating and freeing data structures.

The AllocatePages() and FreePages() boot services are used by UEFI drivers to allocate and free larger buffers that are guaranteed to be aligned on a 4 KB boundary. These services allow buffers to be allocated at any available address, at specific addresses, or below a specific address.

ackyacky

#include <Library/BaseMemoryLib.h>

これを追加

https://edk2-docs.gitbook.io/edk-ii-uefi-driver-writer-s-guide/5_uefi_services/51_services_that_uefi_drivers_commonly_use/512_miscellaneous_services#example-24-allocate-and-clear-a-buffer-using-basememorylib

The code fragments in this section also show examples that use the EDK II library class BaseMemoryLib as an alternative to using the UEFI Boot Services directly. The advantage of using this library class is that the source code can be implemented just once. The EDK II DSC file used to build a UEFI Driver can specify mappings to different implementations of the BaseMemoryLib library class that meet the requirements of a specific target.

ackyacky
        if let Ok(gop) = bt.locate_protocol::<GraphicsOutput>() {
            let gop = gop.expect("Warnings encountered while opening GOP");
            let gop = unsafe { &mut *gop.get() };
            writeln!(
                stdout,
                "resolution {:?}",
                gop.current_mode_info().resolution()
            )
            .unwrap();
            writeln!(
                stdout,
                "pixel format {:?}",
                gop.current_mode_info().pixel_format()
            )
            .unwrap();
            let mi = gop.current_mode_info();
            let stride = mi.stride();
            let (width, height) = mi.resolution();
            let mut fb = gop.frame_buffer();
            type PixelWriter = unsafe fn(&mut FrameBuffer, usize, [u8; 3]);
            unsafe fn write_pixel_rgb(fb: &mut FrameBuffer, pixel_base: usize, rgb: [u8; 3]) {
                fb.write_value(pixel_base, rgb);
            }
            unsafe fn write_pixel_bgr(fb: &mut FrameBuffer, pixel_base: usize, rgb: [u8; 3]) {
                fb.write_value(pixel_base, [rgb[2], rgb[1], rgb[0]]);
            }
            let write_pixel: PixelWriter = match mi.pixel_format() {
                PixelFormat::Rgb => write_pixel_rgb,
                PixelFormat::Bgr => write_pixel_bgr,
                _ => {
                    panic!("This pixel format is not supported by the drawing demo");
                }
            };
            for row in 100..200 {
                for column in 100..200 {
                    unsafe {
                        let pixel_index = (row * stride) + column;
                        let pixel_base = 4 * pixel_index;
                        write_pixel(&mut fb, pixel_base, [255, 0, 0]);
                    }
                }
            }
        }
ackyacky

UEFIだと一応フレームバッファ(ただし解像度800x600x32)

仕様書だとここか

説明
PixelRedGreenBlueReserved8BitPerColor A pixel is 32-bits and byte zero represents red, byte one represents green, byte two represents blue, and byte three is reserved. This is the definition for the physical frame buffer. The byte values for the red, green, and blue components represent the color intensity. This color intensity value range from a minimum intensity of 0 to maximum intensity of 255.
PixelBlueGreenRedReserved8BitPerColor A pixel is 32-bits and byte zero represents blue, byte one represents green, byte two represents red, and byte three is reserved. This is the definition for the physical frame buffer. The byte values for the red, green, and blue components represent the color intensity. This color intensity value range from a minimum intensity of 0 to maximum intensity of 255.

http://raphine.hatenablog.com/entry/2017/05/06/171437

ackyacky

最初の目標

  • [] kernel側に構造体を渡す
  • [] 矩形をkernelから指定する
ackyacky

できた

#![no_std]
#![no_main]
#![feature(abi_efiapi)]
#![feature(asm)]
#![feature(lang_items)]

extern crate rlibc;
use core::panic::PanicInfo;

use kernel::graphics::{Graphics, PixelData};
use uefi::proto::console::gop::{FrameBuffer, ModeInfo};

struct ScreenBuffer<'a> {
    pub mode_info: &'a mut ModeInfo,
    pub frame_buffer: &'a mut FrameBuffer<'a>,
}

#[no_mangle]
extern "efiapi" fn kernel_main(screen_buffer: &'static mut ScreenBuffer) {
    let mut graphics = Graphics::new(screen_buffer.frame_buffer, screen_buffer.mode_info);
    // Fill Full screen
    let (horizontal, vertical) = graphics.mode_info.resolution();
    let stride = graphics.mode_info.stride() as usize;
    let back_color = PixelData {
        red: 255u8,
        green: 255u8,
        blue: 255u8,
    };
    for v in 0..vertical {
        let vertical_addr = 4 * ((v as usize) * stride);
        for h in 0..horizontal {
            let address = vertical_addr + 4 * (h as usize);
            graphics.write_pixel_color(address, &back_color);
        }
    }

    // Draw square
    let back_color = PixelData {
        red: 100u8,
        green: 255u8,
        blue: 100u8,
    };
    for v in 300..400 {
        let vertical_addr = 4 * ((v as usize) * stride);
        for h in 400..600 {
            let address = vertical_addr + 4 * (h as usize);
            graphics.write_pixel_color(address, &back_color);
        }
    }

    loop {
        unsafe {
            asm!("hlt");
        }
    }
}

#[lang = "eh_personality"]
fn eh_personality() {}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}

全塗と緑矩形を描いた

ackyacky

描画関係の処理は別途構造体を作った

#![no_std]

use uefi::proto::console::gop::{FrameBuffer, ModeInfo, PixelFormat};
pub struct PixelColor(pub u8, pub u8, pub u8);

pub struct Graphics<'a> {
    pub frame_buffer: &'a mut FrameBuffer<'a>,
    pub mode_info: &'a mut ModeInfo,
    pub pixel_writer: unsafe fn(&mut FrameBuffer, usize, PixelColor),
}

pub struct PixelData {
    pub red: u8,
    pub blue: u8,
    pub green: u8,
}

impl<'a> Graphics<'a> {
    pub fn new(frame_buffer: &'a mut FrameBuffer<'a>, mode_info: &'a mut ModeInfo) -> Self {
        unsafe fn write_pixel_rgb(frame_buffer: &mut FrameBuffer, address: usize, rgb: PixelColor) {
            Graphics::write_pixel(frame_buffer, address, [rgb.0, rgb.1, rgb.2]);
        }
        unsafe fn write_pixel_bgr(frame_buffer: &mut FrameBuffer, address: usize, rgb: PixelColor) {
            Graphics::write_pixel(frame_buffer, address, [rgb.2, rgb.1, rgb.0]);
        }
        let pixel_writer = match mode_info.pixel_format() {
            PixelFormat::Rgb => write_pixel_rgb,
            PixelFormat::Bgr => write_pixel_bgr,
            _ => {
                panic!("This pixel format is not supported by the drawing demo");
            }
        };

        Graphics {
            frame_buffer,
            mode_info,
            pixel_writer,
        }
    }

    pub fn get_frame_size(&self) -> usize {
        self.frame_buffer.size()
    }

    pub fn write_byte(frame_buffer: &mut FrameBuffer, address: usize, value: u8) {
        unsafe { frame_buffer.write_value(address, value) }
    }

    pub fn write_pixel(frame_buffer: &mut FrameBuffer, address: usize, value: [u8; 3]) {
        unsafe { frame_buffer.write_value(address, value) }
    }

    pub fn write_pixel_color(&mut self, address: usize, value: &PixelData) {
        let buffer = match self.mode_info.pixel_format() {
            PixelFormat::Rgb => [value.red, value.green, value.blue],
            PixelFormat::Bgr => [value.blue, value.green, value.red],
            _ => {
                panic!("This pixel format is not supported by the drawing demo");
            }
        };

        Graphics::write_pixel(&mut self.frame_buffer, address, buffer);
    }
}
ackyacky

これで動作を確認した後にアドレスの開始位置を改変

[unstable]
build-std = ["core", "alloc"]

[target.x86_64-unknown-linux-gnu]
rustflags = [
    "-Clink-args=-no-pie",
    "-Clink-args=-e kernel_main",
    "-Clink-args=-static -nostdlib",
    "-Clink-args=-Tsrc/linker.ld"
]

[target.x86_64-unknown-none-mikankernel]
linker = "ld.lld"
rustflags = [
    "-C", "no-redzone=yes",
    "-C", "relocation-model=static",
    "-C", "link-args=-entry=kernel_main -static -nostdlib",
    "-C", "link-args=--image-base=0x80000"
]

ベースアドレスを 0x80000 にしたった

ackyacky

[GDBでのデバッグ方法]
1.gdb kernel.elf でGDB起動して,
2.(gdb) b XX でブレイクポイント設定してから
3. qemuのオプションに-s をつけて起動する.
4. tianocoreと表示されたら
 (gdb) target remote :1234 でGDBからQEMUに接続し,
5. (gdb) c で再開

ackyacky

ちょっとづつ、ELFファイルのロードについて解説を書いていこう

ackyacky

ファイルをkernel_file_bufに展開し、elf_rsクレートのelf_rs::Elfを使ってフォーマットを確認

        let elf = match Elf::from_bytes(&kernel_file_buf).unwrap() {
            Elf::Elf64(e) => e,
            Elf::Elf32(_) => {
                panic!("Elf32 is not supported");
            }
        };
ackyacky
        let mut kernel_first = u64::max_value();
        let mut kernel_last = u64::min_value();
        for h in elf.program_header_iter() {
            let header = h.ph;
            if matches!(header.ph_type(), ProgramType::LOAD) {
                let v = header.vaddr();
                let len = header.memsz();
                kernel_first = core::cmp::min(kernel_first, v);
                kernel_last = core::cmp::max(kernel_last, v + len);
            }
        }
        let kernel_first = kernel_first as usize / 0x1000 * 0x1000;
        let load_size = kernel_last as usize - kernel_first;
        let n_of_pages = (load_size + 0xfff) / 0x1000;

ELFファイルのLOADの項目からKernelの先頭と末尾のアドレスを確認している。これは、参考書と同じ。

↓ちなみにProgramHeaderGen構造体が1つのProgramHeaderを管理する
https://github.com/vincenthouyi/elf_rs/blob/d040e18b4cc6b87139f6856802e9dca3d298f726/src/program_header.rs

ackyacky

これでやっと最初の先頭アドレスが取れるようになったので、最初に変更したallocate_pagesの部分をここで再実装

        let kernel_first = kernel_first as usize / 0x1000 * 0x1000;
        let load_size = kernel_last as usize - kernel_first;
        let n_of_pages = (load_size + 0xfff) / 0x1000;

        //カーネルファイルの読み込み
        system_table
            .boot_services()
            .allocate_pages(
                AllocateType::Address(kernel_first),
                MemoryType::LOADER_DATA,
                n_of_pages,
            )
            .unwrap()
            .unwrap();
        // load kernel
        for h in elf.program_header_iter() {
            let header = h.ph;
            if matches!(header.ph_type(), ProgramType::LOAD) {
                let segment = h.segment();
                let dest = header.vaddr();
                let len = header.filesz();
                let dest =
                    unsafe { core::slice::from_raw_parts_mut(dest as *mut u8, len as usize) };
                (0..len as usize).for_each(|i| {
                    dest[i] = segment[i];
                });
            }
        }
ackyacky

あとはエントリポイントを取得する

        // kernel_entryの取得
        let entry_pointer = unsafe { *entry_pointer_address } as *const ();
        let kernel_entry = unsafe {
            core::mem::transmute::<
                *const (),
                extern "efiapi" fn(screen_buffer: &mut ScreenBuffer) -> (),
            >(entry_pointer)
        };