ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [x86] 멀티 부트
    프로젝트/운영체제 만들기 2023. 6. 27. 17:20

    글의 참고

    - https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html


    글의 전제

    - 밑줄로 작성된 글은 강조 표시를 의미한다.

    - 그림 출처는 항시 그림 아래에 표시했다.


    글의 내용

    - Overview

    : 멀티 부트는 부트 로더와 운영 체제 사이의 인터페이스를 정의한 스펙이다. 멀티 부트2에서 중요시 다루는 3가지 요점은 아래와 같다(참고로, 멀티 부트는 스펙 1, 2로 나뉘어져 있다. 당연히 2가 최신이다)

    1" 부트 로더 입장에서 본 OS 이미지 포맷
    2" 부트 로더가 운영체제를 시작시킬 때, 머신의 상태
    3" 부트 로더가 운영체제에게 파라미터를 던질 때 파라미터의 포맷

     

    - 운영체제 이미지 포맷

    : 운영체제 이미지 파일은 대개 표준 32비트 실행 파일 포맷이다. 운영체제마다 실행 파일 포맷이 다를 수 있다. 멀티 부트2 스펙에서는 OS 이미지 파일에서 지켜줘야 하는 규칙을 명시하고 있다.

    1" 멀티 부트2 헤더는 OS 이미지의 맨 처음 32KB안에서 정의되야 하고, 8바이트 정렬이 되어야 한다.
    2" 멀티 부트2 헤더는 실제 실행가능한 헤더(ELF, PE 등) 보다는 뒤쪽에 위치하며, 텍스트 세그먼트의 시작 되는 앞 쪽에 삽입된다.

     

    : 멀티 부트2 헤더는 반드시 다음의 구조로 구성되어 있어야 한다.

    오프셋 타입 필드 참고
    0 u32 magic required
    4 u32 architecture required
    8 u32 header_length required
    12 u32 checksum required
    16-XX   tags required

     

    : 위의 구조는 2가지 파트로 나눠볼 수 있다. 

    1" 헤더 매직 필드
    2" 헤더 태그

     

    : 헤더 매직 헤더

    " 헤더 매직 필드는 4개의 필드로 구성되어 있다.

    1" magic - 말 그대로 매직 넘버를 의미한다. 즉, 멀티 부트2 헤더를 인식시키기 시그니처와 같다. 이 값은 `0xE85250D6` 이다
    2" archtecture - 이 값에는 CPU의 ISA를 입력한다. 0은 32비트 보호 모드 i386을 의미하고, 4는 32비트 MIPS를 의미한다.
    3" header_length - 멀티 부트2 헤더 전체 길이를 명시한다. 즉, 위에서 `헤더 매직 필드 + 헤더 태그` 길이를 의미한다.
    4" checksum - 이 값은 unsigned int 이다. 즉, 무조건 이 값은 무조건 양수여야 한다. 그리고 대게 스펙에서 이 체크섬을 구할 때, 주변 필드들도 이용해서 구한다. 멀티 부트2 헤더에서도 체크섬을 구할 때, 위의 `magic`, `archtecture`, `header_length`를 통해 구한다. 체크섬을 구하는 방법은 아래 예제 코드에서 다시 설명한다.

     

     

    : 헤더 태그

    " 이 내용은 너무 많아서 간단하게만 설명한다. 구조는 아래와 같다.

    타입 필드
    u16 type
    u16 flags
    u16 size

    : 타입에 지정되는 값에 따라서 위의 헤더에 추가되는 필드들이 생긴다. 위에 3개는 모든 헤더 태그의 공통 속성이지만, 타입값에 따라 헤더 태그에 추가되는 속성들이 생긴다는 뜻이다.

     

    : 참고로, 멀티 부트헤더에서는 ELF 포맷이 아닌 다른 포맷을 사용하면 반드시 어드레스 태그까지 구현을 해야한다.

     

    - 머신의 상태

    : i386 머신 상태

    " i386을 기준으로 멀티 부트2 부트 로더는 I386 32비트 커널을 로드하면, 해당 머신의 상태는 아래와 같다.
    " EAX - 반드시 `0x36d76289` 값을 갖는다. 이 값의 의미는 운영 체제가 멀티 부트2 호환 부트 로더를 통해 로드됬다는 것을 의미한다.
    " EBX - 부트 로더에 의해서 제공되는 정보들의 주소가 들어있다. 
    "  CS - 32비트 READ/EXECUTE 코드 세그먼트 디스크립터 인덱스가 들어가 있다. 베이스는 0으로, 리밋은 0xFFFFFFFF 설정되어 있다.
    " DS, ES, FS, GS, SS - 32비트 READ/WRITE 데이터 세그먼트 디스크립터 인덱스가 들어가 있다. 베이스는 0으로, 리밋은 0xFFFFFFFF 설정되어 있다.
    " A20 - 반드시 활성화되어 있음.
    " CR0 - PAGE는 비활성화 PG는 활성화. 나머지 비트는 언디파인.
    " ESP - 되도록이면 최대한 빨리 재설정을 추천.

     

    - 체크섬

    : 매직 넘버만으로는 해당 OS 이미지가 멀티 부트를 지원한다고 판단하기 어렵다. `0xE85250D6` 라는 값은 멀티 부트를 지원하지 않는 실행 파일에서도 너무 쉽게 발견될 수 있는 값이다. 그래서 이 값 하나만으로 현재 OS 이미지가 멀티 부트를 지원한다고 판단해버려서는 안된다. 여기서 체크섬까지 검사해서 해당 파일이 멀티 부트를 지원하는지 못을 박아버리는 것이다. 물론, 체크섬은 파일이 변조되었는지에 여부로도 사용이 된다.  

     

    - 예제

    : 멀티 부트2 스펙에는 해당 스펙을 지원하는 32비트 부트 로더를 어떻게 작성해야 하는지에 대한 예제가 나와있다. 멀티 부트 매직 헤더쪽을 한 번 보자. 아래 코드는 x86 기반의 `AT&T` 문법을 따른다.

    _start:
            jmp     multiboot_entry

            /*  Align 64 bits boundary. */
            .align  8
            
            /*  Multiboot header. */
    multiboot_header:
            /*  magic */
            .long   MULTIBOOT2_HEADER_MAGIC ; 0xE85250D6.
            /*  ISA: i386 */
            .long   GRUB_MULTIBOOT_ARCHITECTURE_I386 ; 0
            /*  Header length. */
            .long   multiboot_header_end - multiboot_header
            /*  checksum */
            .long   -(MULTIBOOT2_HEADER_MAGIC + GRUB_MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
    #ifndef __ELF__
    address_tag_start:      
            .short MULTIBOOT_HEADER_TAG_ADDRESS
            .short MULTIBOOT_HEADER_TAG_OPTIONAL

    ...
    ...

    multiboot_header_end:
    multiboot_entry:
            /*  Initialize the stack pointer. */
            movl    $(stack + STACK_SIZE), %esp

            /*  Reset EFLAGS. */
            pushl   $0
            popf

            /*  Push the pointer to the Multiboot information structure. */
            pushl   %ebx
            /*  Push the magic value. */
            pushl   %eax

            /*  Now enter the C main function... */
            call    EXT_C(cmain)

            /*  Halt. */
            pushl   $halt_message
            call    EXT_C(printf)
            
    loop:   hlt
            jmp     loop

    halt_message:
            .asciz  "Halted."

            /*  Our stack area. */
            .comm   stack, STACK_SIZE

    : 멀티 부트의 매직 헤더는 총 4바이트를 차지한다. 여기서는 체크섬을 구하는 식을 잠깐 보자. 왜 저 식이 됬는지는 의미가 없다. 저 스펙을 정의한 사람들도 저 식을 만든 이유를 설명하라고 하면 무조건 `unsigned int`가 되야 해서 저렇게 만들었다고 설명할 수 도 있다. 즉, 저 식은 수학에서 정의와 같다고 보면 된다. 멀티 부트2 헤더 MAGIC 상수는 `0xE85250D6` 로 고정이다. 이 값만 사용해도 이미 음수다. 32비트에서 0x80000000 이상의 값들은 모두 음수다. 그래서 최종적으로 저 앞에 `-`를 붙여서 양수를 만들어준다. 

     

    : 나머지 값들을 더한 이유는 크게 의미가 없다. 스펙에서도 설명이 없다. 단지 주변에 같이 정의한 필드들과 사칙연산을 통해서 구한다는 특징정도가 있다. 

     

     

    - 에러 사항

    : 멀티 부트 헤더 에러

    " 아래 에러는 멀티 부트 헤더를 잘못 작성했거나 grub.cfg에서 멀티 부트 버전을 잘못작성했을 경우, 발생할 수 있다. 

    error: no multiboot header found.
    error: you need to load the kernel first.

    " 예를 들어, 나는 멀티 부트 버전2를 사용하는데 아래의 코드는 GRUB에게 멀티 부트1 헤더를 찾으라고 명령한다. 

     

    set default=0
    
    menuentry "YohdaOS" {
        multiboot /kernel32.bin # For multiboot1                                                                          
        boot
    }

    " 멀티 부트2 헤더를 찾으라고 명령하려면, 아래와 같이 수정한다.

     

    set default=0
    
    menuentry "YohdaOS" {
        multiboot2 /kernel32.bin # For multiboot1                                                                          
        boot
    }

     

    : 정렬 및 섹션 에러

    " 멀티 부트2는 8바이트 정렬이다. 즉, LBS 3비트는 반드시 0이여야 한다. 안 그러면 아래의 에러가 발생한다.

    error: unsupported tag: 0xXXXX

    : 그리고 멀티 부트 헤더는 반드시 파일에 가장 앞에 와야 한다. 

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    TSS  (0) 2023.07.06
    [x86] Unreal mode  (0) 2023.06.28
    BOCHS  (0) 2023.06.24
    Long mode  (0) 2023.06.20
    [리눅스] - printf & printk  (0) 2023.06.19
Designed by Tistory.