上一篇,讲到我们已经成功编译和烧录了程序,uboot能启动,但是什么都没有打印,今天就让我们去探一探我们程序究竟在哪里挂死的。
二、从start.S说起1,链接脚本
其实,裸机程序在编译的时候,是通过一个链接脚本,指定编译的过程的,这个跟在系统上编程是不一样的,系统上的C程序可以直接找到main函数,然后执行(这是编译器做了一大部分的事情,具体我也不清楚啊哈哈哈),裸机需要我们指定起始的位置,这个指定的过程就是在链接脚本中指定的。
uboot里面包含的链接脚本很多,那我们工程究竟是用了哪一个脚本呢?这个还是需要分析Makefile。
在Makefile的中527行
# If board code explicitly specified LDSCRIPT or CONFIG_SYS_LDSCRIPT, use
# that (or fail if absent). Otherwise, search for a linker script in a
# standard location.
ifndef LDSCRIPT
#LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug
ifdef CONFIG_SYS_LDSCRIPT
# need to strip off double quotes
LDSCRIPT := $(srctree)/$(CONFIG_SYS_LDSCRIPT:"%"=%)
endif
endif
# If there is no specified link script, we look in a number of places for it
ifndef LDSCRIPT
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/$(CPUDIR)/u-boot.lds
endif
ifeq ($(wildcard $(LDSCRIPT)),)
LDSCRIPT := $(srctree)/arch/$(ARCH)/cpu/u-boot.lds
endif
endif
上面的注释也写的很清楚了,如果定义了LDSCRIPT or CONFIG_SYS_LDSCRIPT,就默认使用它,否则,在标准位置搜索链接描述文件。我在整个工程搜索都没有找到定义上面两个宏的,所以要到指定位置搜索。进入下面的分支,
这时候不得不说Makefile关键字了:wildcard
在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN…) 。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。
第一个if语句会进入,这时候变量 LDSCRIPT 会被赋值为 ./board/samsung/goni/u-boot.lds,就是指定到board/samsung/goni/目录下寻找u-boot.lds,然后在进入下一个if,这个就是通配符的作用了,如果这个目录下存在这个u-boot.lds文件的话,$(wildcard $(LDSCRIPT)),返回值就不为空,使用的链接脚本就是goni目录下的,如果不为空,继续进入第二个if,然后再次赋值,然后再到第三个if,这里就不讲后面两个了,原理都是一样的。
经过上面的三个if判断,最终我们的uboot使用的链接脚本文件是./arch/arm/cpu/u-boot.lds。
我们就看看这个链接脚本文件(其实上一节也提到过了),这里只是讲一下前部分,后面一些字段以后我学会了再讲。。。
/*
* Copyright (c) 2004-2008 Texas Instruments
*
* (C) Copyright 2002
* Gary Jennejohn, DENX Software Engineering, <garyj@denx.de>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) //这就是程序的入口地址
SECTIONS
{
. = 0x00000000; //这是程序的起始地址
. = ALIGN(4);
.text :
{
*(.__image_copy_start) //这个就是上一节说的 占位的字段
*(.vectors) //这个是中断向量的字段
CPUDIR/start.o (.text*) //这个就是第一个文件start.S 以前版本的uboot是start.S是第一个文件,现在改版了,CPUDIR这个变量是Makefile传参就来的,待会会分析
*(.text*) //这些都是代码段了
}
//未完待续
CPUDIR这个变量是哪里来的,可以搜索一下,就看到是在顶层的config.mk里面赋值的
ARCH := $(CONFIG_SYS_ARCH:"%"=%)
CPU := $(CONFIG_SYS_CPU:"%"=%)
ifdef CONFIG_SPL_BUILD
ifdef CONFIG_TEGRA
CPU := arm720t
endif
endif
BOARD := $(CONFIG_SYS_BOARD:"%"=%)
ifneq ($(CONFIG_SYS_VENDOR),)
VENDOR := $(CONFIG_SYS_VENDOR:"%"=%)
endif
ifneq ($(CONFIG_SYS_SOC),)
SOC := $(CONFIG_SYS_SOC:"%"=%)
endif
# Some architecture config.mk files need to know what CPUDIR is set to,
# so calculate CPUDIR before including ARCH/SOC/CPU config.mk files.
# Check if arch/$ARCH/cpu/$CPU exists, otherwise assume arch/$ARCH/cpu contains
# CPU-specific code.
CPUDIR=arch/$(ARCH)/cpu$(if $(CPU),/$(CPU),)
这些ARCH和CPU其实都是在.config里面读取的值,具体怎么从.config读取的值,我现在也不知道。。
# CONFIG_ARC is not set
CONFIG_ARM=y
# CONFIG_AVR32 is not set
# CONFIG_BLACKFIN is not set
# CONFIG_M68K is not set
# CONFIG_MICROBLAZE is not set
# CONFIG_MIPS is not set
# CONFIG_NDS32 is not set
# CONFIG_NIOS2 is not set
# CONFIG_OPENRISC is not set
# CONFIG_PPC is not set
# CONFIG_SANDBOX is not set
# CONFIG_SH is not set
# CONFIG_SPARC is not set
# CONFIG_X86 is not set
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="s5pc1xx"
CONFIG_SYS_VENDOR="samsung"
CONFIG_SYS_BOARD="goni"
CONFIG_SYS_CONFIG_NAME="s5p_goni"
把.config里面的值写到CPUDIR变量中,就会得到CPUDIR的值
CPUDIR=arch/arm/cpu$(if $(CPU),/$(CPU),)
写到这里不得不普及一下if的语法
#if 函数的语法是:
#$(if <condition>,<then-part> )
#或
#$(if <condition>,<then-part>,<else-part> )
#<condition>参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算
#
#if函数的返回值是,
# 如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,
# 如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。
SRC_DIR := src
#if函数---设置默认值
#如果变量SRC_DIR的值不为空,则将SRC_DIR指定的目录作为SUBDIR子目录;否则将/home/src作为子目录
SUBDIR += $(if $(SRC_DIR) $(SRC_DIR),/home/src)
all:
@echo $(SUBDIR)
这个是借鉴了这篇文章的:https://www.jianshu.com/p/d4ce01b75ddb
看了if的语法,$(if $(CPU), /$(CPU),) ,这句话,其实就是在判断CPU这个字符串是否为空,如果不为空就设置为/$(CPU),如果为空就设置为空。
这样的话,CPUDIR=arch/arm/cpu/armv7
就可以在这个文件夹下找到我们的start.S了。
2、分析vectors.S
__image_copy_start 字段就不分析了,上一节讲过了。
接下来根据程序的入口地址 _start来寻找一下uboot的启动位置
位置在arch/arm/lib目录下的vectors.S文件,打开看看
/*
* vectors - Generic ARM exception table code
*
* Copyright (c) 1998 Dan Malek <dmalek@jlc.net>
* Copyright (c) 1999 Magnus Damm <kieraypc01.p.y.kie.era.ericsson.se>
* Copyright (c) 2000 Wolfgang Denk <wd@denx.de>
* Copyright (c) 2001 Alex Züpke <azu@sysgo.de>
* Copyright (c) 2001 Marius Gröger <mag@sysgo.de>
* Copyright (c) 2002 Alex Züpke <azu@sysgo.de>
* Copyright (c) 2002 Gary Jennejohn <garyj@denx.de>
* Copyright (c) 2002 Kyle Harris <kharris@nexus-tech.net>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <config.h>
/*
*************************************************************************
*
* Symbol _start is referenced elsewhere, so make it global
*
*************************************************************************
*/
.globl _start //全局声明 _start 在这里,大家快来找
/*
*************************************************************************
*
* Vectors have their own section so linker script can map them easily
*
*************************************************************************
*/
.section ".vectors", "ax" //.section ".vectors" 把这一段代码定义成 .vectors段,就是我们链接脚本看到的第二个字段
//"ax"表示该节区可分配并且可执行 ax是 allocation execute的缩写
/*
*************************************************************************
*
* Exception vectors as described in ARM reference manuals
*
* Uses indirect branch to allow reaching handlers anywhere in memory.
*
*************************************************************************
*/
_start: //这个就是链接脚本指定的程序入口地址
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
b reset //b 是函数跳转 就是已启动就跳转到reset这个函数中
ldr pc, _undefined_instruction //下面都是中断函数的跳转,如果出现异常中断,就会在这里取地址,然后跳转
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
/*
*************************************************************************
*
* 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 //这一句指令是让当前地址对齐排布,如果当前地址不对齐则自动向后走地址直到对齐,
//并且向后走的那些内存要用0xdeadbeef来填充。
/*
*************************************************************************
*
* Interrupt handling
*
*************************************************************************
*/
/* SPL interrupt handling: just hang */
#ifdef CONFIG_SPL_BUILD //没有进这个分支,以后可以试着移植看看
.align 5
undefined_instruction:
software_interrupt:
prefetch_abort:
data_abort:
not_used:
irq:
fiq:
1:
bl 1b /* hang and never return */
#else /* !CONFIG_SPL_BUILD */
/* IRQ stack memory (calculated at run-time) + 8 bytes */ //中断使用的栈
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif /* CONFIG_USE_IRQ */
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
@ carve out a frame on current user stack
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Save user registers (now in svc mode) r0-r12
ldr r2, IRQ_STACK_START_IN
@ get values for "aborted" pc and cpsr (into parm regs)
ldmia r2, {r2 - r3}
add r0, sp, #S_FRAME_SIZE @ grab pointer to old stack
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp @ save current stack into r0 (param register)
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
@ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm
.macro get_bad_stack
ldr r13, IRQ_STACK_START_IN @ setup our mode stack
str lr, [r13] @ save caller lr in position 0 of saved stack
mrs lr, spsr @ get the spsr
str lr, [r13, #4] @ save spsr in position 1 of saved stack
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13 @ switch modes, make sure moves will execute
mov lr, pc @ capture return pc
movs pc, lr @ jump to next instruction & switch modes.
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
/*
* exception handlers
*/
//下面就是中断函数 真正执行的地方,都会调用保护现场的函数,到时候遇到了再看,我们还是回到正题
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif /* CONFIG_USE_IRQ */
#endif /* CONFIG_SPL_BUILD */
3、分析start.S
从程序里面可以看到直接跳转了reset函数,我们找找reset函数在那?可不就是在我们的start.S里面,接下来分析一下start.S
.globl reset
.globl save_boot_params_ret
reset: //程序跳转到这里来
/* Allow the board to save important registers */
b save_boot_params
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
* 英文翻译过来就知道了,把中断关了,uboot目前是不使用中断的,我们移植完之后,可以打开中断玩玩
* 把cpu设成svc32模式,这是啥,我也不知道,先跳过
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD)) //设置中断向量表的
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
//把 CP15中的C1 的V 清除 查找V寄存器的定义:
/*
对于支持高端异常向量表的系统,本控制位控制向量表的位置
0 :选择低端异常中断向量 0x0~0x1c
1 :选择高端异常中断向量0xffff0000~ 0xffff001c
对于不支持高端异常向量表的系统,读取时该位返回0,写入时忽略
*/
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
/* Set vector address in CP15 VBAR register */
//把中断向量的地址写到c12里 看看c12寄存器的介绍
/*
CP15寄存器C12用来设置异常向量基地址,其编码格式如下所示:
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
//这个就是具体的操作一些cp15,比如关mmu啥的,具体的可以看看,函数体就在start.S里面
bl cpu_init_cp15
//这个就是我们重量级的初始化动作了
//这个函数体就做了一件事 b lowlevel_init 跳转到lowlevel_init进行初始化,下节会详细说明,并且移植
bl cpu_init_crit
#endif
//假如lowlevel_init 初始化成功了,就会返回到这里执行,led2是我自己为了调试加的,因为要判断在哪里卡死的,一种直观的判断
bl led2
//这个就是跳转动作了,会跳转到第二阶段,但是这个在源码中是b _main,为什么会这么改呢,等到下一节再分析。哈哈哈
ldr pc, _start_armboot
_start_armboot:
.word _main
有关start.S就分析到这里,想不到uboot改变了之后start.S跟之前差别这么大,还以为还想之前那样都在start.S中处理很多事情。
不过也没关系,下一节,接着分析重量级初始化lowlevel_init 这个函数。
如果想分析CP15协处理的,可以到百度中搜索其他人写的文章,这里我参考了这篇文章,才知道了CP12 的 C1和C12寄存器是做什么的,有兴趣的可以看一看:
javascript:void(0)
由于水平有限,难免不会出现错误,如果那里说的有问题,及时联系在下面留言,好做修改.最后大家一起努力,争取早日移植完成.