ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

GNU Makefile--调试器remake

2021-06-27 21:02:39  阅读:292  来源: 互联网

标签:SCAN -- cfg Makefile mk mconf remake openwrt 调试器


remake调试器

国外的开发人员对GNU Make进行了一定程度上的重构,增加了Makefile的动态调试功能,命名为remake。调试器的操作界面类似于GDB的命令行,开发者为其编写了详细的文档,有兴趣的可以参考。GNU Make自身也支持若干个调试选项,但其调试功能比较有限,remake的作者对此很不满。不过,remake工具对递归的调试(即一个Makefile脚本中调用$(MAKE)执行新的Makefile)支持不够完善,对单个Makefile的调试功能则相对完整。本文记录了笔者通过remake工具调试openwrt工程中的Makefile的过程,对一些复杂工程的构建脚本做一些初步的了解。

openwrt对编译脚本的更新检测

openwrt是一个优秀的开源嵌入式软件项目,虽然常用于做网关、路由等设备的系统软件,但其前身之一LEDE确实尝试作为“通用”的嵌入式Linux开发环境(Linux Embedded Development Environment)来设计的。完整的工程代码仓库带有上千个开源软件包。作为一名嵌入式底层开发人员,我们了解到,系统级的开源软件包之间会有复杂的依赖关系。在openwrt中,大部分开源软件包之前的依赖关系是通过各个Makefile中的DEPENDS变量来指定的,这个依赖关系需要经过处理,生成mconf(用于显示工程配置的菜单界面的可执行文件,menuconfig)可读取的依赖信息,这样在menuconfig选择界面就可以自动推断、处理各个软件包间的依赖关系,如选择一个软件包会自动选择其依赖的另一个软件包等。

不过,在执行make menuconfig时,openwrt不会每次都进行这样的转换:读取上千个Makefile中的依赖关系并生成mconf可读取的依赖信息:

$ make V=s -j1 menuconfig
make[1]: Entering directory '/home/yejq/program/openwrt/scripts/config'
set -e; mkdir -p ./; trap "rm -f ./.mconf-cfg.tmp" EXIT; { /bin/sh mconf-cfg.sh; } > ./.mconf-cfg.tmp; if [ ! -r mconf-cfg ] || ! cmp -s mconf-cfg ./.mconf-cfg.tmp; then true '  UPD     mconf-cfg'; mv -f ./.mconf-cfg.tmp mconf-cfg; fi
make[1]: Leaving directory '/home/yejq/program/openwrt/scripts/config'
*** End of the configuration.
*** Execute 'make' to start the build or try 'make help'.

$ touch package/system/ubus/Makefile

$ make V=s -j1 menuconfig
make[1]: Entering directory '/home/yejq/program/openwrt/scripts/config'
set -e; mkdir -p ./; trap "rm -f ./.mconf-cfg.tmp" EXIT; { /bin/sh mconf-cfg.sh; } > ./.mconf-cfg.tmp; if [ ! -r mconf-cfg ] || ! cmp -s mconf-cfg ./.mconf-cfg.tmp; then true '  UPD     mconf-cfg'; mv -f ./.mconf-cfg.tmp mconf-cfg; fi
make[1]: Leaving directory '/home/yejq/program/openwrt/scripts/config'
Collecting package info: done
*** End of the configuration.
*** Execute 'make' to start the build or try 'make help'.

对比以上两次make menuconfig,二者间通过touch命令行工具更新了package/system/ubus/Makefile,第二次多了一行Collecting package info: done,下面就会使用remake工具协助调试,openwrt是如何检测到ubus的Makefile被更新了,需要重新更新软件包信息的(Collect package info)。

调试检索软件包的Makefile

输出Collecting package info信息的Makefile脚本工程路径为include/scan.mk,相关的内容如下:

$(TMP_DIR)/.$(SCAN_TARGET): $(TARGET_STAMP)
    $(call progress,Collecting $(SCAN_NAME) info: merging...)
    -cat $(FILELIST) | awk '{gsub(/\//, "_", $$0);print "$(TMP_DIR)/info/.$(SCAN_TARGET)-" $$0}' | xargs cat > $@ 2>/dev/null
    $(call progress,Collecting $(SCAN_NAME) info: done)
    echo

上面的规则依赖$(TARGET_STAMP),修改include/scan.mk,增加陷入remake的操作指令:

diff --git a/include/scan.mk b/include/scan.mk
index aee24cb3e5..e6f17cc18e 100644
--- a/include/scan.mk
+++ b/include/scan.mk
@@ -99,6 +99,7 @@ $(TMP_DIR)/info/.files-$(SCAN_TARGET).mk: $(FILELIST)
 -include $(TMP_DIR)/info/.files-$(SCAN_TARGET).mk
 
 $(TARGET_STAMP)::
+       $(debugger "trap into remake debugger")
        +( \
                $(NO_TRACE_MAKE) $(FILELIST); \
                MD5SUM=$$(cat $(FILELIST) $(OVERRIDELIST) | $(MKHASH) md5 | awk '{print $$1}'); \

新增debugger的一下行以加号"+"开始,会影响到$(NO_TRACE_MAKE)的一些参数,详见官方文档的说明。因该规则有两个冒号,且无依赖,那么该规则总是会执行。之后使用touch再次更新package/system/ubus/Makefile,找到上面的规则对应的行号,以remake V=s -j1 menuconfig命行进入调试操作界面:

$ remake V=s -j1 menuconfig
remake[1]: Entering directory '/home/yejq/program/openwrt/scripts/config'
set -e; mkdir -p ./; trap "rm -f ./.mconf-cfg.tmp" EXIT; { /bin/sh mconf-cfg.sh; } > ./.mconf-cfg.tmp; if [ ! -r mconf-cfg ] || ! cmp -s mconf-cfg ./.mconf-cfg.tmp; then true '  UPD     mconf-cfg'; mv -f ./.mconf-cfg.tmp mconf-cfg; fi
remake[1]: Leaving directory '/home/yejq/program/openwrt/scripts/config'
debugger() function called with parameter "trap into remake debugger"
:o (/home/yejq/program/openwrt/include/scan.mk:101)
/home/yejq/program/openwrt/tmp/info/.files-packageinfo.stamp
remake<<0>> where
=>#0  /home/yejq/program/openwrt/tmp/info/.files-packageinfo.stamp at /home/yejq/program/openwrt/include/scan.mk:101
  #1  /home/yejq/program/openwrt/tmp/.packageinfo at /home/yejq/program/openwrt/include/scan.mk:113
  #2  all at /home/yejq/program/openwrt/include/scan.mk:5
Most-recent (level 1) invocation:
	remake  V=ss -j1 -r -s -f include/scan.mk SCAN_TARGET=packageinfo SCAN_DIR=package SCAN_NAME=package SCAN_DEPTH=5 SCAN_EXTRA=
remake<<1>> bt
=>#0  /home/yejq/program/openwrt/tmp/info/.files-packageinfo.stamp at /home/yejq/program/openwrt/include/scan.mk:101
  #1  /home/yejq/program/openwrt/tmp/.packageinfo at /home/yejq/program/openwrt/include/scan.mk:113
  #2  all at /home/yejq/program/openwrt/include/scan.mk:5
Most-recent (level 1) invocation:
	remake  V=ss -j1 -r -s -f include/scan.mk SCAN_TARGET=packageinfo SCAN_DIR=package SCAN_NAME=package SCAN_DEPTH=5 SCAN_EXTRA=

remake提供的where命令与栈回溯指令bt功能相似,反映了Makefile中的规则间依赖的关系。通过print指令可以查看变量的值,通过expand命令(简写为x)可以对字符串展开:

remake<<2>> print TARGET_STAMP
include/scan.mk:10 (origin: makefile) TARGET_STAMP = /home/yejq/program/openwrt/tmp/info/.files-packageinfo.stamp
remake<<3>> expand $(NO_TRACE_MAKE) $(FILELIST)
remake V=ss /home/yejq/program/openwrt/tmp/info/.files-packageinfo-7109
remake<<4>> x $$(cat $(FILELIST) $(OVERRIDELIST) | $(MKHASH) md5 | awk '{print $$1}')
$(cat /home/yejq/program/openwrt/tmp/info/.files-packageinfo-7109 /home/yejq/program/openwrt/tmp/info/.overrides-packageinfo-7109 | /home/yejq/program/openwrt/staging_dir/host/bin/mkhash md5 | awk '{print $1}')

可见,该规则会调用remake生成$(FILELIST),之后会将其内空导出进行MD5的较验。如果生成了新的较验值的文件($@.$$MD5SUM,即较验值改变了),就会通过touch命令更新$(TARGET_STAMP);当$(TARGET_STAMP)被更新了,下面的规则就会被执行(否则不会被执行),从而重新计算openwrt工程中的软件包依赖关系:

$(TMP_DIR)/.$(SCAN_TARGET): $(TARGET_STAMP)
    $(call progress,Collecting $(SCAN_NAME) info: merging...)
    -cat $(FILELIST) | awk '{gsub(/\//, "_", $$0);print "$(TMP_DIR)/info/.$(SCAN_TARGET)-" $$0}' | xargs cat > $@ 2>/dev/null
    $(call progress,Collecting $(SCAN_NAME) info: done)
    echo

那么,更新了package/system/ubus/Makefile文件的时间,如何导致$(FILELIST)文件的较验值发生改变呢?实际上,其较验值没有发生改变,仅当软件包列表发生变化时才会改变。在include/scan.mk中可以找到$(FILELIST)的规则:

$(FILELIST): $(OVERRIDELIST)
    rm -f $(TMP_DIR)/info/.files-$(SCAN_TARGET)-*
    find -L $(SCAN_DIR) $(SCAN_EXTRA) -mindepth 1 $(if $(SCAN_DEPTH),-maxdepth $(SCAN_DEPTH)) -name Makefile | xargs grep -aHE 'call $(GREP_STRING)' | sed -e 's#^$(SCAN_DIR)/##' -e 's#/Makefile:.*##' | uniq | awk -v of=$(OVERRIDELIST) -f include/scan.awk > $@

可以肯定更新package/system/ubus/Makefile的文件时间,通过find指令生成的文件是相同的。关键在于include/scan.mk下面的操作:

$(TMP_DIR)/info/.files-$(SCAN_TARGET).mk: $(FILELIST)
    ( \
        cat $< | awk '{print "$(SCAN_DIR)/" $$0 "/Makefile" }' | xargs grep -HE '^ *SCAN_DEPS *= *' | awk -F: '{ gsub(/^.*DEPS *= */, "", $$2); print "DEPS_" $$1 "=" $$2 }'; \
        awk -F/ -v deps="$$DEPS" -v of="$(OVERRIDELIST)" ' \
        BEGIN { \
            while (getline < (of)) \
                override[$$NF]=$$0; \
            close(of) \
        } \
        { \
            info=$$0; \
            gsub(/\//, "_", info); \
            dir=$$0; \
            pkg=""; \
            if($$NF in override) \
                pkg=override[$$NF]; \
            print "$$(eval $$(call PackageDir," info "," dir "," pkg "))"; \
        } ' < $<; \
        true; \
    ) > $@.tmp
    mv $@.tmp $@

-include $(TMP_DIR)/info/.files-$(SCAN_TARGET).mk

根据GNU Make对包含的文件的重建机制,以上规则总是会被执行。这一规则涉及到多行变量PackageDir,它通过awk命令行工具生成了$(TMP_DIR)/info/.files-$(SCAN_TARGET).mk,即动态地生成了Makefile,通过对PackageDir的引用,导入openwrt中各个软件包的信息,其中就包括了软件包之间的依赖关系。本文提到的Collecting package info实际上是对ubus展开PackageDir生成的消息。最后,自动生成的$(TMP_DIR)/info/.files-$(SCAN_TARGET).mk部分内容如下:

DEPS_package/firmware/linux-firmware/Makefile=*.mk
DEPS_package/kernel/linux/Makefile=modules/*.mk $(TOPDIR)/target/linux/*/modules.mk $(TOPDIR)/include/netfilter.mk
$(eval $(call PackageDir,base-files,base-files,))
$(eval $(call PackageDir,boot_arm-trusted-firmware-mediatek,boot/arm-trusted-firmware-mediatek,))
$(eval $(call PackageDir,boot_arm-trusted-firmware-mvebu,boot/arm-trusted-firmware-mvebu,))
$(eval $(call PackageDir,boot_arm-trusted-firmware-rockchip,boot/arm-trusted-firmware-rockchip,))
$(eval $(call PackageDir,boot_arm-trusted-firmware-sunxi,boot/arm-trusted-firmware-sunxi,))
$(eval $(call PackageDir,boot_arm-trusted-firmware-tools,boot/arm-trusted-firmware-tools,))

remake的缺陷

上面的调试过程虽然用到了remake,但用到的不多。固然有openwrt的构建脚本比较复杂的原因,但主要在于其存在一些缺陷,导致在调试时经常崩溃:

$ remake V=s -j1 menuconfig
remake[1]: Entering directory '/home/yejq/program/openwrt/scripts/config'
set -e; mkdir -p ./; trap "rm -f ./.mconf-cfg.tmp" EXIT; { /bin/sh mconf-cfg.sh; } > ./.mconf-cfg.tmp; if [ ! -r mconf-cfg ] || ! cmp -s mconf-cfg ./.mconf-cfg.tmp; then true '  UPD     mconf-cfg'; mv -f ./.mconf-cfg.tmp mconf-cfg; fi
remake[1]: Leaving directory '/home/yejq/program/openwrt/scripts/config'
debugger() function called with parameter "trap into remake debugger"
:o (:32)
Segmentation fault (core dumped)
/home/yejq/program/openwrt/include/toplevel.mk:85: *** [prepare-tmpinfo] error 139

还有一些内存异常:

remake<<22>> continue
remake[1]: *** virtual memory exhausted.  Stop.
/home/yejq/program/openwrt/include/toplevel.mk:85: *** [prepare-tmpinfo] error 2

#0  prepare-tmpinfo at /home/yejq/program/openwrt/include/toplevel.mk:76
#1  menuconfig at /home/yejq/program/openwrt/include/toplevel.mk:132

尽管如此,remake仍然是学习GNU Make的重要工具之一:对于简单的工程,调试过程中不会出现很多的崩溃异常。

标签:SCAN,--,cfg,Makefile,mk,mconf,remake,openwrt,调试器
来源: https://blog.csdn.net/yeholmes/article/details/118279594

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有