type
status
slug
date
summary
tags
category
password
icon
前言:
这里是接着makefile基础部分的,初步想法是先把学到的一些用来实现我综设的项目迁移,并且积累一些makefile的高级的,更多的用法和细节。
综设项目迁移
从例子出发,先看再写
给出在基础篇咱们最后的一个juicy的例子,感觉我们项目的迁移可以借鉴这种形式。
我们再看看cubemx产生的makefile,看看汇编部分它是怎么结合的:
看了一下,还是从cubemx的makefile入手比较好,它里面有不少表达是基础篇里面没有的,我们从这个makefile里面一个一个细节看,并且也还会介绍一些gnu编译工具链里面的选项配置:
前期准备环节
1、CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
这里面
$(@:%.o=%.d)
很怪,这个是用的基础提过的String Substitution,并且是使用了它的简单等价写法:$(text:pattern=replacement)
老实说感觉使用这个function最少都是用这个shorthand,基本没有见过用原来的表达式的。这里回忆之前简单的例子:可以看到foo作为一个变量是没有美元符号的$,所以我们这里@其实就是那个automatic variable—$@代表了target。
2、OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
$(addprefix
prefix
,
names
…)
The argument names is regarded as a series of names, separated by whitespace; prefix is used as a unit. The value of prefix is prepended(追加prepend) to the front of each individual name and the resulting larger names are concatenated(串连) with single spaces between them. For example,
$(addprefix src/,foo bar)
produces the result ‘src/foo src/bar’.
同样names部分是被看成用空格分隔的一组name。
$(notdir
names
…)
Extracts(提取) all but the directory-part of each file name in names. If the file name contains no slash, it is left unchanged. Otherwise, everything through the last slash is removed from it.
A file name that ends with a slash becomes an empty string. This is unfortunate, because it means that the result does not always have the same number of whitespace-separated file names as the argument had; but we do not see any other valid alternative.
For example,
$(notdir src/foo.c hacks)
produces the result ‘foo.c hacks’.拿出names里面除了文件目录部分的所有文件名。如果文件名没有/这个标志,那就不会改动,否则所有在最后一个slash之前的内容都会被去掉。所以有个细节是如果一个文件名以slash结尾就会变成空字符串。
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
这个例子就简单了,首先在addprefix的规则里面,prefix是
$(BUILD_DIR)/
然后names来自于经过notdir处理的文件名,但是这些文件名首先来自于把源文件C_SOURCES
变量,替换后缀.c为.o得到,才会在去经过notdir处理。3、vpath %.c $(sort $(dir $(C_SOURCES)))
有问题的地方有sort函数,dir这里和notdir类似,就是从names里面取出字符串,不过我都还是把原文贴出来,也便于以后自己参考。
$(dir
names
…)
Extracts the directory-part of each file name in names. The directory-part of the file name is everything up through (and including) the last slash in it. If the file name contains no slash, the directory part is the string ‘./’. For example,
$(dir src/foo.c hacks)
produces the result ‘src/ ./’sort函数居然是放在String substitution部分
$(sort
list
)
Sorts the words of list in lexical order(词典顺序), removing duplicate words(去掉重复字). The output is a list of words separated by single spaces. Thus,
$(sort foo bar lose)
returns the value ‘bar foo lose’.
Incidentally(顺带提一下), since sort
removes duplicate words, you can use it for this purpose even if you don’t care about the sort order.sort可以去重,哪怕你不需要排序,但是你可以需要用它去重。
vapath在基础篇就说过了,是用来指明寻找target/prerequisite的路径的,后面跟一个pattern指明是对哪些pattern指定搜索路径,后面是用空格分隔的路径名称
vpath %.c $(sort $(dir $(C_SOURCES)))
这个就很清楚了,就是对
C_SOURCES
这个里面保存了C的源文件的完整路径名,从这个里面选出源文件存放的目录,这些路径就是.c文件的搜索路径。同样的对于汇编源文件(asm)也是这样连贯的处理:
build环节
看看make的默认任务
接着就看all的三个prerequisites是怎么构建的了。
第一个elf文件:
这里有个问题,Makefile这个prerequisite是什么玩意?
思考了一下,这个是更规范的写法(感觉有点东西),把Makefile算成一个依赖,这样保证Makefile如果出现修改,就会出现target的构建时间晚于这个Makefile依赖(Makefile newer),那么就会重新运行command,所以相当于检测了Makefile本身的改动,如果改动了的话,保证这些部分也会重新make
这里有cc和sz变量解释一下:
我这里是提取的关键,省略了一些小逻辑。
arm-none-eabi-size这个工具是干嘛的?你看command里面是
$(SZ) $@
参数传的是target—elf文件。计算大小的,我单独试试。
所以你编译完成才会产生这个文件信息,get了。
再看看
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
把变量打开(这里我objects就带成 test.o asm.o,但是LDFLAGS有点长,我省略了,后面会解释LDFLAGS里面的具体含义)
arm-none-eabi test.o asm.o -o ./build/LEDF4.elf
后面两个文件的构建方式类似:
这里就有个问题是
| $(BUILD_DIR)
是什么意思|
符号表示order-only依赖:通常,如果规则中依赖文件(prerequisite)中的任何一个被更新,则规则(rule)的目标(target)相应地也应该被更新。有时,需要定义一个这样的规则,在更新目标(目标文件已经存在)时只需要根据依赖文件中的部分来决定目标是否需要被重建,而不是在依赖文件的任何一个被修改后都重建目标。为了实现这一目的,相应的就需要对规则的依赖进行分类,一类是在这些依赖文件被更新后,需要更新规则(rule)的目标(target);另一类是更新这些依赖,但可不需要更新规则的目标(target)。我们把第二类称为:“order-only”依赖。书写规则时,“order-only”依赖使用管道符号“|”开始,作为目标的一个依赖文件。规则依赖列表中管道符号“|”左边的是常规依赖,管道符号右边的就是“order-only”依赖。这样的规则书写格式如下:
显然,我们的BUILD_DIR这个依赖,只需要存在就好,不需要在它存在后,有更新时去更新target。
而这个BUILD_DIR的依赖,它作为target的构建也很简单,就是创建即可:
然后就是HEX和BIN这两个工具的用处:
arm-none-eabi-objcopy是什么呢?
arm-none-eabi-objcopy
是 GNU 工具链中的一个工具,用于在不同的目标文件格式之间进行转换和操作。具体来说,
arm-none-eabi-objcopy
用于复制、转换和修改目标文件的内容。它可以执行以下操作:- 复制目标文件:将一个目标文件的内容复制到另一个文件中,可以用于备份或生成副本。
- 格式转换:将目标文件从一种格式转换为另一种格式。例如,可以将可执行文件转换为二进制文件、Intel Hex文件、S-Record文件等。
- 节(Section)操作:对目标文件中的特定节进行操作,如删除、重命名、合并等。这对于裁剪和优化目标文件非常有用。
- 符号表操作:对目标文件中的符号表进行操作,如提取符号表、重命名符号等。
-O ihex
就是转成hex文件,-O binary
就是转成二进制文件 -S
的意思是保留符号表信息,方便调试和其他符号相关的操作。这个automatic variable也得说一说:
$<
The name of the first prerequisite. If the target got its recipe from an implicit rule, this will be the first prerequisite added by the implicit rule (see Using Implicit Rules).
我们这里就应该是那个完整路径的.elf文件
.c build 出.o
和着这个一起看看:
第一个小问题:
target都是$(BUILD_DIR)/%.o,那怎么匹配呢?我猜测是:比如一个target是aaa/b.o,然后如果存在b.c文件那么就使用第一个rule进行,如果存在b.s文件且没有b.c,就使用第二个rule进行(如果有b.c就会使用第一个),但是需要找到证据验证。
这个图就很清楚的说明了这个规则,先是看target的名称对应的依赖文件是否存在,按照代码里面的顺序查找,这里.s依赖先,所以,一旦找到,就使用这个rule,看看变式:
之所以结果不同,是因为这个功能并没有那么智能,它只是检查有没有a.s或者a.b,来决定使用哪个rule,如果都没有,才会还是按照先后顺序去寻找依赖项是否对应一个rule。
这里试验的时候遇到个小trick:
莫名奇妙给我删除了,为什么呢?这是makefile的一个鉴别中间文件的机制,最后会删除中间文件,只要正常情况下写规范是不会出现这种的:
第二个是参数部分的问题:
这个是什么意思?
-c是指将源文件编译为目标文件,不进行链接操作
这里就要温习一下之前gcc编译的操作,分步是怎么进行的:
提取我们用到的点,就是使用gcc -c一次性进行预处理、编译、汇编:
输入可以是.c文件,输出.o文件
还有
-Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst))
这串参数,是什么意思呢?这里有个大聪明也在学:
-Wa,option
Pass option as an option to the assembler. If option contains commas, it is split into multiple options at the commas.
对commpiler参数的解释:
这里
=$(BUILD_DIR)/$(notdir $(<:.c=.lst))
就指定了目录是在build dir里面并且名字就是把依赖的.c文件的名字的后缀换成.lst文件即可。可以看到基本上就是这样的。
再看看
CFLAGS
里面是什么:先看看
-MMD -MP -MF”.d文件”
这些是什么意思-MMD
是 GCC(GNU 编译器集合)的选项之一,用于生成依赖关系文件。-MP
是 GCC 的选项,用于在依赖关系文件中生成一个虚拟目标。这个虚拟目标用于解决 Makefile 中可能存在的问题,例如当某个依赖项被删除时,避免导致无法正确构建的情况。-MF"$(@:%.o=%.d)"
是 GCC 的选项,用于指定依赖关系文件的输出路径和文件名。$(@:%.o=%.d)
是一个 Makefile 表达式,将目标文件的扩展名 .o
替换为 .d
,以生成相应的依赖关系文件名,$(@)
是自动变量,表示当前目标文件。什么是依赖关系文件:
依赖关系文件是一种文件,其中包含了源文件及其所依赖的头文件 在编译过程中,编译器会使用这些文件来确定源文件和头文件之间的依赖关系。这些文件通常以.d或.dep为扩展名,并且可以使用makefile等构建工具来自动创建
还有一个需要知道的,
C_DEFS
部分,需要知道,编译器也是可以通过命令行参数传入全局的宏定义的,是通过-D
选项:同样还有指定include文件的路径
-I
:例子中就是:
还有这几个参数
Wall -fdata-sections -ffunction-sections
-Wall
:这是 GCC(GNU 编译器集合)的选项之一,表示开启所有的警告信息。它会让编译器尽可能发现潜在的问题或不规范的代码,并生成相应的警告消息。开启Wall
可以帮助开发者发现潜在的错误或不良编码实践。
-fdata-sections
:这是 GCC 的选项,指示编译器将全局变量和静态变量放置在单独的数据节(section)中。这样做的好处是,在链接过程中,当某些变量未被使用时,链接器可以丢弃这些未使用的数据节,从而减小最终可执行文件的大小。
-ffunction-sections
:这是 GCC 的选项,指示编译器将每个函数放置在单独的代码节(section)中。类似于fdata-sections
,这可以在链接过程中丢弃未使用的函数节,减小最终可执行文件的大小。
同样我们看看.s文件部分是怎么处理的:
看看AS:
AS = $(PREFIX)gcc -x assembler-with-cpp
主要就是加了这个参数
-x assembler-with-cpp
- -x language Specify explicitly the language for the following input files (rather than letting the compiler choose a default based on the file name suffix). This option applies to all following input files until the next -x option. Possible values for language are:
所以我改了原来cubemx的表达,我认为应该是用
ASFLAGS
而不是CFLAGS
作为AS命令的参数重要参数了解:
LDFLAGS
用到了
LDSCRIPT
LDSCRIPT = STM32F401RETx_FLASH.ld
这个是指的链接脚本,链接脚本的作用是:
它可以让你精细控制程序在内存中的布局,非常适用于资源受限的嵌入式系统 链接脚本可以帮助你管理和优化程序的内存使用,确保代码和数据被放置在正确的位置,避免冲突和溢出问题,这对于确保程序的正确运行是非常重要的
还有关于LIBS选项:
LIBS = -lc -lm -lnosys
是一个编译器选项,用于指定在链接时需要链接的库文件。其中 -lc
表示链接 C 标准库,-lm
表示链接数学库,-lnosys
表示不链接系统库,用于指示链接器不使用操作系统提供的系统调用,而是依赖于用户提供的实现。这在某些嵌入式系统中很常见,因为它们可能没有标准的操作系统支持。Map=$(BUILD_DIR)/$(TARGET).map
指定生成的map文件路径
移植中的问题
之前工具链是putchar,这里就需要改成gcc的_write函数,重写它即可完成printf的改写。
还有就是汇编部分的改写,确实没办法,在格式上gcc工具链和mdk有较大区别,虽然指令都是那些,只能后面有时间的话改写,现在就退回之前的非汇编版本代替。
- 作者:liamY
- 链接:https://liamy.clovy.top/facf22257aad48e0ada39382575d67c4
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。