2009年6月4日 星期四

trace linux kernel source - ARM - 01

想開始閱讀文章的網友,建議是對assembly有大略的認識或者手上有語法可以查閱,雖然文章內容大多會解釋語法的用途,但畢竟不會做完整的解說。

以下的文章一些已經在某些討論區貼過,重新編排之後再貼重貼到blog,

此篇原文撰寫於2008-0806
--------
昨天下載了linux-2.6.26的kernel source,打算trace一下 (以ARM為例子)看看能不能多了解一下kernel booting時候的一些動作,一些文章提到是從/arch/arm/boot/bootp/init.S開始,所以就從它開始吧!!

19 .section .start,#alloc,#execinstr
20 .type _start, #function
21 .globl _start
22
23 _start: add lr, pc, #-0x8 @ lr = current load addr
24 adr r13, data
25 ldmia r13!, {r4-r6} @ r5 = dest, r6 = length
26 add r4, r4, lr @ r4 = initrd_start + load addr
27 bl move @ move the initrd
.....
76 .type data,#object
77 data: .word initrd_start @ source initrd address
78 .word initrd_phys @ destination initrd address
79 .word initrd_size @ initrd size
80

line19,宣告了叫做.start的section,line 20,21宣告了一個叫做_start的function,程式碼似乎從line 23開始line 23, 『add lr, pc, #-0x8』,add就是將pc+(-0x8)的結果放到lr之中,pc和lr都是ARM裡頭暫存器pc就是program counter,CPU用來指著目前要執行的指令,執行完CPU就會自動把PC+1這樣就會拿到下一道指令,lr通常是第14個register, r14,常被用來繼續functionreturn時候的返回位置。奇怪的是為什麼要-0x8??

原因應該是ARM本身有pipeline的設計,3 stages: prefetch->decode->execution,當指令被執行的時候,其實已經預先去偷偷抓下一道,所以PC值不是真的指在目前執行的地方,三級pipeline剛好多了兩個cycle所以4 bytes x 2必須要-8才是正在執行指令的位址。line 24, 『adr r13, data』adr會去讀取data所在的位址當作值,寫到r13裡頭。r13通常是用來放stack pointer,常縮寫成sp,所以現在r13就指到data所在的位置上去。line 25, 『ldmia r13!, {r4-r6}』ldmia, load multiple increment after,顧名思義就是可以做很多次load的動作,每此load完就把位址+1,執行完之後r4 = initrd_startr5 = initrd_physr6 = initrd_sizeline 26, 『add r4, r4, lr』r4 = r4+lr, lr是剛剛我們算過的,指到一開始執行指令的地方,看程式碼的解釋,被當成載入的位址,所以執行完 r4 = initrd_start+程式被載入的位置,但是意圖不明,看看之後有沒有被用到。line 27, 『bl move』bl, b是branch,相當於C語言goto的動作,l就是goto之前,先把目前位置存放到lr裡面,所以bl除了goto之外,也改寫了lr,應該是有利於等一下返回的時候,可以用lr的值。

由於上面的 code 裡面出現了一些還沒定義的symbol,例如 initrd_start, initrd_size等等,因此我們先岔斷出來做說明,其實這些是被定義在另外一個檔 ./arch/arm/boot/bootp/initrd.S

1 .type initrd_start,#object
2 .globl initrd_start
3 initrd_start:
4 .incbin INITRD
5 .globl initrd_end
6 initrd_end:

line 2, 3, 5, 6定義了兩個symbol initrd_start和initrd_end,中間還用.incbin INITRD 將 ramdisk 的 image include進來,這邊有點複雜,假如compiler kernel的時候

1) 有選擇使用ramdisk當成boot device的話,會對從環境變數去設置 INITRD 這個檔名會被帶入到MAKEFILE,並且在做assembler動作的時候丟進來。

2) 假如沒有使用initrd,那就.incbin應該就包不到東西,initrd_start 和 initrd_end 就會相等

另外有個 script ./arch/arm/boot/bootp/bootp.lds,它規範了所有link起來的object code裡面的section要怎麼編排,特定的編排方式,對於撰寫kernel的時候,程式本身可以很方便到特定的section取得想要的資料或是計算某個section的大小

10 OUTPUT_ARCH(arm)
11 ENTRY(_start)
12 SECTIONS
13 {
14 . = 0;
15 .text : {
16 _stext = .;
17 *(.start)
18 *(.text)
19 initrd_size = initrd_end - initrd_start;
20 _etext = .;
21 }
22
23 .stab 0 : { *(.stab) }
24 .stabstr 0 : { *(.stabstr) }
25 .stab.excl 0 : { *(.stab.excl) }
26 .stab.exclstr 0 : { *(.stab.exclstr) }
27 .stab.index 0 : { *(.stab.index) }
28 .stab.indexstr 0 : { *(.stab.indexstr) }
29 .comment 0 : { *(.comment) }
30 }

對於object file的格式不熟悉的話可以參考ELF format的相關文件

接著繼續看 init.S,之前的code已經goto到move這邊來,所以貼一些move的程式碼

66 move: ldmia r4!, {r7 - r10} @ move 32-bytes at a time
67 stmia r5!, {r7 - r10}
68 ldmia r4!, {r7 - r10}
69 stmia r5!, {r7 - r10}
70 subs r6, r6, #8 * 4
71 bcs move
72 mov pc, lr

line 66, 將r4所指到的位置,分別將值讀出來放到r7, r8, r9, r10, 可以發現剛剛計算過的r4這邊被用到了,但是為什麼r4不是用initrd_start,卻還要加上load addr??

原因應該是bootp.lds的14行『. = 0;』表示最後被link好的address會從0x0開始,所以 initrd_start 所記錄的位置可以當成是offset,加上load到DRAM或是擺在flash上的位址後就剛好是initrd所在的地方。

line 67, 『stmia r5!, {r7 - r10}』stmia, store multiple increment after, 和ldmia動作相同,只是用來寫資料。r5是存放著initrd要擺放的位置,猜測應該是為了一開始image放在flash上,但是可以將initrd拷貝到DRAM上r7寫到r5指到的位置

r8->r5+1
r9->r5+2
r10->r5+3

所以我們發現,66,67行就是將r4所指的東西搬到r5。line 68, 69也是一樣copy了4x4bytes,一共是32bytes。line 70,『subs r6, r6, #8 * 4』,將length - 32bytesline 71,『bcs move』,b是branch的意思,cs是表示condition的條件,要是條件符合的話,就做branch的動作,這邊的用意是判斷前一個length是不是已經到0,如果不為零就繼續copy。line 72,『mov pc, lr』接著就把剛剛bl指令預先存放好的lr 填入pc,這樣CPU就會跳回去原本的return address。以上的動作,慢慢看得出來有在做些什麼事

1. 找出initrd的所在位置
2. 將它copy到一個指定的destination去

程式返回之後我們接著看下一行

33 ldmia r13, {r5-r9} @ get size and addr of initrd
34 @ r5 = ATAG_CORE
35 @ r6 = ATAG_INITRD2
36 @ r7 = initrd start
37 @ r8 = initrd end
38 @ r9 = param_struct address
39
40 ldr r10, [r9, #4] @ get first tag
41 teq r10, r5 @ is it ATAG_CORE?

line 33, 繼續從r13的地方取出資料到r5, r6, r7 ,r8, r9,註解的說明有提到各個資料的意義,注意一下這邊的r7是initrd的destination address不是source address。line 40, 讀入第一個tag,這邊的tag是指bootloader丟給kernel的一個boot arguments,會被用一個叫做ATAG的structure包起來,並且放到系統的某個地方。然後kernel跑init.S,的時候就會去這個地方拿ATAG的資料,這些資訊包括記憶體要使用多大,螢幕的解析度多大等等。line 41, t是test, eq是equal, 判斷拿到的第一個tag是不是等於atag core. 應該是看 atag list 是不是成立的。繼續接著看

45 movne r10, #0 @ terminator
46 movne r4, #2 @ Size of this entry (2 words)
47 stmneia r9, {r4, r5, r10} @ Size, ATAG_CORE, terminator

發現45, 46, 47的指令都帶有condition "ne", not equal,表示是剛剛 line 41發現atag不成立所做的事情,注釋是寫『If we didn't find a valid tag list, create a dummy ATAG_CORE entry.』所以以上三行就是用來創造一個假的entry,假設一切順利這三行指令會bypass過去不會被執行到。接著來看init.S最後一段程式碼 (終於~)

54 taglist: ldr r10, [r9, #0] @ tag length
55 teq r10, #0 @ last tag (zero length)?
56 addne r9, r9, r10, lsl #2
57 bne taglist
58
59 mov r5, #4 @ Size of initrd tag (4 words)
60 stmia r9, {r5, r6, r7, r8, r10}
61 b kernel_start @ call kernel

line 54, 將r9指到的位址的offset 0x0的值載入到r10。看註解是tag length,所以這邊得要去翻翻atag的規範這邊有個文章有提到 http://www.simtec.co.uk/products ... ooting_article.html ,一開始應該是去讀atag_header所看第一個欄位,確認一下是size,應該沒問題。

struct atag_header {
u32 size; /* legth of tag in words including this header */
u32 tag; /* tag value */
};

line 55,測試一下size是不是0。line 56, 57也有condition ne,表示是不為0的時候做的。將拿到的length(r10)乘以4,這邊的lsl是將r10往左shift的意思,因為一個欄位是4bytes,所以乘4之後就跳到下一個tag,一直跳到最後沒東西。line 59, 將r5設成4line 60, 將r5, r6, r7, r8 ,r10存到r9所指到的位置,應該就是跟在atag list的後面。line 61, jump 到 kernel_start ,注意這邊是用b而不是bl,因為跳過去kernel就不需要返回了。BL會用到lr紀錄返回位置。以上,走過一整個init.S,我們可以粗略的知道他會

1) 處理ramdisk
2) 處理atag (也就是bootargs)

接著會跳到./arch/arm/boot/compressed/head.S。kernel_start的定義方式跟initrd_start有點類似,中間有透過 kernel.S去用.incbin把kernel image包進來。

3 則留言:

  1. zImage 的入口程序即arch/arm/boot/compressed/head.S,bootpImage 的入口程序即arch/arm/boot/bootp/init.S,一般而言不會同時使用bootImage和zImage

    回覆刪除
  2. 補充一下,一般正常開機流程會使用zImage,除非使用bootp開機才會去包bootpImage(由zImage和initrd組成)。

    回覆刪除
  3. 不好意思
    不是很懂MIPS_ARM的意思 ^^
    是說只有要zImage+ramdisk(initrd)合併成一個檔案的時候
    進入點才會是init.S
    一般單純只有zImage or vmlinux的話
    只會從head.S進入
    是嗎?

    回覆刪除

搜尋此網誌