分类: 嵌入式
2011-02-06 18:48:49
执行一下make,看下make的过程中发生了什么。
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可执行文件,并生成u-boot.map文件。U-boot中包含了一些调试信息,比如说debug_frame ,debug_info等。u-boot.map中记录了各函数/变量的地址。
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可以产生S记录格式文件。
通过使用binary作为输出目标(使用命令行选项-o binary),Objcopy可以产生原始的二进制文件。使用Objcopy产生一个原始的二进制文件,实质上是进行了一回输入目标文件内容的内存转储。所有的符号和重定位信息都将被丢弃。内存转储起始于输入目标文件中那些将要拷贝到输出目标文件去的部分的最小虚地址处。
使用Objcopy生成S记录格式文件或者原始的二进制文件的过程中,-S选项和-R选项可能会比较有用。-S选项是用来删掉包含调试信息的部分,-R选项是用来删掉包含了二进制文件不需要的内容的那些部分。
arm_v5t_le-objcopy --gap-fill=0xff -O binary u-boot dhboot.bin
将uboot可执行文件生成RAW二进制文件。对于在PC上运行的程序或者在设备上运行的应用程序,没有这一步,因为操作系统会解析并根据可执行文件中的信息加载程序执行。但对于系统运行的第一个程序u-boot,没有谁会去解析加载这个程序,所以,需要将其做成RAW的二进制文件,使其能一开始就能在设备上运行。
连接脚本对于.lds文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。虽然现在我还没怎么用它,但感觉还是挺重要的,有必要了解一下。
先看一下对.lds文件形式的完整描述:
SECTIONS { |
secname和contents是必须的,其他的都是可选的。下面挑几个常用的看看:
1、secname:段名
2、contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)
3、start:本段连接(运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start。GNU网站上说start可以用任意一种描述地址的符号来描述。
4、AT(ldadr):定义本段存储(加载)的地址。
看一个简单的例子:(摘自《2410完全开发》)
/* nand.lds */ |
以上,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) } /*只读数据段, 保存已经初始化的全局只读数据,比如只读字符串*/
. = ALIGN(4);
.data : { *(.data) } /*数据段, 保存已经初始化的全局数据 */
. = 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
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
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的作用是实现UBOOT对中断的处理以及一些必须要做的低级的工作,比如代码拷贝等。有三种情况需要拷贝:
1. 因为有些CPU的代码可以在NOR FLASH中执行,有些可以在片内的ROM中执行,这样的话,速度有了限制。所以,在START.S中会有代码的COPY,将代码拷贝到RAM中
2.程序虽然一开始在RAM中运行,但期待的运行地址与程序一开始运行的地址不一样。比如说程序一开始运行在0x81000000,但这个位置我们需要存放LINUX内核或者我想将其作为栈的起始位置,那我们就需要把代码转移到另外一个位置。
3.还有种情况是代码开始在片内ROM中执行,因为片内ROM容量太小,不包含所有的代码,也需要拷贝,但这里对于我们所用的CPU不考虑这情况。
程序一开始,程序做一些初始化,比如禁止MMU和CACHE,然后就跳到函数lowlevel_init 中去了。对于我们的程序,lowlever_init在board/dahua_davinci/lowlevel_init.S中定义。这个函数主要做一些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
cmp r0, r1比较两者的值,如果相等,说明是在RAM中运行,如果不等,则是在其它地方运行,需要COPY。
拷贝首先计算拷贝的大小和结束的位置,大小size可以用_bss_start -_armboot_start,结束的地址可以用_start+size。拷贝的过程使用ldm与stm的循环完成。
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中执行,也就是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
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
/*这个声明告诉编译器使用寄存器r8来存储gd_t类型的指针gd,即这个定义声明了一个指针,并且指明了它的存储位置。*/
__asm__ __volatile__("": : :"memory");
/*
1)__asm__用于指示编译器在此插入汇编语句
2)__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
3) memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
4)"":::表示这是个空指令。
*/
C语言中嵌入汇编格式: _asm_("asm statements":outputs:inputs:registers-modified)
其中,"asm statements"是汇编语句表达式,outputs,inputs,register-modified都是可选参数,以冒号隔开,且一次以0~9编号,如outputs的寄存器是0号,inputs寄存器是1号,往后依次类推。outputs是汇编语句执行完后输出到的寄存器,inputs是输入到某个寄存器。
例1:_asm_("pushl %%eax\n\t" "movl $0,%%eax\n\t" "popl %%eax");
在嵌入汇编中,寄存器前面要加两个%,因为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(数字0,不是o!)表示使用上一个寄存器