U-Boot Makefile分析

注:本帖转自:http://www.cnblogs.com/heaad/archive/2010/07/17/1779806.html

     感谢作者:heaad

 

 

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

#include

#include

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

       #include

       #include

       下面总结命令 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

       #include

       #include

3.      make all 命令执行过程

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

       System not configured - see README

       U-Boot 是如何知道用户没有执行过 “make _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 _config” 命令执行过程生成的,若从没有执行过 “make _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 > [email protected] && /

       mv [email protected] $@

       include/autoconf.mk 依赖于 make _config 命令生成的 include/config.h 。因此执行 make _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 ,这个文件主要是提供给用户或外部程序调试时使用的。

你可能感兴趣的:(uboot,makefile,编译器,build,shell,脚本,bash)