본문 바로가기
프로그래밍/PC

FPC 로 OS 커널 만들기... - 3 -

by 사악신 2014. 4. 30.

다른분들처럼 저 또한 세월호 참사로 인해 대부분 작업이 중지된 상황입니다. 지금 시점에서 무언가 집중해서 한다는 것이 많이 힘드네요. 부디 삼가 고인의 명복을 빌며...


일단, 지난 커널 작업에서 조금 개선되거나 수정된 내용이 있어 정리해봅니다. 먼저, GRUB 이랑 연동하는 부분인데... 부팅이 완료된 후의 시스템 상태는 다음과 같습니다.

 

  • CS : 코드 세그먼트 디스크립터를 가리킴. Base Address 0, Limit 4G - 1
  • DS, SS, ES, FS, GS : 데이터 세그먼트 디스크립터를 가리킴. Base Address 0, Limit 4 G - 1
  • A20 : 활성화 됨.
  • Paging : 사용안함.
  • 인터럽트 : 중지.
  • IDT : 등록되지 않음.
  • GDT : 크기 및 위치, 셀렉터의 값이 정해지지 않음.(가능한 빨리 커널에서 GDT를 생성한 후 등록해야 함)
  • EAX : 0x2BADB002 - Magic Number
  • EBX : 시스템과 부트스트랩 관련 정보를 담고 있는 주소

 

이에 맞춰 스타트업 코드와 멀티부트 관련 유닛을 재작성하였습니다.

 

prt0.asm

 

[bits 32]

 

;  header flag

MULTIBOOT_MODULE_ALIGN equ 1 << 0     ; all boot modules loaded along with th os must be aligned on page(4KB) boundaries.

MULTIBOOT_MEMORY_MAP equ 1 << 1       ; information on available memory

MULTIBOOT_GRAPHICS_FIELDS equ 1 << 2  ; information about the video mode

MULTIBOOT_ADDRESS_FIELDS equ 1 << 16  ; the fields at offsets 12-28 in the Multiboot header are valid. ELF 는 안해도 됨.

 

; header defines

MULTIBOOT_HEADER_MAGIC equ 0x1BADB002

MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_MODULE_ALIGN | MULTIBOOT_MEMORY_MAP   ; bits 0-15 indicate requirements. bits 16-31 indicate optional features.

MULTIBOOT_HEADER_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)

;MULTIBOOT_HEADER_HEADER_ADDR equ 0x0                  ; 멀티부트 헤더의 시작주소

;MULTIBOOT_HEADER_LOAD_ADDR equ 0x0                    ; text 세그먼트 시작주소(물리)

;MULTIBOOT_HEADER_LOAD_END_ADDR equ 0x0                ; data 세그먼트 종료주소(물리)

;MULTIBOOT_HEADER_BSS_END_ADDR equ 0x0                 ; bss 세그먼트 종료주소(물리)

;MULTIBOOT_HEADER_ENTRY_ADDR equ 0x0                   ; OS 구동을 위해 jump 해야할 주소(물리)

;MULTIBOOT_HEADER_MODE_TYPE equ 0x0                    ; 0 (linear graphics mode) or 1 (EGA-standard text mode)

;MULTIBOOT_HEADER_WIDTH equ 0x0                        ; columns

;MULTIBOOT_HEADER_HEIGHT equ 0x0                       ; lines

;MULTIBOOT_HEADER_DEPTH equ 0x0                        ; graphics mode - bits per pixel, text mode - 0

 

; kernel stack size

KERNEL_STACKSIZE equ 0x4000

 

 

section .text

global _start

extern PASCALMAIN, MultiBootInfo, MagicNumber

 

 

; multiboot header

align 4

dd MULTIBOOT_HEADER_MAGIC

dd MULTIBOOT_HEADER_FLAGS

dd MULTIBOOT_HEADER_CHECKSUM

;dd MULTIBOOT_HEADER_HEADER_ADDR             ; MULTIBOOT_ADDRESS_FIELDS

;dd MULTIBOOT_HEADER_LOAD_ADDR               ; MULTIBOOT_ADDRESS_FIELDS

;dd MULTIBOOT_HEADER_LOAD_END_ADDR           ; MULTIBOOT_ADDRESS_FIELDS

;dd MULTIBOOT_HEADER_BSS_END_ADDR            ; MULTIBOOT_ADDRESS_FIELDS

;dd MULTIBOOT_HEADER_ENTRY_ADDR              ; MULTIBOOT_ADDRESS_FIELDS

;dd MULTIBOOT_HEADER_MODE_TYPE               ; MULTIBOOT_GRAPHICS_FIELDS

;dd MULTIBOOT_HEADER_WIDTH                   ; MULTIBOOT_GRAPHICS_FIELDS

;dd MULTIBOOT_HEADER_HEIGHT                  ; MULTIBOOT_GRAPHICS_FIELDS

;dd MULTIBOOT_HEADER_DEPTH                   ; MULTIBOOT_GRAPHICS_FIELDS

 

; entry point

_start:

    mov esp, KERNEL_STACK+KERNEL_STACKSIZE ; create kernel

    mov [MagicNumber], eax

    mov [MultiBootInfo], ebx

    call PASCALMAIN

    cli

    hlt

 

 

section .bss

 

; kernel stack

align 32

KERNEL_STACK: resb KERNEL_STACKSIZE

 

스타트업의 파일명은 FPC 의 관례에 따라 stub.asm 에서 prt0.asm 으로 변경하였습니다. 기작성된 파스칼 유닛들은 빠져있는 내용들이 있어 이를 보강하였습니다.

 

EAX, EBX 의 값을 외부 변수에 전달하고 있으며 PASCALMAIN 함수를 호출합니다. 지난 포스팅(2014/04/11 - [프로그래밍/OS] - FPC 로 OS 커널 만들기... - 2 -)에서 RTL 을 올리기 위하여 스타트업 코드에 FPC_THREADVARTABLES 등을 추가하였는데... 대체 어디서 생성되는지 한참을 찾아봐야 했습니다. 일부는 RTL, 일부는 FPC 컴파일러에 의해 추가되더군요. 이를 그대로 활용하기 위하여 기존 커널 메인 소스를 program 파일로 변경하였습니다. 여기저기 손볼 곳이 많더군요.^^

 

일단, 어느 정도 정리가되자 문득 라자루스에서 직접 빌드가 가능하지 않을까하는 생각이 떠올랐습니다. 생각난 김에 바로 해봤죠. 관련 작업을 위한 모든 옵션이 존재하였지만,  다음과 같은 요상한 에러를 뱉어내더군요.(리눅스에서도 테스트해봐야겠습니다.)

 

 

 

하여 현재는 Makefile 을 생성하여 빌드를 하고 있습니다. 이 과정도 순탄치만은 않았습니다. 링커에서 multiple definition 오류가 나와 잠시 멘붕이었거든요. 그러다 문득 터보 파스칼 시절 스마트 링킹 관련한 내용이 떠올라 관련 옵션(-CX -XX)을 추가하였더니 이번엔... link.res contains output sections; did you forget -T? 와 같은 경고를 내뱉더군요. 하지만 무사히 빌드는 완료되었습니다.(스마트 링크 옵션을 사용하면 중복되는 요소를 제거하고 link.res 를 생성합니다. 이를 사용하여 링킹을 하게되는 거죠.)

 

일단은 GNU 링커의 버그(http://www.freepascal.org/faq.var#unix-ld219)로 보이는데... 현재 사용중인 ld 의 버전이 2.20.x 인데 이 문제는 계속 남아있는 거 같습니다.

 

아참 멀티부트와 관련한 파스칼 유닛은 다음과 같습니다.

 

multiboot.pp

 

unit multiboot;

interface

const
  MULTIBOOT_HEADER_SEARCH = 8192; // How many bytes from the start of the file we search for the header.
  MULTIBOOT_HEADER_MAGIC = $1BADB002; // The magic field should contain this.
  MULTIBOOT_BOOTLOADER_MAGIC = $2BADB002; // This should be in %eax.
  MULTIBOOT_UNSUPPORTED = $0000FFFC; // The bits in the required part of flags field we do not support.
  MULTIBOOT_MOD_ALIGN = $00001000; // Alignment of multiboot modules.
  MULTIBOOT_INFO_ALIGN = $00000004; // Alignment of the multiboot info structure.

  // Flags set in the 'flags' member of the multiboot header.
  MULTIBOOT_PAGE_ALIGN = $00000001; // Align all boot modules on i386 page (4KB) boundaries.
  MULTIBOOT_MEMORY_INFO = $00000002; // Must pass memory information to OS.
  MULTIBOOT_VIDEO_MODE = $00000004; // Must pass video information to OS.
  MULTIBOOT_AOUT_KLUDGE = $00010000; // This flag indicates the use of the address fields in the header.

  // Flags to be set in the 'flags' member of the multiboot info structure.
  MULTIBOOT_INFO_MEMORY = $00000001; // is there basic lower/upper memory information?
  MULTIBOOT_INFO_BOOTDEV = $00000002; // is there a boot device set?
  MULTIBOOT_INFO_CMDLINE = $00000004; // is the command-line defined?
  MULTIBOOT_INFO_MODS = $00000008; // are there modules to do something with?

  // These next two are mutually exclusive
  MULTIBOOT_INFO_AOUT_SYMS = $00000010; // is there a symbol table loaded?
  MULTIBOOT_INFO_ELF_SHDR = $00000020; // is there an ELF section header table?
  MULTIBOOT_INFO_MEM_MAP = $00000040; // is there a full memory map?
  MULTIBOOT_INFO_DRIVE_INFO = $00000080; // Is there drive info?
  MULTIBOOT_INFO_CONFIG_TABLE = $00000100; // Is there a config table?
  MULTIBOOT_INFO_BOOT_LOADER_NAME = $00000200; // Is there a boot loader name?
  MULTIBOOT_INFO_APM_TABLE = $00000400; // Is there a APM table?
  MULTIBOOT_INFO_VIDEO_INFO = $00000800; // Is there video information?


type
  TMultiBootHeader = packed record
    magic: DWORD; // Must be MULTIBOOT_MAGIC - see above.
    flags: DWORD; // Feature flags.
    checksum: DWORD; // The above fields plus this one must equal 0 mod 2^32.

    // These are only valid if MULTIBOOT_AOUT_KLUDGE is set.
    header_addr: DWORD; // 멀티부트 헤더의 시작주소
    load_addr: DWORD; // text 세그먼트 시작주소(물리)
    load_end_addr: DWORD; // data 세그먼트 종료주소(물리)
    bss_end_addr: DWORD; // bss 세그먼트 종료주소(물리)
    entry_addr: DWORD; // OS 구동을 위해 jump 해야할 주소(물리)

    // These are only valid if MULTIBOOT_VIDEO_MODE is set.
    mode_type: DWORD; // 0 (linear graphics mode) or 1 (EGA-standard text mode)
    width: DWORD; // columns
    height: DWORD; // lines
    depth: DWORD; // graphics mode - bits per pixel, text mode - 0
  end;

  // The symbol table for a.out.
  PMultibootAOutSymbolTable = ^TMultibootAOutSymbolTable;
  TMultibootAOutSymbolTable = packed record
    tabsize: DWORD;
    strsize: DWORD;
    addr: DWORD;
    reserved: DWORD;
  end;

  // The section header table for ELF.
  PMultibootElfSectionHeaderTable = ^TMultibootElfSectionHeaderTable;
  TMultibootElfSectionHeaderTable = packed record
    num: DWORD;
    size: DWORD;
    addr: DWORD;
    shndx: DWORD;
  end;

  PMultiBootInfo = ^TMultiBootInfo;
  TMultiBootInfo = packed record
    // Multiboot info version number
    flags: DWORD;

    // Available memory from BIOS
    mem_lower: DWORD;
    mem_upper: DWORD;

    // "root" partition
    boot_device: DWORD;

    // Kernel command line
    cmdline: DWORD;

    // Boot-Module list
    mods_count: DWORD;
    mods_addr: DWORD;

    elf_sec: TMultibootElfSectionHeaderTable;

    // Memory Mapping buffer
    mmap_length: DWORD;
    mmap_addr: DWORD;

    // Drive Info buffer
    drives_length: DWORD;
    drives_addr: DWORD;

    // ROM configuration table
    config_table: DWORD;

    // Boot Loader Name
    boot_loader_name: DWORD;

    // APM table
    apm_table: DWORD;

    // Video
    vbe_control_info: DWORD;
    vbe_mode_info: DWORD;
    vbe_mode: Word;
    vbe_interface_seg: Word;
    vbe_interface_off: Word;
    vbe_interface_len: Word;
  end;


  PMultiBootMmapEntry = ^TMultiBootMmapEntry;
  TMultiBootMmapEntry = packed record
    size: DWORD;
    addr_low: DWORD; // addr: QWord;
    addr_high: DWORD;
    len_low: DWord; // len: QWord;
    len_high: DWORD;
    mtype: DWORD;
  end;


  PMultiBootModList = ^TMultiBootModList;
  TMultiBootModList = packed record
    // the memory used goes from bytes 'mod_start' to 'mod_end-1' inclusive
    mod_start: DWORD;
    mod_end: DWORD;

    // Module command line
    cmdline: DWORD;

    // padding to take it to 16 bytes (must be zero)
    pad: DWORD;
  end;


procedure KOutputMultiBootInfo(MultiBootInfo: PMultiBootInfo; Magic: DWORD);

implementation

uses
  crt;

function KCheckFlag(Flags, Bit: DWORD): Boolean;
begin
  Result := False;
  if (Flags and (1 shl Bit)) <> 0 then Result := True;
end;


procedure KOutputMultiBootInfo(MultiBootInfo: PMultiBootInfo; Magic: DWORD);
var
  MultiBootModList: PMultiBootModList;
  MultibootElfSectionHeaderTable: TMultibootElfSectionHeaderTable;
  MultiBootMmapEntry: PMultiBootMmapEntry;
  Val: String[32];
  i: Integer;
begin
  if Magic <> MULTIBOOT_BOOTLOADER_MAGIC then
  begin
    KOutputString('Invalid magic number: $');
    KIntToHex(Magic, 8, Val);
    KOutputStringLn(Val);
    Exit;
  end;

  KOutputString('flags = $');
  KIntToHex(MultiBootInfo^.flags, 8, Val);
  KOutputStringLn(Val);

  // Are mem_* valid?
  if KCheckFlag(MultiBootInfo^.flags, 0) then
  begin
    KOutputString('mem_lower = ');
    KIntToStr(MultiBootInfo^.mem_lower, Val);
    KOutputString(Val);
    KOutputStringLn('KB');

    KOutputString('mem_upper = ');
    KIntToStr(MultiBootInfo^.mem_upper, Val);
    KOutputString(Val);
    KOutputStringLn('KB');

    KOutputString('total memory = ');
    KIntToStr((MultiBootInfo^.mem_lower + MultiBootInfo^.mem_upper + 1024) div 1024, Val);
    KOutputString(Val);
    KOutputStringLn('MB')
  end;

  // Is boot_device valid?
  if KCheckFlag(MultiBootInfo^.flags, 1) then
  begin
    KOutputString('boot_device = $');
    // 각 byte 는 drive(BIOS drive number),part1,part2,par3 - part 는 파티션, 사용하지 않으면 $FF
    KIntToHex(MultiBootInfo^.boot_device, 8, Val);
    KOutputStringLn(Val);
  end;

  // Is the command line passed?
  if KCheckFlag(MultiBootInfo^.flags, 2) then
  begin
    KOutputString('cmdline = ');
    KOutputPCharLn(PChar(MultiBootInfo^.cmdline));
  end;

  // Are mods_* valid?
  if KCheckFlag(MultiBootInfo^.flags, 3) then
  begin
    KOutputString('mods_count = ');
    KIntToStr(MultiBootInfo^.mods_count, Val);
    KOutputStringLn(Val);

    for i := 0 to MultiBootInfo^.mods_count - 1 do
    begin
      MultiBootModList := PMultiBootModList(MultiBootInfo^.mods_addr);
      KOutputString('mod_start = $');
      KIntToHex(MultiBootModList^.mod_start, 8, Val);
      KOutputStringLn(Val);
      KOutputString('mod_end = $');
      KIntToHex(MultiBootModList^.mod_end, 8, Val);
      KOutputStringLn(Val);
      KOutputString('cmdline = ');
      KOutputPCharLn(PChar(MultiBootModList^.cmdline));


      Inc(MultiBootModList);
    end;
  end;

  // Bits 4 and 5 are mutually exclusive! (상호배타적)
  if KCheckFlag(MultiBootInfo^.flags, 4) and KCheckFlag(MultiBootInfo^.flags, 5) then
  begin
    KOutputStringLn('Both bits 4 and 5 are set.');
    Exit;
  end;

  if KCheckFlag(MultiBootInfo^.flags, 4) then
  begin
    KOutputStringLn('symbol table of a.out invalid.');
    Exit;
  end;

  // Is the section header table of ELF valid?
  if KCheckFlag(MultiBootInfo^.flags, 5) then
  begin
    MultibootElfSectionHeaderTable := MultiBootInfo^.elf_sec;
    KOutputStringLn('multiboot_elf_sec:');
    KOutputString(' num = ');
    KIntToStr(MultibootElfSectionHeaderTable.num, Val);
    KOutputString(Val);
    KOutputString(', size = $');
    KIntToHex(MultibootElfSectionHeaderTable.size, Val);
    KOutputString(Val);
    KOutputString(', addr = $');
    KIntToHex(MultibootElfSectionHeaderTable.addr, 8, Val);
    KOutputString(Val);
    KOutputString(', shndx = $');
    KIntToHex(MultibootElfSectionHeaderTable.shndx, Val);
    KOutputStringLn(Val);
  end;

  if KCheckFlag(MultiBootInfo^.flags, 6) then
  begin
    KOutputString('mmap_addr = $');
    KIntToHex(MultiBootInfo^.mmap_addr, 8, Val);
    KOutputString(Val);
    KOutputString(', mmap_length = '); // 144
    KIntToStr(MultiBootInfo^.mmap_length, Val);
    KOutputStringLn(Val);

    MultiBootMmapEntry := PMultiBootMmapEntry(MultiBootInfo^.mmap_addr);
    repeat
      KOutputString(' size = $'); // 자신을 제외한 나머지 구조체의 크기
      KIntToHex(MultiBootMmapEntry^.size, Val);
      KOutputString(Val);

      KOutputString(', base_addr = $');
      KIntToHex(MultiBootMmapEntry^.addr_high, Val);
      KOutputString(Val);
      KIntToHex(MultiBootMmapEntry^.addr_low, Val);
      KOutputString(Val);

      KOutputString(', length = $');
      KIntToHex(MultiBootMmapEntry^.len_high, Val);
      KOutputString(Val);
      KIntToHex(MultiBootMmapEntry^.len_low, Val);
      KOutputString(Val);


      KOutputString(', type = $'); // 1 - 사용가능, 2 - 예약(사용불가), 3 - ACPI가 사용됨(사용불가), 4 - ACPI를 사용(사용불가), 5 - 사용불가
      KIntToHex(MultiBootMmapEntry^.mtype, Val);
      KOutputStringLn(Val);


      MultiBootMmapEntry := PMultiBootMmapEntry(MultiBootMmapEntry + MultiBootMmapEntry^.size + SizeOf(MultiBootMmapEntry^.size));
    until (DWORD(MultiBootMmapEntry) - (MultiBootInfo^.mmap_addr + MultiBootInfo^.mmap_length)) > 0;
  end;

end;

end.

 

KOutputStringLn 등은 일단 제가 추가한 커널 내부 함수인데요. FPC RTL 을 수정하고 있어 추후 다 제거될 놈들입니다.^^ 점점 소스의 양이 많아져 포스팅에 모두 등록하기가 어려운 상태네요. 슬슬 소스 서버를 세팅하고 이를 오픈해야겠습니다.

반응형

댓글