众所周知,GNU Make 是地球上最好的构建工具,它的可读性非常好,完全不需要调试。
但是笔者眼拙,对着稍显复杂的项目没看明白,还得依靠第三方工具
remake
才能勉强梳理。
错误的调试方式
标题强调“正确地调试”,是因为我觉得 GNU Make 本身提供了错误的调试方式:
使用
$(info)
、
$(warn)
和
$(error)
等注入手段。
使用
make [OPTION]
,它确实提供了
-n
(dry run 模式)和
-d
(debug 模式)选项。
第一种做法的介绍可看
这里
,这种方式无异于在一个 C 程序里每次插入
printf()
。侵入式的缺陷就不多说了,只能说可以快速处理有限的问题。
第二种做法看似调试,其实是 dump 出内部的环境以及寻找依赖的过程。思路上似乎没啥问题,可以品鉴下图 dry run 模式输出的信息。
也许该买一个 100 寸的屏幕
我是根本看不懂啊!
如果你有耐心,这种办法应该能足够解决所有问题,因为它提供的信息足够详尽。但我希望有一种更好的办法,能做到指哪打哪。
正确的调试方式
正确的调试方式就应该用调试器,它可以给人运行时的、非侵入式的调试方式。
而
remake
正是如此。
简单点说它就是构建期的 gdb,语法也类似,相信各位看一眼就能找到熟悉的感觉。
NOTE:
remake
不只是调试器,虽然功能不算特别多,但还涵盖了日志跟踪以及性能分析,以及它本身是一个 GNU Make 的 fork 版本,这意味着
make
已有的选项都
大概
能用。
Getting started
我这里简单介绍调试功能,并且用 Linux 内核的
Makefile
作为示例。
要启动调试器只需
remake -X [target]
,关闭就是
quit
。
使用
break
进行断点:
break <line>
:指定当前 Makefile 行数断点。
break <target>
:指定目标断点。
delete <id>
:删除指定断点。
使用
step
、
continue
、
next
、
finish
可完成日常的调试任务。
一个好使的办法是
continue <target>
,可以直接运行并停在对应的目标上。
比如这里用到数千行的 Makefile,省略了单步过程,直接跳到目标
vmlinux_o
,此时对应于
Makefile:1230
。然后 step 两遍,由于有
prepare: prepare0
的依赖关系,最终定位到
prepare0
。
NOTE1: 常用的命令缩写也适用于
remake
,比如
b
、
s
、
n
等等。
NOTE2: 也可以使用
$(debugger)
在 Makefile 文件内生成断点。
NOTE3: 图里多次提示文件不存在是正常的,因为现在就是构建期。
使用
backtrace
查看当前回溯信息。
使用
list
,查看当前定位的行数。
使用
frame
,切换栈帧。
刚才的例子·续
这些都挺直观的,没啥好解释。
使用
print <variable>
输出变量。这个命令非常有用,比如
Makefile:536
行存在空变量
$(LDFLAGS_vmlinux)
,这个变量后续会按条件进行拼接,分析很麻烦。这个时候调试只需要
print LDFLAGS_vmlinux
即可;同时对于宏也可以直接输出,比如
print CONFIG_MODULES
可以得知这个配置是否打开:
使用
target <target>
查看指定目标的信息。注意前面的
print
并不能输出目标,因此用
target
命令。这个命令方便在于可辅助分析自动变量(比如
^?+@
这些符号)和隐式规则。
还有
info
。子命令很多,见下表。
info break
List all Breakpoints
info files
Show Read-in Files
info frame
Show Target Stack Frame
info line
Show the Current Line
info program
Show Makefile Information
info rules
Show Implicit or Pattern Rules
info target
Show Target Name
info targets
Show Targets found in Makefiles
info tasks
Show Targets with Descriptions
info variables
List all Variables
更多可参考文档:
Debugger Commands
。
正如前面所说,
remake
的功能不止于调试。这里简单列下 trace 和 profile 用法。
更友好的 trace
假设存在一个极其简单的 Makefile 文件:
all: hello world
hello:
@echo "Hello"
world:
@echo "World"
使用 remake --trace
可输出清晰的依赖关系和执行顺序:
Reading makefiles...
Updating makefiles...
Updating goal targets...
File 'all' does not exist.
File 'hello' does not exist.
Must remake target 'hello'.
Makefile:4: target 'hello' does not exist
echo "Hello"
Hello
Successfully remade target file 'hello'.
File 'world' does not exist.
Must remake target 'world'.
Makefile:7: target 'world' does not exist
echo "World"
World
Successfully remade target file 'world'.
Must remake target 'all'.
Successfully remade target file 'all'.
即使是大型项目,只要用游标卡尺确认缩进也能得出依赖的嵌套关系。
个人体验和 make -d
差不多,只是后者更加复杂,有 800+ 行的日志。
简单的 profile
使用 remake --profile
即可进行性能分析,生成 callgrind 调用图。
没用过,看着挺方便的,就从 github 搬了张演示图。
本文就简单分享一下也许好用的工具。remake
稳定性可能不算理想,我在实测过程中发现 .mod
依赖识别出错,还需要自己动手修复才能完整调试。但不管怎么说,整体体验是更易于梳理 Makefile。
最后愿天堂没有 GNU Make。
本文已转发到知乎。
References
remake – readthedocs.io
remake – Github