在很多的嵌入式的软件中,我们拿到代码,如果有文档先看看文档,没有文档也只能从Makefile的角度去看看软件的基本架构了。有一些大型的嵌入式软件项目的Makefile写的很复杂,这样在看学习Makefile的时候,需要一些技巧去分析Makefile的流程。看Makefile主要可以看哪些代码都是编译到哪,是编译到动态库,还是通过静态链接的静态库中的。
第一个技巧:make -n
make -n 在编译的时候加上了-n的参数,执行该命令编译并不真正的执行,只是顺序的列出该编译过程都执行哪些编译命令。可以通过这个来查看编译的基本序列,知道make一层不会涉及具体的执行命令,例如gcc xxx。
第二个技巧:makefile的打印调试技巧
makefile存在 info warning error等的函数可以调用,可以在makefile执行到此处(函数所在处)的时候,打印出调试信息,方便查看变量的值,和预展开(eval 函数)的内容。
方法1:$(info “here add the debug info”)
方法2:$(warning “here add the warning info”)
方法3:$(error “error:this will stop the compile”) //在打印编译处,停止编译的进程,相当于跳出当前的编译,该操作比较有用,在加了适当打印的地方停住,能非常方便的查看当前所打印出来的值。分析好当前位置的Makefile去掉该语句(error)在继续向下进行后续的编译。
在makefile中常常见到有类似于 $(eval $(call stampfile,$(curdir),tools,compile,,_$(subst $(space),,$(tools_enabled)))) ---- openwrt tools/Makefile中的一行内容。
这行内容表示,在make执行的时候会根据call所调用的函数生成一段Makefile文件内容放到这行代码的地方,我们把这个叫做展开。展开的文件中包含若干个目标,依赖,和执行的命令,和正常的makefile一样的。对于这种我们不能很明显的看到它所展开的makefile文件是什么样的,需把展开的内容打印出来查看。我们可以采用上边介绍的命令对其进行展开 例如:
$(warning $(eval $(call stampfile,$(curdir),tools,compile,,_$(subst $(space),,$(tools_enabled)))))用makefile的warning语句将上边eval语句展开的内容打印出来。
tools/stamp-compile:=/home/quzhifeng/workspace/lede_wallys_mtk/staging_dir/target-mipsel_24kc_musl/stamp/.tools_compile_yynyyyyynyyyyynyynnyyyynyyyyyyyyyyyyyyynyynynnyyynny
$(tools/stamp-compile): /home/quzhifeng/workspace/lede_wallys_mtk/tmp/.build
@+/home/quzhifeng/workspace/lede_wallys_mtk/scripts/timestamp.pl -n $(tools/stamp-compile) tools || make --no-print-directory $(tools/flags-compile) tools/compile
@mkdir -p $$(dirname $(tools/stamp-compile))
@touch $(tools/stamp-compile)
$(if ,,.SILENT: $(tools/stamp-compile))
.PRECIOUS: $(tools/stamp-compile) # work around a make bug
tools//clean:=tools/stamp-compile/clean
tools/stamp-compile/clean: FORCE
@rm -f $(tools/stamp-compile)
可以通过查看展开来分析makefile文件
warning 可以放在makefile总任何位置的实例:
$(warning A top-level warning) 1
FOO := $(warning Right-hand side of a simple variable)bar 2
BAZ = $(warning Right-hand side of a recursive variable)boo 3
$(warning A target)target: $(warning In a prerequisite list)makefile $(BAZ) 5
$(warning In a command script) 6
ls 7
$(BAZ): 8
$ make
makefile:1: A top-level warning
makefile:2: Right-hand side of a simple variable
makefile:5: A target
makefile:5: In a prerequisite list
makefile:5: Right-hand side of a recursive variable
makefile:8: Right-hand side of a recursive variable
makefile:6: In a command script
ls //执行命令
makefile //显示命令的执行结果
第三个技巧:make --debug
对于一个比较复杂的工程,有超多的makefile文件,有很多的目标需要编译,例如 openwrt的makefile编译系统。这个时候需要你打开makefile的 --debug 选项功能,查看各个目标在编译过程的依赖关系,可以更好的分析make的执行流程和makefile的关系。
从上边可以看出,在编译world的时候,需要优先编译出prepare(是world的依赖目标),在编译prepare还需要依赖于那一长串的目标等等。可以清晰的查看目标的依赖关系,及目标和Makefile之间的关系。
第四个技巧:显示实际执行的命令的方法。make VERBOSE=1
对于GNU的项目一般在执行的make命令后加VERBOSE=1 可以打印出信息的命令执行信息。
对于非GNU项目,一般的隐藏执行命令的方法都是在命令的开头添加了@,这样命令执行的时候并不显示所执行的命令。
$(BIN_DIR)/$(PROG): $(OBJS) $(DEP_LIBS)
@echo "LD => $(subst $(BLD_DIR),build.$(CPU).$(VER),$@)" && \
if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi && \
$(CC) $(LD_FLAGS) -o $@ $(OBJS) $(LD_LIBS)
因为在命令的最开头echo前边加了@,并且三条命令用 \ 做了拼接,所以是一行的命令,因此$(CC) 的详细执行命令不会被真正的显示出来的。只会显示 LD => build.p1010.r/bin/cdbctl 信息。如果去掉@ 能显示出详细的编译命令,如下:
通过这样的信息,非常有利于对Makefile做进一步的分析。
加了VERBOSE=1为什么能打印出详细的命令信息?
是因为内核里加了如下的过度语句,没有直接使用@,而是用Q变量来代替是使用@还是不使用@。
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
可以通过make VERBOSE=1 显示出编译是所执行的命令了,对于私有的Makefile也可以通过这样的方法做这样的改造。
在openwrt里还可以V=s V=99,都是同样的道理。
第五个技巧:如何找到编译出的某个程序是在哪里链接进去的?
一个最为直接的方法就是,将编译出改目标的地方去掉例如:想知道libdrvs.a 的静态库是在哪里链接进去的?
该命令生成libdrvs.a的静态库,用#注释后就不再生成了。在编译会报错,报错的地方就是链接该静态库的地方。
第六个技巧:动态链接库于静态链接库
动态链接库 so:可以通过编译选项把程序编译为 so文件,例如 libcjson.so,动态链接库的编译不会被编译到使用程序中,而是在执行的时候动态的加载运行。
静态链接库 a:也可以把程序编译为.a文件,例如libdrvs.a,在链接时将程序直接和应用程序链接在一起,形成一个程序。
-L/lib/libpath -lcjson //链接动态库 libcjson.so /lib/libpath 为动态库所在的路径
-L/lib/libpath -ldrvs //链接静态库 libdrvs.a 该库放在/lib/libpath路径下
第七个技巧: 如何在编译时抓取编译的日志出来分析?
make V=s 2>&1 | tee build.log | grep -i error
make V=s 2>&1 | tee build.log