前言:
今天看官们对“汇编语言jz指令应用实例”大体比较着重,朋友们都需要剖析一些“汇编语言jz指令应用实例”的相关文章。那么小编在网上搜集了一些有关“汇编语言jz指令应用实例””的相关文章,希望咱们能喜欢,小伙伴们一起来学习一下吧!2.u-boot-2016.03启动过程分析
从添加Jz2440单板这一节可知,成功添加Jz2440单板后,把编译好的u-boot.bin烧写到Jz2440开发板,重新启动开发板,开发板串口没有任何信息输出。所以,这一节需要分析.u-boot-2016.03的启动过程,以便后面的代码修改。
2.1 uboot链接脚本u-boot.lds分析
要分析uboot的启动过程,必须找到程序的入口。程序是如何链接的,它由链接脚本决定。通过分析链接脚本可知知道uboot是从哪里开始运行的。通过上一节的make编译,在uboot的顶层目录会生成一个u-boot.lds链接脚本。(未编译的uboot链接脚本为arch/arm/cpu/u-boot.lds),以下是uboot顶层目录生成的u-boot.lds的代码:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") #指定输出可执行文件是elf格式,32位ARM指令,小端OUTPUT_ARCH(arm) #指定输出可执行文件平台是armENTRY(_start) #指定输出可执行文件的起始代码段为_start,这里跳转到vectors.S执行SECTIONS #SECTIONS 就是整个链接脚本的指定{ . = 0x00000000; #链接地址 . = ALIGN(4); #代码以4字节对齐 .text : #定义代码段 { *(.__image_copy_start) # 映像文件赋值起始地址,它在文件 arch/arm/lib/sections.c 中定义:* char __image_copy_start[0] __attribute__((section(".__image_copy_start"))); *(.vectors) #在arch/arm/lib/vectors.S有代码:.section ".vectors", "ax",所以链接时首先存放的是vectors.S的二进制文件 arch/arm/cpu/arm920t/start.o (.text*) #存放start.S代码 *(.text*) #接着存放其他文件的代码段 } . = ALIGN(4); #4字节对齐 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } #存放只读数据段 . = ALIGN(4); #4字节对齐 .data : { #存放普通的数据段,存放的数据可读可写 *(.data*) } . = ALIGN(4); . = .; . = ALIGN(4); .u_boot_list : { #存放U-BOOT命令段 KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(4); .image_copy_end : { *(.__image_copy_end) #映像文件赋值结束地址 } /* __rel_dyn_start和__rel_dyn_end,这两个符号之间的区域存放着动态链接符号,只要给这里面的符号加上一定的偏移,拷贝到内存中相应位置处,就可以在绝对跳转中找到正确的函数。 */ .rel_dyn_start : { *(.__rel_dyn_start) } .rel.dyn : { *(.rel*) } .rel_dyn_end : { *(.__rel_dyn_end) } .end : { *(.__end) } _image_binary_end = .; . = ALIGN(4096); .mmutable : { #存放MMU 表项 *(.mmutable) } /* bss 段,.bss节包含了程序中所有未初始化的全局变量 */ /* 由链接指令(OVERLAY)可见,.bss_start与__rel_dyn_start,.bss与__bss_base,.bss_end与__bss_limit是重叠的。*/ .bss_start __rel_dyn_start (OVERLAY) : { KEEP(*(.__bss_start)); # bss段起始地址 __bss_base = .; } .bss __bss_base (OVERLAY) : { *(.bss*) . = ALIGN(4); __bss_limit = .; } .bss_end __bss_limit (OVERLAY) : { KEEP(*(.__bss_end)); # bss段结束地址 } .dynsym _image_binary_end : { *(.dynsym) } .dynbss : { *(.dynbss) } .dynstr : { *(.dynstr*) } .dynamic : { *(.dynamic*) } .plt : { *(.plt*) } .interp : { *(.interp*) } .gnu.hash : { *(.gnu.hash) } .gnu : { *(.gnu*) } .ARM.exidx : { *(.ARM.exidx*) } .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }}
通过分析u-boot.lds可以得出以下结论: (1) 程序的入口是_start,它在vector.S文件里; (2) 程序的链接地址是0,对于Jz2440开发把,程序只能从Nor Flash启动
make编译uboot成功后,除了会自动生成u-boot.lds外,还会生成System.map和u-boot.map这两个文件。System.map文件按链接地址由小到大的顺序列出了所有符号与其代表的链接地址,u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址。打开System.map,有如下代码:
00000000 T __image_copy_start00000000 T _start00000020 T _undefined_instruction00000024 T _software_interrupt00000028 T _prefetch_abort0000002c T _data_abort00000030 T _not_used00000034 T _irq00000038 T _fiq00000040 T IRQ_STACK_START_IN00000060 t undefined_instruction000000c0 t software_interrupt00000120 t prefetch_abort00000180 t data_abort000001e0 t not_used00000240 t irq000002a0 t fiq00000300 T reset00000348 T c_runtime_cpu_setup0000034c t cpu_init_crit...
打开u-boot.map,有如下代码:
Linker script and memory mapAddress of section .text set to 0x0 0x00000000 . = 0x0 0x00000000 . = ALIGN (0x4).text 0x00000000 0x613ec *(.__image_copy_start) .__image_copy_start 0x00000000 0x0 arch/arm/lib/built-in.o 0x00000000 __image_copy_start *(.vectors) .vectors 0x00000000 0x300 arch/arm/lib/built-in.o 0x00000030 _not_used 0x0000002c _data_abort 0x00000034 _irq 0x00000040 IRQ_STACK_START_IN 0x00000028 _prefetch_abort 0x00000000 _start 0x00000038 _fiq 0x00000020 _undefined_instruction 0x00000024 _software_interrupt arch/arm/cpu/arm920t/start.o(.text*) .text 0x00000300 0x90 arch/arm/cpu/arm920t/start.o 0x00000300 reset 0x00000348 c_runtime_cpu_setup *(.text*)
从上面System.map和u-boot.map的也证实了程序的链接地址是0,程序的入口是 start,所以接下来就是从start开始分析启动过程。
2.2 uboot启动过程分析2.2.1 _start 入口
从前面的分析可知,uboot程序的入口是 _start,它在vectors.S文件中,在vectors.S有如下代码:(删除了部分注释)
.globl _start.section ".vectors", "ax"_start:#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG /*没有定义*/ .word CONFIG_SYS_DV_NOR_BOOT_CFG /*所以不会定义该变量*/#endif b reset /* 0x00000000 复位异常 */ ldr pc, _undefined_instruction /* 0x00000004 未定义指令异常 */ ldr pc, _software_interrupt /* 0x00000008 软件中断异常 */ ldr pc, _prefetch_abort /* 0x0000000C 指令预取中止异常 */ ldr pc, _data_abort /* 0x00000010 数据访问中止异常 */ ldr pc, _not_used /* 0x00000014 未使用异常,多余的指令 */ ldr pc, _irq /* 0x00000018 外部中断异常 */ ldr pc, _fiq /* 0x00000018 快速中断异常 *//* ************************************************************************* * * Indirect vectors table * * Symbols referenced here must be defined somewhere else * ************************************************************************* */ .globl _undefined_instruction .globl _software_interrupt .globl _prefetch_abort .globl _data_abort .globl _not_used .globl _irq .globl _fiq_undefined_instruction: .word undefined_instruction_software_interrupt: .word software_interrupt_prefetch_abort: .word prefetch_abort_data_abort: .word data_abort_not_used: .word not_used_irq: .word irq_fiq: .word fiq .balignl 16,0xdeadbeef
从地址0x00~0x1c是CPU异常的入口,这些地址保存着b reset、ldr pc, _undefined_instruction、ldr pc, _software_interrupt等函数跳转指令,当发生其中一种异常时,CPU跳到对应异常的入口地址执行这些跳转指令,从而跳转到异常处理函数。当CPU上电时,会产生一个复位异常,CPU会跳转到地址0执行b reset指令,从而跳转到复位处理函数reset。 ##### 2.2.2 reset 函数分析 (1) Jz2440开发板使用的s3c2440这款SoC,它对应的CPU是arm920t,reset 函数在arch/arm/cpu/arm920t/start.S,有如下代码:
.globl resetreset: /* * set the cpu to SVC32 mode */ mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr, r0#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) /* * relocate exception table */ ldr r0, =_start ldr r1, =0x0 mov r2, #16copyex: subs r2, r2, #1 ldr r3, [r0], #4 str r3, [r1], #4 bne copyex#endif#ifdef CONFIG_S3C24X0 /* turn off the watchdog */# if defined(CONFIG_S3C2400)# define pWTCON 0x15300000# define INTMSK 0x14400008 /* Interrupt-Controller base addresses */# define CLKDIVN 0x14800014 /* clock divisor register */#else# define pWTCON 0x53000000# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */# define INTSUBMSK 0x4A00001C# define CLKDIVN 0x4C000014 /* clock divisor register */# endif ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] /* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0]# if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0]# endif /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0]#endif /* CONFIG_S3C24X0 */ /* * we do sys-critical inits only at reboot, * not when booting from ram! */#ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit#endif bl _main...
从上面的reset函数可知,该函数的运行过程如下: ① set the cpu to SVC32 mode (设置CPU为SVC32 模式) ② turn off the watchdog(关看门狗) ③ mask all IRQs by setting all bits in the INTMR (屏蔽所有中断) ④ 设置时钟比例 (设置FCLK、HCLK和PCLK时钟的比例) ⑤ 执行bl cpu_init_crit指令跳转到 cpu_init_crit函数 a.跳转到cpuinitcrit函数进行CPU初始化(刷新指令cache 和 数据cache、关闭MMU 和caches) b.再跳转到底层初始化函数lowlevel_init(初始化内存,也就是设置内存控制器) ⑥ 执行bl _main指令跳转到 _main函数
(2) cpuinitcrit 函数分析:同样在arch/arm/cpu/arm920t/start.S文件中,cpu_init_crit 函数的代码如下:
cpu_init_crit: /* * flush v4 I/D caches */ mov r0, #0 mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */ mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ /* * disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) orr r0, r0, #0x00000002 @ set bit 1 (A) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0 /* * before relocating, we have to setup RAM timing * because memory timing is board-dependend, you will * find a lowlevel_init.S in your board directory. */ mov ip, lr bl lowlevel_init mov lr, ip mov pc, lr /*返回reset函数,继续执行_main函数*/
从上面的cpu_init_crit 函数可知,实现的功能有: ① 进行CPU初始化(刷新指令cache 和 数据cache、关闭MMU 和caches) ② 执行bl lowlevel_init指令,跳转到lowlevel_init函数
(3) lowlevelinit函数分析:lowlevelinit函数定义在对应的单板目录下(board/samsung/jz2440/lowlevelinit.S)的lowlevelinit.S,lowlevel_init函数代码如下:
.globl lowlevel_initlowlevel_init: /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ ldr r0, =SMRDATA /* 将SMRDATA的首地址(第一个.word)内存单元数据放置到r0寄存器中 */ ldr r1, =CONFIG_SYS_TEXT_BASE /* CONFIG_SYS_TEXT_BASE=0x0(include/configs/jz2440.h中默认定义) */ sub r0, r0, r1 /*得到SMRDATA的偏移地址*/ ldr r1, =BWSCON /* Bus Width Status Controller */ /*获取BWSCON寄存器的地址0x48000000*/ add r2, r0, #13*4 /*内存设置相关的寄存器有13个,每一寄存器占4字节,计算出SMRDATA的结束地址,赋值给r2*//* r0:SMRDATA的首地址;r1:BWSCON寄存器的地址;r2:SMRDATA的结束地址*/0: ldr r3, [r0], #4 /*取SMRDATA保存的第一个数据赋给r3,然后地址r0加4,跳到第二个数据的地址*/ str r3, [r1], #4 /*把r3的数据赋值个BWSCON寄存器,然后地址r1加4,指向下一个寄存器*/ cmp r2, r0 /*比较r2是否与r0相等,如果不相等,执行bne 0b指令,跳转到0:继续循环*/ bne 0b /* everything is fine now */ mov pc, lr /*返回到cpu_init_crit函数*/ .ltorg/* the literal pools origin */SMRDATA: .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0x32 .word 0x30 .word 0x30
lowlevelinit函数的功能主要是初始化存储控制器,经过此初始化之后,内存才可以使用,最后经过两次的返回,返回到reset函数,继续往下执行 main函数。 ##### 2.2.3 _main 函数分析 在reset中执行到了bl _main,跳转到main,main入口在arch/arm/lib/crt0.S文件中,有如下代码:
ENTRY(_main)/* * Set up initial C runtime environment and call board_init_f(0). */#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) /*未定义*/ ldr sp, =(CONFIG_SPL_STACK)#else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) /*执行该指令 CONFIG_SYS_INIT_SP_ADDR = 0x30000f50*/ #endif#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ /*未定义*/ mov r3, sp bic r3, r3, #7 mov sp, r3#else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ /*设置栈后,8字节对齐 sp=0x30000f50*/#endif mov r0, sp /*把sp复制给r0,作为board_init_f_alloc_reserve函数的参数*/ bl board_init_f_alloc_reserve mov sp, r0 /*r0是board_init_f_alloc_reserve函数的返回值, 所以此时 sp = 0x3000 0EA0*/ /* set up gd here, outside any C code */ mov r9, r0 /*r9 寄存器存放着全局变量 gd 的地址,r9=0x3000 0EA0,r0作为board_init_f_init_reserve函数的参数*/ bl board_init_f_init_reserve mov r0, #0 bl board_init_f#if ! defined(CONFIG_SPL_BUILD)/* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3#else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */#endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ sub r9, r9, #GD_SIZE /* new GD is below bd */ adr lr, here ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ add lr, lr, r0#if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */#endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ b relocate_codehere:/* * now relocate vectors */ bl relocate_vectors/* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */#endif#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)# ifdef CONFIG_SPL_BUILD /* Use a DRAM stack for the rest of SPL, if requested */ bl spl_relocate_stack_gd cmp r0, #0 movne sp, r0 movne r9, r0# endif ldr r0, =__bss_start /* this is auto-relocated! */#ifdef CONFIG_USE_ARCH_MEMSET ldr r3, =__bss_end /* this is auto-relocated! */ mov r1, #0x00000000 /* prepare zero to clear BSS */ subs r2, r3, r0 /* r2 = memset len */ bl memset#else ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */clbss_l:cmp r0, r1 /* while not at end of BSS */#if defined(CONFIG_CPU_V7M) itt lo#endif strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l#endif#if ! defined(CONFIG_SPL_BUILD) bl coloured_LED_init bl red_led_on#endif /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* call board_init_r */#if defined(CONFIG_SYS_THUMB_BUILD) ldr lr, =board_init_r /* this is auto-relocated! */ bx lr#else ldr pc, =board_init_r /* this is auto-relocated! */#endif /* we should not return here. */#endifENDPROC(_main)
进入 main函数后的第一件事是设置栈,把CONFIGSYSINITSPADDR(在上一节创建的include/configs/jz2440.h有定义)的值赋给sp,通过u-boot的反汇编(arm-linux-objdump -D u-boot > u-boot.dis)文件u-boot.dis可以得知sp=0x30000f50,接着是8字节对齐,然后调用C函数boardinitfallocreserve。 ###### 2.2.3.1 boardinitfallocreserve函数分析 boardinitfallocreserve函数定义在common/init/boardinit.c,有如下代码:
/* include/linux/kernei.h中有如下定义:#define rounddown(x, y) ( \{ \ typeof(x) __x = (x); \ /* 定义x类型的__x变量等于x */ __x - (__x % (y)); \ /* 栈向下增长,所以向下16字节对齐 */} \)ulong board_init_f_alloc_reserve(ulong top){ /* Reserve early malloc arena */#if defined(CONFIG_SYS_MALLOC_F) /*未定义*/ top -= CONFIG_SYS_MALLOC_F_LEN;#endif /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */ top = rounddown(top-sizeof(struct global_data), 16); /*对于Jz2440,top = 0x30000f50*/ /*top = (top -168)&(~0xf)=(0x30000f50-168)&(~0xf)= 0x3000 0EA0*/ return top;}
调用boardinitfallocreserve函数之前,指令mov r0, sp设置了传入boardinitfallocreserve函数的参数r0=sp=0x30000f50(汇编的传参问题可以了解一下ATPCS-ARM寄存器及值传递规则),经过rounddown函数的对齐后top=0x30000EA0,boardinitfallocreserve函数的返回值top即汇编中的r0,所以 main函数调用完 boardinitfalloc_reserve函数后,r0=0x30000EA0。
2.2.3.2 boardinitfinitreserve函数分析
同样 boardinitfinitreserve函数也定义在common/init/board_init.c,有如下代码:
void board_init_f_init_reserve(ulong base) /*base = 0x3000 0EA0*/{ struct global_data *gd_ptr; /* 定义global_data结构体类型指针 */#ifndef _USE_MEMCPY int *ptr;#endif /* * clear GD entirely and set it up. * Use gd_ptr, as gd may not be properly set yet. */ gd_ptr = (struct global_data *)base; /* global_data结构体指针指向base即栈SP地址处,即0x3000 0EA0 */ /* 清0 global_data结构体 */#ifdef _USE_MEMCPY memset(gd_ptr, '\0', sizeof(*gd));#else for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); ) *ptr++ = 0;#endif ... ... /* 无关代码 */}
调用boardinitfinitreserve函数之前,指令mov r9, r0设置了全局变量 gd 的地址r9=r0=0x30000EA0,r0作为boardinitfinitreserve函数的参数;boardinitfinitreserve函数的功能只是将栈SP往上的globaldata结构体大小空间清0。关于r9 寄存器为什么存放的全局变量 gd 的地址,在arch/arm/include/asm/globaldata.h的83行有定义:
#ifdef CONFIG_ARM64#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")#else#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")#endif2.2.3.3 boardinitf函数分析
boardinitf函数定义在common/boardf.c中,去掉无关代码后的boardinit_f函数如下:
void board_init_f(ulong boot_flags){ gd->flags = boot_flags; gd->have_console = 0; if (initcall_run_list(init_sequence_f)) hang();}
boardinitf函数的主要功能是调用initcallrunlist函数循环调用initsequencef数组里的初始化函数,以达到初始化硬件的目的。假如有硬件初始化失败,会调用hang函数,打印### ERROR ### Please RESET the board ###后,进入死循环。initsequencef数组去掉无用宏代码后如下(判断是否定义了宏方法:查看根目录的.config是否定义)
static init_fnc_t init_sequence_f[] = { /* setup_mon_len函数是设置gd结构体成员gd->mon_len的函数; *在setup_mon_len函数中:gd->mon_len = (ulong)&__bss_end - (ulong)_start; *gd->mon_len等于uboot.bin大小加上bss段的大小,_start为0 *从反汇编的setup_mon_len函数可知:(ulong)&__bss_end = 0x000c636c; *所以,gd->mon_len = 0x000c636c; */ setup_mon_len, /* 1.在initf_malloc函数里,由于CONFIG_SYS_MALLOC_F_LEN没定义, * 直接返回0,相当于一个空函数 * 2.initf_console_record函数,同理 */ initf_malloc, initf_console_record,/* 空函数 */ arch_cpu_init, /* 空函数 */ /* basic arch cpu dependent setup */ initf_dm, /* 空函数 */ arch_cpu_init_dm, /* 空函数 */ mark_bootstage, /* 标记名字 *//* need timer, go after init dm */ board_early_init_f, /* 设置系统时钟,设置各个GPIO引脚 */ timer_init, /* initialize timer */ env_init, /* 设置gd的成员,初始化环境变量 *//* initialize environment */ init_baud_rate, /* initialze baudrate settings */ serial_init, /* serial communications setup */ console_init_f, /* stage 1 init of console */ display_options, /* 打印uboot版本等信息 *//* say that we are here */ display_text_info, /* 打印uboot代码信息 *//* show debugging info if required */ print_cpuinfo, /* 打印uboot时钟频率信息 *//* display cpu info (and speed) */ announce_dram_init, /* 打印“ DRAM: ” */ /* TODO: unify all these dram functions? */ dram_init, /* 设置gd->ram_size= 0x04000000(64MB) *//* configure available RAM banks */ setup_dest_addr, /* 将gd->relocaddr、gd->ram_top指向SDRAM最顶端 */ reserve_round_4k, /* gd->relocaddr 4KB对齐 */ reserve_mmu, /* 预留16KB的MMU页表并且64KB对齐 */ reserve_trace, /* 空函数 */ /*reserve_uboot的作用是在SDRAM预留存放u-boot的空间(加上bss段) *gd->relocaddr -= gd->mon_len; *gd->relocaddr &= ~(4096 - 1); * gd->start_addr_sp = gd->relocaddr; */ reserve_uboot, /* reserve_malloc函数: * gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN; * 因为jz2440.h默认定义了CONFIG_ENV_ADDR,所以此时在include/common.h中 * 执行#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE) * 也就是TOTAL_MALLOC_LEN=4*1024*1024+0x10000=4MB+64KB * 预留4MB+64KB MALLOC内存池 */ reserve_malloc, /* reserve_board函数: * gd->start_addr_sp -= sizeof(bd_t); 预留bd_t结构体空间,查看反汇编可知为80字节 * gd->bd = (bd_t *)gd->start_addr_sp; 指定重定位bd地址 * memset(gd->bd, '\0', sizeof(bd_t)); 清零 */ reserve_board, /*setup_machine函数: *gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */ setup_machine, reserve_global_data,/* 预留gd结构体空间,查看反汇编可知为168字节。并设置gd->new_gd */ reserve_fdt, /* 如果设置了gd->new_fdt则预留fdt设备树空间,这里没有设置,不用管 */ reserve_arch,/* 空函数 */ /* reserve_stacks函数: * gd->start_addr_sp -= 16; * gd->start_addr_sp &= ~0xf; * return arch_reserve_stacks();这里调用的不是board_f.c里的arch_reserve_stacks函数 * 因为该函数被__weak修饰符声明,调用的是arch/arm/lib/stack.c里的arch_reserve_stacks函数 * gd->irq_sp = gd->start_addr_sp; * gd->start_addr_sp -= 16; */ reserve_stacks, setup_dram_config,/* 设置gd结构体的SDRAM地址与大小 */ show_dram_config,/* 打印SDRAM信息 */ display_new_sp, /* 打印新的栈地址 */ reloc_fdt, /*没有设置设备树,忽略*/ /*setup_reloc函数: *gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;计算重定位地址与链接地址偏移值,CONFIG_SYS_TEXT_BASE在jz2440.h定义为0,gd->reloc_off = gd->relocaddr *memcpy(gd->new_gd, (char *)gd, sizeof(gd_t)); *把旧的gd复制到新的gd地址里 */ setup_reloc, NULL,};
进过上述的分析,执行了boardinitf函数后,可以得出SDRAM的内存分布图,如下所所示:(下图是未经修改的u-boot-2016.03源码执行smdk2410_defconfig配置的内存分布)
执行完boardinitf函数,uboot启动的第一阶段结束,返回到crt0.S文件继续执行汇编代码。
2.2.3.4 重新设置栈
执行完boardinitf函数后,重新设置栈,代码如下:
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3#else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */#endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ sub r9, r9, #GD_SIZE /* new GD is below bd */
上述的代码涉及了汇编访问结构体成员变量的问题,由前面的分析可知r9是gd结构体的地址,r9 = 0x30000EA0,其中: ① ldr sp, [r9, #GD_START_ADDR_SP],GD_START_ADDR_SP是gd成员变量gd->start_addr_sp在gd的偏移量,从反汇编代码可知, GD_START_ADDR_SP = 60;从前面的uboot内存分布图可知,gd->start_addr_sp = 0x33B18EE0,sp = gd->start_addr_sp = 0x33B18EE0; ② ldr r9, [r9, #GD_BD]同理,从前面的uboot内存分布图可知,r9 = gd->bd = 0x33B18FB0; ③ sub r9, r9, #GD_SIZE,从内存分布图可知,gdt结构体在gdt结构体下面,r9减去GD_SIZE正好得到gd_t结构体的地址, 即 r9 = gd->new_gd = 0x33B18F08。
2.2.3.5 uboot代码重定位
设置完栈空间之后,继续往下分析代码:
adr lr, here /*链接寄存器lr指向下面的here,此时的here的地址是重定位之前的地址*/ ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off,在setup_reloc函数中有 gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE = 0x33F29000 - 0 = 0x33F29000*/ add lr, lr, r0 /* 链接指向地址指向重定位后的here,此时指向地址在SDRAM中 */#if defined(CONFIG_CPU_V7M) /*未定义*/ orr lr, #1 /* As required by Thumb-only */#endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr = 0x33F29000 设置重定位地址*/ b relocate_codehere:/* * now relocate vectors */ bl relocate_vectors
上面的这段代码的作用是:在代码重定位之前设置好程序返回的链接地址(SDRAM中的地址),当代码重定位完成之后,直接去到SDRAM中的here继续执行程序。 (1) relocate_code函数分析:(该函数定义在arch/arm/lib/relocate.S)
ENTRY(relocate_code) ldr r1, =__image_copy_start /* r1=__image_copy_start = 0*/ subs r4, r0, r1 /*得到重定位的偏移地址 r4 = 0x33F29000 */ /*通过判断r4是否等于0来判断uboot是否已经在重定位地址上了, *如果r4=0表示uboot已经在重定位地址上了,不需要再重定位了直接跳到lr指向的地方去(也就是here标号处) */ beq relocate_done /* 重定位完成,跳到lr指向的地方去(也就是here标号处) */ ldr r2, =__image_copy_end /*r2=__image_copy_end的链接地址*/copy_loop: ldmia r1!, {r10-r11} /* copy from source address [r1] */ stmia r0!, {r10-r11} /* copy to target address [r0] */ cmp r1, r2 /* until source end address [r2] */ blo copy_loop /* * fix .rel.dyn relocations */ ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ /*r2 = __rel_dyn_start,也就是.rel.dyn 段的起始地址*/ ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */ /*r3 = __rel_dyn_end,也就是.rel.dyn 段的终止地址 */fixloop: /*从.rel.dyn 段起始地址开始,每次读取两个4字节的数据存放到 r0 和 r1 寄存器中, *r0 存放低4字节的数据,也就是 Label 地址;r1 存放高4字节的数据,也就是 Label 标志, *从反汇编的.rel.dyn段开始可以看到如下数据: *123251 Disassembly of section .rel.dyn: *123252 *123253 00077794 <__rel_dyn_end-0x9570>: *123254 77794: 00000020 .word 0x00000020 *123255 77798: 00000017 .word 0x00000017 *123256 7779c: 00000024 .word 0x00000024 *123257 777a0: 00000017 .word 0x00000017 *123258 777a4: 00000028 .word 0x00000028 *123259 777a8: 00000017 .word 0x00000017 *123260 777ac: 0000002c .word 0x0000002c *123261 777b0: 00000017 .word 0x00000017 *由此可见,Label的标记是0x17 */ ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */ and r1, r1, #0xff /* r1 中给的值与 0xff 进行与运算,其实就是取 r1 的低 8 位*/ cmp r1, #23 /* relative fixup? */ /*判断 r1 中的值是否等于 23(0X17)*/ bne fixnext /*如果 r1 不等于23的话就说明不是描述 Label 的,执行函数 fixnext,否则的话继续执行下面的代码*/ /* relative fix: increase location by offset */ add r0, r0, r4 /*r4 是偏移地址,重定位的偏移地址r4 = 0x33F29000 */ ldr r1, [r0] /*把r0里的数据取取出来放到r1中*/ add r1, r1, r4 /*然后加上偏移地址*/ str r1, [r0] /*修改完后,重新写回r0地址处*/fixnext: cmp r2, r3 blo fixlooprelocate_done:#ifdef __XSCALE__ /* * On xscale, icache must be invalidated and write buffers drained, * even with cache disabled - 4.2.7 of xscale core developer's manual */ mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */ mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */#endif /* ARMv4- don't know bx lr but the assembler fails to see that */#ifdef __ARM_ARCH_4__ mov pc, lr#else bx lr#endifENDPROC(relocate_code)
relocate_code函数主要分为两个部分: ① 把uboot拷贝到SDRAM中,完成代码的重定位; ② 修改动态链接地址数据:地址存放的是Label:Label其实就是一个地址,简单的说就是地址存放的数据是一个地址,所以为了防止代码运行出错,把这个Label拷贝到链接地址后,还需加上一个偏移地址修改这个Label。
(2) relocate_vectors函数分析:(该函数也定义在arch/arm/lib/relocate.S)
ENTRY(relocate_vectors) /* * Copy the relocated exception vectors to the * correct address * CP15 c1 V bit gives us the location of the vectors: * 0x00000000 or 0xFFFF0000. */ ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr *//*取出重定位后uboot的地址:r0=0x33F29000 */ /*mrc p15, 0, r2, c1, c0, 0指令是CPU协处理器指令 *该指令将 CP15 中 C1 寄存器的值读取到 R2 寄存器, *主要是控制 bit[13]: V *对于支持高端异常向量表的系统,本控制位控制向量表的位置: *bit[13]=0 :选择低端异常中断向量 0x0~0x1c *bit[13]=1 :选择高端异常中断向量0xffff0000~ 0xffff001c */ mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ /* ands 后面的 s 会影响CPSR状态的寄存器的标志位 * 若 相与的 结果为0,则CPSR的状态标志位 Z = 1;反之,Z = 0 */ ands r2, r2, #(1 << 13) ldreq r1, =0x00000000 /* If V=0,则Z=1,可执行 ldreq指令 */ ldrne r1, =0xFFFF0000 /* If V=1,则Z=0,可执行 ldrne指令*/ ldmia r0!, {r2-r8,r10} /*r2,r3,r4,r5,r6,r7,r8,r10对应着重定位后的8个异常向量*/ stmia r1!, {r2-r8,r10} /*把这8个异常向量拷贝到0地址里*/ ldmia r0!, {r2-r8,r10} /*此时的R0 = 0x33F29020,把0x33F29020~0x33F2903存放的异常处理函数的地址取出来*/ stmia r1!, {r2-r8,r10} /*然后再把这些函数的地址存放到地址0x20~0x3c*/ bx lrENDPROC(relocate_vectors)
relocate_vectors函数的内容如下: ① 判断系统使用的是低端异常中断向量(0x0~0x1c),还是低高端异常中断向量(0xFFFF0000 ~ 0xFFFF001C); ② 进行异常中断向量重定位:把代码重定位后的异常中断向量复制到0地址;
(3) 异常中断向量重定位前后的区别: 我们可以从uboot的反汇编的开头可以看到异常中断向量是放在最前面的地址0x00~0x1C,该地址存放着异常的跳转指令,当发生CPU发生某种异常时,会通过对应的跳转指令跳转到指定的异常处理函数,如下图所示:
异常处理函数的地址又存放在0x20~0x38的地方,如下图所示:
这些异常处理函数属于Label,编译uboot之后,它们也会被存放在.rel.dyn段中(如下图所示)用于代码重地位之后修改代码;
有前面relocate_vectors函数的分析可知,uboot在进行代码重定位时,会把.rel.dyn段里存放的地址数据取出来加上一个偏移地址(0x33F29000)得到一个新的地址,然后再取新地址里面的数据加上这个偏移地址,就可以得到重定位后异常处理函数的地址;所以,.rel.dyn段里的数据 加0x33F29000后的地址里的数据对应如下:
0x33F29020 存放 0x33F290600x33F29024 存放 0x33F290C00x33F29028 存放 0x33F291200x33F2902C 存放 0x33F291800x33F29030 存放 0x33F291E00x33F29034 存放 0x33F292400x33F29038 存放 0x33F292A0
所以,uboot重定位后,各异常处理函数所在的地址如下:
0x33F29060 -> _undefined_instruction0x33F290C0 -> _software_interrupt0x33F29120 -> _prefetch_abort0x33F29180 -> _data_abort0x33F291E0 -> _not_used0x33F29240 -> _irq0x33F292A0 -> _fiq
所以,uboot异常向量重定位后,地址0x00~0x1c的内容不变,仍然如下:
当存放异常向量处理函数地址0x20~0x3c的内容发生了变化,它们存放的内容如下:
0x00000020 存放 0x33F290600x00000024 存放 0x33F290C00x00000028 存放 0x33F291200x0000002C 存放 0x33F291800x00000030 存放 0x33F291E00x00000034 存放 0x33F292400x00000038 存放 0x33F292A00x0000003C 存放 0xdeadbeef
因此,异常向量重定位之后,当CPU发生异常时,会跳转到存放在SDRAM的异常处理函数执行,而不是执行存放在Flash的异常处理函数。
2.2.3.6 清bss段
bl relocate_vectors之后(去掉无关代码)的代码如下 (arch/arm/lib/crt0.S文件中):
bl c_runtime_cpu_setup /* mov pc, lr,跳到下一条指令 */ ldr r0, =__bss_start /* this is auto-relocated! */ ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */clbss_l:cmp r0, r1 /* while not at end of BSS */ strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l bl coloured_LED_init bl red_led_on /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ /* r0等于新的gd结构体 */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* r1等于gd->relocaddr也就是重定位地址 */ /*把r0作为参数id, r1作为参数dest_addr传给board_init_r函数*/ /* call board_init_r */ ldr pc, =board_init_r /* this is auto-relocated! */
经过对上述代码的分析,这部分代码的主要功能如下: ① 清bss段; ② 初始化LED,点亮LED; ③ 调用 boardinitr函数,进入uboot初始化的第二阶段。
2.2.3.7 boardinitr函数分析
board_init_r函数去掉无关代码后如下 (common/board_f.c文件中):
void board_init_r(gd_t *new_gd, ulong dest_addr){ if (initcall_run_list(init_sequence_r)) hang(); /* NOTREACHED - run_main_loop() does not return */ hang();}
从上面的代码可知,board_init_r与board_init_f函数类似,它也是通过函数initcall_run_list循环调用init_sequence_r数组里的初始化函数,init_sequence_r数组去掉无用宏代码后如下:
init_fnc_t init_sequence_r[] = { initr_trace, /* 空函数 */ initr_reloc, /* gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT; 标记重定位完成*/#ifdef CONFIG_ARM initr_caches, /* 打印"WARNING: Caches not enabled\n" */#endif initr_reloc_global_data, /* monitor_flash_len = _end - __image_copy_start; //计算uboot映像的大小*/ initr_barrier, /* 空函数 */ /* initr_malloc函数: * malloc_start = gd->relocaddr - TOTAL_MALLOC_LEN;//TOTAL_MALLOC_LEN = 64MB+64kB,算出malloc内存池的起始地址 * mem_malloc_init((ulong)map_sysmem(malloc_start, TOTAL_MALLOC_LEN),TOTAL_MALLOC_LEN); */ initr_malloc, initr_console_record, /* 空函数 */ bootstage_relocate, initr_bootstage, /* 标记名字 */#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) /* board_init函数: * gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; * gd->bd->bi_boot_params = 0x30000100; * icache_enable(); * dcache_enable(); */ board_init, #endif stdio_init_tables, initr_serial, /* 调用serial_initialize() */ initr_announce, /* debug "Now running in RAM - U-Boot at: %08lx\n" */ power_init_board, /* 空函数 */#ifndef CONFIG_SYS_NO_FLASH initr_flash, /* 初始化NOR Flash */#endif#ifdef CONFIG_CMD_NAND initr_nand, /* 初始化NAND Flash */#endif initr_env, /* 初始化环境变量 */ initr_secondary_cpu, /* 空函数 */ stdio_add_devices, /*添加标准io设备,例如I2C、LCD、键盘等*/ /* initr_jumptable函数: * gd->jt = malloc(sizeof(struct jt_funcs)); * #include <_exports.h> */ initr_jumptable, console_init_r, /* 控制台初始化 */ interrupt_init, /* 中断初始化 */#if defined(CONFIG_ARM) || defined(CONFIG_AVR32) initr_enable_interrupts, /* 使能中断 */#endif#ifdef CONFIG_CMD_NET initr_ethaddr, /* eth_getenv_enetaddr("ethaddr", bd->bi_enetaddr); */#endif#ifdef CONFIG_CMD_NET INIT_FUNC_WATCHDOG_RESET initr_net, /* 网卡初始化 */#endif run_main_loop, /* 进入main_loop */};
执行完boardinitr函数,uboot第二阶段的初始化完成,uboot启动完成,最后调用runmainloop进入main_loop循环函数,若控制台有任意输入,则进入控制台命令解析-执行的循环。若无,则U-Boot将启动内核。
标签: #汇编语言jz指令应用实例