Makefile常用调试方法

时间:2021-04-30 20:25:34

转载自 陈皓《跟我一起写 Makefile》《GNU Make项目管理》

GNU make 提供了若干可以协助调试的内置函数以及命令行选项。

1、warning函数

$(warning string)函数可以放在makefile 中的任何地方,执行到该函数时,会将string输出,方便定位make执行到哪个位置。warning函数可以放在makefile 中的任何地方:开始的位置、工作目标或必要条件列表中以及命令脚本中。这让你能够在最方便查看变量的地方输出变量的值。例如:

$(warning A top-level warning)

FOO := $(warning Right-hand side of a simple variable)bar

BAZ = $(warning Right-hand side of a recursive variable)boo

$(warning A target)target: $(warning In a prerequisite list)makefile

$(BAZ)

$(warning In a command script)

ls

$(BAZ):

这会产生如下的输出:

$ 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

注意,warning函数的求值方式是按照make标准的立即和延后求值算法。虽然对BAZ的赋值动作中包含了一个warning函数,但是直到BAZ在必要条件列表中被求值后,这个信息才会被输出来。

2.命令行选项

有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:

“-n” “--just-print” “--dry-run” “--recon” 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。

“-t” “--touch” 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。

“-q” “--question” 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。

“-W <file>;” “--what-if=<file>;” “--assume-new=<file>;” “--new-file=<file>;” 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。

三个最适合用来调试的命令行选项:

--just-print(-n)

--print-database(-p)

--warn-undefined-variables

2.1 --just-print

在一个新的makefile 工作目标上,我所做的第一个测试就是以--just-print(-n)选项来调用make。这会使得make读进makefile并且输出它更新工作目标时将会执行的命令,但是不会真的执行它们。GNU make有一个方便的功能,就是允许你为将被输出的命令标上安静模式修饰符(@)。

这个选项被假设可以抑制所有命令的执行动作,然而这只在特定的状况下为真。实际上,你必须小心以对。尽管make不会运行命令脚本,但是在立即的语境之中,它会对shell函数

调用进行求值动作。例如:

Makefile常用调试方法

正如我们之前所见,_MKDIRS 简单变量的目的是触发必要目录的创建动作。如果这个Makefile 是以--just-print 选项的方式运行的,那么当make 读进Makefile 时,shell命令将会一如往常般被执行。然后,make 将会输出(但不会执行)更新$(objects)文件列表所需要进行的每个编译命令。

2.2 --print-data-base

--print-data-base(-p)是另一个你常会用到的选项。它会运行Makefile,显示GNU版权信息以及make 所运行的命令,然后输出它的内部数据库。数据库里的数据将会依种类划分成以下几个组:variables、directories、implicit rules、pattern-specific variables、files(explicit rules)以及vpath earch path。如下所示:

# GNU Make 3.80

# Copyright (C) 2002 Free Software Foundation, Inc.

# This is free software; see the source for copying conditions.

# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A

# PARTICULAR PURPOSE.

正常的命令将会在此处执行

# Make data base, printed on Thu Apr 29 20:58:13 2004

# Variables

...

# Directories

...

# Implicit Rules

...

# Pattern-specific variable values

...

# Files

...

# VPATH Search Paths

让我们更详细地查看以上这几个区段。

变量区段(variable)将会列出每个变量以及具描述性的注释:

Makefile常用调试方法

自动变量不会被显示出来,但是通过它们可以方便变量的获得,像$(<D)。注释所指出的是origin 函数所返回的变量类型(参见“较不重要的杂项函数”一节)。如果变量被定义在一个文件中,则会在注释中指出其文件名以及该定义所在的行号。简单变量和递归变量的差别在于赋值运算符。简单变量的值将会被显示成右边部分被求值的形式。

下一个区段标示为Directories,它对make 开发人员比对make 用户有用。它列出了将会被make 检查的目录,包括可能会存在的SCCS 和RCS 子目录,但它们通常不存在。对每个目录来说,make 会显示实现细节,比如设备编号、inode 以及文件名模式匹配的统计数据。

接着是Implicit Rules 区段。这个区段包含了make 数据库中所有的内置的和用户自定义的模式规则。此外,对于那些定义在文件中的规则,它们的注释将会指出文件名以及行号:

%.c %.h: %.y

# commands to execute (from `../mp3_player/makefile', line 73):

$(YACC.y) --defines $<

$(MV) y.tab.c $*.c

$(MV) y.tab.h $*.h

%: %.c

# commands to execute (built-in):

$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.c

# commands to execute (built-in):

$(COMPILE.c) $(OUTPUT_OPTION) $<

查看这个区段,是让你能够熟悉make 内置规则的变化和结构的最佳方法。当然,并非所有的内置规则都会被实现成模式规则。如果你没有找到你想要的规则,可以查看Files区段,旧式后缀规则就列在该处。

下一个区段被标示为Pattern-specific variables,此处所列出的是定义在makefile 里的模式专属变量。所谓模式专属变量,就是变量定义的有效范围被限定在相关的模式规则执行的时候。例如,模式变量YYLEXFLAG 被定义成:

%.c %.h: YYLEXFLAG := -d

%.c %.h: %.y

$(YACC.y) --defines $<

$(MV) y.tab.c $*.c

$(MV) y.tab.h $*.h

将会被显示成:

# Pattern-specific variable values

%.c :

# makefile (from `Makefile', line 1)

# YYLEXFLAG := -d

# variable set hash-table stats:

# Load=1/16=6%, Rehash=0, Collisions=0/1=0%

%.h :

# makefile (from `Makefile', line 1)

# YYLEXFLAG := -d

# variable set hash-table stats:

# Load=1/16=6%, Rehash=0, Collisions=0/1=0%

# 2 pattern-specific variable values

接着是Files 区段,此处所列出的都是与特定文件有关的自定义和后缀规则:

# Not a target:

.p.o:

# Implicit rule search has not been done.

# Modification time never checked.

# File has not been updated.

# commands to execute (built-in):

$(COMPILE.p) $(OUTPUT_OPTION) $<

lib/ui/libui.a: lib/ui/ui.o

# Implicit rule search has not been done.

# Last modified 2004-04-01 22:04:09.515625

# File has been updated.

# Successfully updated.

# commands to execute (from `../mp3_player/lib/ui/module.mk', line 3):

ar rv $@ $^

lib/codec/codec.o: ../mp3_player/lib/codec/codec.c ../mp3_player/lib/codec/codec.c

../mp3_player/include/codec/codec.h

# Implicit rule search has been done.

# Implicit/static pattern stem: `lib/codec/codec'

# Last modified 2004-04-01 22:04:08.40625

# File has been updated.

# Successfully updated.

# commands to execute (built-in):

$(COMPILE.c) $(OUTPUT_OPTION) $<

中间文件与后缀规则会被标示为Not a target,其余是工作目标。每个文件将会包含注释,用以指出make 是如何处理此规则的。被找到的文件在被显示的时候将会通过标准的vpath 搜索来找出其路径。

最后一个区段被标示为VPATH Search Paths,列出了VPATH 的值以及所有的vpath模式。

对于大规模使用eval 以及用户自定义函数来建立复杂的变量和规则的makefile 来说,查看它们的输出结果通常是确认宏是否已被扩展成预期值的唯一方法。

2.3 --warn-undefined-variables

这个选项会使得make 在未定义的变量被扩展时显示警告信息。因为未定义的变量会被扩展成空字符串,这常见于变量名称打错而且很长一段时间未被发现到。这个选项有个问题,这也是为什么我很少使用这个选项的原因,那就是许多内置规则都会包含未定义的变量以作为用户自定义值的挂钩。所以使用这个选项来运行make必然会产生许多不是错误的警告信息,而且对用户的makefile 没有什么用处。例如:

$ make --warn-undefined-variables -n

makefile:35: warning: undefined variable MAKECMDGOALS

makefile:45: warning: undefined variable CFLAGS

makefile:45: warning: undefined variable TARGET_ARCH

...

makefile:35: warning: undefined variable MAKECMDGOALS

make: warning: undefined variable CFLAGS

make: warning: undefined variable TARGET_ARCH

make: warning: undefined variable CFLAGS

make: warning: undefined variable TARGET_ARCH

...

make: warning: undefined variable LDFLAGS

make: warning: undefined variable TARGET_ARCH

make: warning: undefined variable LOADLIBES

make: warning: undefined variable LDLIBS

不过,此命令在需要捕获此类错误的某些场合上可能非常有用。

3.--debug 选项

当你需要知道make 如何分析你的依存图时,可以使用--debug 选项。除了运行调试器,这个选项是让你获得最详细信息的另一个方法。你有五个调试选项以及一个修饰符可用,分别是:basic、verbose、implicit、jobs、all 以及makefile。

如果调试选项被指定成--debug,就是在进行basic 调试;如果调试选项被指定成-d,就是在进行all调试;如果要使用选项的其他组合,则可以使用--debug=option1,option2 这个以逗号为分隔符的列表,此处的选项可以是下面任何一个单词(实际上,make 只会查看第一个字母):

3.1 basic

这是所提供的信息最不详细的基本调试功能。启用时,make会输出被发现尚未更新的工作目标并更新动作的状态。它的输出会像下面这样:

File all does not exist.

File app/player/play_mp3 does not exist.

File app/player/play_mp3.o does not exist.

Must remake target app/player/play_mp3.o.

gcc ... ../mp3_player/app/player/play_mp3.c

Successfully remade target file app/player/play_mp3.o.

3.2 verbose

这个选项会设定basic 选项,以及提供关于“哪些文件被分析、哪些必要条件不需要重建等”的额外信息:

File all does not exist.

Considering target file app/player/play_mp3.

File app/player/play_mp3 does not exist.

Considering target file app/player/play_mp3.o.

File app/player/play_mp3.o does not exist.

Pruning file ../mp3_player/app/player/play_mp3.c.

Pruning file ../mp3_player/app/player/play_mp3.c.

Pruning file ../mp3_player/include/player/play_mp3.h.

Finished prerequisites of target file app/player/play_mp3.o.

Must remake target app/player/play_mp3.o.

gcc ... ../mp3_player/app/player/play_mp3.c

Successfully remade target file app/player/play_mp3.o.

Pruning file app/player/play_mp3.o.

3.3 implicit

这个选项会设定basic 选项,以及提供关于“为每个工作目标搜索隐含规则”的额外信息:

File all does not exist.

File app/player/play_mp3 does not exist.

Looking for an implicit rule for app/player/play_mp3.

Trying pattern rule with stem play_mp3.

Trying implicit prerequisite app/player/play_mp3.o.

Found an implicit rule for app/player/play_mp3.

File app/player/play_mp3.o does not exist.

Looking for an implicit rule for app/player/play_mp3.o.

Trying pattern rule with stem play_mp3.

Trying implicit prerequisite app/player/play_mp3.c.

Found prerequisite app/player/play_mp3.c as VPATH ../mp3_player/app/

player/play_mp3.c

Found an implicit rule for app/player/play_mp3.o.

Must remake target app/player/play_mp3.o.

gcc ... ../mp3_player/app/player/play_mp3.c

Successfully remade target file app/player/play_mp3.o.

3.4 jobs

这个选项会输出被make 调用的子进程的细节,它不会启用basic 选项的功能

Got a SIGCHLD; 1 unreaped children.

gcc ... ../mp3_player/app/player/play_mp3.c

Putting child 0x10033800 (app/player/play_mp3.o) PID 576 on the chain.

Live child 0x10033800 (app/player/play_mp3.o) PID 576

Got a SIGCHLD; 1 unreaped children.

Reaping winning child 0x10033800 PID 576

Removing child 0x10033800 PID 576 from chain.

3.5 all

这会启用前面的所有选项,当你使用-d 选项时,默认会启用此功能。

3.6 makefile

它不会启用调试信息,直到Makefile 被更新—— 这包括更新任何的引入文件。如果使用此修饰符,make 会在重编译makefile 以及引入文件的时候,输出被选择的信息。这个选项会启用basic 选项,all 选项也会启用此选项。

如何调试Makefile变量

转载自:http://coolshell.cn/articles/3790.html

六、七年前写过一篇《跟我一起写Makefile》,直到今天,还有一些朋友问我一些Makefile的问题,老实说,我有一段时间没有用Makefile了,生疏了。回顾,这几年来大家问题我的问题,其实很多时候是makefile的调试问题。所以,就像我在之前的那篇关于GDB的技巧的文章中做的一样,在这里向大家介绍一个小小的调试变量的技巧。相信一定对你有用。

对于Makefile中的各种变量,可能是我们比较头痛的事了。我们要查看他们并不是很方便,需要修改makefile加入echo命令。这有时候很不方便。其实我们可以制作下面一个专门用来输出变量的makefile(假设名字叫:vars.mk)

vars.mk

%:

@echo '$*=$($*)'

d-%:

@echo '$*=$($*)'

@echo '  origin = $(origin $*)'

@echo '   value = $(value  $*)'

@echo '  flavor = $(flavor $*)'

这样一来,我们可以使用make命令的-f参数来查看makefile中的相关变量(包括make的内建变量,比如:COMPILE.c或MAKE_VERSION之类的)。注意:第二个以“d-”为前缀的目标可以用来打印关于这个变量更为详细的东西(后面有详细说明)

假设我们的makefile是这个样子(test.mk)

test.mk

OBJDIR := objdir

OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

foo = $(bar)bar = $(ugh)ugh = Huh?

CFLAGS = $(include_dirs) -O

include_dirs = -Ifoo -Ibar

CFLAGS := $(CFLAGS) -Wall

MYOBJ := a.o b.o c.o

MYSRC := $(MYOBJ:.o=.c)

那么,我们可以这样进行调试:

演示

[hchen@RHELSVR5]$ make -f test.mk -f var.mk OBJS

OBJS=objdir/foo.o objdir/bar.o objdir/baz.o

[hchen@RHELSVR5]$ make -f test.mk -f var.mk d-foo

foo=Huh?

origin = file

value = $(bar)

flavor = recursive

[hchen@RHELSVR5]$ make -f test.mk -f var.mk d-CFLAGS

CFLAGS=-Ifoo -Ibar -O -O

origin = file

value = -Ifoo -Ibar -O -O

flavor = simple

[hchen@RHELSVR5]$  make -f test.mk -f var.mk d-COMPILE.c

COMPILE.c=cc -Ifoo -Ibar -O -Wall   -c

origin = default

flavor = recursive

value = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

我们可以看到:

  • make的第一个-f后是要测试的makefile,第二个是我们的debug makefile。
  • 后面直接跟变量名,如果在变量名前加”d-“,则输出更为详细的东西。

说一说”d-” 前缀(其意为details),其中调用了下面三个参数。

  • $(origin):告诉你这个变量是来自哪儿,file表示文件,environment表示环境变量,还有environment override,command line,override,automatic等。
  • $(value):打出这个变量没有被展开的样子。比如上述示例中的 foo 变量。
  • $(flavor):有两个值,simple表示是一般展开的变量,recursive表示递归展开的变量。

make调试工具:remake

http://bashdb.sourceforge.net/remake/