make 调试方法
前言
驱动、内核等大型工程包含众多 .c .h
文件,如果手动一个个去编译这些文件是不现实的,通常的做法是使用 make 命令进行自动化编译。make 命令执行时,需要 makefile 文件,以告诉 make 命令需要怎样去编译和链接程序。
不过这些大型工程的 makefile 文件往往不止一个,且常常分散在各个层级。如果编译时报错,调试起来就比较麻烦了。联想到 C 语言 Debug 的方法,通常是在出现问题的语句前面添加打印,来判断变量的值是否符合预期,语句是否被执行等等。那么在 Makefile 中有没有类似的 Debug 方法呢?
控制 make 的函数
答案是有的,那就是控制 make 的函数(Functions That Control Make)。
make 提供了一些函数,可以检测 makefile 的运行时信息,来控制 make 的运行。
- info 函数
$(info <text ..>)
功能:打印信息
- error 函数
$(error <text ...>)
功能:产生一个致命的错误,终止 make 运行,<text …> 是错误信息。
- warning 函数
$(warning <text ...>)
功能:这个函数很像 error 函数,只是它并不会让 make 推出,只是输出一段警告信息,而 make 继续执行。
案例分析
期望:config.mk 作为 Makefile 的配置文件,ARM = y 表明想编译出 ARM 架构程序,即使用 arm-linux-gcc 进行编译。但是执行后,make 并没有按照预期使用 arm-linux-gcc 进行编译,而是使用默认的 cc 进行编译。问题出在哪呢?
先看代码
$ tree
.
├── config.mk
├── hello.c
└── Makefile
config.mk
ARM = y
Makefile
include config.mk
ifeq ($(ARM), y)
CC = arm-linux-gcc
endif
all:
$(CC) hello.c
hello.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf("hello world\n");
return EXIT_SUCCESS;
}
执行 make
$ make
cc hello.c
看到执行结果,很显然 CC 没有被正确赋值为 arm-linux-gcc,难道 ifeq ($(ARM), y) 条件不成立?测试一下
include config.mk
ifeq ($(ARM), y)
111
CC = arm-linux-gcc
endif
all:
$(CC) hello.c
在 ifeq ($(ARM), y) 下面加入错误语句 111,结果执行没有报错,说明 ifeq ($(ARM), y) 条件确实是不成立的。但是我已经包含了 config.mk 这个文件了啊,而且 config.mk 里面定义了变量 ARM,并且赋值了 y。为什么 ifeq ($(ARM), y) 条件就是不成立呢?百思不得其解!!
这时候就可以启用我们的调试方法了,先上 info 函数
include config.mk
$(info "ARM=$(ARM)")
ifeq ($(ARM), y)
111
CC = arm-linux-gcc
endif
all:
$(CC) hello.c
执行
$ make
"ARM=y "
cc hello.c
原来是 y 后面多了一个空格,导致条件判断不通过。可以看到,使用 info 函数进行 make 打印调试,可以很快速地定位问题。
其实这里 ifeq ($(ARM), y) 的写法不好,更好的写法是 ifeq ($(strip $(ARM)), y),这样,即便在 y 前后有空格,strip 函数也会帮助我们去除,增强脚本的健壮性。
include config.mk
$(info "ARM=$(ARM)")
ifeq ($(strip $(ARM)), y)
CC = arm-linux-gcc
endif
all:
$(CC) hello.c
运行
$ make
"ARM=y "
arm-linux-gcc hello.c
可以看到,就算 y 后面有空格,由于使用 strip 函数的原因,帮我们把空格去除了,ifeq ($(strip $(ARM)), y) 条件也就成立了,最终使用了 arm-linux-gcc 进行编译。
回过头来,这一切还是要归功于 info 函数给我们调试 make 带来的便利性,才能有后面我们的解决方案和优化方案。
除了 info 函数,我们还可以使用 warning 函数,它比 info 函数会打印更多的信息:文件名称和行号。
不过上述两个函数只能打印调试信息,并不能控制 make 的运行,我们在 make 大型工程时,可能 make 流程已经不符合我们的预设了,但是 make 还在继续,这是我们只能手动停止,然后向上滚动查找出错位置。其实我们可以有更好的方法,那就是使用 error 函数,可以让 make 停止在非预期分支,比如上面示例中,我们想要使用 arm-linux-gcc 进行编译,如果 CC 没能被正确赋值为 arm-linux-gcc,我们就让 make 报错停止下来,这样非常方面我们定位问题。
include config.mk
# $(info "ARM=$(ARM)")
# $(error "ARM=$(ARM)")
$(warning "ARM=$(ARM)")
ifeq ($(ARM), y)
CC = arm-linux-gcc
else
$(error "ARM=$(ARM)")
endif
all:
$(CC) hello.c
运行
$ make
Makefile:5: "ARM=y "
Makefile:10: *** "ARM=y "。 停止。