Home 使用remake正确地调试Makefile
Post
Cancel

使用remake正确地调试Makefile

众所周知,GNU Make是地球上最好的构建工具,它的可读性非常好,完全不需要调试。

但是笔者眼拙,对着稍显复杂的项目没看明白,还得依靠第三方工具remake才能勉强梳理。

错误的调试方式

标题强调“正确地调试”,是因为我觉得GNU Make本身提供了错误的调试方式:

  1. 使用$(info)$(warn)$(error)等注入手段。
  2. 使用make [OPTION],它确实提供了-n(dry run模式)和-d(debug模式)选项。

第一种做法的介绍可看这里,这种方式无异于在一个C程序里每次插入printf()。侵入式的缺陷就不多说了,只能说可以快速处理有限的问题。

第二种做法看似调试,其实是dump出内部的环境以及寻找依赖的过程。思路上似乎没啥问题,可以品鉴下图dry run模式输出的信息。

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>:删除指定断点。

使用stepcontinuenextfinish可完成日常的调试任务。

一个好使的办法是continue <target>,可以直接运行并停在对应的目标上。

breakpoint 一个例子

比如这里用到数千行的Makefile,省略了单步过程,直接跳到目标vmlinux_o,此时对应于Makefile:1230。然后step两遍,由于有prepare: prepare0的依赖关系,最终定位到prepare0

NOTE1: 常用的命令缩写也适用于remake,比如bsn等等。

NOTE2: 也可以使用$(debugger)在Makefile文件内生成断点。

NOTE3: 图里多次提示文件不存在是正常的,因为现在就是构建期。

定位

使用backtrace查看当前回溯信息。

使用list,查看当前定位的行数。

使用frame,切换栈帧。

backtrace 刚才的例子·续

这些都挺直观的,没啥好解释。

观测

使用print <variable>输出变量。这个命令非常有用,比如Makefile:536行存在空变量$(LDFLAGS_vmlinux),这个变量后续会按条件进行拼接,分析很麻烦。这个时候调试只需要print LDFLAGS_vmlinux即可;同时对于宏也可以直接输出,比如print CONFIG_MODULES可以得知这个配置是否打开:

examine

使用target <target>查看指定目标的信息。注意前面的print并不能输出目标,因此用target命令。这个命令方便在于可辅助分析自动变量(比如^?+@这些符号)和隐式规则。

examine2

还有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

profile

使用remake --profile即可进行性能分析,生成callgrind调用图。

没用过,看着挺方便的,就从github搬了张演示图。

就这样吧

本文就简单分享一下也许好用的工具。remake稳定性可能不算理想,我在实测过程中发现.mod依赖识别出错,还需要自己动手修复才能完整调试。但不管怎么说,整体体验是更易于梳理Makefile。

最后愿天堂没有GNU Make。


本文已转发到知乎

References

remake – readthedocs.io
remake – Github

This post is licensed under CC BY 4.0 by the author.
Contents