5. C언어로 커널을 작성하자

들어가기

빌드 시 자동으로 TOTALSECTORCOUNT 값을 업데이트하여 작업 환경을 개선하고, C를 어셈블리어 코드와 연결해 함께 빌드 해보자.

본론

5.1 실행 가능한 C 코드 커널 생성 방법

  • 이번 장에서는 C 소스 파일을 추가하고 이를 빌드해 보호 모드 커널 이미지에 통합.

5.1.1 빌드 조건과 제약 사항

  • 엔트리 포인트가 C 코드를 실행하려면 세 가지 제약 조건을 만족해야 함.
    1. C 라이브러리를 사용하지 않게 빌드
      • 보호 모드 커널이 실행되면 C 라이브러리가 존재 하지 않음.
    2. 0x10200 위치에서 실행하게끔 빌드.
      • 0x10000에는 엔트리 포인트가 존재하므로 512바이트 이후인 0x10200에 로딩.
    3. 코드나 데이터 외에 기타 정보를 포함하지 않은 순수한 바이너리 파일 형태.
      • GCC를 통해 실행 파일을 생성하면 ELF 또는 PE 파일 포맷과 같이 특정 OS에서 실행할 수 있는 포맷이 생성되고 해당 포맷들 불필요한 정보를 포함.

5.1.2 소스 파일 컴파일 - 라이브러리를 사용하지 않는 오브젝트 파일 생성 방법

x_64-pc-linux-gcc -c -m32 -ffreestanding Main.c
  • C 코드를 컴파일 해 오브젝트 파일을 생성하는 방법은 간단.
  • -ffreestading 옵션으로 라이브러리를 사용하지 않고 오브젝트 파일을 생성.

5.1.3 오브젝트 파일 링크 - 라이브러리를 사용하지 않고 특정 어드레스에서 실행 가능한 커널 이미지 파일 생성 방법

  • 실행 파일이 링크될 때 코드나 데이터 이외에 디버깅 관련 정보와 심볼릭 정보들이 포함 -> 재배치 필요.

섹션 배치와 링커 스크립트, 라이브러리를 사용하지 않는 링크

  • 섹션은 실행 파일 또는 오브젝트 파일에 있으며 공통된 속성(코드, 데이터 등)을 담는 영역.
  • 무수히 많은 섹션 중 핵심 역할을 하는 섹션 세 가지.
    1. 실행 가능한 코드가 들어 있는 .text 섹션.
    2. 초기화 된 데이터가 들어 있는 .data 섹션.
    3. 초기화 되지 않은 데이터가 있는 .bss 섹션.
  • 오브젝트 파일은 중간 단계이기 때문에 다른 오브젝트와 합쳐지는 여부에 따라 주소가 언제든지 변할수 있으므로 각 섹션의 크기와 파일 내에 있는 오프셋 정보만 저장.
  • 오브젝트 파일을 결합하고 실제 메모리에 로딩될 위치를 결정하는 것이 링커.
    • 링커의 주 역할은 오브젝트 파일을 모아 섹션을 통합하고 어드레스 조정.
    • 링커가 실행 파일을 만들기 위해 파일 구성에 대한 정보가 필요 -> 링커 스크립트.
OUTPUT_FORMAT("elf32-i386", "elf32-i386", elf32-i386)
OUTPUT_ARCH(i386)
ENTRY(_start)
SEARCH_DIR("/usr/cross/x86_64-pc-linux/lib")

SECTION
{
    PROVIDE (__executable_start = 0x8048000); = 0x8048000 + SIZEOF_HEADERS;

    --- 생략 ---

    .text
    {
        --- 생략 ---
    }
    .data
    {
        --- 생략 ---
    }
    .bss
    {
        --- 생략 ---
    }
}
  • 세부적인 내용까지는 알수 없지만 .text, .data, .text를 확인.
  • 코드 및 데이터에 관련된 섹션을 앞으로 이동.
    재배치된 소스 보기

로딩할 메모리 어드레스와 엔트리 포인트 지정

  • 어셈블리어로 작성된 부트 로더나 보호 모드 엔트리 포인트처럼 C 코드 역시 로딩될 메모리를 미리 예측하고 이미지를 생성해야 함.
  • 메모리에 로딩하는 어드레스를 지정하는 방법.
    1. 링커 스크립트를 수정.
      • .text 섹션을 수정. 그 이후의 .data 나 .bss는 자동으로 계산된 주소가 적용.
    2. 프로그램 명령줄 옵션 사용.
  • 소스 파일 내의 함수 위치와 오브젝트 파일의 순서를 변경해 엔트리 포인트로 지정.

실행 파일을 바이너리로 변환

x_64-pc-linux-objcopy -j .text -j .data -j .rotate -j .bss -S -O binary kernel32.elf kernel32.bin
  • 실행 파일은 코드 섹션과 데이터 섹션 이외의 정보를 포함하므로 이를 제거해야함 -> objcopy 사용.

5.2 C 소스 파일 추가와 보호 모드 엔트리 포인트 통합

5.2.1 C 소스 파일 추가

보호 모드 커널용 공용 헤더 보기

  • 보호 모드 전반에 걸쳐 사용할 헤더 파일.
  • CHARACTER는 텍스트 모드 화면을 구성하는 문자 하나.

C 코드 엔트리 포인트 파일

  • Main 함수는 C 코드의 엔트리 포인트 함수로서 0x10200 어드레스에 위치.

5.2.2 보호 모드 엔트리 포인트 코드 수정

[BITS 32]
PROTECTEDMODE: 
--- 생략 ---
push (switch SWITCHSUCCESSMESSAGE - $$ + 0x10000)
push (2)
push (0)
call PRINTMESSAGE

// 10200으로 이동
jmp dword 0x08: 0x10200
  • 앞서 작성했던 보호 모드 커널의 엔트리 포인트 코드(EntryPoint.s)를 변경.
  • 보호 모드 커널 엔트리 포인트 이후에 무한 루프 대신 0x10200으로 이동.

5.2.3 makefile 수정

  • 이후 다수의 파일을 컴파일 해야하므로 makefile을 보다 편리하게 수정해야 함.
CSOURCEFILES = $(wildcard Source/*.c)
  • 특정 파일 밑에 확정자를 검색.
%.o : %.c
    gcc -c $< 
  • 파일 패턴을 활용한 빌드룰을 적용.
COBJECTFILES = $(patsubst %.c, %.o, $(CSOURCEFILES))
  • 소스 파일 목록에 포함된 파일의 확장자를 .c에서 .o로 수정.
CENTRYPOINTOBJECTFILE = Main.o
COBJECTFILES = $(patsubst %.c, %.o, $(CSOURCEFILES))
COTHEROBJECTFILES = $(subst Main.o, , $(COBJECTFILE))
kernel32.elf: $(CENTRYPOINTOBJECTFILE) $(COBJECTFILES) - Main.o가 가장 앞
    x86_64-pc-linux-ld -o $@ $^

보호 모드 커널 makefile 코드 보기

  • 커널 디렉터리는 소스 디렉터리와 임시 디렉터리로 구분되며 커널 빌드 작업은 임시 디렉터리를 기준을 수행.

5.3 커널 빌드와 실행

  • 소스를 빌드해 보면 커널 이미지가 약 650바이트 정도로 2섹터에 못 미치기 때문에 마지막 섹터를 로딩하는데 문제 발생.
  • 모자란 부분을 0x00과 같은 임의의 값으로 채워주면 문제 해결.

5.3.1 이미지 메이커 프로그램 작성

all : ImageMaker.exe

ImageMake.exe : ImageMaker.c
    gcc -o $@ $<

clean :
    rm -f ImageMaker.exe

이미지 메이커 코드 보기

  • 자동으로 크기에 커널 크기에 따라 부트 로더의 TOTALSECTORCOUNT를 변경.

5.3.2 커널 이미지 생성과 실행

수정된 메인 makefile 코드 보기

마치며

왜 이렇게 어렵냐 이거..

Share