zImageのロード&展開調査/ARM (1)
だ,である調で断定的に書いてありますが,間違い等絶対あると思うので,よろしければご指摘お願いします.
Linuxでは,起動するカーネルイメージとしてvmlinux/圧縮版vmlinux/Image/zImageを用いる.ここでは,ARMアーキテクチャにおいてzImageがROM/CFカードに存在し,自己解凍コードによってRAMに展開&ロードされる手順を述べる.解説に使用するカーネルソースは2.6.22である.
以下が実行環境の設定である(armadillo-300の環境を想定).この設定でアセンブル/実行されないコード部分については省略している.
- CONFIG_CPU_CP15=y
- CONFIG_CPU_ABRT_EV5TJ=y #ARMv5TEJ ARM9
以下を実行前の状態とする.
- zImageがROMにある場合(CONFIG_ZBOOT_ROM時)はPCがzImageの先頭アドレスを指している
- CFカードの場合はブートローダがzImageをRAMにコピーし,PCがその先頭アドレスを指している
- レジスタr0にアーキテクチャID*1,レジスタr1にatag*2のアドレスが入っている
zImageとvmlinuxの関係
素のカーネルイメージvmlinuxは
$ objcopy -O binary -R .note -R .comment -S vmlinux arch/arm/boot/Image
により,Imageに変換される*3.Imageは,
$ gzip -f -9 < arch/arm/boot/compressed/../Image > arch/arm/boot/compressed/piggy.gz
により,piggy.gzに変換される*4.piggy.gzは
$ gcc -Wp,-MD,arch/arm/boot/compressed/.piggy.o.d -nostdinc -isystem /usr/lib/gcc/arm-unknown-linux-gnu/4.1.1/include -D__KERNEL__ -Iinclude -include include/linux/autoconf.h -mlittle-endian -D__ASSEMBLY__ -mabi=apcs-gnu -mno-thumb-interwork -D__LINUX_ARM_ARCH__=5 -march=armv5te -mtune=arm9tdmi -msoft-float -c -o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/piggy.S
により,piggy.oに変換される*5.この際,piggy.oの前後にシンボルinput_data,input_data_endがつく.piggy.oは
$ ld -EL --defsym zreladdr=0x00028000 --defsym initrd_phys=0x00500000 --defsym params_phys=0x00000100 -p --no-undefined -X /usr/lib/gcc/arm-unknown-linux-gnu/4.1.1/libgcc.a -T arch/arm/boot/compressed/vmlinux.lds arch/arm/boot/compressed/head.o arch/arm/boot/compressed/piggy.o arch/arm/boot/compressed/misc.o -o arch/arm/boot/compressed/vmlinux
により,圧縮版vmlinuxに組み込まれる*6.この際,下記メモリマップのpiggydataセクション中にpiggy.oは組み込まれる.
カーネル展開時(arch/arm/boot/compressed/misc.c:decompress_kernel())には,先ほど述べたシンボルinput_data,input_data_endを元に,圧縮されたImageが参照される.
圧縮版vmlinuxは,
$ objcopy -O binary -R .note -R .comment -S arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage
により,zImageに変換される*7
メモリマップ(arch/arm/boot/compressed/vmlinux.lds)
OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0; /* arch/arm/boot/compressed/Makefile中のZTEXTADDRで決定される. CONFIG_ZBOOT_ROM時にはCONFIG_ROM_TEXTで指定されたアドレスが入る.それ以外は0.*/ _text = .; .text : { _start = .; *(.start) *(.text) ... *(.rodata) *(.rodata.*) ... *(.piggydata) . = ALIGN(4); } _etext = .; _got_start = .; .got : { *(.got) } _got_end = .; .got.plt : { *(.got.plt) } .data : { *(.data) } _edata = .; . = ALIGN(4); /* arch/arm/boot/compressed/Makefile中のZBSSADDRで決定される. CONFIG_ZBOOT_ROM時にはCONFIG_ROM_BSSで指定されたアドレスが入る.それ以外はALIGN(4).*/ __bss_start = .; .bss : { *(.bss) } _end = .; .stack (NOLOAD) : { *(.stack) } ... }
実行コード(arch/arm/boot/compressed/head.S)
zImageが先頭から実行されるとき,下記startセクションが実行される.
.section ".start", #alloc, #execinstr /* * sort out different calling conventions */ .align start: .type start,#function @NOPx8の理由: http://lists.arm.linux.org.uk/pipermail/linux-arm-kernel/2006-August/035247.html .rept 8 mov r0, r0 .endr b 1f .word 0x016f2818 @ Magic numbers to help the loader .word start @ absolute load/run zImage address .word _edata @ zImage end address @ブートローダから渡される引数の保存 1: mov r7, r1 @ save architecture ID mov r8, r2 @ save atags pointer ... @デバッグモニタangelが起動しないように割り込みを禁止する mrs r2, cpsr @ turn off interrupts to orr r2, r2, #0xc0 @ prevent angel from running msr cpsr_c, r2 ... .text @LC0が実際にロードされた際のアドレスをr0に入れる (add r0, pc, #208; PC相対アドレスを元に求める) adr r0, LC0 @下記のシンボルアドレスは圧縮版(arch/arm/boot/compressed/)vmlinuxのリンク時に決定される /* * LC0: LC0 @ r1/実際にロードされたアドレスとリンク時に指定したアドレス(_start/TEXT_START)の差を求めるために利用 * __bss_start @ r2/bss領域の先頭アドレス.bss領域は動的に確保される(zImageに含まれない) * _end @ r3/bss領域の終端アドレス * zreladdr @ r4/zImageを展開してできるカーネルイメージ(Image)が配置されるアドレス * @ arch/arm/mach-*/Makefile.bootで各ボード毎に定義される. * _start @ r5/リンク時に指定するzImageの先頭アドレス * _got_start @ r6/GOT領域の先頭アドレス,GOT中の各エントリは再配置時に変更される必要がある.GOT領域はzImageに含まれる. * _got_end @ ip/GOT領域の終端アドレス * user_stack+4096 @ sp/stack領域の底.stack領域は動的に確保される(zImageに含まれない) */ ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp} @実際にロードされたアドレスとリンク時に指定したアドレス(_start/TEXT_START)の差を求めr0に格納 subs r0, r0, r1 @ calculate the delta offset @zImageがロードされたアドレスが,リンク時に指定したTEXT_STARTと異なっているかどうか確認 @ if delta is zero, we are beq not_relocated @ running at the address we @ were linked at. @異なっていた場合,以下の3つのアドレスを実際にロードされたアドレスに再配置する /* * We're running at a different address. We need to fix * up various pointers: * r5 - zImage base address * r6 - GOT start * ip - GOT end */ add r5, r5, r0 add r6, r6, r0 add ip, ip, r0 @ROMからの起動では「ない」場合 #ifndef CONFIG_ZBOOT_ROM @以下の3つのアドレスを修正する(これらのアドレスが指す領域は,zImage中に存在せずRAM上に確保される). /* * If we're running fully PIC === CONFIG_ZBOOT_ROM = n, * we need to fix up pointers into the BSS region. * r2 - BSS start * r3 - BSS end * sp - stack pointer */ add r2, r2, r0 add r3, r3, r0 add sp, sp, r0 @GOT(Global Offset Table)のエントリを実際にロードされたアドレスに再配置する /* * Relocate all entries in the GOT table. */ @r6(GOT start)が指すエントリの値をr1に入れ 1: ldr r1, [r6, #0] @ relocate entries in the GOT @r1にr0(TEXT_STARTとzImageのロードアドレスの差分)を加算し add r1, r1, r0 @ table. This fixes up the @更新する(更新後r6は4加算され,次のエントリを指す) str r1, [r6], #4 @ C references. @以上をエントリの終端まで繰り返す(r6をip(GOT end)と比較し,r6が符号無しで小さい場合繰り返す) cmp r6, ip blo 1b #else @ROMからの起動の場合(かつカーネルコンパイル時に指定したロードアドレスと実際のアドレスが異なる場合) @リンカスクリプト(vmlinux.lds)中で既にBSS(,とそれに続くstack)の先頭アドレス(BSS_START)を指定しているため,r2,r3,spを修正する必要はない @同様に,GOT中のBSSエントリも修正する必要はない /* * Relocate entries in the GOT table. We only relocate * the entries that are outside the (relocated) BSS region. */ @r6(GOT start)が指すエントリの値をr1に入れ 1: ldr r1, [r6, #0] @ relocate entries in the GOT @エントリが__bss_startと_end(bssセクションの終端アドレス)の間にない時,再配置を行う(r1にr0を加算する) cmp r1, r2 @ entry < bss_start || cmphs r3, r1 @ _end < entry addlo r1, r1, r0 @ table. This fixes up the @エントリの更新 str r1, [r6], #4 @ C references. @以上をエントリの終端まで繰り返す cmp r6, ip blo 1b #endif @この段階で,zImageの再配置が完了する @bss領域(r2(__bss_start)〜r3(_end))の0クリア not_relocated: mov r0, #0 1: str r0, [r2], #4 @ clear bss str r0, [r2], #4 str r0, [r2], #4 str r0, [r2], #4 cmp r2, r3 blo 1b /* * The C runtime environment should now be setup * sufficiently. Turn the cache on, set up some * pointers, and start decompressing. */ @bl命令は戻りアドレスをlr(リンクレジスタ)に格納する @cache_onについては次回解説 bl cache_on @展開処理に必要なmalloc領域の確保 mov r1, sp @ malloc space above stack add r2, sp, #0x10000 @ 64k max @展開後のImageがロードされているzImageの領域と重なるかどうか.(下記補足2を参照) @重ならない場合,上書きの必要なしと判断しwont_overwriteへジャンプ /* * Check to see if we will overwrite ourselves. * r4 = final kernel address * r5 = start of this image * r2 = end of malloc space (and therefore this image) * We basically want: * r4 >= r2 -> OK (補足2でのパターン2) * r4 + image length <= r5 -> OK (補足2でのパターン1) */ @パターン2の確認 @疑問: ページテーブルを考慮していないが,大丈夫か cmp r4, r2 bhs wont_overwrite @パターン1の確認 @展開してできるImageのサイズをzImage(+bss+stack)の4倍と見積り,r4+4倍したサイズがr5に届かなかったら,上書きの必要なし @疑問: zImageにはbssとstackは入っていないが,下記の方法では展開前のサイズとしてこれらを含んでいる(r5からspまでの領域) @(問題はないが,気持ち悪い) sub r3, sp, r5 @ > compressed kernel size add r0, r4, r3, lsl #2 @ allow for 4x expansion cmp r0, r5 bls wont_overwrite @上書きが必要な場合,一時的にmalloc下の領域にカーネルイメージを展開する(後にzImage上に上書きを行う) mov r5, r2 @ decompress after malloc space mov r0, r5 mov r3, r7 @ r7: architecture ID @カーネルイメージの展開を行う @decompress_kernel()の引数/返り値については補足3を参照 bl decompress_kernel @r0は展開後のImageのサイズ @128byte境界にアライメントする @疑問: 余分に128byte確保しているが何に使う? add r0, r0, #127 + 128 @ alignment + stack bic r0, r0, #127 @ align the kernel length @上書き処理(補足4を参照) @次回解説するreloc_startが,実際の上書き処理を行うコピーコードである /* * r0 = decompressed kernel length * r1-r3 = unused * r4 = kernel execution address * r5 = decompressed kernel start * r6 = processor ID * r7 = architecture ID * r8 = atags pointer * r9-r14 = corrupted */ @r5+r0が展開されたImageの(アライメント済み)終端アドレスであり,ここにコピーコードを移す add r1, r5, r0 @ end of decompressed kernel @このreloc_startはPC相対アドレスを元にしてr2に入るので,再配置の影響を受けずにロードされたアドレスを取得できる adr r2, reloc_start @LC1:reloc_start(コピーコード)のサイズ ldr r3, LC1 add r3, r2, r3 @コピーコード自体がzImageに含まれており,これから上書きされるため @コピーするコードを上書きされない領域に移し,移したコードを実行する必要がある(補足4参照) 1: ldmia r2!, {r9 - r14} @ copy relocation code stmia r1!, {r9 - r14} ldmia r2!, {r9 - r14} stmia r1!, {r9 - r14} cmp r2, r3 blo 1b @r1は移す際にインクリメントされるので,ここではコピーコードの終端を指している @移したコピーコードの終端から128Byte後にspを置き,スタック領域とする add sp, r1, #128 @ relocate the stack bl cache_clean_flush @移したコピーコードの実行( & 呼び出し先でcall_kernelが実行される) add pc, r5, r0 @ call relocation code @上書きの必要がない場合,カーネルを展開し,call_kernelを呼ぶ /* * We're not in danger of overwriting ourselves. Do this the simple way. * * r4 = kernel execution address * r7 = architecture ID */ @decompress_kernelの引数については補足3を参照 wont_overwrite: mov r0, r4 mov r3, r7 bl decompress_kernel b call_kernel .type LC0, #object LC0: .word LC0 @ r1 .word __bss_start @ r2 .word _end @ r3 .word zreladdr @ r4 .word _start @ r5 .word _got_start @ r6 .word _got_end @ ip .word user_stack+4096 @ sp LC1: .word reloc_end - reloc_start .size LC0, . - LC0 以下,cache_on,call_kernel 等のコードが続く.次回解説予定.
補足1 alloc,execinstrの意味
.section ".start", #alloc, #execinstr
について,asのinfo,7.84 .section NAMEの一番下に説明あり
`#alloc' section is allocatable `#execinstr' section is executable
補足2
パターン1 ------- | | : Page Table (16KB) ------- <- r4 (zreladdr) | | : Image ------- ------- <- r5 (ブートローダ依存) | text | : zImage |-------| | got | |-------| | bss | |-------| | stack | |-------| <- r1 (sp) | | ------- <- r2 パターン2 ------- | | : Page Table (16KB) ------- <- r4 (zreladdr) | | : Image -------
補足3
decompress_kernel()呼び出し時の引数
1) 上書き無しの場合
r0:output_start (zreladdr) r1:free_mem_ptr_p (sp) r2:free_mem_ptr_end_p (sp+64k) r3:arch_id (r7)
2) 上書き有りの場合
r0:output_start (sp+64k) r1:free_mem_ptr_p (sp) r2:free_mem_ptr_end_p (sp+64k) r3:arch_id (r7)
decompress_kernel()の返り値
r0:展開されたImageのサイズ
補足4
------- <- (ブートローダ依存)
text | : zImage |
------- | |
got | |
------- | |
bss | |
------- | |
stack | : 64k |
------- | |
: 64k(malloc分) | |
------- | <- r5 |
: 展開されたImage | |
------- | <- r5+r0 (128バイトアライメント+謎の128バイト) |
: (zImageから移された)コピーコード |
GOTエントリの構成: (objdump -D vmlinux)
セクション .got の逆アセンブル: 001a0d28 <.got>: 1a0d28: 001a0d98 muleqs sl, r8, sp 1a0d2c: 001a8dc0 andeqs r8, sl, r0, asr #27 1a0d30: 001a8db8 ldreqh r8, [sl], -r8 1a0d34: 00002f2c andeq r2, r0, ip, lsr #30 1a0d38: 001a0d25 andeqs r0, sl, r5, lsr #26 1a0d3c: 001a8dc8 andeqs r8, sl, r8, asr #27 1a0d40: 00002eb0 streqh r2, [r0], -r0 1a0d44: 001a8d9c muleqs sl, ip, sp 1a0d48: 001a8da4 andeqs r8, sl, r4, lsr #27 1a0d4c: 000031e4 andeq r3, r0, r4, ror #3 1a0d50: 00002e64 andeq r2, r0, r4, ror #28 1a0d54: 00002fa4 andeq r2, r0, r4, lsr #31 1a0d58: 001a91c8 andeqs r9, sl, r8, asr #3 1a0d5c: 001a8dbc ldreqh r8, [sl], -ip 1a0d60: 001a8dc4 andeqs r8, sl, r4, asr #27 1a0d64: 00002f68 andeq r2, r0, r8, ror #30 1a0d68: 001a8dac andeqs r8, sl, ip, lsr #27 1a0d6c: 00002eee andeq r2, r0, lr, ror #29 1a0d70: 001a8db0 ldreqh r8, [sl], -r0 1a0d74: 001a8da8 andeqs r8, sl, r8, lsr #27 1a0d78: 001a0d94 muleqs sl, r4, sp 1a0d7c: 001a0d9c muleqs sl, ip, sp 1a0d80: 001a8da0 andeqs r8, sl, r0, lsr #27 1a0d84: 001a8db4 ldreqh r8, [sl], -r4
*1:arch/arm/tools/mach-types中のnumberフィールドで定義される
*2:http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html#d0e428
*3:arch/arm/boot/.Image.cmd
*4:arch/arm/boot/compressed/.piggy.gz.cmd
*5:arch/arm/boot/compressed/.piggy.o.cmd
*6:arch/arm/boot/compressed/.vmlinux.cmd
*7:arch/arm/boot/..zImage.cmd