Makefile使用:
近日,在学习imx6ull裸机开发时候,对于其Makefile文件编写一直处于懵懂状态,为方便日后深入学习,本文章以正点原子BSP-工程管理实验
闪烁小灯为例,对其中Makefile文件语法进行注释解析。
代码及解释
实验imx6ul.lds
代码如下
SECTIONS{
. = 0X87800000;
.text :
{
obj/start.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
实验中Makefile
代码如下:
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= ledc
其中赋值符有如下几种类型:
=
:在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值
:=
:不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别
?=
:表示如果变量时,前面没有被赋值,那么此变量就是当前赋值的值,如果前面已经赋过值了,那么就使用前面赋的值。
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
此处CC经赋值后,结果为:
CC = arm-linux-gnueabihf-gcc
这样做的目的是便于我们之后使用变量,其他的同理。
INCUDIRS := imx6u \
bsp/clk \
bsp/led \
bsp/delay
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay
此段代码定义的两个变量分别代表含义:
INCUDIRS
:包含头文件定义的目录;
SRCDIRS
:包含源文件定义的目录;
\
:表示语句还没有写完,这样我们可以另起一行写,使得代码更加整洁美观。
INCLUDE := $(patsubst %, -I %, $(INCUDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %.S, obj/%.o, $(SFILENDIR))
COBJS := $(patsubst %.c, obj/%.o, $(CFILENDIR))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHON:clean
其中对于各个变量进行解释:
INCLUDE
:
在找到所有".h"文件后,编译时候必须加"-I",编译源码时候需要指定头文件路径。比如bsp_ledc_h
变为-I bsp_ledc_h
。此处使用patsubst
函数完成快速添加;
SFILES
:
表示通过foreach
函数循环将变量SRCDIRS(定义的文件目录)
中的值放入dir
·中,执行wildcard
通配符获取所有变量dir/
目录下的.S
文件提取出来,循环完成后赋值给变量SFILES
。
.PHON
:
表示clean是个伪目标文件。
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
链接:
链接用来将众多的.o 文件链接到一个指定的链接位置。我们需要确定可执行文件其运行起始地址,也就是链接地址。
这里我们要区分
存储地址
和运行地址
这两个概念:
存储地址
就是可执行文件存储在哪里,可执行文件的存储地址可以随意选择。
运行地址
就是代码运行的时候所处的地址,这个我们在链接的时候就已经确定好了,代码要运行,那就必须处于运行地址处,否则代码肯定运行出错。
确定了链接地址以后就可以将前面编译出来的.o
文件链接到imx6ul.lds
指定的地址,本条命令相当于:arm-linux-gnueabihf-ld -Timx6ul.lds -o $(TARGET).elf $^
imx6ul.lds
:其相当于以前的text 0X87800000 led.o
是指定链接地址
-o
:是指定链接生成的 elf 文件名。
$^
:自动化变量$^
意思是所有依赖文件的集合
上述命令执行完以后就会在工程目录下多一个led.elf
文件。
但是led.elf
文件也不是我们最终烧写到 SD 卡中的可执行文件,我们要烧写的.bin
文件,因此还需要将 led.elf 文件转换为.bin 文件了。
$(OBJCOPY) -O binary -S $(TARGET).elf $@
格式转换
本条命令相当于arm-linux-gnueabihf-objcopy -O binary -S $(TARGET).elf $@
其类似于格式转化。
-O
:指定以什么格式输出
binary
:以二进制格式输出
-S
:不要复制源文件中的重定位信息和符号信息
-g
:不复制源文件中的调试信息
$@
:自动化变量, 意思是目标集合,这里就是ledc.bin
命令执行完成以后,生成最终的 led.bin 文件。
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
反汇编
大多数情况下我们都是用 C 语言写试验例程的,有时候需要查看其汇编代码来调试代码,因此就需要进行反汇编,一般可以将 elf 文件反汇编,本条命令相当于
arm-linux-gnueabihf-objdump -D -m arm $(TARGET).elf > $(TARGET).dis
-D
选项表示反汇编所有的段
$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
编译文件
将 .s
文件编译为.o
文件
-c
选项是编译源文件,但是不链接
-o
选项是指定编译产生的文件名字
执行上述命令以后就会编译生一个工程中所有的 C文件和汇编文件都会编译生成一个对应的.o 文件,我们需要将这.o 文件链接起来组合成可执行文件。
clean:
rm -rf $(TARGET).elf $(TARGET).bin $(TARGET).dis $(OBJS)
print:
@echo INCLUDE = $(INCLUDE)
@echo SFILES = $(SFILES)
@echo CFILES = $(CFILES)
@echo SFILENDIR = $(SFILENDIR)
@echo CFILENDIR = $(CFILENDIR)
@echo SOBJS = $(SOBJS)
@echo COBJS = $(COBJS)
@echo OBJS = $(OBJS)
clean
: 清除所有的.elf
, .bin
, .dis
和OBJS(此处变量OBJS表示.o)
文件。
print: @echo 变量
:在终端打印出变量名。
代码使用的Makefile函数详解:
patsubs函数
函数模型:
$(patsubst <pattern>, <replacement>, <text>)
查找text
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式pattern
,如果匹配的话,则以replacement
替换。
这里,pattern
可以包括通配符%
,表示任意长度的字串。如果replacement
中也包含“%”,那么,replacement
中的这个“%”将是pattern
中的那个“%”所代表的字串。其结果返回为:替换过后的字符串。
示例: $(patsubst %, -I %, bsp_led.h bsp_delay.h)
把字串bsp_led.h bsp_delay.h
符合模式%
(其表示全部的text
符合匹配,也就是选取全部的字符串text
)的单词替换成-I bsp_led.h -I bsp_delay.h
,返回结果是-I bsp_led.h -I bsp_delay.h
wildcard通配符
函数模型:
$(wildcard *.o)
当我们在变量中想使用通配符可以使用此方式
例如:
objects = *.o
这里的*.o
并不会展开,所以objects的值就是“*.o”。
那当我们想要让通配符在变量中展开,也就是让 objects 的值是所有[.o]的文件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
foreach函数
函数模型:
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数list
中的单词逐一取出放到参数var
所指定的变量中,然后再执行text
所包含的表达式。
每一次text
会返回一个字符串,循环过程中,text
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,text
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。
VPATH
VPATH
不是变量,这是一个 make 的关键字.
当我们定义VPATH
,那么如果在我们当前目录下找不到目标文件和依赖文件,它可以到我们指定不同的文件中去寻找。