下例:
第一阶段程序流程图
SVC模式切换
|
针对特定SOC的设置
|
lowlevel_init---->I/Dcache, MMU,sdram
|
relocate
|
进入引导第二阶段
uboot1.1.6
ARCH=arm920t
CPU=S3C2410
编译后生成的二进制文件,也就是机器码,这里只是该文件的前16行,不过第一阶段引导的关键代码都在这里了,首先是机器加电PC=0,而这部分代码会被加载到steppingstone里执行。
存储方式:高位高字节
- 0000000: 1200 00ea 14f0 9fe5 14f0 9fe5 14f0 9fe5 ................
- 0000010: 14f0 9fe5 14f0 9fe5 14f0 9fe5 14f0 9fe5 ................
- 0000020: 4001 f833 a001 f833 0002 f833 6002 f833 @..3...3...3`..3
- 0000030: c002 f833 2003 f833 8003 f833 efbe adde ...3 ..3...3....
- 0000040: 0000 f833 0000 f833 d479 f933 fcc0 f933 ...3...3.y.3...3
- 0000050: 0000 0fe1 1f00 c0e3 d300 80e3 00f0 29e1 ..............).
- 0000060: 5304 a0e3 0010 a0e3 0010 80e5 0010 e0e3 S...............
- 0000070: 6003 9fe5 0010 80e5 5c13 9fe5 5c03 9fe5 `.......\...\...
- 0000080: 0010 80e5 5803 9fe5 0310 a0e3 0010 80e5 ....X...........
- 0000090: 1800 00eb 9c00 4fe2 6010 1fe5 0100 50e1 ......O.`.....P.
- 00000a0: 0700 000a 6820 1fe5 6830 1fe5 0220 43e0 ....h ..h0... C.
- 00000b0: 0220 80e0 f807 b0e8 f807 a1e8 0200 50e1 . ............P.
- 00000c0: fbff ffda 8c00 1fe5 0308 40e2 8000 40e2 ..........@...@.
- 00000d0: 0cd0 40e2 9400 1fe5 9410 1fe5 0020 a0e3 ..@.......... ..
- 00000e0: 0020 80e5 0400 80e2 0100 50e1 fbff ffda . ........P.....
- 00000f0: 04f0 1fe5 e00b f833 0000 a0e3 170f 07ee .......3........
执行的第一句指令是
ea000012,这句指使得程序跳到PC+0x50处继续执行和地址无关, 此处的机器码是模式转换到svc,转换完成后有个带返回的跳转lowlevel_init,对应的机器码在
eb000018,跳到PC+0x68处继续执行和地址无关,返回以后程序还会接着执行,接着是代码的搬运,搬运到ram目的地址_TEXT_BASE存储在0000040【4bytes】,访问该数据是通过相对于PC的偏移,不是直接访问地址,其实arm的B,BL,LDR,都是通过先计算出当前PC的偏移量,再把该内存单元的值取出来。
从44开始的4bytes数据存放的是armboot_start的值=33f80000,
从48开始的4bytes存放的是_BSS_START的值=33f80000,
从4C开始的4bytes存放的是_BSS_END值=33f9c0fc,
取这些值时用的指令有ldr和adr,这两条指令都是通过相对PC的偏移来计算的,是和地址无关。
_BSS_START - armboot_start的值等于uboot的大小。从_start标号处开始复制代码,然后设置SP和BSS段,完成后执行00000f0处的代码e51ff004,将以当前的地址+4的内存单元的值加载到PC里,而该内存单元恰好是33f80000,这里开始到sdram里去执行第二阶段的引导程序。所以第一阶段的机器码不管加载在什么地址在什么介质都可以正常运行。如果直接通过arm-linux-objdump反汇编的代码去分析很容易被链接器链接后那些链接地址欺骗。当然如果知道链接器工作原理后应该不会被反汇编的代码欺骗
在uboot第一阶段引导的最后一步做了一下代码
- ldr pc, _start_armboot
- 33f800f0: e51ff004 ldr pc, [pc, #-4] ; 33f800f4 <_start_armboot>
- 33f800f4 <_start_armboot>:
- 33f800f4: 33f80be0 mvnccs r0, #229376 ; 0x38000
该指令是把当前指令地址+4的内存地址的值赋值为PC,PC=0x33f80be0;
第一阶段的代码是地址无关代码,但是现在PC却指向了0x33f80be0处,此处的代码在uboot(1.1.6)的根目录下/lib_arm/board.c中的start_armboot函数,主要是一些初始化。其中关系到两个数据结构
- typedef struct global_data {
- bd_t *bd;
- unsigned long flags;
- unsigned long baudrate;
- unsigned long have_console;/* serial_init() was called */
- unsigned long reloc_off; /* Relocation Offset */
- unsigned long env_addr; /* Address of Environment struct */
- unsigned long env_valid; /* Checksum of Environment valid? */
- unsigned long fb_base; /* base address of frame buffer */
- #ifdef CONFIG_VFD
- unsigned char vfd_type; /* display type */
- #endif
- void **jt; /* jump table */
- } gd_t;
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
gd指针的值存放在r8寄存器里,register是将gd指针和r8绑定在一起,volatile关键字的意思是每次使用该变量时都
直接存取原始地址
,和内核中的task_struct结构中的state变量一样
- typedef struct bd_info {
- int bi_baudrate; /* serial console baudrate */
- unsigned long bi_ip_addr; /* IP Address */
- unsigned char bi_enetaddr[6]; /* Ethernet adress */
- struct environment_s *bi_env;
- ulong bi_arch_number; /* unique id for this board */
- ulong bi_boot_params; /* where this board expects params */
- struct /* RAM configuration */
- {
- ulong start;
- ulong size;
- } bi_dram[CONFIG_NR_DRAM_BANKS];
- #ifdef CONFIG_HAS_ETH1
- /* second onboard ethernet port */
- unsigned char bi_enet1addr[6];
- #endif
- } bd_t;
在start_armboot函数里先是gd的初始化然后再进行系统初始化,在2011.03版本中这两部分的初始化是放在两个函数里,而且gd的初始化是在relocate_code之前进行的。
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
从这里可以发现gd的数据在内存里存放的地址是位于uboot代码和malloc区域的上方,由于传统栈的生长方向满递减,所以gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
接下来是把初始化队列里的函数按顺序执行下来,如果执行错误,则执行hang(),实际是死循环。
接着是flash的初始化,
malloc区域的初始化,
uboot的环境的初始化先是在malloc区域申请指定大小的内存,再是初始化,并设置好gd结构里相应的标志位,和该区域的地址,
- #ifdef ENV_IS_EMBEDDED
- /*
- * The environment buffer is embedded with the text segment,
- * just relocate the environment pointer
- */
- env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
- DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
- #else
- /*
- * We must allocate a buffer for the environment
- */
- env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
- DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
- #endif
- ......
- ......
- memset (env_ptr, 0, sizeof(env_t));
- memcpy (env_ptr->data,
- .....
- gd->env_valid = 1;
- ......
- gd->env_addr = (ulong)&(env_ptr->data);
ip地址的配置并将地址放到gd结构里等网卡的初始化时再去读取,
初始化板子上有的设备,例如usb,keyboard,lcd等,
- /* Initialize the list */
- devlist = ListCreate (sizeof (device_t));
- if (devlist == NULL) {
- eputs ("Cannot initialize the list of devices!\n");
- return -1;
- }
- #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
- i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
- #endif
- ......
- ......
- return (0);
填充jumptable里的数据
- gd->jt[XF_get_version] = (void *) get_version;
- gd->jt[XF_malloc] = (void *) malloc;
- gd->jt[XF_free] = (void *) free;
- gd->jt[XF_getenv] = (void *) getenv;
- gd->jt[XF_setenv] = (void *) setenv;
- gd->jt[XF_get_timer] = (void *) get_timer;
- gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
- gd->jt[XF_udelay] = (void *) udelay;
- #if defined(CONFIG_I386) || defined(CONFIG_PPC)
- gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
- gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
- #endif /* I386 || PPC */
- #if (CONFIG_COMMANDS & CFG_CMD_I2C)
- gd->jt[XF_i2c_write] = (void *) i2c_write;
- gd->jt[XF_i2c_read] = (void *) i2c_read;
- #endif /* CFG_CMD_I2C */
再就是控制台的初始化,开启中断,初始化相应的网卡,最后进入了main_loop。
uboot生产的文件的地址分布入下
- . = 0x00000000;
- . = ALIGN(4);
- .text :
- {
- cpu/arm920t/start.o (.text)
- *(.text)
- }
- ......
- ......
链接的起始地址是0x00000000,而且对start.o文件进行反汇编如下,此时没有链接
- ./cpu/arm920t/start.o: file format elf32-littlearm
- Disassembly of section .text:
- 00000000 <_start>:
- 0: ea000012 b 50 <reset>
- 4: e59ff014 ldr pc, [pc, #20] ; 20 <_undefined_instruction>
- 8: e59ff014 ldr pc, [pc, #20] ; 24 <_software_interrupt>
- ......
- ......
- 00000050 <reset>:
- 50: e10f0000 mrs r0, CPSR
- 54: e3c0001f bic r0, r0, #31 ; 0x1f
- 58: e38000d3 orr r0, r0, #211 ; 0xd3
- 5c: e129f000 msr CPSR_fc, r0
- 60: e3a00453 mov r0, #1392508928 ; 0x53000000
- ......
- ......
对uboot进行反汇编
- .globl _start
- _start: b reset
- 33f80000: ea000012 b 33f80050 <reset>
- ldr pc, _undefined_instruction
- 33f80004: e59ff014 ldr pc, [pc, #20] ; 33f80020 <_undefined_instruction>
- ldr pc, _software_interrupt
- 33f80008: e59ff014 ldr pc, [pc, #20] ; 33f80024 <_software_interrupt>
链接完成后的地址并不是0x00000000,而是0x33f80000,当然机器码没有改变。
至于TEXT_BASE的定义是在board/***/config.mk文件里
uboot根目录下config.mk文件中
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE)
将链接的地址改为0x33f80000
test : .word 0x12345678
;假设test的内存地址0x00
那么0x00内存单元的值为0x12345678
_test : .word test
;假设_testd 内存地址0x10
那么0x10内存单元的值为0x00000000
FIY:arm汇编语言中对变量的操作使用的是变量的内存地址,不是该变量的内存地址中的值