[ARM] compressed kernel
2013/10/20
zImageの展開処理
調査対象は、【kernel 3.0.39 "Sneaky Weasel"】です。例として、u-bootから起動する場合を考えます。
ここで上げるのは、とあるボードへの移植時に調査した値です。
ボード依存部の値は、デタラメに書いている可能性があるので、注意してください。
NOR bootの場合は、ROM上のコード実行が可能ですが、昨今の方式*1ではbootloaderがDRAM上に展開してから実行することが多いでしょう。
u-bootが読み込むファイル、uImageは、zImageにuImageヘッダがついたもの。
図にしてみると以下のイメージ。(AAでスミマセン)
+------------------------+ | u-boot header(64oct.) | +------------------------+ | zImage decompress code | | .text | | (with compressed data )| | .got | | .got.plt | | .bss | +------------------------+このデータの読み込みは、u-bootによって行われるため、任意のアドレスに展開されます。
ldscriptを見ると、ざっくりとこんな感じ。
_text = .; .text : {} _etext = .; _got_start = .; .got : { *(.got) } _got_end = .; .got.plt : { *(.got.plt) } _edata = .; __bss_start = .; .bss : { *(.bss) } _end = .;
事前準備
実際には、コードを読みながら参照していった結果です。FILE: arch/arm/boot/compressed/Makefile
# Supply ZRELADDR to the decompressor via a linker symbol. ifneq ($(CONFIG_AUTO_ZRELADDR),y) LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)ZRELADDRは、以下で定義されます。
FILE: arch/arm/boot/Makefile
ZRELADDR := $(zreladdr-y)右辺は、SoC依存部にて定義されています。
FILE: arch/arm/mach-*/Makefile.boot
zreladdr-y := 0x40008000 params_phys-y := 0x40000100 initrd_phys-y := 0x40800000
zImage先頭から
エントリーポイントはここ。bootloaderから飛んでくるところ
アドレスは任意。FILE: ./boot/compressed/head.S
start: ※cache有効, TLBはVA-PA一対一対応, restart: .align 2 .type LC0, #object LC0: .word LC0 @ r1 .word __bss_start @ r2 .word _end @ r3 .word _edata @ r6 .word input_data_end - 4 @ r10 (inflated size location) .word _got_start @ r11 .word _got_end @ ip(r12) .word .L_user_stack_end @ sp .size LC0, . - LC0 /* * We might be running at a different address. We need * to fix up various pointers. */ sub r0, r0, r1 @ calculate the delta offset add r6, r6, r0 @ _edata add r10, r10, r0 @ inflated kernel size locationLC0(DRAMの位置)から、"LC0"の配置した位置(ldscriptでは先頭0、コード配置後のLC0の位置)を差っ引いて、r0に保持する。
圧縮カーネルの、展開後のサイズは、build systemによって、圧縮カーネルの後ろにLEで付与される。したがって、r10へloadする値は、input_data_endから4引いてある。(32bit決め打ちだね)
spも、relocate後のアドレスをセット。r10にsp+64kのアドレスをセット(malloc用に64k確保)(CONFIG_ZBOOT_ROM未定義の場合。定義時はコード本体がROMに入るから、_edataの後ろに配置する)
このイメージがロードされる位置がどこでも動けるようになっている。
展開後のkernel位置と、この展開コード、edata類の空間が重なっているかをチェックする。重複している場合、reset〜edataまでを、後ろからコピーする(memmove相当)。その後、コピーした後のrestartへjumpする。(これで重複しない位置で再度relocateする)
※ユーザモードで入ってきた場合、SVCで特権を得るようになってます。
ヴェクタ位置へのロードを期待しているようですが、必ずしもそうではないから、ちょっと不味そうな気がするネ。
relocate後
decompress処理のソフトと、圧縮イメージとを再配置して、kernel imageを展開できる状態になって、wont_overwriteへたどり着く。前述のように、最初のバイナリがロードされる位置によって、ここへ直接来ることもあれば、relocateすることもありますね。wont_overwrite: /* * If delta is zero, we are running at the address we were linked at. * r0 = delta * r2 = BSS start * r3 = BSS end * r4 = kernel execution address * r7 = architecture ID * r8 = atags pointer * r11 = GOT start * r12 = GOT end * sp = stack pointer */・GOT書き換え(オフセット分加算して、飛び先がrelocate先のアドレスになるように)
・BSSをゼロクリア
本来のImage(vmlinux)へ
kernelの展開先アドレスを第一引数、spからsp+64kを第二〜三引数に渡して、第四引数にアーキテクチャIDをのせてcall。void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p, unsigned long free_mem_ptr_end_p, int arch_id)展開が終わったら、cache clean、cache-offしてから、圧縮前のkernel先頭へ飛ぶ。