zImageのロード&展開調査/ARM (1)

だ,である調で断定的に書いてありますが,間違い等絶対あると思うので,よろしければご指摘お願いします.

Linuxでは,起動するカーネルイメージとしてvmlinux/圧縮版vmlinux/Image/zImageを用いる.ここでは,ARMアーキテクチャにおいてzImageがROM/CFカードに存在し,自己解凍コードによってRAMに展開&ロードされる手順を述べる.解説に使用するカーネルソースは2.6.22である.


以下が実行環境の設定である(armadillo-300の環境を想定).この設定でアセンブル/実行されないコード部分については省略している.

  1. CONFIG_CPU_CP15=y
  2. CONFIG_CPU_ABRT_EV5TJ=y #ARMv5TEJ ARM9

以下を実行前の状態とする.

  1. zImageがROMにある場合(CONFIG_ZBOOT_ROM時)はPCがzImageの先頭アドレスを指している
  2. CFカードの場合はブートローダがzImageをRAMにコピーし,PCがその先頭アドレスを指している
  3. レジスタ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
@r1r0(TEXT_STARTzImageのロードアドレスの差分)を加算し
                add     r1, r1, r0              @ table.  This fixes up the
@更新する(更新後r64加算され,次のエントリを指す)
                str     r1, [r6], #4            @ C references.
@以上をエントリの終端まで繰り返す(r6ip(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セクションの終端アドレス)の間にない時,再配置を行う(r1r0を加算する)
                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にはbssstackは入っていないが,下記の方法では展開前のサイズとしてこれらを含んでいる(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_startPC相対アドレスを元にして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_oncall_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