作者:old five zhang
地址:swust
UBoot版本:記不太清楚了
http://blog.chinaunix.net/space.php?uid=14782631
本文檔是自己學習UBoot的總結,只涉及到自己以前迷惑的地方
初探執(zhí)行一下make,看下make的過程中發(fā)生了什么。
UNDEF_SYM=`arm_v5t_le-objdump -x lib_generic/libgeneric.a board/dahua_davinci/libdahua_davinci.a cpu/arm926ejs/libarm926ejs.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;
查找各源文件中的u_boot_cmd段。
arm_v5t_le-ld -Bstatic -T uboot/Trunk/board/dahua_davinci/u-boot.lds -Ttext 0x81080000 $UNDEF_SYM cpu/arm926ejs/start.o --start-group lib_generic/libgeneric.a board/dahua_davinci/libdahua_davinci.a cpu/arm926ejs/libarm926ejs.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a --end-group -L /opt/toolchains/davinci/pro/devkit/arm/v5t_le/bin/../lib/gcc/armv5tl-montavista-linuxeabi/3.4.3 -lgcc -Map u-boot.map -o u-boot
鏈接各源程序為u-boot可執(zhí)行文件,并生成u-boot.map文件。U-boot中包含了一些調試信息,比如說debug_frame ,debug_info等。u-boot.map中記錄了各函數(shù)/變量的地址。
arm_v5t_le-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec
將u-boot生成S記錄文件,這個文件一般在MOTOROLA的機子上有用。
objcopy的作用是拷貝一個目標文件的內容到另一個目標文件中。Objcopy使用GNU BFD庫去讀或寫目標文件。Objcopy可以使用不同于源目標文件的格式來寫目的目標文件(也即是說可以將一種格式的目標文件轉換成另一種格式的目標文件)。通過以上命令行選項可以控制Objcopy的具體操作。
通過使用srec作為輸出目標(使用命令行選項-o srec),Objcopy可以產(chǎn)生S記錄格式文件。
通過使用binary作為輸出目標(使用命令行選項-o binary),Objcopy可以產(chǎn)生原始的二進制文件。使用Objcopy產(chǎn)生一個原始的二進制文件,實質上是進行了一回輸入目標文件內容的內存轉儲。所有的符號和重定位信息都將被丟棄。內存轉儲起始于輸入目標文件中那些將要拷貝到輸出目標文件去的部分的最小虛地址處。
使用Objcopy生成S記錄格式文件或者原始的二進制文件的過程中,-S選項和-R選項可能會比較有用。-S選項是用來刪掉包含調試信息的部分,-R選項是用來刪掉包含了二進制文件不需要的內容的那些部分。
arm_v5t_le-objcopy --gap-fill=0xff -O binary u-boot dhboot.bin
將uboot可執(zhí)行文件生成RAW二進制文件。對于在PC上運行的程序或者在設備上運行的應用程序,沒有這一步,因為操作系統(tǒng)會解析并根據(jù)可執(zhí)行文件中的信息加載程序執(zhí)行。但對于系統(tǒng)運行的第一個程序u-boot,沒有誰會去解析加載這個程序,所以,需要將其做成RAW的二進制文件,使其能一開始就能在設備上運行。
連接腳本對于.lds文件,它定義了整個程序編譯之后的連接過程,決定了一個可執(zhí)行程序的各個段的存儲位置。雖然現(xiàn)在我還沒怎么用它,但感覺還是挺重要的,有必要了解一下。
先看一下GNU官方網(wǎng)站上對.lds文件形式的完整描述:
SECTIONS { ... secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) { contents } >region :phdr =fill ... } |
secname和contents是必須的,其他的都是可選的。下面挑幾個常用的看看:
1、secname:段名
2、contents:決定哪些內容放在本段,可以是整個目標文件,也可以是目標文件中的某段(代碼段、數(shù)據(jù)段等)
3、start:本段連接(運行)的地址,如果沒有使用AT(ldadr),本段存儲的地址也是start。GNU網(wǎng)站上說start可以用任意一種描述地址的符號來描述。
4、AT(ldadr):定義本段存儲(加載)的地址。
看一個簡單的例子:(摘自《2410完全開發(fā)》)
/* nand.lds */ SECTIONS { firtst 0x00000000 : { head.o init.o } second 0x30000000 : AT(4096) { main.o } } |
以上,head.o放在0x00000000地址開始處,init.o放在head.o后面,他們的運行地址也是0x00000000,即連接和存儲地址相同(沒有AT指定);main.o放在4096(0x1000,是AT指定的,存儲地址)開始處,但是它的運行地址在0x30000000,運行之前需要從0x1000(加載處)復制到0x30000000(運行處),此過程也就用到了讀取Nand flash。這就是存儲地址和連接(運行)地址的不同,稱為加載時域和運行時域,可以在.lds連接腳本文件中分別指定。
arm_v5t_le-ld -Bstatic -T uboot/Trunk/board/dahua_davinci/u-boot.lds -Ttext 0x81080000 $UNDEF_SYM cpu/arm926ejs/start.o --start-group lib_generic/libgeneric.a board/dahua_davinci/libdahua_davinci.a cpu/arm926ejs/libarm926ejs.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a --end-group -L /opt/toolchains/davinci/pro/devkit/arm/v5t_le/bin/../lib/gcc/armv5tl-montavista-linuxeabi/3.4.3 -lgcc -Map u-boot.map -o u-boot
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm926ejs/start.o (.text) /*.text段表示代碼段,起始位置由-Ttext選項指定 */
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) } /*只讀數(shù)據(jù)段, 保存已經(jīng)初始化的全局只讀數(shù)據(jù),比如只讀字符串*/
. = ALIGN(4);
.data : { *(.data) } /*數(shù)據(jù)段, 保存已經(jīng)初始化的全局數(shù)據(jù) */
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;/* 指定u_boot_cmd段, uboot把所有的uboot命令放在該段. */
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) } /*堆棧段,未初始化的全局變量也保存在此*/
_end = .;
}
__u_boot_cmd_end ,__bss_start,_end相當是在這里定義的變量,變量的值是‘.’的地址,變量的地址也是‘.’的地址。這個在程序里可以引用,但__u_boot_cmd_end ,__bss_start,_end它們不占用存儲空間。以__bss_start為例,在反匯編的代碼中,有
0x8109928c __u_boot_cmd_version
0x8109925c __u_boot_cmd_help
0x810992a4 __u_boot_cmd_end = .
0x810992a4 . = ALIGN (0x4)
0x810992a4 __bss_start = .
.bss 0x810992a4 0x197f8
*(.bss)
.bss 0x810992a4 0x8 cpu/arm926ejs/libarm926ejs.a(interrupts.o)
.bss 0x810992ac 0x30 lib_arm/libarm.a(board.o)
0x810992ac monitor_flash_len
0x810992b0 hwid
從這里來看,__u_boot_cmd_end,__bss_start不占用存儲空間,因為他們的值一樣且與cpu/arm926ejs/libarm926ejs.a(interrupts.o)的起始位置的地址一樣。通常,我們只是去讀__u_boot_cmd_end,__bss_start的值去確定一個邊界,例如,在start.S中,
_bss_start:
.word __bss_start
這句話的意思是,在_bss_start這個地址的位置放一個值,該值為_bss_start的地址。
查看其匯編代碼:
81080048 <_bss_start>:
81080048: 810992a4 smlatbhi r9, r4, r2, r9
可見,81080048這個地址中的值就是810992a4。而810992a4正好是__bss_start的值。
再來看另外一個例子,在do_help()中,C代碼為:
int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
int i;
int rcode = 0;
if (argc == 1) { /*show list of commands */
int cmd_items = &__u_boot_cmd_end -
&__u_boot_cmd_start; /* pointer arith! */
其匯編代碼為:
8108aca4 <do_help>:
8108aca4: e1a0c00d mov ip, sp
8108aca8: e92ddef0 stmdb sp!, {r4, r5, r6, r7, r9, sl, fp, ip, lr, pc}
8108acac: e3520001 cmp r2, #1 ; 0x1
8108acb0: e24cb004 sub fp, ip, #4 ; 0x4
8108acb4: e1a04002 mov r4, r2
8108acb8: e1a0a003 mov sl, r3
8108acbc: e3a07000 mov r7, #0 ; 0x0
8108acc0: 13a06001 movne r6, #1 ; 0x1
8108acc4: e24dd008 sub sp, sp, #8 ; 0x8
8108acc8: 1a000044 bne 8108ade0 <do_help+0x13c>
8108accc: e59f3184 ldr r3, [pc, #388] ; 8108ae58 <.text+0xae58>
8108acd0: e59f2184 ldr r2, [pc, #388] ; 8108ae5c <.text+0xae5c>
8108ae58: 81098e0c tsthi r9, ip, lsl #28
8108ae5c: 810992a4 smlatbhi r9, r4, r2, r9
可見,對&__u_boot_cmd_end的引用被解析成了對810992a4這個地址,810992a4正好是__u_boot_cmd_end的地址。
(可以在內核中測試一下,修改這個變量,如能修改,說明。打印出這個變量的值和地址。。。。)
Start.SStart.S的作用是實現(xiàn)UBOOT對中斷的處理以及一些必須要做的低級的工作,比如代碼拷貝等。有三種情況需要拷貝:
1. 因為有些CPU的代碼可以在NOR FLASH中執(zhí)行,有些可以在片內的ROM中執(zhí)行,這樣的話,速度有了限制。所以,在START.S中會有代碼的COPY,將代碼拷貝到RAM中
2.程序雖然一開始在RAM中運行,但期待的運行地址與程序一開始運行的地址不一樣。比如說程序一開始運行在0x81000000,但這個位置我們需要存放LINUX內核或者我想將其作為棧的起始位置,那我們就需要把代碼轉移到另外一個位置。
3.還有種情況是代碼開始在片內ROM中執(zhí)行,因為片內ROM容量太小,不包含所有的代碼,也需要拷貝,但這里對于我們所用的CPU不考慮這情況。
程序一開始,程序做一些初始化,比如禁止MMU和CACHE,然后就跳到函數(shù)lowlevel_init 中去了。對于我們的程序,lowlever_init在board/dahua_davinci/lowlevel_init.S中定義。這個函數(shù)主要做一些PLL,DDR的初始化。
初始化完成后,程序看是否需要拷貝代碼。
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
adr r0, _start將_start的相對位置加載到r0 到,ADR指令的效果與當前PC指針的位置有關,其匯編代碼為:
81080064: e24f006c sub r0, pc, #108 ; 0x6c
如果代碼一開始在0X0運行,那這里的r0就為0,如果在0x8000運行,那么r0就為0x8000。
ldr r1, _TEXT_BASE將_TEXT_BASE的值加載到r1中,效果與PC的位置無關,即是說無論開始在0X0運行,還是在81080000運行,這里r1的值都為0x81080000(_TEXT_BASE=0x81080000)。
81080000 <_start>:
81080000: ea000012 b 81080050 <reset>
cmp r0, r1比較兩者的值,如果相等,說明是在RAM中運行,如果不等,則是在其它地方運行,需要COPY。
拷貝首先計算拷貝的大小和結束的位置,大小size可以用_bss_start -_armboot_start,結束的地址可以用_start+size?截惖倪^程使用ldm與stm的循環(huán)完成。
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
拷貝完成后就是棧的設置,將_TEXT_BASE-CFG_MALLOC_LEN-CFG_GBL_DATA_SIZE到_TEXT_BASE 這段空間設為棧,使SP指針的值為_TEXT_BASE-CFG_MALLOC_LEN-CFG_GBL_DATA_SIZE 。
然后就是bss的初始化,將__bss_start到__bss_end這段空間的內容初始化為0。
接著,程序跳到RAM中執(zhí)行,也就是810800c4這個地址:
ldr pc, _start_armboot
_start_armboot:
.word start_armboot
其匯編代碼為:
810800c0: e51ff004 ldr pc, [pc, #-4] ; 810800c4 <_start_armboot>
810800c4 <_start_armboot>:
810800c4: 810810c0 smlabthi r8, r0, r0, r1
start_armboot#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
/*這個聲明告訴編譯器使用寄存器r8來存儲gd_t類型的指針gd,即這個定義聲明了一個指針,并且指明了它的存儲位置。*/
__asm__ __volatile__("": : :"memory");
/*
1)__asm__用于指示編譯器在此插入?yún)R編語句
2)__volatile__用于告訴編譯器,嚴禁將此處的匯編語句與其它的語句重組合優(yōu)化。即:原原本本按原來的樣子處理這這里的匯編。
3) memory強制gcc編譯器假設RAM所有內存單元均被匯編指令修改,這樣cpu中的registers和cache中已緩存的內存單元中的數(shù)據(jù)將作廢。cpu將不得不在需要的時候重新讀取內存中的數(shù)據(jù)。這就阻止了cpu又將registers,cache中的數(shù)據(jù)用于去優(yōu)化指令,而避免去訪問內存。
4)"":::表示這是個空指令。
*/
C語言中嵌入?yún)R編 格式: _asm_("asm statements":outputs:inputs:registers-modified)
其中,"asm statements"是匯編語句表達式,outputs,inputs,register-modified都是可選參數(shù),以冒號隔開,且一次以0~9編號,如outputs的寄存器是0號,inputs寄存器是1號,往后依次類推。outputs是匯編語句執(zhí)行完后輸出到的寄存器,inputs是輸入到某個寄存器。
例1:_asm_("pushl %%eax\n\t" "movl $0,%%eax\n\t" "popl %%eax");
在嵌入?yún)R編中,寄存器前面要加兩個%,因為gcc在編譯是,會先去掉一個%再輸出成匯編格式。
例2:{ register char _res;\
asm("push %%fs\n\t"
"movw %%ax,%%fs\n\t"
"movb %%fs:%2,%%al\n\t"
"pop %%fs"
:"=a"(_res):"0"(seg),"m"(*(addr)));\
_res;}
movb %%fs:%2,%%al\n\t一句中是把以fs為段地址,以后面的第二號寄存器即后面的seg中的值為偏移地址所對應的值裝入al。"=a"(_res):"0"(seg),"m"(*(addr)))一句中,"=a"(_res)表示把a寄存器中的內容給_res,"0"(seg)表示把seg中的內容給0所對應的寄存器,而0即表示使用和前一個寄存器相同的寄存器,這里即使用a寄存器,也就是說把seg中的內容個a寄存器。
需要解釋以下的是,a,b,c,d分別表示寄存器eax,ebx,ecx,edx
S,D分別表示寄存器esi,edi
r表示任意寄存器
0(數(shù)字0,不是o。┍硎臼褂蒙弦粋寄存器