커널 이미지 빌드 과정 (Kernel Image Build Process)

Linux Kernel 코드 분석은 기나긴 여행이다.

Linux Kernel 분석 과정은 상당한 끈기와 노력 및 정신적 무장을 필요로 한다.
그래서, 누구나 시작은 할 수 있으나 목표한 과정까지 완주하여 달콤한 열매를 맛보는 이는 그리 많지 않은 것 같다.
나 또한 매주 주말 중 하루를 반납하고 스터디 모임에 꼬박꼬박 참여하고 있으며, 평일 또한 업무 이외의 시간 중 상당수를 Kernel 분석에 투자하고 있는 중이다.
이러한 노력이 수년 후 엄청난 결실로 돌아오리라는 것을 믿어 의심치 않고 있다.

다만, 이러한 노력은 어디까지나 의무감이 아닌 내 자신의 호기심과 재미에 바탕을 두고 있다는 것이 중요하다. 재미없는 것을 어떻게 수년씩 꾸준히 해나갈 수 있겠는가 ?
그래서, 분석할 Architecture 도 내가 주로 업무를 수행하는 x86 환경이 아닌 Embedded 용으로 사용되는 ARM Architecture 를 선택하였다. (Embedded 는 평소 내가 항상 해보고 싶던 분야였다.)

Linux Kernel 은 아주 다양한 Architecture 를 지원하기 때문에 특정 Architecture 와 SoC(System On Chip)를 결정하고 분석을 해야 일관된 분석 작업이 가능하다.
이러한 결정없이 분석을 하게 되면 불필요한 코드까지 분석을 해야하고, 코드의 흐름을 따라가기가 무척 어려워지게 된다.
현재 내가 분석하고 있는 환경이다.

  • Kernel Version : 3.12.20
  • 개발보드 : Exynos 5420
코드 분석을 하다보면 분석하는 부분에 따라 공부해야할 책과 문서들이 넘쳐난다.
이러한 Reference 들은 그때그때 포스팅에 올릴 것이다.
대부분 Kernel 코드 분석의 시작을 Bootloader 또는 head.S 라는 파일에서부터 시작하는데, 이 head.S 파일을 분석하기 위해서는 기본적인 ARM Architecture 와 더불어 Assembly 의 해독능력도 필요하다.
head.S 뿐 아니라 커널의 C 소스들을 분석하는 중간중간에도 Assembly 코드를 분석해야하는 상황들은 자주 만난다.
그렇다고 커널분석을 하기전부터 이미 Assembly 의 대가일 필요는 없다.
ARM System Developers’ Guide 라는 책을 먼저 정독하길 추천한다.
ARM Architecture 및 Assembly 의 기본 명령어에 대한 어느정도 선행적 이해는 있어야 한다.
고급 명령어나 Operand Register 의 Bit 별 쓰임새 등은 그때그때 찾아보면 된다.

자… 그럼 이제부터 망설이지 말고 Linux Kernel 의 바다속으로 모두 뛰어들어보자.

Linux Kernel 이라는 것은 결국 하나의 Binary Image 에 불과하다.
그 어렵고 방대한 Linux Kernel 이 빌드과정을 거치면 겉보기에는 보잘 것 없는 2~3 MB 짜리 파일하나(zImage)만 덩그러니 남는 것이다.

하지만, 이 파일 하나를 우습게 보지 마시라.

이 파일 하나가 현재 인류가 생산해내고 있는 가장 높은 차원의 지적/논리적 산물이며, 인류의 미래를 긍정적으로 변화시키거나 또는 파국으로 몰고갈 수 있는 엄청난 내공이 깃들여 있는 작품인 것이다.
인류가 만들어낸 드래곤볼이라고나 할까 ? ^^

먼저 ARM 용 Kernel Image 를 빌드하려면 Tool Chain 을 설치하여 Cross Compile 환경을 꾸며야 한다.
아래와 같은 과정을 수행하면 Kernel 분석에 필요한 .config 파일이나 이미지들이 생성될 것이다.
반드시 아래의 환경으로만 해야하는 것은 아니므로 각자 다른 환경에서 빌드하는 것도 상관없다.

  1. VirtualBox 설치
  2. 우분투 12.04 LTS 버전 설치
  3. https://www.kernel.org/ 에서 3.12.20 Stable 버전 Kernel 소스 Download
  4. KERNEL_ROOT 디렉토리에서 아래와 같이 zImage 빌드
    • sudo apt-get install gcc-arm-linux-gnueabihf
    • export ARCH=arm
    • export CROSS_COMPILE=arm-linux-gnueabihf-
    • make ARCH=arm exynos_defconfig
      – You can see .config file.
    • make ARCH=arm menuconfig
      – System Type -> Enter
      – SAMSUNG EXYNOS SoCs Support -> Enter
      – [*]SAMSUNG EXYNOS5 -> Select using spacebar
      – [ ]SAMSUNG EXYNOS4 -> Deselect
      – Save & Exit
    • make -j5

위의 과정을 수행하고 나면 여러분은 다음과 같은 핵심 파일들이 생성되는 것을 볼 수 있을 것이다.
linux-3.12.20 디렉토리가 KERNEL_ROOT 디렉토리라고 보면 된다.

  • linux-3.12.20/.config
  • linux-3.12.20/vmlinux
  • linux-3.12.20/arch/arm/boot/Image
  • linux-3.12.20/arch/arm/boot/compressed/vmlinux
  • linux-3.12.20/arch/arm/boot/zImage
위와 같은 파일들은 Kernel Build 시스템에 의해 다음과 같은 과정 속에서 생겨난 부산물이다.
Kernel 빌드에 대한 것은 아래 그림만 이해하고 있으면 코드 분석에 큰 무리는 없는 듯하다.
(실제 빌드 과정에서 아래의 과정을 확인하고 싶다면 make 시 V=1 옵션을 주면 된다.)

맨 처음 생기는 Kernel 빌드의 부산물은 KERNEL_ROOT 디렉토리에 생기는 vmlinux 이다.
이는 file 명령을 통해 확인해 보면 ELF(Excutable Linking Format) 파일이라는 것을 확인할 수 있다. 크기는 약 50 M 정도 된다.

$ file vmlinux
vmlinux.ARM11A: ELF 32-bit LSB executable, ARM, version 1 (SYSV), statically linked, BuildID[sha1]=0x5f29cda7984375a0eaac1f17849c97b0e5ea2a15, not stripped

$ ls -al vmlinux

-rwxrwxr-x 1 dplee dplee 59133322  6월 22 21:40 vmlinux.ARM11A

이 파일에서 (1) 의 과정을 통해 .comment, symbol table, relocation 정보 등을 제거한 파일이 arch/arm/boot/Image 파일이다. 이 파일은 ELF 형식이 아니고 그냥 Data Binary File 이다.

쉽게 얘기하면 순수한 Kernel 의 코드와 데이터만을 빼놓은 이미지라고 보면 되겠다.

Image 파일을 (2) 의 과정을 통해 gzip 으로 압축한 파일이 piggy.gzip 파일이다.
따라서, piggy.gzip 파일은 단순히 Data Binary File 을 압축만 한 파일이라고 할 수 있다.

이러한 압축 파일을 통째로 piggy.gzip.S 에 포함하여 Assembling 하면 piggy.gzip.o 파일이 만들어진다.
이렇게 하는 이유는 본격적인 Kernel 의 시작(start_kernel)을 하기 전에 초기화 작업을 하고 압축을 해제하는 코드인 head.o 와 misc.o 를 묶어서 다시 Linking 하기 위한 것이다.

piggy.gzip.o, head.o, misc.o 파일 3 개를 Link 하여 새로이 만들어낸 파일이 arch/arm/boot/compressed/vmlinx 파일이며 이 파일은 새로이 Linking 하여 만들어낸 파일이므로 역시 ELF 파일이 된다. KERNEL_ROOT 에 맨처음 생성되었던 파일과 다르다는 것을 알아야 한다.

마지막으로, Linking 과정에서 .comment, symbol, relocation 정보들이 또 들어가 있을 것이므로 이를 objcopy 를 통해 다시 제거하면 우리가 얻고자 하는 최종 zImage 파일이 만들어진다.
크기는 약 2~3 M 정도 된다.

이 파일이 바로 드래곤볼의 위력을 가진 Kernel Image 인 것이다.

Computer 를 Booting 하면 Boot Loader 는 필요한 하드웨어 초기화 작업을 수행한 후, 이 zImage 를 특정한 메모리 영역에 로드하고 PC(Program Counter)를 해당 zImage 의 첫번째 명령어(Instruction)로 셋팅해 줌으로서 zImage 가 시작되도록 한다.

그 첫 시작 지점은 위에서 실질적인 Kernel 이미지와 함께 Linking 되었던 head.o 파일의 코드가 된다.
아직은 진정한 Kernel 코드의 시작은 아니다.
head.o  에서는 진정한 Kernel 시작 전에 적절한 초기화 작업을 수행한 후,
misc.o 의 코드들을 호출하여 piggy.gzip.o, 즉 압축된 원래의 Kernel Image 의 압축을 해제하여 또 다른 특정 메모리 영역에 복제한 후, 최종적으로 PC 를 그 Kernel Image 의 첫 부분(start_kernel 함수)으로 분기하도록 해준다.

우리는 커널 코드의 분석을 head.o (head.S)부터 시작할 것이다.

zImage 를 메모리의 어느 위치에 로드하고 압축을 해제하여 어느 영역에 위치시키며, kernel 초기화 작업은 어떤 것들을 수행하는지 면밀히 살펴볼 것이다.

You may also like...