1. Compiling U-Boot
First, let’s review how to compile U-Boot.
(1) Set temporary environment variables
export ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
(2) Specify board configuration and generate .config file
make xxx_defconfig
(3) Compile
make -j8
(4) Clean build
make distclean
Next, we will analyze the top-level Makefile in the U-Boot root directory to explore what happens behind these three compilation commands.
2. Makefile Part One – Make Related Settings
1. U-Boot Version Information
VERSION = 2016
PATCHLEVEL = 03
SUBLEVEL =
EXTRAVERSION =
NAME =
# VERSION: Major version number
# PATCHLEVEL: Patch version number
# SUBLEVEL: Minor version number
# EXTRAVERSION: Additional version information
# NAME: Name
2. MAKEFLAGS
# o Do not use make's built-in rules and variables
# (this increases performance and avoids hard-to-debug behaviour);
# o Look for make include files relative to root of kernel src
MAKEFLAGS += -rR --include-dir=$(CURDIR)
# +=: Appending value to MAKEFLAGS variable
# -rR: Prohibits using make's built-in implicit rules and variable definitions
# --include-dir: Specifies search path
# $(CURDIR): Represents the current directory
3. Recursive Build Settings
# Avoid funny character set dependencies
# Avoid character set dependencies
unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC
# Avoid interference with shell env settings
# Avoid interference with shell environment settings
unexport GREP_OPTIONS
Make supports recursive builds, calling Makefiles in subdirectories to complete the compilation of subdirectories. The syntax is as follows:
$(MAKE) -C subdir
When calling a sub-Makefile, you can specify which variables to pass using export and which to not pass using unexport. However, the SHELL variable and MAKEFLAGS are passed to the sub-Makefile by default unless explicitly unexported.
4. Beautifying Output
# Use 'make V=1' to see the full commands
# Use 'make V=1' to see the complete commands executed by U-Boot
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
# If the user is running make -s (silent mode), suppress echoing of
# commands
# If the user runs make -s (silent mode), suppress command echoing
ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
quiet=silent_
endif
else # make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
quiet=silent_
endif
endif
export quiet Q KBUILD_VERBOSE
(1) Check if the user specified variable V
The origin function in the Makefile is used to check where a variable comes from. If variable V is defined in the command line, its origin is “command line”, and thus its value is assigned to KBUILD_VERBOSE, determining whether the log output is detailed.
If the user does not define variable V, KBUILD_VERBOSE defaults to 0.
(2) Control make log output
The Makefile uses a clever operation to control whether to output the complete command to the terminal using the variables quiet and Q, for example:
$(Q)$(MAKE) $(build)=scripts/basic
If V=1, Q is empty, and this command will be fully output to the terminal when executed; if V=0, Q=@, the command becomes:
@make $(build) = scripts/basic
At this point, the command execution will not be output to the terminal.
(3) Silent output
In the case of V=0, the U-Boot terminal displays a short command, but logs are still output. Using the make -s option sets silent output, with no logs at all.
(4) Export variables quiet, Q, KBUILD_VERBOSE to sub-Makefiles to maintain the same log settings.
5. Set Compilation Output Directory
# KBUILD_SRC is set on invocation of make in OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
# KBUILD_SRC is set when make is invoked in the OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
ifeq ($(KBUILD_SRC),)
# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
# Set KBUILD_OUTPUT environment variable
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
# That's our default target when none is given on the command line
# When no parameters are passed on the command line, this is our default target
PHONY := _all
_all:
# Cancel implicit rules on top Makefile
# Cancel implicit rules in the top-level Makefile
$(CURDIR)/Makefile Makefile: ;
ifneq ($(KBUILD_OUTPUT),)
# Invoke a second make in the output directory, passing relevant variables
# Check that the output directory actually exists
# Call a second make in the output directory, passing relevant variables to check if the output directory actually exists
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
&& /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
$(error failed to create output directory "$(saved-output)"))
PHONY += $(MAKECMDGOALS) sub-make
$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
@:
sub-make: FORCE
$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
# Leave processing to above invocation of make
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)
Kbuild supports saving the compiled output files to a separate directory. To locate output files in a separate directory, two syntaxes are supported:
(1) O=
make O=dir/to/store/output/files/
(2) Set KBUILD_OUTPUT environment variable
export KBUILD_OUTPUT=dir/to/store/output/files/
make
Using O= takes precedence over setting the KBUILD_OUTPUT environment variable.
6. Code Checking
# Call a source code checker (by default, "sparse") as part of the
# C compilation.
# Call a source code checker (default is "sparse") as part of C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "Documentation/sparse.txt" for more details, including
# where to get the "sparse" utility.
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
Use the C= parameter to enable code checking: 1: Check files that need recompilation; 2: Check all source files.
Similarly, if the C parameter comes from the command line, it assigns the value to the environment variable KBUILD_CHECKSRC; if not, KBUILD_CHECKSRC defaults to 0.
7. Module Compilation
# If building an external module we do not care about the all: rule
# but instead _all depend on modules
# If building an external module, we do not care about the all: rule
# but instead _all depends on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif
ifeq ($(KBUILD_SRC),)
# building in the source tree
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
# building in a subdirectory of the source tree
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
objtree := .
src := $(srctree)
obj := $(objtree)
VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
export srctree objtree VPATH
Use the M=dir parameter to specify the output directory for external module compilation. If the command line has the M parameter, its value is assigned to KBUILD_EXTMOD; if not, KBUILD_EXTMOD is empty, and it directly compiles all.
3. Makefile Part Two – Preparation Before Compilation
1. Get Host CPU Architecture and Operating System
# Get host architecture
HOSTARCH := $(shell uname -m | \
sed -e s/i.86/x86/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ \
-e s/sa110/arm/ \
-e s/ppc64/powerpc/ \
-e s/ppc/powerpc/ \
-e s/macppc/powerpc/\
-e s/sh.*/sh/)
# Get host operating system
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
sed -e 's/\(cygwin\).*/cygwin/')
export HOSTARCH HOSTOS
2. Set Compiler and Configuration File
Do you remember? U-Boot needs to specify the values of ARCH and CROSS_COMPILE before compiling, which will come into play here!
Compare the HOSTARCH obtained in the previous step with the user-specified ARCH:
(1) If equal, it means compiling on the local machine, directly using the local compiler, no cross-compiler needed, set to empty; KCONFIG_CONFIG indicates using the .config configuration file.
# set default to nothing for native builds
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$BASH" ]; then echo $BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
HOSTCC = cc
HOSTCXX = c++
HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
HOSTCXXFLAGS = -O2
ifeq ($(HOSTOS),cygwin)
HOSTCFLAGS += -ansi
endif
(2) If the host OS is Darwin (the kernel of MAC OS), related settings are made.
# Mac OS X / Darwin's C preprocessor is Apple specific. It
# generates numerous errors and warnings. We want to bypass it
# and use GNU C's cpp. To do this we pass the -traditional-cpp
# option to the compiler. Note that the -traditional-cpp flag
# DOES NOT have the same semantics as GNU C's flag; all it does
# is invoke the GNU preprocessor in stock ANSI/ISO C fashion.
#
# Apple's linker is similar; thanks to the new 2-stage linking
# multiple symbol definitions are treated as errors; hence the
# -multiply_defined suppress option to turn off this error.
#
ifeq ($(HOSTOS),darwin)
# get major and minor product version (e.g. '10' and '6' for Snow Leopard)
DARWIN_MAJOR_VERSION = $(shell sw_vers -productVersion | cut -f 1 -d '.')
DARWIN_MINOR_VERSION = $(shell sw_vers -productVersion | cut -f 2 -d '.')
os_x_before = $(shell if [ $(DARWIN_MAJOR_VERSION) -le $(1) -a \
$(DARWIN_MINOR_VERSION) -le $(2) ] ; then echo "$(3)"; else echo "$(4)"; fi ;)
# Snow Leopards build environment has no longer restrictions as described above
HOSTCC = $(call os_x_before, 10, 5, "cc", "gcc")
HOSTCFLAGS += $(call os_x_before, 10, 4, "-traditional-cpp")
HOSTLDFLAGS += $(call os_x_before, 10, 5, "-multiply_defined suppress")
# since Lion (10.7) ASLR is on by default, but we use linker generated lists
# in some host tools which is a problem then ... so disable ASLR for these
# tools
HOSTLDFLAGS += $(call os_x_before, 10, 7, "", "-Xlinker -no_pie")
endif
3. Introduce Generic Definitions
# We need some generic definitions (do not try to remake the file).
# We need some generic definitions
scripts/Kbuild.include: ;
include scripts/Kbuild.include
The Kbuild.include file contains some definitions that will be used in the subsequent compilation process.
4. Create Variables – Full Name of Compiler
# Make variables (CC, etc...)
# Create variables
AS = $(CROSS_COMPILE)as
# Always use GNU ld
ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
LD = $(CROSS_COMPILE)ld.bfd
else
LD = $(CROSS_COMPILE)ld
endif
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
AWK = awk
PERL = perl
PYTHON = python
DTC = dtc
CHECK = sparse
If it is cross-compilation, this is very important. If the value of environment variable CROSS_COMPILE is arm-linux-gnueabi-hf-, the resulting compiler will be:
CC = arm-linux-gnueabi-hf-gcc
The names of the other tools are similarly derived.
5. Export Required Variables for Sub-Makefile
export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
export MAKE AWK PERL PYTHON
export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS
export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS KBUILD_AFLAGS
Some of these variables have already been defined, while others have not appeared, such as the variables on the second line, which come from the config.mk file in the root directory:
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
The CONFIG_SYS_xxx variables here come from the configuration file .config, as follows:
CONFIG_SYS_ARCH="arm"
CONFIG_SYS_CPU="armv7"
CONFIG_SYS_SOC="mx6"
CONFIG_SYS_VENDOR="freescale"
CONFIG_SYS_BOARD="mx6ullatk"
CONFIG_SYS_CONFIG_NAME="mx6ullatk"
From the actual compilation printout, these variables have the following actual values:
At this point, you should understand why U-Boot needs to set the ARCH and CROSS_COMPILE environment variables before compilation.
export ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
4. The Process Behind make xxx_defconfig
1. %config Target Dependency
First, match if the parameter of the make command is config or xxx_config, then set the flag config-targets to 1:
ifeq ($(KBUILD_EXTMOD),)
ifneq ($(filter config %config,$(MAKECMDGOALS)),)
config-targets := 1
ifneq ($(words $(MAKECMDGOALS)),1)
mixed-targets := 1
endif
endif
endif
If the config-targets flag is set to 1, it enters the corresponding target:
ifeq ($(config-targets),1)
# ===========================================================================
# *config targets only - make sure prerequisites are updated, and descend
# in scripts/kconfig to make the *config target
KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG
config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
else
You can see that the xxx_config target is %config, with dependencies being scripts_basic outputmakefile FORCE. The build command is:
$(Q)$(MAKE) $(build)=scripts/kconfig $@
(1) The scripts_basic dependency can be identified in the Makefile:
# Rules shared between *config targets and build targets
# Shared rules between *config targets and build targets
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
Here, Q is used for log printing, MAKE is make, and build is defined in the scripts/Kbuild file:
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj
The srctree variable is passed from the top-level Makefile, defined as follows:
ifeq ($(KBUILD_SRC),)
# building in the source tree
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
# building in a subdirectory of the source tree
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
Since KBUILD_SRC is empty, the value of srctree is .
In summary, the build rule for scripts_basic can be expanded as follows:
make -f ./scripts/Makefile.build obj=scripts/basic
This command will further call the Makefile.build script, which we will analyze later.
(2) The outputmakefile dependency can also be identified in the Makefile:
PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif
In the build rule of this dependency, it first checks if KBUILD_SRC is empty. From previous analysis, this variable is empty, so this dependency build is not useful.
(3) The FORCE dependency can also be identified in the Makefile:
PHONY += FORCE
FORCE:
FORCE has no rules or dependencies, so it will always be regenerated. When FORCE is a dependency of other targets, the rules containing it will always execute because FORCE is always updated.
Now that we have analyzed the three dependencies, we can expand the %config build rule as follows, where #@ is shell script syntax, representing the entire list of parameters passed in.
make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
It can be seen that the first two commands are building the scripts_basic dependency, while the third command is building the %config target.
2. Makefile.build Script
(1) scripts_basic target
make -f ./scripts/Makefile.build obj=scripts/basic
Function: Compile the script/basic/fixdep software.
(2) %config target
make -f ./scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
Function: Use the xxx_defconfig file to output its contents to the configuration file .config, generating the .config file.
At this point, you should understand how the make xxx_defconfig generates the .config file before compiling U-Boot.
5. U-Boot Compilation Process
In the previous four chapters, the top-level Makefile of U-Boot has set various variables needed for compilation and generated the .config file. Everything is ready, and now we start compiling U-Boot to generate the executable file.
1. Dependency Relationships
(1) all target
all: $(ALL-y)
ifneq ($(CONFIG_SYS_GENERIC_BOARD),y)
@echo "===================== WARNING ======================"
@echo "Please convert this board to generic board."
@echo "Otherwise it will be removed by the end of 2014."
@echo "See doc/README.generic-board for further information"
@echo "===================================================="
endif
ifeq ($(CONFIG_DM_I2C_COMPAT),y)
@echo "===================== WARNING ======================"
@echo "This board uses CONFIG_DM_I2C_COMPAT. Please remove"
@echo "(possibly in a subsequent patch in your series)"
@echo "before sending patches to the mailing list."
@echo "===================================================="
endif
(2) Dependencies of the all target
# Always append ALL so that arch config.mk's can add custom ones
ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
ALL-$(CONFIG_ONENAND_U_BOOT) += u-boot-onenand.bin
ifeq ($(CONFIG_SPL_FSL_PBL),y)
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot-with-spl-pbl.bin
else
ifneq ($(CONFIG_SECURE_BOOT), y)
# For Secure Boot The Image needs to be signed and Header must also
# be included. So The image has to be built explicitly
ALL-$(CONFIG_RAMBOOT_PBL) += u-boot.pbl
endif
endif
ALL-$(CONFIG_SPL) += spl/u-boot-spl.bin
ALL-$(CONFIG_SPL_FRAMEWORK) += u-boot.img
ALL-$(CONFIG_TPL) += tpl/u-boot-tpl.bin
ALL-$(CONFIG_OF_SEPARATE) += u-boot.dtb
ifeq ($(CONFIG_SPL_FRAMEWORK),y)
ALL-$(CONFIG_OF_SEPARATE) += u-boot-dtb.img
endif
ALL-$(CONFIG_OF_HOSTFILE) += u-boot.dtb
ifneq ($(CONFIG_SPL_TARGET),)
ALL-$(CONFIG_SPL) += $(CONFIG_SPL_TARGET:"%"=%)
endif
ALL-$(CONFIG_REMAKE_ELF) += u-boot.elf
ALL-$(CONFIG_EFI_APP) += u-boot-app.efi
ALL-$(CONFIG_EFI_STUB) += u-boot-payload.efi
ifneq ($(BUILD_ROM),)
ALL-$(CONFIG_X86_RESET_VECTOR) += u-boot.rom
endif
(3) u-boot.bin target
ifeq ($(CONFIG_OF_SEPARATE),y)
u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
$(call if_changed,cat)
u-boot.bin: u-boot-dtb.bin FORCE
$(call if_changed,copy)
else
u-boot.bin: u-boot-nodtb.bin FORCE
$(call if_changed,copy)
endif
This distinguishes whether the user is using the device tree. In this article, imx6ull is not used, so it depends on the u-boot-nodtb.bin file.
(4) u-boot-nodtb.bin target
u-boot-nodtb.bin: u-boot FORCE
$(call if_changed,objcopy)
$(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
$(BOARD_SIZE_CHECK)
(5) u-boot target
u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
$(call if_changed,u-boot__)
ifeq ($(CONFIG_KALLSYMS),y)
$(call cmd,smap)
$(call cmd,u-boot__) common/system_map.o
endif
The u-boot target depends on u-boot-init, u-boot-main, and u-boot.lds.
u-boot-init and u-boot-main are two variables defined in the top-level Makefile:
u-boot-init := $(head-y)
u-boot-main := $(libs-y)
head-y is not defined; this variable is related to the CPU architecture and is defined in the corresponding architecture’s sub-Makefile, for example, defined as follows in arch/arm/Makefile:
head-y := arch/arm/cpu/$(CPU)/start.o
libs-y in the top-level Makefile:
libs-y += lib/
libs-y += lib/
libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
libs-$(CONFIG_OF_EMBED) += dts/
libs-y += fs/
libs-y += net/
libs-y += disk/
libs-y += drivers/
libs-y += drivers/dma/
libs-y += drivers/spi/
# ...
libs-y += cmd/
libs-y += common/
libs-$(CONFIG_API) += api/
libs-$(CONFIG_HAS_POST) += post/
libs-y += test/
libs-y += test/dm/
libs-$(CONFIG_UT_ENV) += test/env/
libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)
libs-y := $(sort $(libs-y))
u-boot-dirs := $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples
u-boot-alldirs := $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-)))
libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
The code snippet shows that libs-y is a collection of built-in.o files from various subdirectories of U-Boot.
u-boot.lds is located in each architecture directory, for example, arch/arm/cpu/u-boot.lds.
In summary, the u-boot target links u-boot.lds as the linking script, linking arch/arm/cpu/armv7/start.o and built-in.o files from various subdirectories to generate U-Boot.
2. How built-in.o Files Are Generated
Taking driver/gpio/built-in.o as an example, there is a file named .built-in.o.cmd in the drivers/gpio/ directory, which contains the following:
cmd_drivers/gpio/built-in.o := arm-linux-gnueabihf-ld.bfd -r -o drivers/gpio/built-in.o drivers/gpio/mxc_gpio.o
It can be seen that built-in.o is further linked from drivers/gpio/built-in.o and drivers/gpio/mxc_gpio.o; mxc_gpio.c is the GPIO driver file for the NXP i.MX series.
The -r parameter is a linker parameter indicating the generation of a relocatable output, used for linking a small number of files.
Finally, all built-in.o files from various subdirectories are linked together, and then linked with arch/arm/cpu/armv7/start.o to form the U-Boot target.