上一篇,讲到我们已经成功编译和烧录了程序,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)

由于水平有限,难免不会出现错误,如果那里说的有问题,及时联系在下面留言,好做修改.最后大家一起努力,争取早日移植完成.