2007. 11. 14. 05:57
     

Part11. 커널 로더(Kernel Loader) 설명

원문 :  http://kkamagui.springnote.com/pages/347834

 

들어가기 전에...

0.시작하면서...

 커널 로더(Kernel Loader)는 프레임워크 소스 중에서 가장 CPU Architecture에 의존적이고 복잡한 부분이다. 커널 로더를 이해하려면 Intel Architecture에 대한 이해가 필요한데, 일일이 설명하기에 양이 너무 많다. 따라서 부트 로더와 마찬가지로 기능적인 부분으로 나누어 간략히 설명하겠다.

 커널 로더의 소스는 01Boot 폴더에 01Kloader 에 있다. 소스를 같이 참고하도록 하자.

 커널 로더에서 수행하는 순서는 아래와 같다.

  1. 세그먼트 레지스터의 재설정 
  2. 16bit 모드에서 32bit 모드로 전환 
  3. 커널 코드를 1M 주소 영역으로 이동 
  4. 커널 실행

 이제 각각의 순서에 대해서 살펴보자.

 

1.세그먼트 레지스터의 재설정

 부트 로더에서 커널 로더로 실행이 이행되면 세그먼트 레지스터 설정을 따로 할 필요가 없다. 왜냐하면 부트 로더에서 이미 다 설정해서 넘겨주기 때문이다. 하지만 프레임워크의 부트 로더가 커널 로더를 실행하라는 법이 없으므로 다시 세그먼트를 초기화 해준다.

  1.          ;   일단 세그먼트의 초기화
            mov     ax, cs
            mov     ds, ax
            mov     es, ax

 

2.16bit 모드에서 32bit 모드(Protected Mode)로 전환

 32bit 모드는 보호 모드 또는 Protected Mode라고 불리며 일반 16bit 모드와는 달리 여러가지 권한 설정과 영역 보호를 하드웨어적으로 수행할 수 있는 모드이다. 모든 기재를 사용하면 세세한 권한 설정 및 체크가 가능하지만  프레임워크에서는 이를 간단히 설정하여 사용한다.

 앞서 16bit와 32bit 모드간의 차이에 대해서는 Part5. Intel Architecture에 대한 소개에서 설명했으므로 참고하도록 하고, 지금은 전환에 대한 내용만 소개하겠다. 32bit 전환" href="http://kkamagui.springnote.com/pages/363853">참고. Intel i386 CPU의 16bit->32bit 전환 문서도 같이 참고하면 도움이 될 것이다.

 

2.1 디스크립터(Descriptor) 설정 

 전환을 위해 가정 먼저 해야될 준비는 32bit 모드에서 사용할 코드/데이터/스택 등등의 영역(세그먼트)에 대한 정보를 기술하는 디스크립터(Descriptor)를 생성하는 일이다.

 세그먼트를 세세하게 나누면 유저 모드용/커널 모드용 영역으로 크게 구분하고 다시 각 영역을 코드/데이터/스택 등등의 영역으로 각각 분할하여 처리할 수 있다. 하지만 프레임워크에서는 0 ~ 0xFFFFFFFF 크기의 세그먼트를 잡아서 코드/데이터에 할당하여 이를 간단하게 정의하였다. 이러한 모드를 일반적으로 Flat 모드라고 하는데, 논리주소가 물리주소에 1:1로 대응되고 메모리 관가 편리하므로 간단한 커널에서는 많이 쓰인다.

 

 자 그럼 실제로 디스크립터를 설정하는 부분에 대해서 살펴보자.

  1. gdt:
        ;null describtor
            dw 0
            dw 0
            db 0
            db 0
            db 0
            db 0
        ;새로 옮겨진 커널의 CodeOffset
        kernelCodeOffset equ $ - gdt
            dw 0xffff
            dw 0x0000
            db 0x0000
            db 0x9a
            db 0xcf
            db 0
        ;새로 옮겨진 커널의 DataOffset
        kernelDataOffset equ $ - gdt
            dw 0xffff
            dw 0x0000
            db 0x0000
            db 0x92
            db 0xcf
            db 0
        ;videoMemory
        videoOffset equ $ - gdt
        videoDesc:
            dw 0xffff
            dw 0x8000
            db 0x0b
            db 0x92
            db 0xcf
            db 0
        ;linear모드 데이터
        linearDataOffset equ $ - gdt
        linearDataDesc:
            dw 0xffff
            dw 0
            db 0
            db 0x92
            db 0xcf
            db 0
        ;code describtor
        codeOffset equ $ - gdt
        codeDesc:
            dw 0xffff
            dw 0
            db 0
            db 0x9a
            db 0xcf
            db 0
        ;data 영역의 descriptor
        dataOffset equ $ - gdt
        dataDesc:
            dw 0xffff
            dw 0
            db 0
            db 0x92
            db 0xcf
            db 0
    gdt_end:

 주석이 친절히 달려있으니 해당 디스크립터가 어떤 디스크립터인지 쉽게 파악할 수 있을 것이다 . 위에서 주의해서 볼 것은 디스크립터의 첫번째가 NULL 디스크립터로 전부 0으로 채워 할당한 부분이다. 이 부분은 CPU에 의해 예약된 영역이므로 다른 값으로 초기화 하거나 하면 곤란하다(영영 커널이 부팅되는 모습을 볼 수 없을지도 모른다).

 조금 전에 프레임워크에서 코드와 데이터 2개만 할당해 준다고 했는데, 왜 디스크립터가 6개나 되는 걸까? 주로 사용하는 영역은 코드와 데이터 디스크립터이고 나머지는 부수적으로 사용하는 디스크립터라서 그렇다. 필요에 의해 만든 것이므로 크게 신경쓰지 않아도 된다. 실제 동작을 위해서는 32bit 코드/데이터 디스크립터, 그리고 NULL 디스크립터만 있어도 된다.

 

 각 필드에 대한 의미나 플래그 값들은 Intel Architecture의 Volume 3을 참조하도록 하고 일단 디스크립터 형태에 대해서 잠깐 보고 넘어가자.

디스크립터.PNG

<Segment Descriptor>

 

 디스크립터는 위와같이 8byte로 이루어져있으며 각 속성(코드/데이터)에 맞게 플래그를 설정해주면 된다. 실제로 이 작업은 굉장히 까다로운 작업이며 약간 실수(??)하면 재부팅을 연발한다. 벗어나는 방법은 잘 설정된 코드를 참조하거나 끊임없는 수정을 통해 적절한 값을 찾는 방법말고는 없다.... ㅡ_ㅜ... 위에 디스크립터 구조와 부트 로더 코드를 보면서 비교하면 설정된 값의 의미를 알 수 있으므로 넘어간다.

 디스크립터의 설정이 끝나고나면 기대하던 16bit -> 32bit 전환의 단계가 남아있다.  전환하는 방법은 아주 간단하며 CR0 레지스터에 플래그 하나를 바꿔주는 것으로 전환은 끝이난다. 하지만 그 전에 전환 준비가 좀 까다롭다.

 

2.2 Global Descriptor Table Register(GDTR) 설정

 시스템 전체적인 디스크립터의 테이블을 Global Descriptor Table(GDT)이라고 하는데, 단 하나만 존재하는 영역으로써 코드/데이터/스택 등의 세그먼트에 대한 디스크립터나 태스크에 대한 디스크립터 등등을 저장하는 역할을 한다.

 위에서 디스크립터를 연속적으로 할당하여 값을 설정해 주었는데, 이 영역이 바로 GDT가 되는 것이다. GDT가 따로 존재하는 것이 아니고 연속된 메모리 위치에 디스크립터를 생성하면 그것이 GDT가 된다. 이 GDT 영역을 16bit->32bit 전환 전에 GDT Register에 등록해 줘야 하는데 GDTR은 아래와 같은 구조를 가지는 구조체의 주소를 가진다.

 디스크립터2.PNG

<GDTR 구조체>

 

GDT의 크기와 시작주소를 가지는 간단한 구조체이다. 이 구조체에 값을 설정하고 32bit 모드로 전환했을 때 사용할 디스크립터에 값을 설정하는 코드는 아래와 같다.

  1.         ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            ;   GDT를 설정한다.
            xor     ebx, ebx
            xor     eax, eax
            mov     bx, ds
            shl     ebx, 4
            mov     eax, ebx
            mov     word [codeDesc + 2], ax
            mov     word [dataDesc + 2], ax
            shr     eax, 16
            mov     byte [codeDesc + 4], al
            mov     byte [dataDesc + 4], al
            mov     byte [codeDesc + 7], ah
            mov     byte [dataDesc + 7], ah
           
            ; gdtr 레지스터 설정
            ;lea    eax, [gdt+ebx]
            ;lea 명령은 아래의 2줄과 같다.
            mov     eax, ebx
            add     eax, gdt
  2.         mov     [gdtr + 2], eax
  3. ...... 생략 ......
  4. gdtr:
            dw gdt_end - gdt - 1
            dd gdt

 설정된 gdtr의 값을 GDTR 레지스터에 설정하는 코드는 뒤에 16bit->32bit 모드 전환에서 나온다.

 

2.3 16bit->32bit 모드 전환

 마지막 작업은 인터럽터를 불가로 설정하고 실제 32bit 모드로 변환해 주는 것이다. 인터럽터를 불가로 설정하는 이유는 32bit 모드에서 인터럽트 처리를 위한 기반 작업(Interrupt Descriptor Table 설정과 같은 것들..)이 되어있지 않기 때문이다. 실제로 이 작업은 커널이 완전히 로딩된 후 설정하는 작업이기 때문에 커널 실행 전까지는 인터럽트 불가 상태로 설정해 놓는다.

 실제 모드를 변경하는 코드는 아래와 같다.

  1.         ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            ; 인터럽터 불가 설정
            cli
           
            lgdt    [gdtr]   <= 실제로 이부분이 GDT를 로딩하는 부분이다.
            ;   보호모드를 설정하고
            ;   인덱스 2 비트가 Floating Point 비트다.
            ;   Floating Point계산 유닛을 내부 유닛을 사용하도록
            ;   설정한다 그렇지 않으면 7번 인터럽터가 발생한다.
            mov     eax, cr0
  2.         or      eax, 0x80000000
            xor     eax, 0x80000000
            or      al, 1
            or      al, 0x04
            xor     al, 0x04
            mov     cr0, eax
  3.         jmp     codeOffset:code_32
  4. ;   여기에 진입하면 이미 보호모드다. 이제 리얼모드와는
    ;   상관없고 커널이 시작되면 돌아갈수도 없다.
    [bits 32]
        code_32:
            ;   레지스터를 초기화 하고
            mov     ax, dataOffset
            mov     ds, ax
            mov     es, ax
            mov     ss, ax
            mov     gs, ax
            xor     esp, esp
            mov     esp, 0x8ffff
            mov     ebp, 0x8ffff

 위에서 점프 명령을 이용하는 이유는 코드 영역을 설정하는 CS 레지스터 같은 경우는 일반적인 mov 명령으로 값을 바꿀 수 없기 때문이다. 따라서 far jump 명령을 이용해서 CS의 값을 커널 코드 영역을 가리키는 코드 세그먼트의 값으로 바꾸어 준다.

 32bit 영역으로 이동한 후에는 DS, ES, SS, GS, FS와 같은 데이터 관련 레지스터를 모두 커널 데이터 디스크립터로 변경하여 이후 데이터 접근 시에 문제가 발생하지 않도록 한다. 마지막 작업으로 스택을 설정함으로써 32bit 모드로 진입을 끝낸다.

 

3.커널 코드 이동

3.1 A20 Line Enable

 이제 남은 작업은 커널 코드를 1M 주소로 이동해서 재배치 하는 작업이다. 굳이 옮기지 않아도 되지만 옮긴 이유는 1M 이하 영역은 BIOS의 코드도 포함되어있고 DMA 관련 데이터 송/수신 시 사용할 영역을 확보한다는 의미도 있다(구버전의 DMA의 경우에는 1M 이상의 메모리에 접근이 불가능 했다).

 커널 코드 이동은 단순이 커널 존재하는 메모리를 1M 영역으로 복사하는 것을 의미하는 것은 아니다. IBM 호환 PC의 경우 16bit 모드일때 Address Line을 다 사용하지 않기 때문에 Address Line을 확장하도록 설정해 줘야 한다. 즉 확장하지 않으면 1M 이상의 메모리로 접근을 해도 이것이 다시 1M 안쪽의 메모리로 Wrapping되어서 접근된다.

 

 그럼 1M 이상의 메모리로 접근하기위해서는 어떻게 해야 할까? A20 Line을 활성화 하면 된다!!. A20이 무엇인지에 대해서는 http://www.resultspk.net/create_os/os-faq-memory.html#what_is_a20를 참고하도록 하고 1M 이상의 메모리로 접근하기위해 Off 상태로 되어있는 Address Line을 활성화 한다는 정도만 알아놓자.

 

 아래는 A20을 활성화하는 코드이다.

  1. ;   A20 영역을 Enable 시킨다.
    ;   wrapping된 line을 Enable한다.
    enable_A20:
        call    a20wait
        mov     al,0xAD
        out     0x64,al
  2.     call    a20wait
        mov     al,0xD0
        out     0x64,al
  3.     call    a20wait2
        in      al,0x60
        push    eax
  4.     call    a20wait
        mov     al,0xD1
        out     0x64,al
  5.     call    a20wait
        pop     eax
        or      al,2
        out     0x60,al
  6.     call    a20wait
        mov     al,0xAE
        out     0x64,al
  7.     call    a20wait
        ret
  8. a20wait:
    .l0:    mov     ecx,65536
    .l1:    in      al,0x64
        test    al,2
        jz      .l2
        loop    .l1
        jmp     .l0
    .l2:    ret

  9. a20wait2:
    .l0:    mov     ecx,65536
    .l1:    in      al,0x64
        test    al,1
        jnz     .l2
        loop    .l1
        jmp     .l0
    .l2:    ret

 

3.2 커널 이동

 이제 커널을 1M 영역으로 복사하면 끝이다. 코드가 좀 복잡하긴 한데, 주된 흐름은 이동 시작 위치와 목적 위치를 설정해주고 부트 로더에 들어있던 커널 이미지 크기만큼 1M 이상의 영역으로 복사하는 것이다.

  1.         ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
            ;   뒷 메모리에 있는 커널을 읽어 들여서 점프 한다.
            ;   일단 movsd 명령이 si 와 di의 값을 하나씩 증가시키게 한다.
            ;   df = 0 이면 증가 1 이면 감소
            cld
            ;   그리고 커널을 1M 메모리 영역으로 올린다.
            ;   커널이 위치하는 세그먼트 정보는 0x07c0:0x01fa에 있다.
            mov     ax, linearDataOffset
            mov     es, ax
            xor     eax, eax
            xor     ebx, ebx
            mov     ax, word [es:0x7dfa]
            sub     ax, 1
            mov     bx, 0x0200
            mul     ebx
            ;   커널 시작 섹터에서 0x0200을 곱하고 0xfe00을 더하면
            ;   커널이 시작되는 주소가 나온다.
            ;   거기로 옮기면 된다
            add     eax, 0xfe00
            mov     esi, eax
            ;   그리고 0x07c0:0x01fc에 커널의 섹터수가 들어있으므로
            ;   그것을 읽고 movsd의 명령으로 4byte씩 전송하므로 512/4 = 128
            ;   을 곱하면 movsd의 명령을 실행할 횟수가 나온다.
            xor     eax, eax
            xor     ebx, ebx
            mov     ax, word[es:0x7dfc]
            mov     bx, 0x80
            mul     ebx
            mov     ecx, eax
            ;   그리고 마지막으로 옮길 부분의 Offset을 구하면 1Mbyte의 위치이므로
            ;   0x100000 의 위치로 옮긴다.
            mov     eax, 0x100000
            mov     edi, eax
            ;   마지막으로 세그먼트를 설정한다. 둘다 linear로 설정한다.
            mov     ax, linearDataOffset
            mov     ds, ax
            rep     movsd

 위의 코드가 끝이 나면 1M 위치에 커널이 복사가 완료된 것이다. 이제 1M로 Jump하면 커널이 실행된다.

 

4.커널 실행

 커널의 실행은 허무할 정도로 간단하다. 아래의 한줄로 끝이난다.

  1.         jmp     kernelCodeOffset:0x100000

 그 이후는 커널이 실행되고 프레임워크 초기화를 수행하는 등등의 작업을 한다. 커널 코드가 이동된 곳은 1M(0x100000)의 위치다. 다시말해 커널 코드를 컴파일해서 링크했을 때 1M의 위치에서 실행가능하도록 해야한다.

 어떻게 1M의 위치에서 실행가능한 코드를 만들어낼까? 답은 링크 옵션(Link Option)에 있다. 프레임워크 소스 파일에서 커널을 생성하는 makefile을 열어보면 알겠지만, ld의 옵션중에 코드를 생성할때 특정 위치에서 실행가능하도록 링크하는 옵션이 있다.

-Ttext 0x100000

 위의 옵션을 사용하면 접근하는 변수, 상수들의 주소가 1M 위치를 Base로 해서 생성되기 때문에 1M의 위치에서 실행가능한 것이다. 만약 커널을 2M 영역으로 이동할 필요가 있다면 위의 부분을 0x200000 수정하고 부트 로더에서 커널을 이동하는 위치를 1M -> 2M로 수정해 주면 2M의 위치에서 커널을 실행할 수 있다.

 

5.물리 메모리 크기 확인

 실제 머신에 사용가능한 물리 메모리의 크기를 알면 커널 동작 시에 좀 더 능동적으로 작업을 수행할 수 있다. 물리 메모리의 크기를 아는 방법은 BIOS 함수을 이용해서 구하는 방법이 있으며, 직접 수작업으로 확인하는 방법이 있다.

 수작업으로 확인하는 방법은 메모리를 1M씩 증가시키면서 해당 주소에 1byte 값을 쓰고 다시 읽어서 쓴 값이 정상적인가를 확인하는 방법이다. 만약 물리 메모리가 사용가능하다면 쓰고 읽었을 때 정상적인 값이 나오지만, 사용이 불가능하다면 틀린 값이 나올 것이다. 실제로 Bellona2 OS(http://www.bellona2.com)가 이러한 방식을 택하고 있으며, 프레임워크에서도 직접 체크하는 방식을 따르고 있다.

 

 아래는 직접 체크하는 코드이다.

  1. ;   1M 이후 영역부터 루프를 돌면서 그 결과값을 저장한다.
    ;   결과값을 저장하는것은 0x7c00 즉 부트 섹터 시작 점에 적는다.
    ;   값은 Double Word로 한다.
    checkMemoryAmount :
            push        ebp
            push        eax
            push        ecx
            push        ebx
            push        gs
            push        es
  2.         mov     ecx, 0x00
            mov     ebx, 0x000000                  
            mov     ax, linearDataOffset
            mov     gs, ax
            mov     ax, videoOffset
            mov     es, ax
  3.     continue :
            add     ebx, 0x100000
            inc     ecx
            ;   메모리 체크 진행상황을 본다.
            ;mov        byte[es:ecx], '.'
            ;   일단 메모리에 무각기로 쓰고
            mov     byte[gs:ebx], 0x03
            ;   다시 메모리에 있는 내용을 비교한다.
            mov     al, byte[gs:ebx]
            cmp     al, 0x03
            je      continue
           
            mov     ebx, 0x7c00
            mov     dword[gs:ebx], ecx
  4.         pop     es
            pop     gs
            pop     ebx
            pop     ecx
            pop     eax
            pop     ebp
            ret

 

6.마치면서...

 이로서 부트 로더와 커널 로더의 설명이 끝이났다. 원래는 CPU 의존적인 부분들이라 설명을 하지 않고 넘어가려 했으나, 그대로 넘어가는 것이 마음에 걸려서 약간 설명을 한다는 것이.... ㅡ_ㅡ;;;;

 역시나 딱딱한 이야기들로 가득찼는데... ㅡ_ㅜ... 다음부터는 프레임워크를 이용하여 커널을 작성하면서 해당 파트에 대한 설명을 하겠다(쓰면서도 지루해 죽을 껏 같았다는.. ㅡ_ㅡ).

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.


Android App

Posted by 호기심 많은 kkamagui(까마귀, 한승훈)

댓글을 달아 주세요

  1. KLAUS1202 2010.08.29 14:17  댓글주소  수정/삭제  댓글쓰기

    사실 삼성맴버쉽에 os로 지원을 하고싶어서 만들어볼려구햇는데..
    이거너무 힘드네요..
    에효.. 너무 기초가 너무부족한건지;;
    에러가잇으면 뭘좀알면 어떻게하면되겟구나.. 이런게있는데 뭐아무것도모르니
    그런것도모르고 답변만 기달리게되네요 ㅠ.ㅠ...
    포기해야될까요 까마귀님 ㅠ.ㅠ...
    책에서 나온 소스 따라하는기분?? ㅡㅡ;;;;;
    이거어떡해야될까요 흑흑
    시스템쪽으로 하고싶긴한데....막막합니다 ㅠ.ㅠ
    너무 답답해서 넋두리하고갑니다 ㅠ.ㅠ

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.08.31 17:39  댓글주소  수정/삭제

      사실 이 바닥(?)이 좀 막연하긴 하죠. ^^;;;;

      저도 오류가 나면 헤매는 데요, 뭘 ㅎㅎ

      가상 머신의 도움을 받을 수 있는 부분은 최대한 받구요, 그게 안된다면야....

      그냥 스펙과 코드를 최대한 들여다보면서 머리 속으로 시뮬레이션(?)을 하는 것 말고는 뾰족한 방법이 없는 것 같습니다.

      화이팅이에요 ;)

  2. KLAUS1202 2010.09.10 14:16  댓글주소  수정/삭제  댓글쓰기

    헉 홈페이지 테마가 바껴서 까마귀님싸이트아닌줄알고.. 구글링으로 까마귀님의 다른싸이트도 많이봣거든요 ..
    그래서 다른대로옴기신줄알고 계속 찾아해맷네요 ㅡ.ㅡ;;; ㅠㅠ
    질문이 또한가지 들고왓답니다 ㅠ.ㅠ
    LGDT16 MACRO addr ; load from ds segment area
    db 3eh
    db 67h
    db 0fh
    db 01h
    db 15h
    dd addr
    ENDM

    nasm에선 lgdt라는 명령이 존재하지만 masm에선 명령이 존재하지않아서

    기계어로 만들었다고합니다. 저위에 부분입니다.

    x86 opcode and instruction 으로 찾아서 보니

    lgdt라는곳에서
    0F 01 2 02+ 0 LGDT GDTR Ms system Load Global Descriptor Table Register
    이런 문구가나오더군요

    nasm을 기계어로 변경하면 어짜피 기계어는 nasm이나 masm이나 똑같으니 디셈블리해서 알아봣더니
    00000009 0F01168500 lgdt [0x85] 이런값이 나오더군요 0f01이 lgdt명령이 맞는것같은데... 나머지가 먼지를 모르겟네요..

    위에보면 0fh 01h 가 나오는데 이게아닐까 싶은데요
    그렇다면 그위에 3e와 67은 무엇을 뜻하는것일까요 찾아보면 3e는 ds인것같구요..
    67은 뭘까요.. 마지막의 15h는 머고..

    좀어렵네요 ;; 그냥 쓰면 되지만.. 좀정확하게 알고 쓰고싶어서 그렇답니다...ㅠㅠ;;

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.09.12 12:16  댓글주소  수정/삭제

      안녕하세요, 여전이 열심히 하시는군요.

      Intel 문서 중에서 Volume 2A의 2.1 부분을 보시면 Instruction이 어떠한 구조로 되어 있는지 나와있는 부분이 있습니다.

      간략하게 옮겨보자면, (OPT라고 표시한 부분은 Option을 의미합니다)

      Instruction Prefix + Opcode + ModR/M(Opt) + SIB(Opt) + Dispacement + Inmediate

      위에서 말씀하신대로 lgdt가 0x0f + 0x01로 시작하므로 이전에 위치한 0x3E와 0x67은 Prefix가 되고 그 뒤에 위치한 0x15와 ModR/M이나 SIB가 되겠지요.

      Prefix를 가만히 살펴보면 0x3E DS Segment Override Prefix구요 0x67은 Address Size Prefix가 됩니다. ^^

      각 부분에 대한 좀더 자세한 내용은 Intel 문서를 참조하세요 ;)

  3. Klaus1202 2010.09.23 21:11  댓글주소  수정/삭제  댓글쓰기

    자꾸 visual 2008에서 에러가 나네요...
    부트섹터는 손수 dos명령을 써서 만들엇구요
    visual 2008에서 부트로더인 sload.asm 하고 syncos.c를 해서 컴파일을 하는데
    1>MASM : fatal error A1000:cannot open file : Sload.asm 이런문구가 나옵니다..
    파일을 열수없다는건데.. 분명 소스파일에 추가를해서 Sload.asm이 잇는데.. 안되네요..
    제 프로젝트 속성 옵션은 아래와 같이 햇습니다..
    그리고 빌드전 이벤트에서 ml /c /Fl /coff Sload.asm 이렇게 썻구요..
    뭐때문에 이런지 모르겟네요...ㅠㅠ

    C/C++ > 일반 :
    - 디버깅 정보 형식 : 사용 안함
    - 64bit 이식성 문제점 검색 : 아니오


    C/C++ > 최적화 :
    - 최적화 : 사용 안함
    - 전체 프로그램 최적화 : 아니오

    C/C++ > 전처리기
    - 전처리기 정의 : _DEBUG-
    표준 포함 경로 무시 : 예

    C/C++ > 코드 생성
    - 문자열 풀링 사용 : 예
    - 최소 다시 빌드 가능 : 아니오
    - 구조체 멤버 맞춤 : 1byte
    - 버퍼 보안 검사 : 아니오
    - 함수 수준 링크 사용 : 예

    C/C++ > 미리 컴파일된 헤더
    - 미리 컴파일된 헤더 만들기 / 사용 : 미리 컴파일된 헤더 사용 안함

    C/C++ > 출력 파일
    - 어셈블러 출력 : 어셈블리, 기계어 코드, 소스(/FAcs)

    C/C++ > 고급
    - 모든 전처리기 정의 해제 : 예


    링커 > 입력
    - 모든 기본 라이브러리 무시 : 예
    링커 > 디버깅
    - 디버그 정보 생성 : 아니오
    - 맵 파일 생성 : 예
    - 맵 파일 이름 : Syncos.map
    링커 > 시스템
    링커 > 고급
    - 진입점 : MAIN


    - 기준 주소 : 7e00

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.09.24 14:59  댓글주소  수정/삭제

      헛... 안녕하세요 ;)

      추석은 잘 보내셨나 모르겠네요 ㅎㅎ

      저는 eclipse로 작업을 하고 있어서 VS2008에 오류는 잘... ㅠㅠ

      어흑.. ㅠㅠ

  4. Klaus1202 2010.09.30 04:52  댓글주소  수정/삭제  댓글쓰기

    네 ^^ 잘보냇습니다 까마귀님도 추석잘보내셧는지요 ㅎㅎ;;

    아 eclipse로 작업을 하는중이시군요...

    다른에러들은 다풀었는데요 또 링크에러가 나버리네요
    response line too long. 이렇게요..

    뭐때문에 저런 링크에러가 나는지..;;
    분명 extern를 써서 다른파일을 쓸수있게끔 되어있는데
    뭐때문에 그럴까요;;

  5. Klaus1202 2010.10.16 09:08  댓글주소  수정/삭제  댓글쓰기

    ㅠ.ㅠ bochs에서 log파일을 보니 read_virtual_checks(): read beyond limit라는 말이 나오는데요..
    이문자만 계속 로그파일에 찍혓네요;;
    어떤문제일까요;;
    지금 bootsect에서 bootloader로.. 점프를 못하는것같습니다....................ㅠㅠㅠ...
    아흠..........
    masm으로 해서 만들면 점프가 잘되는데.. visual2008로 하게되면.. 점프가 안됩니다.....ㅠㅠ..난감하네요;;

  6. Klaus1202 2010.10.17 19:46  댓글주소  수정/삭제  댓글쓰기

    답변 감사드립니당..
    이 코드 재배치라는것이.. 점프위치를 pe파일을 뛰어서
    실제 bootloader가 있는 주소를 알아내서 점프하면 되는거아닌가요...?
    후미;; 코드재배치... 이건또 어떻게 하는건가욤 ㅠㅠ이런.. pe구조에 대해서 알아봐야겟군요;;
    visual 2008에서 에러가안나서 c로 인제 가겟구나 하고 무진장 좋아라햇는데.. 산넘어 더큰산이 잇군요 ㅡ.ㅡ;;ㅜㅜ

    • Favicon of https://kkamagui.tistory.com BlogIcon 호기심 많은 kkamagui(까마귀, 한승훈) 2010.10.17 20:55 신고  댓글주소  수정/삭제

      재비치 정보가 들어있는 PE 파일 포맷은 코드 자체가 재비치를 한 뒤에 실행되게 되어 있어서 그냥 엔트리 포인트로 jmp하는 것 만으로는 충분치 않습니다. ㅠㅠ

      부트 로더가 로딩된 곳에 맞추어 코드를 재배치를 해야만 그 메모리 어드레스에서 정상적으로 실행될 수 있습니다. ㅠㅠ

      그런데 Visual studio로 만들면 무조건 PE 파일로 나오나요? 옵션을 조절하면 그냥 Binary 파일로도 나오지 않나요?

  7. Klaus1202 2010.10.22 12:09  댓글주소  수정/삭제  댓글쓰기

    visual 로 만들게되면 어쩔수 없이 섹션 재배치를 해야되나봅니다...
    근데 이거 코드 재배치를 어떻게해야될지 난감하군요;;;;
    이론은 알아도 어셈으로 어떻게구현을 해야될지가;;ㅡ.ㅡ;; 흠..

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.10.23 21:22  댓글주소  수정/삭제

      음... 그것 참... 어셈으로 해야 한다라...

      약간 곤란한 상황이군요.. 쿨럭..;;

      어디 좀 쉽게 가는 방법도 있을 것 같은데...

      꼭 Visual Studio로 만들어야 하나요? ㅠㅠ

  8. klaus1202 2010.10.24 17:30  댓글주소  수정/삭제  댓글쓰기

    ㅠ.ㅠ.. 결국 bootsect에서 보호모드로 진입후 pe헤더있는 부분까지점프해서 bootloader을 점프후 c로 점프햇습니다..
    어짜피 고정된곳에 bootloader가 올라가니.. 그곳으로 점프를햇더니 되는군요... ㅠ.ㅠ
    이제 인터럽트랑 해봐야겟군요 ㅠ.ㅠ;;;;;; 그래도왠지 큰고비 넘긴듯하네용...
    이제부터는 또 얼마나 삽질을 해야될지 ㅠ.ㅠ 까마득합니다 흑흑
    까마귀님 친절한 답변 항상 감사드립니다^^ 매번 번거롭게 해드려서 죄송할따름입니다
    아 한가지...

    int SYNCOS_MAIN(void)
    {


    char* hello = "Hello OS!";
    unsigned char* vidmem = (unsigned char*)0xB8000;
    int i=0;

    while(hello[i] == '\0')
    {
    *vidmem++ = hello[i++];
    *vidmem++ = 0x06; // 문자 기본 속성

    for(;;);

    return 0;
    }

    이문장이 실행이안됩니다.. 무엇때문에 그런걸까요
    hello라는 포인터에 문자열이 없는것같은데..

    메모리와 관련이있는건가요.. 음..

    • Favicon of https://kkamagui.tistory.com BlogIcon 호기심 많은 kkamagui(까마귀, 한승훈) 2010.10.25 00:37 신고  댓글주소  수정/삭제

      문자열 같은 경우는 코드가 있는 .text 섹션과 달리 .data와 같은 섹션에 존재합니다. 만일 코드가 재배치를 하도록 생성되었다면, "Hello OS!"라는 문자열의 어드레스 역시 재배치를 해야 진짜 어드레스가 나오겠지요. 아마 그래서 제대로 출력이 되지 않은 것이 아닐까요?

      이것이 재배치에 문제인지를 확실하게 아는 방법은....

      코드를 조금 수정해서

      *vidmem++ = 'h';
      *vidmem++ = 0x06;
      *vidmem++ - 'e';
      ...

      처럼 분리해서 써서 화면에 제대로 출력되나 보면 됩니다. 저렇게 값을 직접 넣으면 코드에 'h'라는 값이 그대로 저장되거든요.

      그나저나 하나는 해결하셨다니 다행입니다. 화이팅 ;)

  9. klaus1202 2010.10.25 11:32  댓글주소  수정/삭제  댓글쓰기

    갑자기 궁금해졋는데요..
    real모드에서도 c로점프가 가능한가요?
    해보니 되는것같은데

    *viemem = 'A';
    해봣는데 이건 출력이 안되는군용;;
    주소는 분명 c코드있는곳인데.. bochs 로그파일을 보니;;

  10. klaus1202 2010.10.25 13:03  댓글주소  수정/삭제  댓글쓰기

    헛..역시 그것때문에 그런것이엿군요;;
    네 저렇게 테스트햇엇는데 나와서.. 메모리 문제인듯싶엇습니다...ㅠㅠ
    그럼 저걸 어떻게 해야되지요... 어쩔수없이 재배치를 해야되는걸까요.

    int SYNCOS_MAIN(void)
    {
    char *kernel = "SyncOS!";
    unsigned char * vidmem = (unsigned char*)0xB8000;
    unsigned char * hello = (unsigned char*)0x20600;

    while(*hello != '\0')
    {
    *vidmem++ = *hello++;
    *vidmem++ = 0x06; // 문자 기본 속성
    }



    for(;;);

    return 0;
    }
    이와같이 SyncOS 라는 문자열이 있는곳을 가르키고 출력을하면 출력이 잘되네요..
    어쩔수 없이 다시 bootsect로 돌아가 .data 섹션재배치를 해야될것같은데.. 맞나요?
    현재 bootsect.bin + syncos.bin = kernel.img 이렇게 되어잇는데요
    현재 bootstect코드는 binary코드이고 syncos가 bload.asm + syncos.c =syncos.bin입니다
    syncos.c 에서 섹션재배치 코드를 하면되는건가요 섹션재배치를 받아야될 파일인데;;
    이게안된다면 bootsect에서 섹션재배치를.. 어셈으로 해야된다는건데...ㅠㅠ 복잡하네요 머리속이 워낙모르니..
    어떻게 해결을해야될려나요 그냥 상관없이 .data 상관안하고.. 코딩하면되는걸까요;;
    이렇게되면.. data가 다.. 없어져버리면 코딩하기 좀그럴껏같은데..

    항상 죄송합니다..바쁘실텐데..


    그렇다면.. 16비트 어셈으로 어떻게 .. 재배치를 해야될지 에고;; ㅡ.ㅡ;;eclips로 가야될려나요..ㅠ방법이없으면..
    eslips로 갈렵니다 ㅠㅠ...
    어셈으로 섹션재배치를 할빠에야..ㅠㅠ

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.10.26 02:20  댓글주소  수정/삭제

      으음... 사실 커널 같은 경우는 고정된 어드레스에서 실행되기 때문에.... 가만히 생각해보면 굳이 재배치 파일을 만들지 않아도 됩니다만...

      재배치는 프로그램이 메모리 내에 어디서 실행될지 모르는 상태에서 동적으로 올려 실행할 때 유용한 것입니다. 커널은... 사실 움직이지도 않고 위치도 고정되어 있으니... 커널 파일을 생성하실 때 VS에서 Binary 파일로 만들면 안될까요? ^^;;;; 그리고 엔트리 포인트 쪽으로 Jump하면 편하게 처리할 수도 있습니다. ;)

  11. klaus1202 2010.10.26 13:54  댓글주소  수정/삭제  댓글쓰기

    네 그렇게 하고있습니다 ㅠ.ㅠ그런데 문자열이 20600번지에 있는데도 출력이안됩니다
    20600번지를 가르키면 출력이 되구요.. 왜 자동으로 c언어처럼 안되는지.. 섹션재배치를 해야만 하는건지모르겟네요
    그냥 그대로 printf 함수 만들면 되는건지.....
    그리고 현재 binary파일로 만들었는데요 그런데.. mz라는 문자열이 binary파일에도 등록이되는건가요??
    왜 syncos.bin이라는 파일을 hex에디터로 열어보면.. mz ...........dos 이런 pe구조가 들어있는지;;
    exe파일같은경우 들어있는걸루 일고잇는데요.. visual로 만들면 전부다 pe가 붙어서 나오는것같기도하고요;;

    • Favicon of https://kkamagui.tistory.com BlogIcon 호기심 많은 kkamagui(까마귀, 한승훈) 2010.10.27 19:33 신고  댓글주소  수정/삭제

      MZ가 붙어있으면 Binary가 아닙니다. 쿨럭..;;;

      MZ라는 놈은 PE 파일의 전신인 COFF 파일에 붙어있던건데... 그게 나온다는 말은 바이너리 파일이 아니라는 이야기지요.. ㅠㅠ

      코드와 데이터만 있는 "진짜" 바이너리 파일을 만드셔야 할 것 같습니다.

  12. klaus1202 2010.10.29 21:07  댓글주소  수정/삭제  댓글쓰기

    그렇군요.. bin파일로 출력되어 나오길래 binary인줄알았습니다...어쩐지 이상햇습니다..
    exe와 dll에나 들어잇어야되는곳에 mz가 들어잇어서..
    visual로 binary로 만들수있는건가요...음...

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.10.30 01:00  댓글주소  수정/삭제

      으음.... 글쎄요....

      VS로는 작업을 거의 안해봐서 잘... ㅠㅠ

      그래도 가능하지 않을까요? 나름 '최강' 칭호를 듣는 개발 툴인데... ^^;;;;

  13. klaus1202 2010.11.15 13:06  댓글주소  수정/삭제  댓글쓰기

    까마귀님.. 안녕하세요 ㅎㅎ 잘지내시나요???!! 오랜만에 들리네요^^;;
    오랜만에 다시 작업을 시작할려고합니다 ㅎㅎ;;(사정때문에)
    그런데 pe 섹션재배치를 어떻게해야될지 모르겟습니다.
    현재 bootsect가 잇고 bootsect에서 gdt를 설정하여 보호모드로 들간후에
    파일이 bootloader와 kernel.c 파일을 kernelos.bin을 만들고있는데요
    섹션재배치를 부트섹터에서 해야되는건가요
    아니면 bootloader에서 해도되는건가요 아니면 kernel.c에서 해도되는걸까요?
    boosect에서 올려주니 bootsect에서 해야되는것같은데..
    상관없을것도같고.. bootsect에서 할려니 512바이트를 넘어가버려서그런지 안되더라구요

    그리고.. code와 data섹션재배치를 해야되는것같은데 어떤 연산을해서 어디에 넣어야되는지..
    .reloc 여기에서 섹션재배치를 해야되는지.. 어떻게 해야될지모르겟네요
    조언좀 부탁드립니다 ^^;;
    아니면 eclipse로 넘어갈까합니다 ㅠ.ㅠ 이렇게 삽질하고 못하느니 쉬운방법으로 가서
    어느정도 완성을 해놓고 다시 도전할까합니다..ㅠ.ㅠ

    • Favicon of http://www.mint64os.pe.kr BlogIcon kkamagui 2010.11.15 21:59  댓글주소  수정/삭제

      bootloader가 순수 Binary로만 되어 있다면, 여기서 하는게 좋을 듯 싶습니다. ^^;;;

      Relocation에 대한 정보는 http://kkamagui.tistory.com/73 와 제가 쓴 04 PE 파일 분석 시리즈 문서를 참고하시면 될 것 같습니다. ^^

      아니면 "Windows 시스템 실행 파일의 구조와 원리"라는 책을 참고하셔도 됩니다.

      그나저나 고생하시는군요. 화이팅입니다. ;)

  14. klaus1202 2010.11.15 22:04  댓글주소  수정/삭제  댓글쓰기

    네 window 시스템 실행파일의 구조와 원리책을 구입하며 보앗구요 까마귀님의 글도 봣는데요^^;;
    이 이론을 어떻게 코드로 구현을 해야될지 모르겟습니다...ㅠㅠ
    bootloader가 순수 바이너리로 되어잇지 않아요...;;
    bootloader.asm+syncos.c 가 합쳐져서 -> syncos.bin 파일을 만들어내요 ㅠ.ㅠ...
    그래서 이 syncos.bin에 pe구조가 붙었습니다... 그래서 좀 난감합니다...흑흑...

    • Favicon of https://kkamagui.tistory.com BlogIcon 호기심 많은 kkamagui(까마귀, 한승훈) 2010.11.15 23:33 신고  댓글주소  수정/삭제

      아, 그러시군요. 그러면 하는 수 없이 부트로더에서 작업하는 게 맞을 것 같습니다.

      그리고 "실행파일의 구조와 원리"를 보셨는데도 감이 오지 않는다면... 예제를 한번 따라해보시는 것은 어떨까요?

      사실 책에 이미 재배치를 실제로 해주는 부분이 있기 때문에 조금만 따라해보시면 금방 이해할 수 있을 겁니다. ^^

  15. klaus1202 2010.11.15 22:09  댓글주소  수정/삭제  댓글쓰기

    까마귀님ㅠㅠ.. kernel을 잠시 잡으려고합니다..
    제실력으로는 너무 안되겟네요.. 까마귀님도 한번 포기하셧다고하셧는데..
    저도 한번은 뒤로 물러나 스펙을 쌓은후 다시 도전해야겟습니다..
    그래서그러는데 까마귀님의 포트폴리오를 보니.. 시스템의 이해가 생긴후에 하셧다고하셧는데요
    디바이스 드라이버 쪽으로 공부를 하다보니 시스템쪽이해가 느신건가요??!!
    디바이스드라이버를 공부하면 시스템프로그래밍도 같이 공부할수밖에없는건가요??^^;;
    같이된다면 디바이스드라이버로 어떤프로젝트를 해보고싶은데 흐흐..
    한번공부해볼려구요^^;; 시스템쪽으로 좀되면 전자쪽도배워서 임베디드도 해보고싶고
    해보고싶은게 너무많습니다.. low level쪽에 관련된것들이요 ^^;;
    까마귀님이 로드맵좀 그려주시면 안될까요.. 실무에 있으시니 저보다 훨씬 많이 아시니^^
    부탁드립니다 ^^;;

    • Favicon of https://kkamagui.tistory.com BlogIcon 호기심 많은 kkamagui(까마귀, 한승훈) 2010.11.15 23:30 신고  댓글주소  수정/삭제

      klaus 님이 기초가 부족하다고 느끼신다면, 잠시 시간을 갖고 내공(?)을 닦는 것도 좋은 방법일 것 같습니다. 아무래도 이해가 부족하면 언젠가는 벽에 부딪히거든요(이건 제 경험담입니다...ㅠㅠ).

      저같은 경우는 한번 포기했다가 병특 시절에 과도하게 윈도우 프로그래밍을 했더니만 갑자기 머리가 트이더라구요. ㅡ_ㅡa... (병특하신 분들은 약간 공감하실듯...)

      이때 서버 프로그램도 만들어보고 윈도우 유틸리티부터 간단한 게임까지 닥치는 대로 만들었는데, 어이 없지만 이게 윈도우 구조를 이해하는데 많은 도움이 되었습니다. ^^;;;; 디버깅을 하다보니 어셈코드와도 자연스레 친해졌구요, 나중에 어셈코드를 다시 봤는데 전혀 낯설지 않더라구요. ㅎㅎ

      말이 조금 길어진 것 같은데, 디바이스 드라이버도 좋고 간단한 어플도 좋으니 다양한 프로그램을 많이 하는게 도움이 되실겁니다.

      그리고 한 말씀 더 드리자면, 어느 분야나 마찬가지겠지만 왕도는 없는 것 같습니다. 그냥 꾸준히 갈고 닦다보면 어느 순간 머리가 트여서 전에 안보이던 것도 보이는 날이 올겁니다. ;)

      ps) 예전에 임베디드 하신 분들은 굉장히 Low 한 부분을 다뤘겠지만, 요즘은 대부분 C로 작업해서 C 프로그래밍만 익숙하시면 접근하는데 큰 어려움은 없을 것 같습니다. ;)

  16. klaus1202 2010.11.16 11:17  댓글주소  수정/삭제  댓글쓰기

    답변 정말 감사드립니다^^;;
    흠...근데 어떤프로그램을 만들어보는게 좋을까요 ㅠㅠ;;
    매번 댓글마다 답변을 친절히 달아주시다니 정말 감사드립니다ㅠ.ㅠ
    까마귀님같은 선배가 저희학교에도 있엇다면 얼마나 좋았을까요 흑흑.. 시스템쪽은 어렵다고
    해보지도 않고 겁부터 먹고 아예 거들더 보지도않네요 사람들이...
    그래서 더 하고싶어지는거지만요 ㅎㅎ;;
    어플리케이션쪽이랑 디바이스 드라이버쪽이랑 어떤 프로그램을 만들어보는게좋을까요
    혼자서 공부하니.. 같이 의논하고 그럴사람이 없네요 ㅡ.ㅡ;;;
    ㅠ.ㅠ;;;;
    어플리케이션은 바이러스백신프로그램을 만들어볼려구합니다
    해킹쪽에 조금공부를 했엇는데 어떻게 막는지 알고싶어서요 공부에 굉장히 도움될것같아서요 ㅎㅎ;;
    그런데 디바이스드라이버쪽은.. usb밖에 생각이안나네욤 ㅡ.ㅡ;;
    다른거 뭐 없을까요 ^^;;

  17. klaus1202 2010.11.17 16:12  댓글주소  수정/삭제  댓글쓰기

    1년전에 원격제어프로그램을 한번 만들어본 경험이있어서.. 디바이스 드라이버한번 해볼려구합니당 흐흐;;

  18. klaus1202 2010.11.24 20:12  댓글주소  수정/삭제  댓글쓰기

    관리자의 승인을 기다리고 있는 댓글입니다