Linux Kernel - vmlinux, bzImage
1. vmlinux
vmlinux는 압축되지 않은 커널 이미지를 ELF 형식으로 담고 있는 정적 링크된 실행 파일이라서, 사실상 커널 그 자체라고 할 수 있다. 이 파일은 심볼이나 재배치 정보가 살아 있어서, 커널 디버깅 시에 유용하게 사용할 수 있다고 한다. 그러나, 이 파일만으로는 컴퓨터를 부팅(운영체제를 메모리에 로딩)할 수 없다. 심볼을 제거하고, 압축도 해서 크기를 줄인 뒤에, 부팅에 관계된 코드까지 넣어야 부팅 가능한 이미지가 되는 것이다.
vmlinux를 IDA로 디컴파일한 모습 / 심볼이 살아있다
이 파일에서 심볼을 뽑아내면, System.map 파일이 되는 것이다. 이 파일은 커널을 컴파일할 때 생성되는 심볼 테이블 파일이고, 비슷한 것으로는 /proc/kallsyms가 있다. 전자는 파일 시스템에 실존하는 파일이라는 것이고, 후자는 proc 파일 시스템에 위치한 파일이기에 실제 파일이 아니고, 커널의 자료 구조와 상호작용하는 더미 파일이라는 차이가 있다. 전자는 컴파일 시에 생성되는 고정 값이기 때문에, KASLR이 활성화되어 있을 때는 실제 주소와 일치하지 않지만, 후자는 실제 주소와 일치한다는 특징이 있어 커널 디버깅 시에 쓰인다. 또한, 후자는 동적으로 로딩된 모듈의 심볼까지 포함하고 있어 더더욱 자주 쓰인다.
2. arch/($ARCH)/boot/compressed/vmlinux
vmlinux가 2개라고 당황하지 마시라, 이 vmlinux는 그 경로에서 알 수 있듯이 압축된 커널 이미지를 압축하였고, 압축을 풀고 커널을 초기화하는 코드를 추가한 리눅스 커널 이미지이다. 즉, 아직 이걸로는 부팅이 불가능하다는 뜻이고, 크기만 작아졌다고 생각하면 된다. ($TOP_DIR)/vmlinux에서 심볼 및 디버깅 정보를 없애고 압축한 것이 piggy.o이고, 리눅스 커널을 초기화하는 코드는 Head.o에, 커널 이미지의 압축을 해제하는 코드는 misc.o에 위치해 있다.
vmlinux가 arch/arm/boot/zImage로 변환되는 과정 (그림추가)
3. arch/($ARCH)/boot/bzImage
bzImage는 bbootsect(512 bytes) + bsetup + bvmlinux.out을 합쳐서 만든 것이다.
bbootsect는 LILO나 GRUB와 같은 부트로더가 따로 없을 때 그 역할을 대신하는 코드이고, bsetup은 커널이 압축이 제대로 풀릴 수 있도록 준비하는 코드이다. 그리고, bvmlinux.out은 이전에 소개한 arch/($ARCH)/boot/compressed/vmlinux 파일에 대한 심볼 및 재배치 정보를 제거하고, 일부 섹션을 제거하여 만든 커널 이미지 파일이다.
비슷한 것으로는 zImage가 있는데, 만드는 방법이 거의 동일한데, 딱 하나 차이가 존재한다. 바로 커널 이미지가 압축 해제되는 메모리 주소이다. zImage는 640KB(0xA0000) 보다 낮은 메모리 주소에 압축 해제되고, bzImage는 그보다 높은 메모리 주소(1MB 이상)에 압축 해제된다. 그 이유는 bzImage가 big + zImage이기 때문이다. 예전 CPU는 리얼 모드라고, 최대 1MB의 메모리 주소 공간을 관리하는 운영 모드를 사용하였고, 이걸로 충분했다.
그러나, 커널의 크기가 점점 커지면서, zImage의 방식으로는 더 이상 관리할 수 없게 되었다. 아래는 zImage의 부팅 방식인데, 0x7C00에 있는 코드를 0x90000으로 옮기고, 0x90200부터 그다음 섹터를 읽어 들인 뒤, 0x10000에 마지막 bvmlinux.out을 디스크로부터 읽어 들인다. 그 뒤에, 0x1000으로 이미지를 다시 옮긴 뒤, 보호 모드로 진입한 뒤, 0x1000에 뛰어서 0x100000 이상의 주소에 압축 해제를 수행한다. 커널 이미지의 크기가 커지면, 기준점이었던 0x90000을 침범할 것이기 때문에 안 된다는 것은 충분히 안 된다는 것을 알 수 있다.
그렇다면, bzImage는 어떤 방법을 이용하였기에 커널의 크기가 커져도 입출력 공간(I/O space)를 침범하지 않는 것을 보장할까? 바로, bvmlinux.out을 애초에 0x100000에 읽어오는 것이다. 내 예상이지만 최신 CPU는 애초에 A20 게이트가 활성화되어 있기 때문에 가능한 것이 아닐까.. 싶다. 그래서, 0x100000으로 로드하고, 보호 모드로 전환한 뒤에, 0x100000으로 바로 뛰는 것이다. 대충 아래 그림처럼 움직인다고 이해하고 있다.
Reference
[1] wikipedia, “vmlinux”, https://en.wikipedia.org/wiki/Vmlinux
[2] Aiden, “[ Linux Kernel ] vmlinux / vmlinuz / bzImage”, https://aidencom.tistory.com/781
[3] wikipedia, “System.map”, https://en.wikipedia.org/wiki/System.map
[4] 남상규, “임베디드 시스템 엔지니어를 위한 리눅스 커널 분석 - 2장. Makefile 분석”, http://wiki.kldp.org/KoreanDoc/html/EmbeddedKernel-KLDP/kernel-image-file-structure.html
[5] Richong, “리눅스 커널 이미지 구조”, https://richong.tistory.com/307
[6] IAMROOT, “vmlinux가 arch/arm/boot/zImage로 변환되는 과정 (그림추가)”, http://www.iamroot.org/xe/index.php?mid=Kernel&document_srl=24595
[7] umbum, “[kernel] get sys_call_table”, https://umbum.dev/520
[8] stackoverflow, “What is the need of having both System.map file and /proc/kallsyms?”, https://stackoverflow.com/questions/28936630/what-is-the-need-of-having-both-system-map-file-and-proc-kallsyms
[9] Alessandro Rubini, “Booting the Kernel”, https://johnvidler.co.uk/linux-journal/LJ/038/2239.html
[10] stackoverflow, “What is the use of vmlinux file generated when we compile linux kernel”, https://stackoverflow.com/questions/41326607/what-is-the-use-of-vmlinux-file-generated-when-we-compile-linux-kernel