ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 에러 사항 2
    카테고리 없음 2023. 6. 29. 15:32

    글의 참고


    글의 전제

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

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


    글의 내용

    - relocation truncated to fit: R_X86_64_32 against `.rodata'(미해결)

    : 해결은 했지만, 아직 원인은 찾지 못했다. 일단 `-fPIC` 옵션을 통해서 해결을 했다.

     

     

    - 64비트 전환 이후 모든 변수 초기화?(미해결)

    : 64비트 전환이후, 아래의 코드로 64비트 진입을 출력하려고 했는데, 출력이 안됨.

    #include "io.h"

    int main();
    int main()
    {
        kprintf("64bit Kernel Start!!!\n");                                                                                          
        while(1);   
    }

    : 디버깅 해보니, vga_text_bp, vga_text_cp 에 0xb8000의 값이 사라져 있고, 0으로 되어 있음. 즉, 뭔가 모드 전환이후 초기화가 된 것 같음. GDT를 64비트로 교체하면서 발생한 문제가 아닐까 생각중. 아래처럼 VGA를 한 번 초기화해주면 출력 잘됨.

     

    #include "io.h"

    int main();
    int main()
    {
        vga_text_init();
        kprintf("64bit Kernel Start!!!\n");                                                                                                
        while(1);   
    }

     

    - 어셈블리어 주의 사항

    : `rep 명령어`는 [E]CX 만큼 뒤에 명령어를 반복해주는 함수다. 문제는 이걸 NASM에서 쓸 때, 문제가 있었다... 아래의 코드의 차이를 보자. 왼쪽은 GDB로 `rep movsb`를 지나도 아무 반응을 하지 않는다. 오른쪽은 정상적으로 잘 동작한다.

    ; From here, start to get into protected mode.                                
    _pre_pmode: 
        mov eax, cr0
        or al, 1
        mov cr0, eax    

        jmp GDT32_CODE:_penter

    _penter:
        mov eax, GDT32_DATA     
        mov ds, eax             ; Load 32bit Date Segment Discriptor
        mov es, eax
        mov fs, eax
        mov gs, eax

        xor eax, eax
        mov ds, eax
        mov es, eax 
        mov edi, 0x100000
        mov esi, 0x10000
        mov ecx, 0x40000
        rep movsb 

        jmp $
        
        ; below codes for protected mode
        push word [total_read_sec]
        jmp PMODE_ENTRY_POINT   ; boot loader entry point of protected mode
    ; From here, start to get into protected mode.                                
    _pre_pmode: 
        mov eax, cr0
        or al, 1
        mov cr0, eax    

        jmp GDT32_CODE:_penter

    [bits 32]
    _penter:
        mov eax, GDT32_DATA     
        mov ds, eax             ; Load 32bit Date Segment Discriptor
        mov es, eax
        mov fs, eax
        mov gs, eax

        xor eax, eax
        mov ds, eax
        mov es, eax 
        mov edi, 0x100000
        mov esi, 0x10000
        mov ecx, 0x40000
        rep movsb 

        jmp $
        
        ; below codes for protected mode
        push word [total_read_sec]
        jmp PMODE_ENTRY_POINT   ; boot loader entry point of protected mode

    : 지시어로 `[bits 32]`에 주목해야 한다. 아래는 어셈블러가 빌드를 통해 확인된 나온 오브젝트 파일을 `objdump`로 확인해본 것이다. `[bits 16]` 지시어는 16비트 이상 MSB 값은 모두 0으로 대체하는 것으로 확인된다. 결론적으로 왼쪽의 코드는 `edi = 0 , esi = 0, ecx = 0`이 되버린다.

     

    00000125 <_penter>:
         125: 66 b8 10 00           mov    $0x10,%ax
         129: 00 00                 add    %al,(%eax)
         12b: 8e d8                 mov    %eax,%ds
         12d: 8e c0                 mov    %eax,%es
         12f: 8e e0                 mov    %eax,%fs
         131: 8e e8                 mov    %eax,%gs
         133: 66 31 c0              xor    %ax,%ax
         136: 8e d8                 mov    %eax,%ds
         138: 8e c0                 mov    %eax,%es
         13a: 66 bf 00 00           mov    $0x0,%di
         13e: 10 00                 adc    %al,(%eax)
         140: 66 be 00 00           mov    $0x0,%si
         144: 01 00                 add    %eax,(%eax)
         146: 66 b9 00 00           mov    $0x0,%cx
         14a: 04 00                 add    $0x0,%al
         14c: f3 a4                 rep movsb %ds:(%esi),%es:(%edi)
         14e: eb fe                 jmp    14e <_penter+0x29>
         150: ff 36                 pushl  (%esi)
         152: bf 01 e9 fe ff        mov    $0xfffee901,%edi
    00000125 <_penter>:
         125: b8 10 00 00 00        mov    $0x10,%eax
         12a: 8e d8                 mov    %eax,%ds
         12c: 8e c0                 mov    %eax,%es
         12e: 8e e0                 mov    %eax,%fs
         130: 8e e8                 mov    %eax,%gs
         132: 31 c0                 xor    %eax,%eax
         134: 8e d8                 mov    %eax,%ds
         136: 8e c0                 mov    %eax,%es
         138: bf 00 00 10 00        mov    $0x100000,%edi
         13d: be 00 00 01 00        mov    $0x10000,%esi
         142: b9 00 00 04 00        mov    $0x40000,%ecx
         147: f3 a4                 rep movsb %ds:(%esi),%es:(%edi)
         149: eb fe                 jmp    149 <_penter+0x24>
         14b: 66 ff 35 bf 01 00 00  pushw  0x1bf
         152: e9 fc ff 0f 00        jmp    100153 <PMODE_ENTRY_POINT+0x153>

     

     

    - GRUB2 멀티 부트 이후, #GP(미해결)

    : GRUB2를 통해 MULTIBOOT2 헤더를 지원하는 32비트 부트 로더를 만들었다. 그런데, 2가지 에러가 새롭게 실행할 때마다, 번갈아 가면서 발생한다. 계속 #GP가 발생한다. 

    check_exception old: 0xffffffff new 0xd
         1: v=0d e=0008 i=0 cpl=0 IP=0010:0010ce37 pc=0010ce37 SP=0018:0007eda8 env->regs[R_EAX]=000009eb
    EAX=000009eb EBX=00000000 ECX=0000000b EDX=000009ea
    ESI=00000000 EDI=00000000 EBP=0007edb8 ESP=0007eda8
    EIP=0010ce37 EFL=00200206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     000010b0 00000020
    IDT=     001130d0 000007ff
    CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000004 CCD=000009eb CCO=EFLAGS  
    EFER=0000000000000000
    check_exception old: 0xd new 0xd
         2: v=08 e=0000 i=0 cpl=0 IP=0010:0010ce37 pc=0010ce37 SP=0018:0007eda8 env->regs[R_EAX]=000009eb
    EAX=000009eb EBX=00000000 ECX=0000000b EDX=000009ea
    ESI=00000000 EDI=00000000 EBP=0007edb8 ESP=0007eda8
    EIP=0010ce37 EFL=00200206 [-----P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     000010b0 00000020
    IDT=     001130d0 000007ff
    CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000004 CCD=000009eb CCO=EFLAGS  
    EFER=0000000000000000
    check_exception old: 0x8 new 0xd
         0: v=21 e=0000 i=0 cpl=0 IP=0010:0010ad95 pc=0010ad95 SP=0018:0007ed7c env->regs[R_EAX]=00000000
    EAX=00000000 EBX=00000000 ECX=020d0027 EDX=00000f5b
    ESI=00000000 EDI=00000000 EBP=0007ed94 ESP=0007ed7c
    EIP=0010ad95 EFL=00200246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     000010b0 00000020
    IDT=     001130d0 000007ff
    CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000001 CCD=00000000 CCO=LOGICB  
    EFER=0000000000000000
    check_exception old: 0xffffffff new 0xd
         1: v=0d e=010a i=0 cpl=0 IP=0010:0010ad95 pc=0010ad95 SP=0018:0007ed7c env->regs[R_EAX]=00000000
    EAX=00000000 EBX=00000000 ECX=020d0027 EDX=00000f5b
    ESI=00000000 EDI=00000000 EBP=0007ed94 ESP=0007ed7c
    EIP=0010ad95 EFL=00200246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     000010b0 00000020
    IDT=     001130d0 000007ff
    CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000001 CCD=00000000 CCO=LOGICB  
    EFER=0000000000000000
    check_exception old: 0xd new 0xd
         2: v=08 e=0000 i=0 cpl=0 IP=0010:0010ad95 pc=0010ad95 SP=0018:0007ed7c env->regs[R_EAX]=00000000
    EAX=00000000 EBX=00000000 ECX=020d0027 EDX=00000f5b
    ESI=00000000 EDI=00000000 EBP=0007ed94 ESP=0007ed7c
    EIP=0010ad95 EFL=00200246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0010 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0018 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     000010b0 00000020
    IDT=     001130d0 000007ff
    CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000001 CCD=00000000 CCO=LOGICB  
    EFER=0000000000000000
    check_exception old: 0x8 new 0xd

    : 왼쪽 창의 에러는 GDB로 확인해보면 `memset` 함수로 특정 메모리에 뭔가를 쓸 때, 발생한다. `memset`이 아니더라도, 뭐나를 쓰는 함수에서는 발생한다는 것 같고, 우연치 않게 그 위치에 `memset`이 있는 것으로 보인다. 멀티 부트2에서는 커널로 제어권이 이전되고 나서 GDT를 굳이 재설정하라는 권고는 보이지 않지만, 스택은 반드시 재설정하라고 권고하고 있다.

     

    : GRUB이 설정해 놓은 GDT를 왜 다시 설정해야 하는지에 대한 고민을 했던 것 같다. 위의 로그를 보면 데이터 및 코드 세그먼트 디스크립터 모두 베이스 어드레스를 0, 제한을 0xFFFFFFFF로 해서 딱히 건드릴 것은 없어보인다. 그런데 이상한 점은 세그먼트 디스크립터의 인덱스다. 일반적으로 GDT는 8바이트 정렬이라, 0x00, 0x08, 0x10, 0x18 ... 이런식으로 증가하는데 0x08 을 어떤 용도로 사용하는지 모르겠지만, 생략되어 있다. 그리고 에러 코드 `e=0008`는 1번 세그먼트 디스크립터에서 발생한 에러라고 말하고 있다. 그래서 GDT를 다시 세팅했다. 코드는 0x08, 데이터는 0x10으로 재설정하니 잘 동작한다. 그런데, 문제는 잘 동작하는 이유를 모르겠다. 

     

    : 두 번째 창은 `v=21, e=0000`가 보인다. 이 에러는 PIC 리맵핑 후에 발생한 에러다. PIC 리맵핑시에 0x20 ~ 0x30로 리맵핑한다. 그렇면, 0x21는 `키보드 인터럽트`다. 키보드는 아직 브링업을 하지도 않았는데, 에러가 발생했다. 즉, 키보드 영역을 건드렸다는 내용같다. 아직 시스템 메모리 맵을 부트 로더에서 읽어오지 않았는데, 이 부분은 확인이 필요하다. 

     

     

     

    - 32비트 머신에서 64비트 출력시 상위 비트만 출력(미해결)

    : 아래 코드를 보자. `entries` 구조체 변수에는 addr이라는 구조체 필드가 들어있다.

    struct multiboot_tag_mmap *mmap = (struct multiboot_tag_mmap *)tag;
                    struct multiboot_mmap_entry *entries = (struct multiboot_mmap_entry *)mmap->entries;
                    while((uint8_t *)entries < (uint8_t *)mmap->entries + mmap->size) {
                        debug("type#0x%x, addr#0x%x_%x, len#0x%x_%x\n", 
                                        entries->type,
                                        (entries->addr >> 32),
                                        (entries->addr & 0xFFFFFFFF),
                                        (entries->len >> 32),
                                        (entries->len & 0xFFFFFFFF)
                                    );                                                          
                        entries = (struct multiboot_mmap_entry *)((uint8_t *)entries + mmap->entry_size);
                    }
    struct multiboot_tag_mmap *mmap = (struct multiboot_tag_mmap *)tag;
                    struct multiboot_mmap_entry *entries = (struct multiboot_mmap_entry *)mmap->entries;
                    while((uint8_t *)entries < (uint8_t *)mmap->entries + mmap->size) {
                        debug("type#0x%x, addr#0x%x_%x, len#0x%x_%x\n", 
                                        entries->type,
                                        (unsigned int)(entries->addr >> 32),
                                        (unsigned int)(entries->addr & 0xFFFFFFFF),
                                        (unsigned int)(entries->len >> 32),
                                        (unsigned int)(entries->len & 0xFFFFFFFF)
                                    );                                                          
                        entries = (struct multiboot_mmap_entry *)((uint8_t *)entries + mmap->entry_size);
                    }

    : 왼쪽 출력은 다음과 같다.

    : 오른쪽 출력은 아래와 같다.

    : 내가 만든 printf 가 64비트를 지원하지 않아서 인게 제일 큰 문제인 듯 하다. 그런데, 컴파일러에 의해서 64비트 데이터 타입이 가변인자로 어떻게 들어가는지 알아봐야 될 듯 하다.

     

     

     

    - BOCHS에서만 16비트에서 32비트 커널 1MB 이상에 쓸 때, #GP 발생 현상

    : BOCHS 에서 `resp movsb`를 통해서 1MB에 0x40000 만큼 32비트 커널을 로드하는데, #GP 가 발생해 죽게된다. 에러 코드가 명확하긴 한데, QEMU나 Virtualbox에서는 발생하지 않는 현상이라 디버깅이 쉽지 않을 듯 하다. BOCHS 프롬프트에 `trace on`이라고 입력해야 아래의 상세한 내용들을 볼 수 있다. 

    : 아래 죽은 상황에서 어디까지 커널을 로드했는지, 확인해보니 0x11a110 언저리에서 쓰다가 트리플 폴트로 죽은 듯 하다. 다른 에뮬레이터에서도 저 값들에 대해 확인히 필요할 듯 하다.

    : 3~4 시간 걸려서 결국 알아냈다. BOCHS가 x86 기반의 에뮬레이터 중에 그래도 가장 안전성이 좋다는 것이 다시 한 번 느껴진다. 인텔 문서에서는 #GP 발생 시, 에러 코드는 0 혹은 인터럽트 넘버 혹은 세그먼트 셀렉터 값을 의미한다고 한다. 

     

    : 인터럽트와 익셉션이 동시에 발생한다. 0x08 혹은 0x0d다. 일단 저 위치는 32비트가 막 활성화 된 참이다. 그리고 아직 PIC가 리맵핑되기 전이다. 에러 코드를 확인해보니, `0x0043`과 `0x006b`다. 즉, 0번 째 비트가 1이란 소리다. 이 말은, 외부에서 인터럽트가 발생했다는 소리다. 플래그 레지스터를 확인해보니 저 시점에, 인터럽트도 활성화되어 있었다. 이걸 종합적으로 정리하면, 다음과 같다.

    1" 인터럽트가 활성화되어 있음.
    2" 에러가 발생한 지점은 보호 모드가 막 활성화된 지점임.
    3" #GP의 에러 코드를 확인해보면, 외부에서 발생한 인터럽트라는 것이 확인 됨.

    : 결국 인터럽트만 비활성화 시키면 되는 문제였다. 참고로, 이거 때문에 시스템 메모리 맵도 확인했지만, 1MB ~ 64MB 까지는 사용 가능 영역이었다.

     

     

    - 32비트 머신에서 `char`형 가변 인자 전달 시, `char*` 인식해서 va_arg()에서 `v=0x06` 발생(미해결)

    : `kprintf("%c", 'A')` 를 호출하면, 가변 인자에서 'A' 를 `char`가 아니라, `char *` 인식해서 va_arg(args, char)가 아닌, va_arg(args, char*)를 사용해야 올바르게 동작한다. 좀 더 분석할 필요가 있다.

     

     

    - VGA 텍스트 모드에서 스크롤 시, memcpy (미해결)

    : `80 * 25` 사이즈라는 제약때문에, 어쩔 수 없다고 쳐도 한 줄이 내려가는데 `25줄 * 80글자` 이 모두 다시 복사되는 것은 아니라고 생각된다. 그리고 심지어 줄 바꿈이 아닌, 한 글자씩 추가되는데 매번 `25줄 * 80글자` 도 문제가 있다.

     

     

    - kprintf 16진수 포맷 (미해결)

     : 0x00000002를 출력하고 싶은데, 0x2밖에 출력할 방법이 없다. 즉, `%8x`와 같은 포맷이 필요하다.

     

     

    - 키보드 `CapLock, Backspace, Up Arrow` 키 등 지원 (미해결)

     : `키보드 + 스크린`과`협동해서 진행해야 하는데, 상당히 난이도 있는 것 같다. `Backspace`는 일단 화면에 뿌린 다음에 지워야 되는데 좋은 방법이 있는지 모르겠다.

     

     

    - 리얼 모드 및 언리얼 모드에서 1MB 이상 영역에 커널 로드(미해결)

    : 2023-07-04 기준 언리얼 모드에서 BIOS 서비스 호출 시, 세그먼트 디스크립터가 원복되는 현상이 있어서 오프셋의 제한이 1MB로 다시 줄어드는 것 같다. 현재는 0x10000 ~ 0x50000에 썻다가 리얼 모드 진입하고 0x10000 으로 점프한 후, 32비트 데이터 세그먼트 디스크립터 로드하고, `rep movsb`를 이용해서 0x100000 ~ 0x104000에 쓰는데 부팅시 속도가 맘에 안든다. 다른 방법을 고안할 필요가 있어 보인다. 

     

    : 언리얼 모드에서 `BIOS INT=13h AH=2h`를 호출하면 프로그램이 즉각적으로 종료가 됬다. 앞에 내용 저전체가`[bits 16]`으로 진행했던 내용이라, BIOS 서비스를 호출하는 부분만이라도 `[bits 32]`를 해봐야 겠다는 생각이 든다.

     

    : 2023-07-10 기준, 2가지 방법을 사용해봤다. 언리얼 모드에서 로드하기, 리얼 모드와 보호 모드 바꿔가면서 로드하기. 실제 GRUB은 후자를 하는 것 같다. 위키에서 LILO는 전자를 사용한다고 했다. 그런데, 일단 나는 2개다 안된다. 그래서 일단 GRUB을 부트 로더로 쓰고 커널 개발에 좀 더 집중해야 겠다.

     

     

    - 키보드 인터럽트 안받아지는 문제

    : 맨 땅에 헤딩 하다보니, 기초적인 부분들에서 계속 막힌다. 일단 PIC는 반드시 인터럽트 처리가 완료되었으면, `EOI`를 전달해줘야 한다. 그리고 키보드 관련 내용에서 키를 눌렀으면, 반드시 PS/2 컨트롤러의 데이터 레지스터를 읽어 가야한다. 데이터 버퍼를 읽어가지 않으면, 다음에 인터럽트를 발생시키지 않는다.

     

     

     

    - 32비트 페이징 시, 동일 파일에서 Higher-Half 점프를 할 경우 GDB에서 `No Source Available` 현상(미해결)

    : 하나의 파일안에서 0x100000에서 0xC0100000으로 점프했다. 그런데 소스를 찾지 못한다. 

    ; pbl.asm
    section .text.boot ; 0x100000 
        ....
        mov [pde], dword 0x00000083         ; Identify Mapped [0x100000:0x400000]
        mov [pde+768*4], dword 0x00000083   ; Identify Mapped [0xC0000000:0xC0400000]
        mov ecx, pde
        mov cr3, ecx    

        mov ecx, cr0
        or ecx, 1<<31
        mov cr0, ecx

        mov ecx, 0x22222222
        mov edx, 0x3333333

        jmp higher_half_start


    section .text ; 0xC0100000
    higher_half_start: ;
        mov ecx, eax
        mov edx, ebx

    : 그래서 pbl.asm 안에 있던 코드를 2개로 분할했다. pbl.asm과 paging.asm은 다음과 같다.

     

        ;; pbl.asm
        ....
        mov ecx, cr4
        or ecx, 1<<4
        mov cr4, ecx

        mov [pde], dword 0x00000083         ; Identify Mapped [0x100000:0x400000]
        mov [pde+768*4], dword 0x00000083   ; Identify Mapped [0xC0000000:0xC0400000]
        mov ecx, pde
        mov cr3, ecx    

        mov ecx, cr0
        or ecx, 1<<31
        mov cr0, ecx

        jmp higher_half_start
        ....
    ;; paging.asm
    %ifndef _PAGING_NASM_
    %define _PAGING_NASM_

    bits 32

    extern main

    section .text
    global higher_half_start

    higher_half_start:                                                             
        push ebx ; push the pointer to multiboot information structure
        push eax ; push the magic value
            
        call main
            
        jmp $               ; Nevere come here
            
    %endif

     

    - 32비트 페이징 시, 주의점 1

    : 32비트에서 4GB 아이텐티티 페이지를 진행하고 main() 함수가 진행하던중에 갑자기 화면의 글자들이 사라졌다. 디버깅을 해보니, memset()이 문제였는데 보니깐 마치 0xb8000과 0x20b8000이 연결된 것처럼 동작을 했다. 물론, 아이텐티티를 매핑을 했지만, 그래도 이상했다. 아이텐티티 매핑은 1:1인데, 저건 마치 다대일 관계로 보였다. 그래서 확인해보니 페이징 설정에서 문제가 있었다.

     

    (gdb) x/16x 0xb8000
    0xb8000:        0x0f000f33      0x0f000f00      0x0f000f00      0x0f000f00
    0xb8010:        0x0f000f00      0x0f000f00      0x0f000f00      0x0f000f00
    0xb8020:        0x0f000f00      0x0f000f00      0x0f000f00      0x0f000f00
    0xb8030:        0x0f000f00      0x0f000f00      0x0f000f00      0x0f000f00
    (gdb) x/16x 0x20b8000
    0x20b8000:      0x0f000f33      0x0f000f00      0x0f000f00      0x0f000f00
    0x20b8010:      0x0f000f00      0x0f000f00      0x0f000f00      0x0f000f00
    0x20b8020:      0x0f000f00      0x0f000f00      0x0f000f00      0x0f000f00
    0x20b8030:      0x0f000f00      0x0f000f00      0x0f000f00      0x0f000f00
    (gdb) x/x ((u8 *)dst)+n
    0x20b8d3c:      0x0000005d
    (gdb) 
    ....
    section .data.boot
    align 4096
    global pde 
    pde:
        times 1024 dd 0x00000083
    ....

    : 모든 XXXB80000을 B8000으로 매핑시키고 있었다. 저렇게 구체적으로 다음과 같이 매핑된다. 다음부터는 꼭 주의하자. 근데, 신기한건 GDB는 페이징을 인식하는 것 같다. `x/x 0x20b8000`을 했는데, 0xb8000과 동일한 값을 보여주는것을 보면 GDB도 페이징을 인지하고 있는 것 같다.

     

    1" 0x000000 ~ 0x3FFFFF : 0x000000 ~ 0x3FFFFF
    2" 0x400000 ~ 0x7FFFFF : 0x000000 ~ 0x3FFFFF
    3" 0x800000 ~ 0xBFFFFF : 0x000000 ~ 0x3FFFFF
    4" 0xC00000 ~ 0xFFFFFF : 0x000000 ~ 0x3FFFFF
    ....
    ....
    N" 0xFFC00000 ~ 0xFFFFFFFF : 0x000000 ~ 0x3FFFFF

    : 4MB 페이징이라서 아래와 같이 수정했다. 가상 메모리 주소인 3GB ~ 4GB를 물리 메모리 0GB ~ 1GB에 매핑시킨다. 그리고 0번째 페이지는 잠깐의 변환을 위해서 활성화시켜 놓고, 추후에 상위 절반으로 가면 비활성화한다. 

    align 4096
    global pde 
    pde:
        dd 0x00000083                                                                                        
        times 767 dd 0x00000000
    %assign i 0 
    %rep 256 
        dd 0x00000083 + i 
    %assign i i+0x400000
    %endrep                                                                                  

    : QEMU도 그렇지만, GDB도 모두 페이징을 인식하고 있는 것 같다. 아직 0번째 페이지를 비활성화하지 않아서 0x000000 ~ 0x3FFFFF 사이의 주소에 값을 확인하면 값이 나온다. 그러나, 그 사이에 0x400000 ~ 0x2FFFFFFF 사이의 주소에 값을 확인해보면 `Cannot access memory at address XXXXXXXX`가 나온다.

    (gdb) x/100x 0x2000000
    0x2000000:      Cannot access memory at address 0x2000000
    (gdb) x/5x 0xc2000000
    0xc2000000:     0x00000000      0x00000000      0x00000000      0x00000000
    0xc2000010:     0x00000000
    (gdb) x/x 0x400000
    0x400000:       Cannot access memory at address 0x400000
    (gdb) x/x 0x3fffff
    0x3fffff:       Cannot access memory at address 0x3fffff
    (gdb) x/x 0x3fffec
    0x3fffec:       0x00000000
    (gdb) x/x 0xcffffff3 
    0xcffffff3:     0x00000000
    (gdb) x/x 0x123dfe10
    0x123dfe10:     Cannot access memory at address 0x123dfe10
    (gdb) 

     

    - 컨택스트 스위칭 스택 사이즈 주의점(미해결)

    : 프로세스 및 스레드의 커텍스트 스위칭을 진행하면, 스택이 교체된다. 그런데, 이 여기서 스택 사이즈가 문제가 될 수 있다. 예를 들어, 스레드의 스택 사이즈가 2048B이라고 가정하자. 그리고 printf의 버퍼 사이즈가 4096B 이라고 가정하면 printf문을 호출할 때, 마다 4096B 스택을 사용하게 된다. 아래 코드를 보자.

    struct proc_ctrl_blk *p1, *p2;
    void test1()
    {
        while(1) {
            debug("123123\n");
            context_switch(&p1->context, p2->context);
        }
    }

        
    void test2()                                                                                  
    {
        while(1) {
            debug("456456\n");
            context_switch(&p2->context, p1->context);
        }
    }

    ...
    ...

    main()
    {
     ... 
     ...
        proc_init();
        p1 = proc_create(test1);                                                                  
        p2 = proc_create(test2);
                
        context_switch(0xC3000000, p1->context);
    }

    : 프로세스 p1, p2가 만들어진다. 그리고 context_switch()를 통해서 p1은 "123123"을, p2는 "456456"을 출력하게 된다. 그런데, debug() 문은 kprintf()를 사용하는데, 여기서 지역 변수로 4096B나 되는 버퍼를 하나 선언하고 있다. 현재 각 프로세스의 스택 사이즈를 2KB로 했는데, 저 변수가 그 이상을 차지하니 다른 프로세스의 영역까지 침범하게 됬다. 그래서 사이즈를 줄였는데, 이제 정상적으로 잘 출력된다. 이걸 라이브러리로 만들면 문제가 되지 않을까? 커널 스택영역으로 할당되면 크게 문제가 되진 않을 것이라 생각들지만, 그럴 수가 없다. 컨택스트 스위칭이 되면 이제 커널 영역으로 돌아올 여유가 있는지 의문이다. 그렇다고, kprintf 문을 호출할 때마다 커널 영역으로 교체하고 출력이 되면 다시 돌아오는 건 좀 아닌 듯 한데 말이다. 결국, 라이브러리로 만들었는데도 프로세스 영역에 할당되면 전역 변수 및 static 으로 선언해야 되지 않을까 싶다.

     

    struct pr_info {
        va_list *args;
        int len;                                                                                  
        char buf[4096];
    };
    ...
    ...

    void vkprintf(const char *fmt, va_list args)
    {
        u8 state = PRINT_NORMAL;
        struct pr_info pi;
        char c;
        int i = 0;

        memset(&pi, 0, sizeof(pi));
        for(pi.args=&args; c=fmt[i]; i++) {
        ....
        ....

     

    - 상위 절반 전략에서 맨 처음에 페이즈를 비활성화하고 인터럽트 활성화 시, 페이지 폴트 현상(미해결)

    : 이 문제는 아래와 같이 페이지 디렉토리 0번 엔트리(0x000000 ~ 0x3FFFFF)를 비활성화하고 `sti` 명령어를 사용해서 인터럽트를 활성화하면, 거의 곧 바로 페이지 폴트가 발생하는 이슈였다. 

    %ifndef _PAGING_NASM_
    %define _PAGING_NASM_

    bits 32

    extern main
    extern pde 

    section .text
    global higher_half_start

    higher_half_start:
        mov dword [pde], 0                                                                           
        invlpg [pde]    

        push ebx ; push the pointer to multiboot information structure
        push eax ; push the magic value
            
        call main
             
        jmp $               ; Nevere come here
            
    %endif

    : 에러 코드가 0x0000 이라서 존재하지 않는 페이지라는 것을 인지했다. CR2 레지스터를 봐야한다.

     

    check_exception old: 0xffffffff new 0xe
         1: v=0e e=0000 i=0 cpl=0 IP=0008:c0108403 pc=c0108403 SP=0010:c0214fdc CR2=0000801f
    EAX=00000021 EBX=00000000 ECX=00000f9f EDX=0000008f
    ESI=00000004 EDI=00000004 EBP=c0214ff8 ESP=c0214fdc
    EIP=c0108403 EFL=00200286 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     00008017 00000028
    IDT=     c010b020 000007ff
    CR0=80000011 CR2=0000801f CR3=00101000 CR4=00000010
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000010 CCD=c0214fc0 CCO=ADDL    
    EFER=0000000000000000
    check_exception old: 0xe new 0xe
         2: v=08 e=0000 i=0 cpl=0 IP=0008:c0108403 pc=c0108403 SP=0010:c0214fdc env->regs[R_EAX]=00000021
    EAX=00000021 EBX=00000000 ECX=00000f9f EDX=0000008f
    ESI=00000004 EDI=00000004 EBP=c0214ff8 ESP=c0214fdc
    EIP=c0108403 EFL=00200286 [--S--P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     00008017 00000028
    IDT=     c010b020 000007ff
    CR0=80000011 CR2=0000801f CR3=00101000 CR4=00000010
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000010 CCD=c0214fc0 CCO=ADDL    
    EFER=0000000000000000
    check_exception old: 0x8 new 0xe

    : 저 값이 무엇일까를 하루 종일 고민했다. 일단 저 값은 YohdaOS에서 16비트 SBL 부근이다. 그래서 저 영역에 무엇이 있는지 OBJDUMP를 통해 알아봤다.

    $ objdump -d bl.elf | grep -A10 -B10 801f

    00008013 <_error>:
        8013: eb fe                 jmp    8013 <_error>

    00008015 <__isr_gp>:
        8015: eb fe                 jmp    8015 <__isr_gp>

    00008017 <_gdt_null_desp>:
    ...

    0000801f <_gdt32_code_desp>:
        801f: ff ff 00 00 00 9a cf 00                             ........

    00008027 <_gdt32_data_desp>:
        8027: ff ff 00 00 00 92 cf 00                             ........

    0000802f <_gdt64_code_desp>:
        802f: ff ff 00 00 00 9a af 00                             ........

    00008037 <_gdt64_data_desp>:
        8037: ff ff 00 00 00 92 cf 00                             ........

    : `GDT CODE 세그먼트` 였다. 인터럽트를 활성화했는데, GDT CODE(0x801F)를 참조하면서 페이지 폴트가 발생한 것이다. 어떻게 해야할까? 0xC0000000 위쪽으로 위치를 옮겨야 할 것 같다. GDT를 로드할 때는 선형 주소를 사용하므로, 저기에 선언해도 될 것 같다. 

     

     

    인터럽트를 활성화하면 문제가 발생하고, 인터럽트를 활성화하지 않으면 잘된다(근데, 이게 잘 된다고 볼 수 있는건가?). 즉, 페이지 문제는 아닌거 같은데 정확한 이유를 모르겠다.

     

     

    : 다시 정리하면 2가지의 경우로 나누 본다.

      인터럽트 활성화 인터럽트 비활성화
    첫 번째 페이지 유지 O O
    첫 번째 페이지 제거 X ?

    : 결국 페이지 문제로 보이는데, 

     

     

    - 상위 절반 커널 진입 후, 맨 첫번째 페이지 제거하면 페이지 테이블에 접근하지 못하는 현상(미해결)

    : 맨 첫번째 페이지를 비활성화(0x000000 ~ 0x400000)하면, SBL에서 선언한 pdes에 접근조차 되지 않는다. 어떠한 연산을 하더라도 `extern union pde pdes`에 연산을 하면, 무조건 페이지 폴트다. 그러므로, C 진입 전에 일단 첫 번째 페이지를 없애지 않는다.

    4KB로 페이지로 모두 교체가 되면, 그때 진행한다. 

     

     

    - 상위 절반 커널 진입 후, C언어에서 4MB에서 4KB로 페이지 재설정(미해결)

    : 32비트로 처음 진입하는 paging.asm에서 Higher half로 맨 처음 페이지 4MB랑 0xC000_0000 4MB를 아이텐티티 페이징을 진행한다. 그리고 C 메인으로 진입해서 최초에 페이지를 4KB로 재설정하려고 하는데, 이상하게 함수 리턴값이 바껴있다. 진입전에는 제대로 나와있던 값이 페이지 설정을 하면 스택에 들어있는 리턴값이 바껴 있다. QEMU 문제는 절대 아니고, 내 문제 같은데 일단 시간이 없으니 최초에 32비트 진입할 때, 그냥 제대로 처리하고 오는 것이 나을 듯 싶다.

     

     

    - 32비트 모드로 진입후 paging.asm 에서 1MB 페이지 테이블 생성 하고, 상위 절반으로 점프 후 페이지 폴트 에러(미해결)

    : `paging.asm` 파일에서 아래와 같이 코드를 짜면 계속 pbl.asm에 처음 진입하는 순간 페이지 폴트가 발생했다. pbl.asm에 진입하는 순간 에러가 발생했다. 저 `ptes`를 선언하지 않으면, 문제가 발생하지 않는다. 저 ptes의 사이즈는 1MB가 된다. 

    ; paging.c
    align 4096
    global pdes ; in later, main will use them
    pdes:
        dd 0x00000083 ; Identify Mapped [0x000000:0x400000]
        times 767 dd 0x00000000;                                                                   
        dd 0x00000083 ;Identify Mapped [0xC0000000:0xC0400000]
        times 255 dd 0x00000000

    align 4096
    ptes:
        times (128*1024) dd 0
    ; pbl.asm
    higher_half_start:                                                                             
        mov dword [pdes+0], 0
        invlpg [pdes+0]     

        lgdt [_gdtr]
        jmp GDT32_CODE:gdt_load

    : 일단 이 에러의 문제는 16비트 sbl.asm에 있었다. 내가 최초에 하드코딩으로 0x100000에 0x40000만 로딩하도록 해놓은게 문제였다. 파일 사이즈만큼 로딩을 해놔야 했는데, 이게 문제가 된다. `ptes`가 무려 1MB가 차지하니 sbl은 32비트 커널 코드는 고사하고 ptes의 절반도 로딩하지 못했을 것이다. 

     

    : 그러면, sbl.asm에 32비트 커널 이미지 크기를 보내야 할 텐데, 이걸 어떻게 보내야 할까? 16비트 커널은 32비트와 별개로 빌드하고 있기 때문에 같은 링크스크립트를 사용하지 않는다. 16비트 커널 코드와 32비트 커널 코드를 결합하지 않은 이유는 파일 사이즈가 증가하기 때문이다. 32비트 커널이 0x100000로 로딩되기 때문에 16비트 커널이 BIOS에 로딩되는 0x7C00와 중간에 차이가 꾀 있다. 그래서 파일을 나눴다. 

     

    : 일단 방법은 NASM 빌드 시 `-D` 옵션을 통해 매크로를 하나 넣어줄 계획이다. 

     

     

    - Makefile 에서 파일 사이즈 관련 명령어들이 적용되지 않음(미해결)

    : 이 링크의 모든 내용을 적용해봤지만, makefile에서 해당 내용이 적용이 되지 않았다. 그래서 shell script에서 작성했는데 잘 작동한다. 이유를 알지 못하고 넘어가 여기에 글을 남겨논다.

     

     

     

    - 16비트 GDB 갑자기 안되는 이슈

    : 16비트 디버깅을 하는데, 갑자기 `n` 키를 눌렀는데, 아무 응답도 하지를 않았다. 근데, `c`를 누르니 디버깅이 잡힌다. 처음 시작에서 레지스터 상태를 보면 아래와 같다. [CS:EIP]를 보면 BIOS부터 시작하는걸 볼 수 있다. BOCHS만 그러는 주 알았는데, `qemu-system-i386`도 BIOS부터 시작하는 걸 OS 개발을 한지 4개월만에 처음 알았다. BIOS가 이미 다 지나고 GDB를 연결하는주 알았는데, 아닌가 보다. 결론은 `c`를 누르면 된다.

    eax            0x0      0        ecx            0x0      0             edx            0x663    1635     │
    ebx            0x0      0        esp            0x0      0x0         ebp            0x0      0x0      │
    esi            0x0      0        edi            0x0      0               eip            0xfff0   0xfff0   │
    eflags         0x2      [ ]      cs             0xf000   61440    ss             0x0      0        │
    ds             0x0      0        es             0x0      0               fs             0x0      0        │
    gs             0x0      0

     

    - 컨택스트 스위칭이 안되는 현상

    : 컨택스트 스위칭 코드는 아래와 같다. 아주 간단한 코드다. 함수의 형태를 띄우고 있진 않지만, 실제로는 함수의 역할을 하고 있다. 함수 형태로 만들게 되면 구조가 망가지므로, 함수로 만들지는 않는다. 아래의 핵심 코드는 `esp` 를 통해서 2개의 프로세스가 서로 스택을 스위칭하는데 있다.

     

    context_switch:                                                                                   
        mov eax, [esp+4] ; old context
        mov ecx, [esp+8] ; new context

        push edi 
        push esi 
        push ebx 
        push ebp            

        mov [eax], esp 
        mov esp, ecx 
            
        pop ebp 
        pop ebx 
        pop esi 
        pop edi 

        ret 

    : 그런데, `SAVE` 되어야 하는 `old context` 쪽에 스택 정보가 저장이 되지 않았다. 확인해보니, 연산자 우선순위가 문제인데 아직도 이해가 가질 않는다. 왼쪽은 되고, 오른쪽 안된다. 

    context_switch(&(scheduler.context), new->context); context_switch(&scheduler.context, new->context);

    : 연산자 우선순위는 아래와 같다. `.`은 `&` 보다 우선 순위가 앞선다. 그런데, 괄호를 쳐야하는 이유를 모르겠다.

     

    - Syntax error: Bad for loop variable

    : `bash`와 `sh`의 차이다. for문은 `sh`에서만 가능하다. `bash`에서는 `while`을 사용해야 한다. `#!/bin/sh`이 아닌, `#!/bin/bash`로 바꾼다. 혹은, for문을 while로 바꾼다.

     

     

     

    - TSS 디스크립터 로딩 오류

    : 분노의 빡침으로 글을 적는다. TSS 디스크립터를 로딩하는데, 계속 오류가 났다. 도대체 이유가 뭘지를 고민하다. 뭔가 이상함을 느꼈다. 결국 원인은 `LTR` 명령어를 잘못사용해서 발생한 문제다. 아래에서 왼쪽은 안되고, 오른쪽은 된다.

    ...
    ltr [_tss_desc - _gdt_tbl]
    ...
    ...
    xor cx, cx
    mov cx, _tss_desc - _gdt_tbl
    ltr cx
    ...

    : 괄호를 사용해서 저안에 값을 참조하도록 했다. 즉, 0x28(_tss_desc - _gdt_tbl) 주소에 있는 값(0xd71c)을 참조하도록 했다. 그러니 계속 오류가 발생했다. 올바르게 하려면, 오른쪽처럼 당연히 괄호가 없이 사용해야 한다. 아래는 #GP가 발생한 오류를 볼 수 있는데, `e=d7c1`을 보고 바로 눈치챌 수 있어야 했다. 그러나, 이걸 하루 종일 하고 앉아 있었다. 미쳤지 진짜 ...

    check_exception old: 0xffffffff new 0xd
         0: v=0d e=d71c i=0 cpl=0 IP=0008:c010a04e pc=c010a04e SP=0010:c011b6e0 env->regs[R_EAX]=36d76289
    EAX=36d76289 EBX=00112dc0 ECX=0000d71e EDX=40000083
    ESI=00000000 EDI=00000000 EBP=c011b6e0 ESP=c011b6e0
    EIP=c010a04e EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     c0112d20 0000002f
    IDT=     00000000 00000000
    CR0=80000011 CR2=00000000 CR3=00109000 CR4=00000010
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000044 CCD=000000c0 CCO=EFLAGS  
    EFER=0000000000000000
    check_exception old: 0xd new 0xd
         1: v=08 e=0000 i=0 cpl=0 IP=0008:c010a04e pc=c010a04e SP=0010:c011b6e0 env->regs[R_EAX]=36d76289
    EAX=36d76289 EBX=00112dc0 ECX=0000d71e EDX=40000083
    ESI=00000000 EDI=00000000 EBP=c011b6e0 ESP=c011b6e0
    EIP=c010a04e EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
    ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
    SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
    LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
    TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
    GDT=     c0112d20 0000002f
    IDT=     00000000 00000000
    CR0=80000011 CR2=00000000 CR3=00109000 CR4=00000010
    DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
    DR6=ffff0ff0 DR7=00000400
    CCS=00000044 CCD=000000c0 CCO=EFLAGS  
    EFER=0000000000000000
    check_exception old: 0x8 new 0xd

     

    : GDT는 `[]`를 사용했는데, 왜 될까? `LGDT` 명령어로 오는 인자는 6바이트의 데이터를 포함하고 있어야 한다. 그리고 `LTR`은 세그먼트 셀렉터가 와야 한다.

    ...
    higher_half_start:                                                                                                   
        mov dword [pdes+0], 0
        invlpg [pdes+0]     
        
        mov ecx, _tss
        mov word [_tss_desc+2], cx

        shr ecx, 16
        mov byte [_tss_desc+4], cl
        
        shr ecx, 8
        mov byte [_tss_desc+7], cl

        lgdt [_gdtr]
        jmp KER32_CODE:gdt_load

    gdt_load:
        mov cx, KER32_DATA 

        mov ds, cx
        mov es, cx
        mov fs, cx
        mov gs, cx
        
        mov ss, cx
        mov ebp, stack_top
        mov esp, stack_top
        
        xor cx, cx
        mov cx, _tss_desc - _gdt_tbl
        ltr cx

    ...
    ...

    section .data
    align 8
    _gdt_start:
    _gdt_tbl:
    ; NULL Segment
    _gdt_null_desp:
        dw 0x0000
        dw 0x0000
        dw 0x0000
        dw 0x0000

    ; 32 Code Segment
    _ker32_code_desp:
        dw 0xFFFF
        dw 0x0000

    ...
    ...

    _tss_desc:
        dw (_tss_end - _tss) - 1
        dw 0x0000 ; base0
        db 0x00 ; base1
        db 0b10001001 ; P=1, DPL=00, 0, TYPE=1001
        db 0b00000000 ;
        db 0x00 ; base2
    _tss_desc_end:
    _gdt_end:


    _gdtr:
        dw _gdt_end - _gdt_start - 1 
        dd _gdt_tbl

    ...
    ...

    _tss: 
        dw 0x0000 ; ptl 2
        dw 0x0000 ; rsvd 4
        dd stack_top ; esp0 8
        dw 0x10 ; ss0 10
        dw 0x0000 ; rsvd 12
    ...

    : `LGDT`는 `LGDT m16&32` 형식을 띄고, `LTR`은 `LTR r/m16` 다. 이 내용은 인텔 명령어 공식 문서를 참조하자.

    Loads the values in the source operand into the global descriptor table register (GDTR) or the interrupt descriptor table register (IDTR). The source operand specifies a 6-byte memory location that contains the base address (a linear address) and the limit (size of table in bytes) of the global descriptor table (GDT) or the interrupt descriptor table (IDT).

     

    - 페이징 활성화 후, 코딩 시 주의점 2

    : 페이징이 활성화 된 후에, 가상 주소를 할당하지 않은 주소에 액세스하면 무조건 페이지 폴트가 발생한다. 아래 코드를 보면서 주의할 점에 대해 알아보자(0x0000_0000 ~ 0xBFFF_FFFF 까지는 가상 주소가 할당되지 않았다고 전제한다).

    uint32_t *p = 0x100000;
    uint32_t *p1 = p;
    uint32_t tmp = *p; // 페이지 폴트

    : 위에서 메모리의 주소에 액세스 하는 순간 페이지 폴트가 발생한다. 주소 자체에 액세스 하는 것이 에러이기 때문에, 그게 읽기든 쓰기든 상관없이 에러다. 그렇다면 어떻게 해야 할까? 물리 주소를 가상 주소로 변경해야 한다. 아래 함수 중 무엇이 올바른 변환 함수일까?

    void *vmm_phy_to_virt(const uint8_t *phy)
    {
        return (void *)(phy + 0xC0000000);
    }
    void *vmm_phy_to_virt(const uint32_t phy)
    {
        return (void *)(phy + 0xC0000000);
    }

    : 2개다 괜찮다. 직접 주소에 접근하지 않기 때문에 상관없다. 최종적으로 아래와 같이 변경해야 한다.

     

    uint32_t *p = 0x100000;
    uint32_t *p1 = p;
    uint32_t tmp = (uint32_t)vmm_phy_to_virt(p);

     

Designed by Tistory.