您的位置:首页 > 其它

U-Boot编译过程完全分析

2010-12-29 12:52 573 查看

2.1      

U-Boot
Makefile

分析

2.1.1            

U-Boot

编译命令

      

对于
mini2440
开发板,编译
U-Boot
需要执行如下的命令:



make 

mini2440_config



make 

all

      

使用上面的命令编译
U-Boot
,编译生成的所有文件都保存在源代码目录中。为了保持源代码目录的干净,可以使用如下命令将编译生成的文件输出到一个外部目录,而不是在源代码目录中,下面的
2
种方法都将编译生成的文件输出到
/tmp/build
目录:



export 

BUILD_DIR=/tmp/build



make 

mini2440_config



make 

all





make 

O=/tmp/build 

mini2440_config 

(注意是字母
O
,而不是数字
0




make 

all

 

      

为了简化分析过程,方便读者理解,这里主要针对第一种编译方式(目标输出到源代码所在目录)进行分析。

2.1.2            

U-Boot

配置、编译、连接过程

      

U-Boot
开头有一些跟主机软硬件环境相关的代码,在每次执行
make
命令时这些代码都被执行一次。

 


1.     


U-Boot


配置过程




1

)定义主机系统架构


HOSTARCH := $(shell uname -m | /

      

sed
-e s/i.86/i386/ /

      
   

-e s/sun4u/sparc64/ /

      
   

-e s/arm.*/arm/ /

      
   

-e s/sa110/arm/ /

      
   

-e s/powerpc/ppc/ /

      
   

-e s/ppc64/ppc/ /

      
   

-e s/macppc/ppc/)

      

“sed –e”
表示后面跟的是一串命令脚本,而表达式
“s/abc/def/”
表示要从标准输入中,查找到内容为
“abc”
的,然后替换成
“def”
。其中
“abc”
表达式用可以使用
“.”
作为通配符。

      

命令
“uname –m”
将输出主机
CPU
的体系架构类型。作者的电脑使用
Intel Core2
系列的
CPU
,因此
“uname
–m”
输出
“i686”


i686
”可以匹配命令“
sed -e s/i.86/i386/
”中的“
i.86
”,
因此在作者的机器上执行
Makefile

HOSTARCH
将被设置成
“i386”




2

)定义主机操作系统类型


HOSTOS := $(shell uname -s | tr
'[:upper:]' '[:lower:]' | /

      
   

sed -e 's//(cygwin/).*/cygwin/')

      

“uname –s”
输出主机内核名字,作者使用
Linux
发行版
Ubuntu9.10
,因此
“uname –s”
结果是
“Linux”

“tr '[:upper:]' '[:lower:]'”
作用是将标准输入中的所有大写字母转换为响应的小写字母。因此执行结果是将
HOSTOS

设置为
“linux”




3

)定义执行

shell

脚本的

shell


# Set shell to bash if possible, otherwise
fall back to sh

SHELL := $(shell if [ -x
"$$BASH" ]; then echo $$BASH; /

      

else
if [ -x /bin/bash ]; then echo /bin/bash; /

      

else
echo sh; fi; fi)

      

"$$BASH"
的作用实质上是生成了字符串
“$BASH”
(前一个
$
号的作用是指明第二个
$
是普通的字符)。若执行当前
Makefile

shell
中定义了
“$BASH”
环境变量,且文件
“$BASH”
是可执行文件,则
SHELL
的值为
“$BASH”
。否则,若
“/bin/bash”
是可执行文件,则
SHELL
值为
“/bin/bash”
。若以上两条都不成立,则将
“sh”
赋值给
SHELL
变量。

      

由于作者的机器安装了
bash shell
,且
shell
默认环境变量中定义了
“$BASH”
,因此
SHELL
被设置为
$BASH




4

)设定编译输出目录


ifdef O

ifeq ("$(origin O)",
"command line")

BUILD_DIR := $(O)

endif

endif

      

函数
$( origin, variable)
输出的结果是一个字符串,输出结果由变量
variable
定义的方式决定,若
variable
在命令行中定义过,则
origin
函数返回值为
"command line"
。假若在命令行中执行了
“export
BUILD_DIR=/tmp/build”
的命令,则
“$(origin O)”
值为
“command line”
,而
BUILD_DIR
被设置为
“/tmp/build”


ifneq ($(BUILD_DIR),)

saved-output := $(BUILD_DIR)

 

# Attempt to create a output directory.

$(shell [ -d ${BUILD_DIR} ] || mkdir -p
${BUILD_DIR})

      


${BUILD_DIR}
表示的目录没有定义,则创建该目录。

# Verify if it was successful.

BUILD_DIR := $(shell cd $(BUILD_DIR)
&& /bin/pwd)

$(if $(BUILD_DIR),,$(error output directory
"$(saved-output)" does not exist))

endif # ifneq ($(BUILD_DIR),)

      


$(BUILD_DIR)
为空,则将其赋值为当前目录路径(源代码目录)。并检查
$(BUILD_DIR)
目录是否存在。

OBJTREE          

:=
$(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))

SRCTREE         

:=
$(CURDIR)

TOPDIR            

:=
$(SRCTREE)

LNDIR       

:=
$(OBJTREE)

… …

MKCONFIG     

:=
$(SRCTREE)/mkconfig

… …

ifneq ($(OBJTREE),$(SRCTREE))

obj := $(OBJTREE)/

src := $(SRCTREE)/

else

obj :=

src :=

endif

      

CURDIR
变量指示
Make
当前的工作目录,由于当前
Make

U-Boot
顶层目录执行
Makefile
,因此
CURDIR
此时就是
U-Boot
顶层目录。

      

执行完上面的代码后,
SRCTREE

src
变量就是
U-Boot
代码顶层目录,而
OBJTREE

obj
变量就是输出目录,若没有定义
BUILD_DIR
环境变量,则
SRCTREE

src
变量与
OBJTREE

obj
变量都是
U-Boot
源代码目录。而
MKCONFIG
则表示
U-Boot
根目录下的
mkconfig
脚本。

2.     


make
mini2440_config

命令执行过程


      

下面分析命令
“make mini2440_config”
执行过程,为了简化分析过程这里主要分析将编译目标输出到源代码目录的情况。

mini2440_config

:     

unconfig

      

@$(MKCONFIG)
$(@:_config=) arm arm920t mini2440 samsung s3c24x0

      

其中的依赖
“unconfig”
定义如下:

unconfig:

      

@rm
-f $(obj)include/config.h $(obj)include/config.mk /

             

$(obj)board/*/config.tmp
$(obj)board/*/*/config.tmp /

             

$(obj)include/autoconf.mk
$(obj)include/autoconf.mk.dep

      
 

其中
“@”
的作用是执行该命令时不在
shell
显示。
“obj”
变量就是编译输出的目录,因此
“unconfig”
的作用就是清除上次执行
make *_config
命令生成的配置文件(如
include/config.h

include/config.mk
等)。

      

$(MKCONFIG)
在上面指定为
“$(SRCTREE)/mkconfig”

$(@:_config=)
为将传进来的所有参数中的
_config
替换为空(其中
“@”
指规则的目标文件名,在这里就是
“mini2440_config ”

$(text:patternA=patternB)
,这样的语法表示把
text
变量每一个元素中结尾的
patternA
的文本替换为
patternB
,然后输出)
。因此
$(@:_config=)
的作用就是将
mini2440_config
中的
_config
去掉,得到
mini2440


      

因此
“@$(MKCONFIG) $(@:_config=) arm
arm920t mini2440 samsung s3c24x0”
实际上就是执行了如下命令:

./mkconfig mini2440 arm arm920t mini2440
samsung s3c24x0

      

即将
“mini2440 arm arm920t mini2440
samsung s3c24x0”
作为参数传递给当前目录下的
mkconfig
脚本执行。

      


mkconfig
脚本中给出了
mkconfig
的用法:

# Parameters: 

Target 

Architecture 

CPU 

Board [VENDOR] [SOC]

      

因此传递给
mkconfig
的参数的意义分别是:

mini2440

Target
(目标板型号)

arm

Architecture
(目标板的
CPU
架构)

arm920t

CPU
(具体使用的
CPU
型号)

mini2440

Board

samsung

VENDOR
(生产厂家名)

s3c24x0

SOC

      

下面再来看看
mkconfig
脚本到底做了什么。


1
)确定开发板名称
BOARD_NAME

      


mkconfig
脚本中有如下代码:

APPEND=no     

#
no
表示创建新的配置文件,
yes
表示追加到配置文件中

BOARD_NAME=""

# Name to print in make output

TARGETS=""

 

while [ $# -gt 0 ] ; do

   

case "$1" in

   

--) shift ; break ;;

   

-a) shift ; APPEND=yes ;;

   

-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;

   

-t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ;
shift ;;

   

*) 

break ;;

   

esac

done

 

[ "${BOARD_NAME}" ] ||
BOARD_NAME="$1" 

      

环境变量
$#
表示传递给脚本的参数个数,这里的命令有
6
个参数,因此
$#

6

shift
的作用是使
$1=$2

$2=$3

$3=$4….
,而原来的
$1
将丢失。因此
while
循环的作用是,依次处理传递给
mkconfig
脚本的选项。由于我们并没有传递给
mkconfig
任何的选项,因此
while
循环中的代码不起作用。

      

最后将
BOARD_NAME
的值设置为
$1
的值,在这里就是
“mini2440”



2
)检查参数合法性

[ $# -lt 4 ] && exit 1

[ $# -gt 6 ] && exit 1

 

if [ "${ARCH}" -a
"${ARCH}" != "$2" ]; then

      

echo
"Failed: /$ARCH=${ARCH}, should be '$2' for ${BOARD_NAME}"
1>&2

      

exit
1

fi

      

上面代码的作用是检查参数个数和参数是否正确,参数个数少于
4
个或多于
6
个都被认为是错误的。


3
)创建到目标板相关的目录的链接

#

# Create link to architecture specific
headers

#

if [ "$SRCTREE" !=
"$OBJTREE" ] ; then        

#
若编译目标输出到外部目录,则下面的代码有效

      

mkdir
-p ${OBJTREE}/include

      

mkdir
-p ${OBJTREE}/include2

      

cd
${OBJTREE}/include2

      

rm
-f asm

      

ln
-s ${SRCTREE}/include/asm-$2 asm

      

LNPREFIX="http://www.cnblogs.com/include2/asm/"

      

cd
../include

      

rm
-rf asm-$2

      

rm
-f asm

      

mkdir
asm-$2

      

ln
-s asm-$2 asm

else              

      

cd
./include

      

rm
-f asm

      

ln
-s asm-$2 asm

fi

      

若将目标文件设定为输出到源文件所在目录,则以上代码在
include
目录下建立了到
asm-arm
目录的符号链接
asm
。其中的
ln -s asm-$2 asm

ln -s asm-arm asm


rm -f asm-$2/arch

 

if [ -z "$6" -o "$6" =
"NULL" ] ; then

      

ln
-s ${LNPREFIX}arch-$3 asm-$2/arch

else

      

ln
-s ${LNPREFIX}arch-$6 asm-$2/arch

fi

      

建立符号链接
include/asm-arm/arch
,若
$6

SOC
)为空,则使其链接到
include/asm-arm/arch-arm920t
目录,否则就使其链接到
include/asm-arm/arch-s3c24x0
目录。(事实上
include/asm-arm/arch-arm920t
并不存在,因此
$6
是不能为空的,否则会编译失败)

if [ "$2" = "arm" ] ;
then

      

rm
-f asm-$2/proc

      

ln
-s ${LNPREFIX}proc-armv asm-$2/proc

fi

      

若目标板是
arm
架构,则上面的代码将建立符号连接
include/asm-arm/proc
,使其链接到目录
proc-armv
目录。

      

建立以上的链接的好处:编译
U-Boot
时直接进入链接文件指向的目录进行编译,而不必根据不同开发板来选择不同目录。


4
)构建
include/config.mk
文件

#

# Create include file for Make

#

echo "ARCH  

= $2" > 

config.mk

echo "CPU   

= $3" >> config.mk

echo "BOARD 

= $4" >> config.mk

 

[ "$5" ] && [
"$5" != "NULL" ] && echo "VENDOR = $5"
>> config.mk

 

[ "$6" ] && [
"$6" != "NULL" ] && echo "SOC   

= $6" >> config.mk

      

上面代码将会把如下内容写入文件
inlcude/config.mk
文件:

ARCH  

= arm

CPU   

= arm920t

BOARD 

= mini2440

VENDOR = samsung

SOC   

= s3c24x0


5
)指定开发板代码所在目录

# Assign board directory to BOARDIR
variable

if [ -z "$5" -o "$5" =
"NULL" ] ; then

   

BOARDDIR=$4

else

   

BOARDDIR=$5/$4

fi

      

以上代码指定
board
目录下的一个目录为当前开发板专有代码的目录。若
$5

VENDOR
)为空则
BOARDDIR
设置为
$4

BOARD
),否则设置为
$5/$4

VENDOR/BOARD
)。在这里由于
$5
不为空,因此
BOARDDIR
被设置为
samsung/mini2440



6
)构建
include/config.h
文件

#

# Create board specific header file

#

if [ "$APPEND" = "yes"
]

# Append to existing config file

then

      

echo
>> config.h

else

      

>
config.h           

# Create new config
file

fi

echo "/* Automatically generated - do
not edit */" >>config.h

 

for i in ${TARGETS} ; do

      

echo
"#define CONFIG_MK_${i} 1" >>config.h ;

done

 

cat << EOF >> config.h

#define CONFIG_BOARDDIR board/$BOARDDIR

#include <config_defaults.h>

#include <configs/$1.h>

#include <asm/config.h>

EOF

exit 0

      

这里的
“cat << EOF >>
config.h”
表示将输入的内容追加到
config.h
中,直到出现
“EOF”
这样的标识为止。

      


APPEND

no
,则创建新的
include/config.h
文件。若
APPEND

yes
,则将新的配置内容追加到
include/config.h
文件后面。由于
APPEND
的值保持
“no”
,因此
config.h
被创建了,并添加了如下的内容:

      

/*
Automatically generated - do not edit */

      

#define
CONFIG_BOARDDIR board/samsung/mini2440

      

#include
<config_defaults.h>

      

#include
<configs/mini2440.h>

      

#include
<asm/config.h>

      

下面总结命令
make mini2440_config
执行的结果(仅针对编译目标输出到源代码目录的情况):

(1)   

创建到目标板相关的文件的链接

      

ln
-s asm-arm asm

      

ln
-s arch-s3c24x0 asm-arm/arch

      

ln
-s proc-armv asm-arm/proc

(2)   

创建
include/config.mk
文件,内容如下所示:

      

ARCH  

= arm

      

CPU   

= arm920t

      

BOARD 

= mini2440

      

VENDOR
= samsung

      

SOC   

= s3c24x0

(3)   

创建与目标板相关的文件
include/config.h
,如下所示:

      

/*
Automatically generated - do not edit */

      

#define
CONFIG_BOARDDIR board/samsung/mini2440

      

#include
<config_defaults.h>

      

#include
<configs/mini2440.h>

      

#include
<asm/config.h>

3.     


make
all

命令执行过程


      

若没有执行过
“make
<board_name>_config”
命令就直接执行
“make all”
命令则会出现如下的才错误信息,然后停止编译:

      

System not configured - see README

      

U-Boot
是如何知道用户没有执行过
“make
<board_name>_config”
命令的呢?阅读
U-Boot
源代码就可以发现了,
Makefile
中有如下代码:

ifeq ($(obj)include/config.mk,$(wildcard
$(obj)include/config.mk)) # config.mk
存在

all: 

sinclude $(obj)include/autoconf.mk.dep

sinclude $(obj)include/autoconf.mk

… …

else
      

# config.mk
不存在

… …

      

@echo
"System not configured - see README" >&2

      

@
exit 1

… …

endif     

#
config.mk

      


include/config.mk
文件存在,则
$(wildcard
$(obj)include/config.mk)
命令执行的结果是
“$(obj)include/config.mk”
展开的字符串,否则结果为空。由于
include/config.mk

“make <board_name>_config”
命令执行过程生成的,若从没有执行过
“make <board_name>_config”
命令则
include/config.mk
必然不存在。因此
Make
就执行
else
分支的代码,在输出
“System not configured -
see README”
的信息后就返回了。

      

下面再来分析
“make all”
命令正常执行的过程,在
Makefile
中有如下代码:


1


include/autoconf.mk

生成过程


all:

sinclude $(obj)include/autoconf.mk.dep

sinclude $(obj)include/autoconf.mk

      

include/autoconf.mk
文件中是与开发板相关的一些宏定义,在
Makefile
执行过程中需要根据某些宏来确定执行哪些操作。下面简要分析
include/autoconf.mk
生成的过程,
include/autoconf.mk
生成的规则如下:

$(obj)include/autoconf.mk:
$(obj)include/config.h

      

@$(XECHO)
Generating $@ ; /

      

set
-e ; /

      

:
Extract the config macros ; /

      

$(CPP)
$(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | /

             

sed
-n -f tools/scripts/define2mk.sed > $@.tmp && /

      

mv
$@.tmp $@

      

include/autoconf.mk
依赖于
make <board_name>_config
命令生成的
include/config.h
。因此执行
make <board_name>_config
命令后再执行
make all
将更新
include/autoconf.mk


      

编译选项
“-dM”
的作用是输出
include/common.h
中定义的所有宏。根据上面的规则,编译器提取
include/common.h
中定义的宏,然后输出给
tools/scripts/define2mk.sed
脚本处理,处理的结果就是
include/autoconf.mk
文件。其中
tools/scripts/define2mk.sed
脚本的主要完成了在
include/common.h
中查找和处理以
“CONFIG_”
开头的宏定义的功能。

      

include/common.h
文件包含了
include/config.h
文件,而
include/config.h
文件又包含了
config_defaults.h

configs/mini2440.h

asm/config.h
文件。因此
include/autoconf.mk
实质上就是
config_defaults.h

configs/mini2440.h

asm/config.h
三个文件中
“CONFIG_”
开头的有效的宏定义的集合。

      

下面接着分析
Makefile
的执行。

# load ARCH, BOARD, and CPU configuration

include $(obj)include/config.mk

export   

ARCH
CPU BOARD VENDOR SOC

      


make mini2440_config
命令生成的
include/config.mk
包含进来。

#
若主机架构与开发板结构相同,就使用主机的编译器,而不是交叉编译器

ifeq ($(HOSTARCH),$(ARCH))

CROSS_COMPILE ?=

endif

      

若主机与目标机器体系架构相同,则使用
gcc
编译器而不是交叉编译器。

# load other configuration

include $(TOPDIR)/config.mk

      

最后将
U-Boot
顶层目录下的
config.mk
文件包含进来,该文件包含了对编译的一些设置。下面对
U-Boot
顶层目录下的
config.mk
文件进行分析:


2


config.mk

文件执行过程


1
设置
obj

src

      


U-Boot
顶层目录下的
config.mk
文件中有如下代码:

ifneq ($(OBJTREE),$(SRCTREE))

ifeq ($(CURDIR),$(SRCTREE))

dir :=

else

dir := $(subst $(SRCTREE)/,,$(CURDIR))

endif

 

obj := $(if
$(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)

src := $(if
$(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)

 

$(shell mkdir -p $(obj))

else

obj :=

src :=

endif

      

由于目标输出到源代码目录下,因此执行完上面的代码后,
src

obj
都是空。

2
设置编译选项

PLATFORM_RELFLAGS =

PLATFORM_CPPFLAGS =         

#
编译选项

PLATFORM_LDFLAGS =          

#
连接选项

      

用这
3
个变量表示交叉编译器的编译选项,在后面
Make
会检查交叉编译器支持的编译选项,然后将适当的选项添加到这
3
个变量中。

#

# Option checker (courtesy linux kernel)
to ensure

# only supported compiler options are used

#

cc-option = $(shell if $(CC) $(CFLAGS)
$(1) -S -o /dev/null -xc /dev/null /

             

>
/dev/null 2>&1; then echo "$(1)"; else echo "$(2)";
fi ;)

      

变量
CC

CFLAGS
在后面的代码定义为延时变量,其中的
CC

arm-linux-gcc
。函数
cc-option
用于检查编译器
CC
是否支持某选项。将
2
个选项作为参数传递给

cc-option

函数,该函数调用
CC
编译器检查参数
1
是否支持,若支持则函数返回参数
1
,否则返回参数
2
(因此
CC
编译器必须支持参数
1
或参数
2
,若两个都不支持则会编译出错)。可以像下面这样调用
cc-option
函数,并将支持的选项添加到
FLAGS
中:

FLAGS +=$(call cc-option,option1,option2)

3
指定交叉编译工具

#

# Include the make variables (CC, etc...)

#

AS 

=
$(CROSS_COMPILE)as

LD 

=
$(CROSS_COMPILE)ld

CC 

=
$(CROSS_COMPILE)gcc

CPP      

=
$(CC) -E

AR

=
$(CROSS_COMPILE)ar

NM

=
$(CROSS_COMPILE)nm

LDR     

=
$(CROSS_COMPILE)ldr

STRIP  

=
$(CROSS_COMPILE)strip

OBJCOPY = $(CROSS_COMPILE)objcopy

OBJDUMP = $(CROSS_COMPILE)objdump

RANLIB     

=
$(CROSS_COMPILE)RANLIB

      

对于
arm
开发板,其中的
CROSS_COMPILE

lib_arm/config.mk
文件中定义:

CROSS_COMPILE ?= arm-linux-

      

因此以上代码指定了使用前缀为
“arm-linux-”
的编译工具,即
arm-linux-gcc

arm-linux-ld
等等。

4
包含与开发板相关的配置文件

# Load generated board configuration

sinclude $(OBJTREE)/include/autoconf.mk

 

ifdef     

ARCH

sinclude $(TOPDIR)/lib_$(ARCH)/config.mk  

# include architecture dependend rules

endif

      

$(ARCH)
的值是
“arm”
,因此将
“lib_arm/config.mk”
包含进来。
lib_arm/config.mk
脚本指定了交叉编译器,添加了一些跟
CPU
架构相关的编译选项,最后还指定了
cpu/arm920t/u-boot.lds

U-Boot
的连接脚本。

ifdef     

CPU

sinclude $(TOPDIR)/cpu/$(CPU)/config.mk            

# include 

CPU

specific
rules

endif

      

$(CPU)
的值是
“arm920t”
,因此将
“cpu/arm920t/config.mk”
包含进来。这个脚本主要设定了跟
arm920t
处理器相关的编译选项。

ifdef     

SOC

sinclude
$(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk      

#
include 

SoC 

specific rules

endif

      

$(SOC)
的值是
s3c24x0
,因此
Make
程序尝试将
cpu/arm920t/s3c24x0/config.mk
包含进来,而这个文件并不存在,但是由于用的是
“sinclude”
命令,所以并不会报错。

ifdef     

VENDOR

BOARDDIR = $(VENDOR)/$(BOARD)

else

BOARDDIR = $(BOARD)

endif

      

$(BOARD)
的值是
mini2440

VENDOR
的值是
samsung
,因此
BOARDDIR
的值是
samsung/mini2440

BOARDDIR
变量表示开发板特有的代码所在的目录。

ifdef     

BOARD

sinclude
$(TOPDIR)/board/$(BOARDDIR)/config.mk  

#
include board specific rules

endif

      

Make

“board/samsung/mini2440/config.mk”
包含进来。该脚本只有如下的一行代码:

TEXT_BASE = 0x33F80000

      

U-Boot
编译时将使用
TEXT_BASE
作为代码段连接的起始地址。

LDFLAGS += -Bstatic -T $(obj)u-boot.lds
$(PLATFORM_LDFLAGS)

ifneq ($(TEXT_BASE),)

LDFLAGS += -Ttext $(TEXT_BASE)

endif

      

执行完以上代码后,
LDFLAGS
中包含了
“-Bstatic -T u-boot.lds ”

“-Ttext 0x33F80000”
的字样。

5
指定隐含的编译规则

# Allow boards to use custom optimize
flags on a per dir/file basis

BCURDIR := $(notdir $(CURDIR))

$(obj)%.s:    

%.S

      

$(CPP)
$(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $<

$(obj)%.o:   

%.S

      

$(CC) 

$(AFLAGS) $(AFLAGS_$(@F))
$(AFLAGS_$(BCURDIR)) -o $@ $< -c

$(obj)%.o:   

%.c

      

$(CC) 

$(CFLAGS) $(CFLAGS_$(@F))
$(CFLAGS_$(BCURDIR)) -o $@ $< -c

$(obj)%.i:    

%.c

      

$(CPP)
$(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c

$(obj)%.s:    

%.c

      

$(CC) 

$(CFLAGS) $(CFLAGS_$(@F))
$(CFLAGS_$(BCURDIR)) -o $@ $< -c -S

      

例如:根据以上的定义,以
“.s”
结尾的目标文件将根据第一条规则由同名但后缀为
“.S”
的源文件来生成,若不存在
“.S”
结尾的同名文件则根据最后一条规则由同名的
“.c”
文件生成。

下面回来接着分析
Makefile
的内容:

# U-Boot objects....order is important
(i.e. start must be first)

 

OBJS 

= cpu/$(CPU)/start.o

LIBS += cpu/$(CPU)/lib$(CPU).a

ifdef SOC

LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a

endif

ifeq ($(CPU),ixp)

LIBS += cpu/ixp/npe/libnpe.a

endif

LIBS += lib_$(ARCH)/lib$(ARCH).a

LIBS += fs/cramfs/libcramfs.a
fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a /

      

fs/reiserfs/libreiserfs.a
fs/ext2/libext2fs.a fs/yaffs2/libyaffs2.a /

      

fs/ubifs/libubifs.a

… …

LIBS += common/libcommon.a

LIBS += libfdt/libfdt.a

LIBS += api/libapi.a

LIBS += post/libpost.a

 

LIBS := $(addprefix $(obj),$(LIBS))

      

LIBS
变量指明了
U-Boot
需要的库文件,包括平台
/
开发板相关的目录、通用目录下相应的库,都通过相应的子目录编译得到的。

      

对于
mini2440
开发板,以上跟平台相关的有以下几个:

cpu/$(CPU)/start.o

board/$(VENDOR)/common/lib$(VENDOR).a

cpu/$(CPU)/lib$(CPU).a

cpu/$(CPU)/$(SOC)/lib$(SOC).a

lib_$(ARCH)/lib$(ARCH).a

      

其余都是与平台无关的。

ifeq ($(CONFIG_NAND_U_BOOT),y)

NAND_SPL = nand_spl

U_BOOT_NAND = $(obj)u-boot-nand.bin

endif

 

ifeq ($(CONFIG_ONENAND_U_BOOT),y)

ONENAND_IPL = onenand_ipl

U_BOOT_ONENAND = $(obj)u-boot-onenand.bin

ONENAND_BIN ?=
$(obj)onenand_ipl/onenand-ipl-2k.bin

endif

      

对于有的开发板,
U-Boot
支持在
NAND Flash
启动,这些开发板的配置文件定义了
CONFIG_NAND_U_BOOT

CONFIG_ONENAND_U_BOOT
。对于
s3c2440

U-Boot
原始代码并不支持
NAND Flash
启动,因此也没有定义这两个宏。

ALL += $(obj)u-boot.srec $(obj)u-boot.bin
$(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)

 

all:        

$(ALL)

      

其中
U_BOOT_NAND

U_BOOT_ONENAND
为空,而
u-boot.srec

u-boot.bin

System.map
都依赖与
u-boot
。因此执行
“make all”
命令将生成
u-boot

u-boot.srec

u-boot.bin

System.map
。其中
u-boot

ELF
文件,
u-boot.srec

Motorola S-Record format
文件,
System.map

U-Boot
的符号表,
u-boot.bin
是最终烧写到开发板的二进制可执行的文件。

      

下面再来分析
u-boot.bin
文件生成的过程。
ELF
格式
“u-boot”
文件生成规则如下:

$(obj)u-boot:      

depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT)
$(obj)u-boot.lds

             

$(GEN_UBOOT)

ifeq ($(CONFIG_KALLSYMS),y)

             

smap=`$(call
SYSTEM_MAP,u-boot) | /

                    

awk
'$$2 ~ /[tTwW]/ {printf $$1 $$3 "////000"}'` ; /

             

$(CC)
$(CFLAGS) -DSYSTEM_MAP="/"$${smap}/"" /

                    

-c
common/system_map.c -o $(obj)common/system_map.o

             

$(GEN_UBOOT)
$(obj)common/system_map.o

endif

      

这里生成的
$(obj)u-boot
目标就是
ELF
格式的
U-Boot
文件了。由于
CONFIG_KALLSYMS
未定义,因此
ifeq ($(CONFIG_KALLSYMS),y)

endif
间的代码不起作用。

      

其中
depend

$(SUBDIRS)

$(OBJS)

$(LIBBOARD)

$(LIBS)

$(LDSCRIPT)

$(obj)u-boot.lds

$(obj)u-boot
的依赖,而
$(GEN_UBOOT)
编译命令。

下面分析
$(obj)u-boot
的各个依赖:

1
依赖目标
depend

# Explicitly make _depend in subdirs
containing multiple targets to prevent

# parallel sub-makes creating .depend files
simultaneously.

 

depend dep:

$(TIMESTAMP_FILE) $(VERSION_FILE) $(obj)include/autoconf.mk

             

for
dir in $(SUBDIRS) cpu/$(CPU) $(dir $(LDSCRIPT)) ; do /

                    

$(MAKE)
-C $$dir _depend ; done

      


对于
$(SUBDIRS)

cpu/$(CPU)

$(dir $(LDSCRIPT))
中的每个元素都进入该目录执行


make
_depend”



生成各个子目录的

.depend

文件,

.depend

列出每个目标文件的依赖文件。


      


2

依赖
SUBDIRS

      

SUBDIRS   

= tools /

      
 

examples/standalone /

      
 

examples/api

 

      

$(SUBDIRS):    

depend

                    

$(MAKE)
-C $@ all

      

执行
tools

examples/standalone

examples/api
目录下的
Makefile


      

3
OBJS

      

OBJS
的值是
“cpu/arm920t/start.o”
。它使用如下代码编译得到:

$(OBJS):     

depend

      

$(MAKE)
-C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))

      

以上规则表明,对于
OBJS
包含的每个成员,都进入
cpu/$(CPU)
目录(即
cpu/arm920t
)编译它们。

4
LIBBOARD

LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a

LIBBOARD := $(addprefix
$(obj),$(LIBBOARD))

… …

$(LIBBOARD):

depend $(LIBS)

             

$(MAKE)
-C $(dir $(subst $(obj),,$@))

      

这里
LIBBOARD
的值是

$(obj)board/samsung/mini2440/libmini2440.a

make
执行
board/samsung/mini2440/
目录下的
Makefile
,生成
libmini2440.a


      

5
LIBS

      

LIBS
变量中的每个元素使用如下的规则编译得到:

$(LIBS):      

depend
$(SUBDIRS)

             

$(MAKE)
-C $(dir $(subst $(obj),,$@))

      

上面的规则表明,对于
LIBS
中的每个成员,都进入相应的子目录执行
“make”
命令编译它们。例如对于
LIBS
中的
“common/libcommon.a”
成员,程序将进入
common
目录执行
Makefile
,生成
libcommon.a


6
LDSCRIPT

LDSCRIPT := $(SRCTREE)/cpu/$(CPU)/u-boot.lds

… …

$(LDSCRIPT):  

depend

             

$(MAKE)
-C $(dir $@) $(notdir $@)

      

“$(MAKE) -C $(dir $@)
$(notdir $@)”
命令经过变量替换后就是
“make
-C cpu/arm920t/ 

u-boot.lds”
。也就是转到
cpu/arm920t/
目录下,执行
“make u-boot.lds”
命令。

7
$(obj)u-boot.lds

$(obj)u-boot.lds: $(LDSCRIPT)

             

$(CPP)
$(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@

      

以上执行结果实质上是将
cpu/arm920t/u-boot.lds
经编译器简单预处理后输出到
U-Boot
顶层目录下的
u-boot.lds
文件。其中的
cpu/arm920t/u-boot.lds
文件内容如下:

/*
输出为
ELF
文件,小端方式,
*/

OUTPUT_FORMAT("elf32-littlearm",
"elf32-littlearm", "elf32-littlearm")

OUTPUT_ARCH(arm)   

ENTRY(_start)

SECTIONS

{

      

.
= 0x00000000;

 

      

.
= ALIGN(4);

      

.text
:

      

{

/* cpu/arm920t/start.o
放在最前面,保证最先执行的是
start.o */

                    

cpu/arm920t/start.o   

(.text)

/*
以下
2
个文件必须放在前
4K
,因此也放在前面,其中
board/samsung/mini2440/lowlevel_init.o

包含内存初始化所需代码,而

board/samsung/mini2440/nand_read.o
包含
U-Boot

NAND Flash
搬运自身的代码
*/

    

          

board/samsung/mini2440/lowlevel_init.o
(.text)

   
            

board/samsung/mini2440/nand_read.o
(.text)

/*
其他文件的代码段
*/

             

*(.text)

      

}

 

/*
只读数据段
*/

      

.
= ALIGN(4);

      

.rodata
: { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

 

/*
代码段
*/

      

.
= ALIGN(4);

      

.data
: { *(.data) }

 

/* u-boot
自定义的
got

*/

      

.
= ALIGN(4);

      

.got
: { *(.got) }

 

      

.
= .;

      

__u_boot_cmd_start
= .;         

/*

__u_boot_cmd_start
指定为当前地址
*/

      

.u_boot_cmd
: { *(.u_boot_cmd) }            

/*
存放所有
U-Boot
命令对应的
cmd_tbl_t
结构体
*/

      

__u_boot_cmd_end
= .;          

/* 


__u_boot_cmd_end
指定为当前地址
 

*/

 

/* bss

*/

      

.
= ALIGN(4);

      

__bss_start
= .;

      

.bss
(NOLOAD) : { *(.bss) . = ALIGN(4); }

      

_end
= .;             

/* 


_end
指定为当前地址
 

*/

}

      

u-boot.lds
实质上是
U-Boot
连接脚本。对于生成的
U-Boot
编译生成的
“u-boot”
文件,可以使用
objdump
命令可以查看它的分段信息:



objdump -x u-boot | more

      

部分输出信息如下:

u-boot:    

file format elf32-little

u-boot

architecture: UNKNOWN!, flags 0x00000112:

EXEC_P, HAS_SYMS, D_PAGED

start address 0x33f80000

 

Program Header:

   

LOAD off   

0x00008000 vaddr
0x33f80000 paddr 0x33f80000 align 2**15

        

filesz 0x0002f99c memsz 0x00072c94 flags rwx

  

STACK off   

0x00000000 vaddr
0x00000000 paddr 0x00000000 align 2**2

        

filesz 0x00000000 memsz 0x00000000 flags rwx

 

Sections:

Idx Name         

Size     

VMA  

    

LMA      

File off 

Algn

 

0
.text        

00024f50 

33f80000 

33f80000 

00008000 

2**5

                 

CONTENTS, ALLOC, LOAD,
READONLY, CODE

 

1
.rodata      

00008b78 

33fa4f50 

33fa4f50 

0002cf50 

2**3

                 

CONTENTS, ALLOC, LOAD, READONLY,
DATA

 

2
.data        

00001964 

33fadac8 

33fadac8 

00035ac8 

2**2

                 

CONTENTS, ALLOC, LOAD, DATA

 

3
.u_boot_cmd  

00000570 

33faf42c 

33faf42c 

0003742c 

2**2

                 

CONTENTS, ALLOC, LOAD, DATA

 

4
.bss         

00043294  

33fafa00 

33fafa00 

0003799c 

2**8

                 

ALLOC

… …

      

u-boot.lds
还跟
U-Boot
启动阶段复制代码到
RAM
空间的过程以及
U-Boot
命令执行过程密切相关,具体请结合
U-Boot
源代码理解。

      

编译命令
GEN_UBOOT

GEN_UBOOT = /

             

UNDEF_SYM=`$(OBJDUMP)
-x $(LIBBOARD) $(LIBS) | /

             

sed 

-n -e
's/.*/($(SYM_PREFIX)__u_boot_cmd_.*/)/-u/1/p'|sort|uniq`;/

             

cd
$(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) /

                    

--start-group
$(__LIBS) --end-group $(PLATFORM_LIBS) /

                    

-Map
u-boot.map -o u-boot

      

以上命令使用
$(LDFLAGS)
作为连接脚本,最终生成
“u-boot”
文件。

u-boot.bin

文件生成过程


      

生成
u-boot.bin
文件的规则如下:

$(obj)u-boot.bin:

$(obj)u-boot

             

$(OBJCOPY)
${OBJCFLAGS} -O binary $< $@

      


U-Boot
编译输出信息中可以知道上面的命令实质上展开为:

      

arm-linux-objcopy
--gap-fill=0xff -O binary u-boot u-boot.bin

      

编译命令中的
“-O binary”
选项指定了输出的文件为二进制文件。而
“--gap-fill=0xff”
选项指定使用
“0xff”
填充段与段间的空闲区域。这条编译命令实现了
ELF
格式的
U-Boot
文件到
BIN
格式的转换。

System.map

文件的生成


      


System.map

U-Boot
的符号表,它包含了
U-Boot
的全局变量和函数的地址信息。将
System.map
生成的规则如下:

SYSTEM_MAP = /

             

$(NM)
$1 | /

             

grep
-v '/(compiled/)/|/(/.o$$/)/|/( [aUw] /)/|/(/./.ng$$/)/|/(LASH[RL]DI/)' | /

             

LC_ALL=C
sort

$(obj)System.map:    

$(obj)u-boot

             

@$(call
SYSTEM_MAP,$<) > $(obj)System.map

arm-linux-nm u-boot | grep -v
'/(compiled/)/|/(/.o$$/)/|/( [aUw] /)/|/(/./.ng$$/)/|/(LASH[RL]DI/)' | LC_ALL=C
sort 

> System.map

      

也就是将
arm-linux-nm
命令查看
u-boot
的输出信息经过过滤和排序后输出到
System.map
。为了了解
System.map
文件的作用,打开
System.map


33f80000 T _start

33f80020 t _undefined_instruction

33f80024 t _software_interrupt

33f80028 t _prefetch_abort

33f8002c t _data_abort

33f80030 t _not_used

33f80034 t _irq

33f80038 t _fiq

33f80040 t _TEXT_BASE

33f80044 T _armboot_start

33f80048 T _bss_start

33f8004c T _bss_end

… …

      

System.map
表示的是地址标号到该标号表示的地址的一个映射关系。
System.map
每一行的格式都是
“addr type name”

addr
是标号对应的地址值,
name
是标号名,
type
表示标号的类型。

    

U-Boot
的编译和运行并不一定要生成
System.map
,这个文件主要是提供给用户或外部程序调试时使用的。

 

作者:heaad

  http://www.cnblogs.com/heaad/
 

邮箱:heaad@qq.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: