ゼロからOS自作入門 4章のメモ
Makefileはこのあたりを確認しつつやったな
そういえばオライリーで無料公開されいた
それを結合したもの
4.2 ピクセルを自在に描く
Rustのやつ忘れないように残しておこう
UEFIの規格はこれかな
前回、Bootとカーネルを分離させたからここではカーネルを修正かな
あっMakefileでkernel.elfが削除されない。追記しよう
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);
最後に実行コマンド
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
4.3 C++の機能を使って書き直す(osbook_day04c)
継承を使うやつか
2つ初めてのことがあったな
4.4 vtable
このあたりが図もあってわかりやすいかな
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
BSSとかが何を意味しているか
参考書もあるがreadelfで見れるものを図解したもの
ELF Formatについて
構造体はこのあたりのほうが良いな
- gBS->AllocatePool
- gBS->AllocatePages
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.
#include <Library/BaseMemoryLib.h>
これを追加
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.
Rust化
cargo xbuild --target x86_64-unknown-uefi
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]);
}
}
}
}
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. |
最初の目標
- [] kernel側に構造体を渡す
- [] 矩形をkernelから指定する
できた
#![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 {}
}
全塗と緑矩形を描いた
描画関係の処理は別途構造体を作った
#![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);
}
}
これで動作を確認した後にアドレスの開始位置を改変
[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
にしたった
[GDBでのデバッグ方法]
1.gdb kernel.elf でGDB起動して,
2.(gdb) b XX でブレイクポイント設定してから
3. qemuのオプションに-s をつけて起動する.
4. tianocoreと表示されたら
(gdb) target remote :1234 でGDBからQEMUに接続し,
5. (gdb) c で再開
ちょっとづつ、ELFファイルのロードについて解説を書いていこう
追加したクレート:elf_rs
This is a no_std library for ELF file handling. It supports ELF32 and ELF64 format.
ファイルの読み込みを変更
allocate_pagesからallocate_poolに変更
- ページ単位からファイル全体を読み込むようにした
ファイルを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");
}
};
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を管理する
これでやっと最初の先頭アドレスが取れるようになったので、最初に変更した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];
});
}
}
あとはエントリポイントを取得する
// 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)
};