들어가기
빌드 시 자동으로 TOTALSECTORCOUNT 값을 업데이트하여 작업 환경을 개선하고, C를 어셈블리어 코드와 연결해 함께 빌드 해보자.
본론
5.1 실행 가능한 C 코드 커널 생성 방법
- 이번 장에서는 C 소스 파일을 추가하고 이를 빌드해 보호 모드 커널 이미지에 통합.
5.1.1 빌드 조건과 제약 사항
- 엔트리 포인트가 C 코드를 실행하려면 세 가지 제약 조건을 만족해야 함.
- C 라이브러리를 사용하지 않게 빌드
- 보호 모드 커널이 실행되면 C 라이브러리가 존재 하지 않음.
- 0x10200 위치에서 실행하게끔 빌드.
- 0x10000에는 엔트리 포인트가 존재하므로 512바이트 이후인 0x10200에 로딩.
- 코드나 데이터 외에 기타 정보를 포함하지 않은 순수한 바이너리 파일 형태.
- GCC를 통해 실행 파일을 생성하면 ELF 또는 PE 파일 포맷과 같이 특정 OS에서 실행할 수 있는 포맷이 생성되고 해당 포맷들 불필요한 정보를 포함.
- C 라이브러리를 사용하지 않게 빌드
5.1.2 소스 파일 컴파일 - 라이브러리를 사용하지 않는 오브젝트 파일 생성 방법
x_64-pc-linux-gcc -c -m32 -ffreestanding Main.c
- C 코드를 컴파일 해 오브젝트 파일을 생성하는 방법은 간단.
- -ffreestading 옵션으로 라이브러리를 사용하지 않고 오브젝트 파일을 생성.
5.1.3 오브젝트 파일 링크 - 라이브러리를 사용하지 않고 특정 어드레스에서 실행 가능한 커널 이미지 파일 생성 방법
- 실행 파일이 링크될 때 코드나 데이터 이외에 디버깅 관련 정보와 심볼릭 정보들이 포함 -> 재배치 필요.
섹션 배치와 링커 스크립트, 라이브러리를 사용하지 않는 링크
- 섹션은 실행 파일 또는 오브젝트 파일에 있으며 공통된 속성(코드, 데이터 등)을 담는 영역.
- 무수히 많은 섹션 중 핵심 역할을 하는 섹션 세 가지.
- 실행 가능한 코드가 들어 있는
.text
섹션. - 초기화 된 데이터가 들어 있는
.data
섹션. - 초기화 되지 않은 데이터가 있는
.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 코드 역시 로딩될 메모리를 미리 예측하고 이미지를 생성해야 함.
- 메모리에 로딩하는 어드레스를 지정하는 방법.
- 링커 스크립트를 수정.
- .text 섹션을 수정. 그 이후의 .data 나 .bss는 자동으로 계산된 주소가 적용.
- 프로그램 명령줄 옵션 사용.
- 링커 스크립트를 수정.
- 소스 파일 내의 함수 위치와 오브젝트 파일의 순서를 변경해 엔트리 포인트로 지정.
실행 파일을 바이너리로 변환
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는 텍스트 모드 화면을 구성하는 문자 하나.
- 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 $@ $^
- 커널 디렉터리는 소스 디렉터리와 임시 디렉터리로 구분되며 커널 빌드 작업은 임시 디렉터리를 기준을 수행.
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 커널 이미지 생성과 실행
마치며
왜 이렇게 어렵냐 이거..