第十三期 U-Boot编译原理《路由器就是开发板》

https://blog.csdn.net/aggresss/article/details/52614234

        大体了解了U-Boot的源码结构后有一个问题必须得掌握透彻,那就是U-Boot的源码是怎么生成可执行文件的,如果把这个问题搞明白,就可以对U-Boot的全局有一个把控能力。这一期我们来分析Ralink_SDK3.6中U-Boot的编译原理,因为这是一个比较关键的节点,我准备换一种风格,争取把每一个细节都讲到。看这期之前,建议你了解一下Makefile的语法,在SDK的DateSheet目录下有一个 Makefile_Tutorial.pdf 的文件可以参考。
        U-Boot是Universal Bootloader的缩写,它的目的就是实现一个框架,可以作为任何架构board的bootloader,所以它的架构是层次分明的,在U-Boot的标准版本里在第一次编译时需要向Makefile文件提交六个参数,这些参数一级一级的锁定了U-Boot的配置,这六个参数按上下级顺序为  ARCH-->CPU-->SOC-->Board-->Vender-->Boardname。在分析U-Boot的编译原理和工作原理时,用这种层次来组织分析的思路很有用。
        linux环境的生态系统里面有很多开源软件不提供bin形式的可执行文件的下载方式,我们通常都会经过源码编译到安装的过程,典型的安装一般会经历三个步骤:(1) ./configure ,(2) make ,(3) make install 。这三个步骤中第一步的目的是根据当前的系统环境和用户选择生成配置文件,配置文件通常是对一些条件编译变量的声明,在代码编译过程中决定编译哪一部分代码,经常使用的文件名是.config,特殊说明一下在linux文件系统里以”.“开始的文件名就是隐藏文件,在使用ls或者一些资源管理器打开文件时通常不会直接显示,这时使用”ll“或者”ls -l“才能看到这种文件;第二步make就是根据Makefile文件中的逻辑关系对源文件进行编译,通常Makefile文件会包含第一步生成的配置文件,通过调用编译器来实现编译过程。如果前两步成功,第三部的作用就是将生成的可执行文件导入系统的默认程序或者库文件夹,并执行一些注册脚本。当然,自己编译源文件的不确定因素是很多的,主要的难点有两个:(1)软件所依赖库函数的安装;(2)编译过程中的bug调试。
        对于类linux内核的编译过程通常经历两个步骤:(1)make menuconfig,(2) make 。make menuconfig以图形界面进行配置,然后 make开始编译,我们讲到的U-Boot就是这样一个编译流程。
        然后我们来简单讲一下程序的编译过程,我这里以gcc来讲解,gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶预处理(也称预编译,Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。gcc的一些常用参数我在这里列出来,一会儿分析Makefile文件时会用到:
-E    只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面. 
        例子用法: 
        gcc -E hello.c > pianoapan.txt 
        gcc -E hello.c | more 
        一个hello word 也要与处理成800行的代码  
-S    只激活预处理和编译,就是指把文件编译成为汇编代码
        例子用法
        gcc -S hello.c
        他将生成.s的汇编代码,你可以用文本编辑器察看 
-c    只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
        例子用法:
        gcc -c hello.c
        他将生成.o的obj文件
-o    制定目标名称,缺省的时候,gcc 编译出来的文件是a.out
        例子用法
        gcc -o hello.exe hello.c
        gcc -o hello.asm -S hello.c 
-Dmacro 或-Dmacro=defn    其作用类似于源程序里的#define。例如:% gcc -c -DHAVE_GDBM -DHELP_FILE=\"help\" cdict.c其中第一个- D选项定义宏HAVE_GDBM,在程序里可以用#ifdef去检查它是否被设置。第二个-D选项将宏HELP_FILE定义为字符串“help”(由于 反斜线的作用,引号实际上已成为该宏定义的一部分),这对于控制程序打开哪个文件是很有用的。
-Idir    将dir目录加到搜寻头文件的目录列表中去,并优先于在gcc缺省的搜索目录。在有多个-I选项的情况下,按命令行上-I选项的前后顺序搜索。dir可使用相对路径,如-I../inc等。
-Ldir    将dir目录加到搜寻-l选项指定的函数库文件的目录列表中去,并优先于gcc缺省的搜索目录。在有多个-L选项的情况下,按命令行上-L选项的前后顺序搜索。dir可使用相对路径。如-L../lib等。
-lname    在连接时使用函数库libname.a,连接程序在-Ldir选项指定的目录下和/lib,/usr/lib目录下寻找该库文件。在没有使用-static选项时,如果发现共享函数库libname.so,则使用libname.so进行动态连接。
-static    禁止与共享函数库连接。
-shared    尽量与共享函数库连接。
        源文件的编译过程很复杂,这里我向大家推荐由 俞甲子/石凡/潘爱民三位大神合作的《程序员的自我修养》一书。
我们看一下U-Boot根目录下参与编译的文件:
        Makefile makefile主文件,不用多说了;
        config.in 这个文件是make menuconfig 配置的参数文件,进入menuconfig时的内容都可以在这里面找到;
        config.mk 可以把他理解为Makefile的一个子文件,里面有很多编译相关的信息,Makefile主文件会包含它;
        .config 执行make menuconfig 配置后的配置信息会保存在这里面。
        我们来详细分析一下Makefile文件,相对U-Boot官方的U-Boot版本简单了不少,比较适合用来初级学习,整个Makefile文件共分4个小节,每个小节使用一行#号隔开。
        第一小节为1-42行,主要是法律说明和声明3个变量:1.HOSTARCH;HOSTS;VENDOR;使用export语句可以让子Makefile也可以使用声明的变量,sed是使用正则表达式进行文本处理的工具,例如 ”sed -e s/i.86/i386/“ 中 ”-e“表示一个命令,”s“代表substitute,将”i.86“替换成”i386“。
        第二节43-154行, 主要的任务还是声明变量,同时包含子Makefile文件./config.mk,config.mk是一个很关键的文件,他同时又会包含很多文件,./config.mk文件首先会包含.config文件,也就是我们make menuconfig时生成的配置文件,限于篇幅原因./config.mk交给读者自己分析吧,.config.mk里面的难点就是CPPFLAGS变量的声明,这个变量目的就是在编译源码时递交给gcc的参数,在查看源代码时有很多找不到定义位置的变量都是通过编译时给gcc传递“-D”参数来定义的,比如start.S中提到的TEXT_BASE变量就是在./config.mk中第154行定义的:
        CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS)\
        -D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE) \
        -I$(TOPDIR)/include \
        -fno-builtin -ffreestanding -nostdinc -isystem\
        $(gccincdir) -pipe $(PLATFORM_CPPFLAGS)
        第三节155-204行,主要就是声明 OBJS 和 LIBS 变量 uboot.bin文件主要就是通过*.o 文件和*.a文件链接形成的;
        第四节204-352行,这一节是U-Boot形成的关键,第211行目标 all 是整个Makefile文件的第一目标,所以当执行make命令时就约等于执行 make all,all目标依赖uboot.bin,uboot.bin依赖目标u-boot,u-boot目标在第256行
u-boot: depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*__u_boot_cmd_.*/-u\1/p'|sort|uniq`;\
$(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) \
--start-group $(LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
u-boot目标的执行过程展开后是这样的:
UNDEF_SYM=`"/opt/buildroot-gcc342/bin"/mipsel-linux-objdump -x lib_generic/libgeneric.a board/rt2880/librt2880.a cpu/ralink_soc/libralink_soc.a lib_mips/libmips.a net/libnet.a rtc/librtc.a drivers/libdrivers.a common/libcommon.a |sed  -n -e 's/.*__u_boot_cmd_.*/-u\1/p'|sort|uniq`;\
        "/opt/buildroot-gcc342/bin"/mipsel-linux-ld -Bstatic -T /home/aggresss/workspace/U-Boot_Ralink_SDK3.6/board/rt2880/u-boot.lds -Ttext 0x80200000  $UNDEF_SYM cpu/ralink_soc/start.o \
        --start-group lib_generic/libgeneric.a board/rt2880/librt2880.a cpu/ralink_soc/libralink_soc.a lib_mips/libmips.a net/libnet.a rtc/librtc.a drivers/libdrivers.a common/libcommon.a --end-group -L /opt/buildroot-gcc342/bin/../lib/gcc/mipsel-linux-uclibc/3.4.2 \
        -Map u-boot.map -o u-boot
蓝色的部分定义了一个变量UNDEF_SYM ,这个变量实际上就是把中间这一群的静态库里面__u_boot_cmd_开头的symbol都提取出来,并在每个的开头加上了-u,以备后用。
接下来就是执行mipsel-linux-ld 进行链接了:
-Bstatic 表示静态链接;
-T xxxxxxx/u-boot.lds 表示使用u-boot.lds,linker script来链接。通过连接脚本可以分析出U-Boot的入口符号是"_start",关于gcc连接脚本的格式也值得去拓展学习一下;
-Ttext 0x80200000 表示将text段,放到绝对地址为0x80200000的地方;
$UNDEF_SYM 是一串-u symbol的东东。这样所有未定位的符号名都在这些库里面寻找;
--start-group --end-group 之间列出了所有的archives. 同一般的直接写archive不同在于,当search了一遍仍然有undefined reference的时候,会继续搜索;
-Map  输出map文件便于后期调试。
        u-boot的elf格式已经出来了,但是这个文件是不能直接烧写到flash。Makefile第220行的目标uboot.bin展开执行后的语句为:
mipsel-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
这条命令生成的u-boot就是可以直接烧写的image了。
--gap-fill=0xff 指明了用0xff来填充section之间的空隙。
-O binary 输出binary格式,可以直接镜像到内存中执行。
        这一期看起来有点乱,只是大略讲到一些容易模糊的地方,这里面有很多东西需要扎实的基本功才能分析起来游刃有余,比如Makefile规则的理解,Regular Express 的精准把握,GCC编译工具链的熟练应用,限于篇幅和能力,我就先写这么多,希望大家读过这一期能产生一些灵感然后继续拓展学习。

----------------------------------------------------

SDK下载地址:   https://github.com/aggresss/RFDemo
 

猜你喜欢

转载自blog.csdn.net/wxh0000mm/article/details/85602607
今日推荐