优快云仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/03/11/Makefile小结/#more
本文主要是记录一些遇到的Makefile知识。
Makefile以韦东山老师第一期裸板视频中的 第8课LCD实验的配套代码lcd_3.5_4.3 中的Makefile为例,该工程有两个Makefile,分别位于根目录和lib目录中:
{% codeblock lang:makefile [根目录Makefile]%}
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
INCLUDEDIR := ( s h e l l p w d ) / i n c l u d e C F L A G S : = − W a l l − O 2 C P P F L A G S : = − n o s t d i n c − I (shell pwd)/include CFLAGS := -Wall -O2 CPPFLAGS := -nostdinc -I (shellpwd)/includeCFLAGS:=−Wall−O2CPPFLAGS:=−nostdinc−I(INCLUDEDIR)
export CC LD AR OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS
objs := head.o init.o nand.o interrupt.o serial.o lcddrv.o/
framebuffer.o lcdlib.o main.o lib libc.a
lcd.bin: $(objs)
${LD} -Tlcd.lds -o lcd_elf $^
${OBJCOPY} -O binary -S lcd_elf $@
${OBJDUMP} -D -m arm lcd_elf > lcd.dis
.PHONY : lib/libc.a
lib/libc.a:
cd lib; make; cd …
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
make clean -C lib
rm -f lcd.bin lcd_elf lcd.dis *.o
{% endcodeblock %}
主Makefile总领全局的就这句:
{% codeblock lang:makefile %}
lcd.bin: $(objs)
{% endcodeblock %}
要生成lcd.bin,依赖于objs列举的一堆文件:head.o init.o nand.o interrupt.o serial.o lcddrv.o framebuffer.o lcdlib.o main.o lib libc.a,所以要先找到这些文件,几个.o,还有一个.a。
- .o目标文件怎么生成?
%.o:%.c和%.o:%.S是生成规则,就是依赖于.c或.S文件,使用交叉编译命令生成。 - .a目标文件怎么生成?
.a是库文件,到lib子目录里去找,在子目录里用make命令生成。
注:
**链接:**将多.o文件,或者.o文件和库文件链接成为可被操作系统执行的可执行程序(Linux环境下,可执行文件的格式为“ELF”格式)。链接器不检查函数所在的源文件,只检查所有.o文件中的定义的符号。将.o文件中使用的函数和其它.o或者库文件中的相关符号进行合并,对所有文件中的符号进行重新安排(重定位),并链接系统相关文件(程序启动文件等)最终生成可执行程序。链接过程使用GNU 的“ld”工具。
**静态库:**又称为文档文件(Archive File)。它是多个.o文件的集合。Linux中静态库文件的后缀为“.a”。静态库中的各个成员(.o文件)没有特殊的存在格式,仅仅是一个.o文件的集合。使用“ar”工具维护和管理静态库。
**共享库:**也是多个.o文件的集合,但是这些.o文件时有编译器按照一种特殊的方式生成(Linux中,共享库文件格式通常为“ELF”格式。共享库已经具备了可执行条件)。模块中各个成员的地址(变量引用和函数调用)都是相对地址。使用此共享库的程序在运行时,共享库被动态加载到内存并和主程序在内存中进行连接。多个可执行程序可共享库文件的代码段(多个程序可以共享的使用库中的某一个模块,共享代码,不共享数据)。另外共享库的成员对象可被执行(由libdl.so提供支持)。
下面进行分析:
第一至五行:
{% codeblock lang:makefile %}
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
{% endcodeblock %}
- 作用:将右边工具链名赋值给左边变量,为了简化书写;
- 分析:
- arm-linux-gcc:编译.c或.s头的C文件或汇编程序;
- arm-linux-ld:连接器,把多个.o文件或库文件连接成一个可执行文件;
- arm-linux-ar:库管理器,把多个.o文件合并成一个.o文件或静态库文件(.a文件);
- arm-linux-objcopy:转换可执行文件的格式;
- arm-linux-objdump:生成反汇编;
第七行:
{% codeblock lang:makefile %}
INCLUDEDIR := $(shell pwd)/include
{% endcodeblock %}
- 作用:将shell命令和include组成的路径立即赋值给左边变量,为了简化书写;
- 分析:Makefile中调用shell命令:$(shell 命令);
‘ = ’与‘ := ’的区别注: - “ = ”:make会将整个Makefile展开后,再决定变量的值。也就是说,变量的值将会是整个,Makefile中最后被指定的值。看例子:
{% codeblock lang:makefile %}
x = foo
y = $(x) bar
x = xyz
{% endcodeblock %}
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。- “ := ”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
{% codeblock lang:makefile %}
x := foo
y := $(x) bar
x := xyz
{% endcodeblock %}
在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。
第八行:
{% codeblock lang:makefile %}
CFLAGS := -Wall -O2
{% endcodeblock %}
- 作用:将gcc的编译参数赋值给左边变量,为了简化书写;
- 分析:-Wall显示所有编译错误或警告;-O2优化选项,编译时使用2级优化
第九行:
{% codeblock lang:makefile %}
CPPFLAGS := -nostdinc -I$(INCLUDEDIR)
{% endcodeblock %}
- 作用:将gcc编译路径参数赋值给左边变量,为了简化书写;
- 分析:-nostdinc忽略系统库目录(这里我们自定义了printf等系统函数,不能再包含系统文件里的相关函数);-I指定搜索路径;
第十一行:
{% codeblock lang:makefile %}
export CC LD AR OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS
{% endcodeblock %}
- 作用:将变量传递到下级Makefile,类似于宏;
- 分析:本文件中指的是生成lib/libc.a库文件时的Makefile;
第十三行:
{% codeblock lang:makefile %}
objs := head.o init.o nand.o interrupt.o serial.o lcddrv.o/
framebuffer.o lcdlib.o main.o lib libc.a
{% endcodeblock %}
- 作用:定义变量objs,包含了生成目标文件所需的文件,为了简化书写;
- 分析:一行代码写不完,使用/符号可继续在下行写;
第十六行:
{% codeblock lang:makefile %}
lcd.bin: $(objs)
{% endcodeblock %}
- 作用:定义生成目标lcd.bin,依赖于objs对象;
- 分析:执行这条命令时,先生成所有依赖文件,然后依次执行后面三条命令;
第十七行:
{% codeblock lang:makefile %}
${LD} -Tlcd.lds -o lcd_elf $^
{% endcodeblock %}
- 作用:根据链接脚本lcd.lds链接,输出目的文件lcd_elf,依赖全部文件;
- 分析:-T指定链接脚本;$^表示全部依赖文件;
第十八行:
{% codeblock lang:makefile %}
${OBJCOPY} -O binary -S lcd_elf $@
{% endcodeblock %}
- 作用:将lcd_elf文件转换成二进制文件;
- 分析:-O表示输出格式;-S表示不从源文件中复制重定位信息和符号信息到目标文件中;$@表示全部目标文件;
第十九行:
{% codeblock lang:makefile %}
${OBJDUMP} -D -m arm lcd_elf > lcd.dis
{% endcodeblock %}
- 作用:将lcd_elf文件反汇编为lcd.dis文件;
- 分析:-m后面跟的是cpu构架;>表示将这个程序的反汇编程序写入到led.dis这个文件中,在终端中不显示出来;
第二十一行:
{% codeblock lang:makefile %}
.PHONY : lib/libc.a
{% endcodeblock %}
- 作用:.PHONY伪目标,不要管lib/libc.a文件是否存在,都执行后面的指令;
- 分析:通常为了避免伪目标和文件重名的这种情况,使用特殊的记“.PHONY”来显示地指明一个目标是“伪目标”;
第二十二、二十三行:
{% codeblock lang:makefile %}
lib/libc.a:
cd lib; make; cd …
{% endcodeblock %}
- 作用:执行lib文件夹的Makefile,从而生成libc.a;
- 分析:执行shell指令,进入lib目录,make,退出;
第二十五至二十九行:
{% codeblock lang:makefile %}
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
{% endcodeblock %}
- 作用:依赖所有的.c和.S文件,生成所有的.o文件;
- 分析:%通配符;-c编译不链接; @ 表 示 目 标 文 件 ; @表示目标文件; @表示目标文件;<表示第一个依赖文件;
第三十一至三十三行:
{% codeblock lang:makefile %}
%.o:%.S
clean:
make clean -C lib
rm -f lcd.bin lcd_elf lcd.dis *.o
{% endcodeblock %}
- 作用:清理所有生成文件;
- 分析:-C lib即清理包括lib文件夹下生成的文件;
下面是lib目录Makefile:
{% codeblock lang:makefile [lib目录Makefile]%}
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o/
vsprintf.o
libc.a: $(objs)
${AR} -r -o $@ $^
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f libc.a *.o
{% endcodeblock %}
第一、二行:
{% codeblock lang:makefile %}
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o/
vsprintf.o
{% endcodeblock %}
- 作用:定义变量objs,包含了生成目标文件所需的文件,为了简化书写;
- 分析:一行代码写不完,使用/符号可继续在下行写;
第四、五行:
{% codeblock lang:makefile %}
libc.a: $(objs)
${AR} -r -o $@ $^
{% endcodeblock %}
- 作用:使用库管理器生成lib.a;
- 分析: @ 表 示 目 标 文 件 ; @表示目标文件; @表示目标文件;^所有依赖文件;
第七至十一行:
{% codeblock lang:makefile %}
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
{% endcodeblock %}
- 作用:依赖所有的.c和.S文件,生成所有的.o文件;
- 分析:%通配符;-c编译不链接; @ 表 示 目 标 文 件 ; @表示目标文件; @表示目标文件;<表示第一个依赖文件;
第十三、十四行:
{% codeblock lang:makefile %}
%.o:%.S
clean:
rm -f libc.a *.o
{% endcodeblock %}
- 作用:清理所有生成文件;
- 分析:-f强制删除;