gcc for arm 工具链使用(二)

蒲寂离
2023-12-01

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


提示:以下是本篇文章正文内容,下面案例可供参考


五、 ld/lds 链接脚本文件的书写规则

1. 基本概念

  先看看下述的简单例子:
  以下脚本将输出文件的 text section 定位在 0x10000, data section 定位在 0x8000000:

SECTIONS 
{ 
	. = 0x10000; 
	.text : { *(.text) } 
	. = 0x8000000; 
	.data : { *(.data) } 
	.bss : { *(.bss) } 
} 

解释一下上述的例子:
1️⃣ = 0x10000 : 把定位器符号置为 0x10000 (若不指定, 则该符号的初始值为 0)。
2️⃣ .text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即 0x10000
3️⃣ = 0x8000000 :把定位器符号置为 0x8000000
4️⃣ data : { *(.data) }:将所有输入文件的.data section合并成一个.data section, 该 section 的地址被置为 0x8000000
5️⃣ bss : { *(.bss) }:将所有输入文件的.bss section 合并成一个.bss section,该 section 的地址被置为 0x8000000 +.data section 的大小。
连接器每读完一个 section 描述后, 将定位器符号的值增加该 section 的大小. 注意: 此处没有考虑对齐约束
  链接器是用来控制如何将多个输入文件拼凑成一个输出文件的,我们把输入文件内的 section 称为输入section(input section), 把输出文件内的 section 称为输出 section(output section)。每个输出 section 都包括两个地址:VMA(Virtual Memory Address, 虚拟内存地址或程序地址空间地址)和 LMA(Load Memory Address, 加载内存地址或进程地址空间地址), 通常 VMALMA 是相同的。但在嵌入式系统中, 经常存在加载地址和执行地址不同的情况: 比如将输出文件加载到开发板的 flash 中(由 LMA 指定),而在运行时将位于 flash 中的输出文件复制到SDRAM 中(由 VMA 指定),这个程序的搬移是用户自己实现的或者芯片厂商实现的(比如stm32 是在 __main中实现的)。

2. GNU-ARM 默认的链接脚本

  GNU for Arm 工具链包含一个默认的链接脚本文件,这个脚本是针对使用工具链的标准库和标准startup启动文件工程的。这个默认脚本中,不包含存储器分块的 MEMORY 指令,即只适用于单存储器的情况。可以通过下面的指令将默认的链接脚本输出到 default.lds 文件。该lds 内容比较多,这里就不贴具体内容了,感兴趣的可以看一下。

arm-none-eabi-ld   --verbose   > default.lds

3. 一个链接脚本文件的构成

  链接脚本文件主要包含两大部分(与 TI-DSP 中的 CMD 文件类似):将存储器分块的 MEMORY 和将输入段合成输出段并填充到分块后的存储器的 SECTION。可以没有 MEMORY 部分,但必须有 SECTION 部分。没有 MEMORY部分时,则暗示系统只有一种类型的存储器,比如只有 ROM 或者只有 RAM
  先来看将存储器分块的 MEMORY 部分,它是用来补充 SECTIONS 命令的,用来描述目标 CPU 中可用的内存区域。它是可选的,如果没有这个命令,LD 会认为 SECTIONS 描述的相邻的内存块之间有足够可用的内存。其实很容易理解但是却很少用,在 SECTIONS 中每个段的分布都没有考虑 ARM 能够寻址的地址区域中,ROM,RAM,FLASH 是不是连续的。如果不是连续的怎么办?1MEMORY1 就是设置各个区的起始位置,大小,属性的命令,在一个脚本中只能有一个。
  一个最简单的例子如下所示,将整个存储空间分为 FLASH 和 RAM 两个区域,这两个区域都可读可执行,但 FLASH 区域不能写。

MEMORY 
{ 
    FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K 
    RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K 
} 

MEMORY 命令的语法是:

MEMORY 
{ 
    name (attr) : ORIGIN = origin, LENGTH = len 
    ... 
} 

其中:

  • name:一个用户定义的名字,Linker 将在内部使用它,所以别把它和 SECTIONS 里用到的文件名,段名等搞重复了,它要求是独一无二的。
  • Attr :如同它的名字一样,这是内存段的属性描述(不区分大小写)。
    • `R’   Read-only sections.
    • `W’   Read/write sections.
    • `X’   Sections containing executable code.
    • `A’   Allocated sections.
    • `I’   Initialized sections.
    • `L’   Same as I.
    • `!’   Invert the sense of any of the following attributes.
  • ORIGIN:区域的起始地址。
  • LENGTH:区域的长度。

  再来看 SECTION 部分,它是脚本文件中最重要的元素,不可缺省。它的作用就是用来描述输出文件的布局。 SECTIONS 命令的语法:

SECTIONS 
{ 
    ... 
    secname start BLOCK(align) (NOLOAD) : AT ( ldadr ) 
    { contents } >region :phdr =fill 
    ... 
} 

  这些参数中,只有 secnamecontents 是必须的,其他都是可选的参数。也就说它的最简单的格式就是:

SECTIONS 
{ 
              ... 
          secname    : { 
				contents 
            }   
            ... 
} 

secname 定义了段名,其实最开始就忽略了一个重要的因素,arm-none-eabi-ld 脚本需要描述输入和输出,而表面上一看却看不出来什么是输出什么是输入,其实 secnamecontents 就是描述这两个信息的参数。secname是输出文件的段,即输出文件有哪些段,而 contents 就是描述输出文件的这个段是由哪些文件的那些段构成的。明确这个了就不难理解为什么 SECTIONS 命令什么都可以不要就是不能没有这两个参数了。

  • secname:定义段,这里的段不一定必须是数据和代码,你可以创建专门用来存放字库和图片段。
  • contents :它的语法比较复杂。

    1️⃣ 你可以简单的把输入文件的段写到代码中,此时被列的目标文件中所有的代码都被链接到.data 中去了。
             .data : { main.o led.o}
    2️⃣ 进一步,指出哪些输入文件的哪些段写入到输出文件的哪个段。下面的脚本表示输入文件 main.o.data段和.text 段,加上led.o.data 段一起输出为输出文件中的.data 段(当然了,实际应用中一般是不会将 text段和data段输出到一个段的)。
        .data : {
            main.o(.data)
            main.o(.text) // 也可以这样写 main.o(.data .text)或者 main.o(.data , .text)
            led.o(.data)
          }

    3️⃣ 还可以使用通配符指定所有文件的那些段写入到输出文件的哪个段,下面的脚本表示所有输入文件的.data段合并为输出文件中的.data 段。
             .data : { *(.data)}

  下面是一个比较完整的链接脚本,其中命令语句 ENTRY 用于定义入口地址, =20000000;是定义 VMA 地址的,另外一个可以定义 VMA 地址的地方为.text 0x30000000 : AT (_eisr_vector),即紧跟在输出段名后面、冒号前面。同样有两个地方可以定义 LMA,例子中的两个 AT 处。ALIGN 用于定义对齐方式。命令语句*(EXCLUDE_FILE(isr.o) .text)表示除去 isr.o 的所有文件的.text 段。> SRAM>FLASH 表示运行时,将相应的输出段放在那块存储器。命令语句 KEEP(*(.isr_vector))是防止.isr_vector段在链接的过程中被优化掉的。

  另外,脚本中还有一些赋值语句,比如_edata = .;,将_eisr_vector、_edata、_text、_etext 等称为特殊符号,它们虽然是在链接脚本中定义的,但是你可以在 C 程序中通过声明外部变量引用它们,比如:

extern unsigned    long    _edata; 
extern unsigned    long    _etext; 
extern unsigned    long    _end; 

  链接器会在将程序最终链接成可执行文件的时候将这些特殊符号解析成正确的值。这些特殊符号主要用于 LMAVMA 不同时的代码搬运。

MEMORY 
{ 
    FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K 
    RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K 
} 
ENTRY(_start) 
SECTIONS 
{ 
    . = 0x20000000; 
    .isr_vector : ALIGN(4) 
    { 
        KEEP(*(.isr_vector)) 
        isr.o(.text) 
        . = ALIGN(4); 
        _eisr_vector = .; 
    }>FLASH 
 
    .text    0x30000000 : AT (_eisr_vector) 
    { 
        _text = .; 
        *(EXCLUDE_FILE(isr.o) .text) 
        *(.rodata) 
        . = ALIGN(4); 
        _etext = .; 
    } > FLASH 
 
    .data : 
    {   
        _data = .; 
        *(.data) 
        . = ALIGN(4); 
        _edata = . ; 
    } > SRAM    AT>_etext   
 
    /* .bss section which is used for uninitialized data */ 
    .bss (NOLOAD) : 
    { 
        _bss = . ; 
        *(.bss) 
        . = ALIGN(4); 
        _ebss = . ; 
    } > SRAM 
 
    _end = . ; 
} 

六、 Makefile 简要教程

1. Makefile 简介

Makefile 是和 make 命令一起配合使用的,很多大型项目的编译都是通过 Makefile 来组织的,如果没有 Makefile,那很多项目中各种库和代码之间的依赖关系不知会多复杂。Makefile 的组织流程的能力如此之强,不仅可以用来编译项目, 还可以用来组织我们平时的一些日常操作. 这个需要大家发挥自己的想象力。

1.1 Makefile 主要的 5 个部分 (显示规则, 隐晦规则, 变量定义, 文件指示, 注释)

Makefile 基本格式如下:

target ... : prerequisites ... 
        command 
        ... 

其中,
target - 目标文件, 可以是 Object File, 也可以是可执行文件
prerequisites - 生成 target 所需要的文件或者目标
command - make 需要执行的命令 (任意的 shell 命令), Makefile 中的命令必须以 [tab] 开头
几个注意点:
显示规则 :: 说明如何生成一个或多个目标文件(包括 生成的文件, 文件的依赖文件, 生成的命令)
隐晦规则 :: make 的自动推导功能所执行的规则
变量定义 :: Makefile 中定义的变量
文件指示 :: Makefile 中引用其他 Makefile; 指定 Makefile 中有效部分; 定义一个多行命令
注释   :: Makefile 只有行注释 “#”, 如果要使用或者输出"#"字符, 需要进行转义, “#”

1.2 GNU make 的工作过程

  1. 读入主 Makefile (主 Makefile 中可以引用其他 Makefile)
  2. 读入被 include 的其他 Makefile
  3. 初始化文件中的变量
  4. 推导隐晦规则, 并分析所有规则
  5. 为所有的目标文件创建依赖关系链
  6. 根据依赖关系, 决定哪些目标要重新生成
  7. 执行生成命令

2. Makefile 初级语法

2.1 Makefile 规则

2.1.1 规则语法

规则主要有 2 部分:依赖关系 和 生成目标的方法.
语法有以下 2 种:(command 太长时,可以用 “\” 作为换行符)

target ... : prerequisites ... 
        command 
        ... 
target ... : prerequisites ; command 
        command 
        ... 

2.1.2 规则中的通配符

*        :: 表示任意一个或多个字符
?        :: 表示任意一个字符
[…] :: ex. [abcd] 表示 a,b,c,d 中任意一个字符, [^abcd]表示除 a,b,c,d 以外的字符, [0-9]表示 0~9 中任意一个数字
~       :: 表示用户的 home 目录

2.1.3 路径搜索

  当一个 Makefile 中涉及到大量源文件时(这些源文件和 Makefile 极有可能不在同一个目录中)。这时, 最好将源文件的路径明确在 Makefile 中,便于编译时查找。Makefile 中有个特殊的变量 VPATH 和关键字 vpath 都是完成这个功能的。指定了 VPATH 和/或 vpath 之后, 如果当前目录中没有找到相应文件或依赖的文件,Makefile 会到 VPATH 和/或 vpath 指定的路径中再去查找。VPATH 用于设定所有类型的文件的搜索路径,vpath 用于设定指定后缀类型文件的搜索路径。

VPATH 的使用方法:
VPATH = directorie1 : directorie2……
vpath 使用方法:
vpath :: 当前目录中找不到文件时, 就从中搜索
vpath :: 符合格式的文件, 就从中搜索
vpath :: 清除符合格式的文件搜索路径
vpath :: 清除所有已经设置好的文件路径

#  示例 1 -  当前目录中找不到文件时,  按顺序从  src 目录  ../parent-dir 目录中查找文件 
VPATH src:../parent-dir       
 
#  示例 2 - .h 结尾的文件都从  ./header  目录中查找 
VPATH %.h ./header 
 
#  示例 3 -  清除示例 2 中设置的规则 
VPATH %.h 
 
#  示例 4 -  清除所有 VPATH 的设置 
VPATH 

2.2 Makefile 中的变量

2.2.1 变量定义 ( = or := or ?=)

OBJS = programA.o programB.o 
OBJS-ADD = $(OBJS) programC.o 
#  或者 
OBJS := programA.o programB.o 
OBJS-ADD := $(OBJS) programC.o 
#  或者 
OBJS ?= programA.o programB.o 
OBJS-ADD ?= $(OBJS) programC.o 

其区别在于,

  • := 只能使用前面定义好的变量,
  • =可以使用后面定义的变量,
  • ?=只有前面没有定义时才起作用。

示例如下:

# Makefile 内容 
OBJS2 = $(OBJS1) programC.o 
OBJS1 = programA.o programB.o 
all: 
        @echo $(OBJS2) 
# bash 中执行  make,  可以看出虽然  OBJS1  是在  OBJS2  之后定义的,  但在  OBJS2 中可以提前使用 
$ make 
programA.o programB.o programC.o 
# Makefile 内容 
OBJS2 := $(OBJS1) programC.o 
OBJS1 := programA.o programB.o 
all: 
        @echo $(OBJS2) 
# bash 中执行  make,  可以看出  OBJS2  中的  $(OBJS1)  为空 
$ make 
programC.o 
# Makefile 内容 
OBJS2 ?=    programC.o 
OBJS1 = programA.o 
OBJS1 ?= programB.o 
all: 
@echo $(OBJS1) 
@echo $(OBJS2) 
# bash 中执行  make,  可以看出  OBJS2  为 programC.o,OBJS1 为 programA.o 
$ make 
programA.o 
programC.o 

2.2.2 变量替换

# Makefile 内容 
SRCS := programA.c programB.c programC.c 
OBJS := $(SRCS:%.c=%.o) 
 
all: 
    @echo "SRCS: " $(SRCS)
    @echo "OBJS: " $(OBJS) 
 
# bash 中运行 make 
$ make 
SRCS:  programA.c programB.c programC.c 
OBJS:  programA.o programB.o programC.o  

2.2.3 变量追加值 +=

# Makefile 内容 
SRCS := programA.c programB.c programC.c 
SRCS += programD.c 
all: 
    @echo "SRCS: " $(SRCS) 
 
# bash 中运行 make 
$ make 
SRCS:  programA.c programB.c programC.c programD.c 

2.2.4 变量覆盖 override

  一般情况下,make 命令行中定义的变量优先级最高,它可以覆盖掉 makefile 脚本中定义的变量。如果想要
使 Makefile 中定义的变量比 make 命令参数中指定的变量优先级高,则需要附加 override 属性。
语法:
override <variable> = <value>
override <variable> := <value>
override <variable> += <value>

# Makefile 内容  (没有用 override) 
SRCS := programA.c programB.c programC.c 
all: 
        @echo "SRCS: " $(SRCS) 
# bash 中运行 make 
$ make SRCS=nothing 
SRCS:    nothing 
################################################# 
# Makefile 内容  (用 override) 
override SRCS := programA.c programB.c programC.c 
all: 
        @echo "SRCS: " $(SRCS) 
# bash 中运行 make 
$ make SRCS=nothing 
SRCS:    programA.c programB.c programC.c 

2.2.5 目标变量

  作用是使变量的作用域仅限于这个目标(target),而不像之前例子中定义的变量,对整个 Makefile 都有效。

语法:
<target ...> :: <variable-assignment>
<target ...> :: override <variable-assignment>(override 作用参见 变量覆盖的介绍)

# Makefile  内容 
SRCS := programA.c programB.c programC.c 
 
target1: TARGET1-SRCS := programD.c 
target1: 
        @echo "SRCS: " $(SRCS) 
@echo "SRCS: " $(TARGET1-SRCS) 
 
target2: 
        @echo "SRCS: " $(SRCS) 
        @echo "SRCS: " $(TARGET1-SRCS) 
 
# bash 中执行 make 
$ make target1 
SRCS:    programA.c programB.c programC.c 
SRCS:    programD.c 
 
$ make target2          <-- target2 中显示不了  $(TARGET1-SRCS) 
SRCS:    programA.c programB.c programC.c 
SRCS: 

2.3 Makefile 命令前缀

Makefile 中书写 shell 命令时可以加 2 种前缀 @-, 或者不用前缀.
3 种格式的 shell 命令区别如下:

  • 不用前缀 :: 输出执行的命令以及命令执行的结果, 出错的话停止执行
  • 前缀 @ :: 只输出命令执行的结果, 出错的话停止执行
  • 前缀 - :: 命令执行有错的话, 忽略错误, 继续执行
# Makefile  内容  (不用前缀) 
all: 
        echo "没有前缀" 
        cat this_file_not_exist 
        echo "错误之后的命令"              <--  这条命令不会被执行 
 
# bash 中执行  make 
$ make 
echo "没有前缀"                          <--  命令本身显示出来 
没有前缀                                        <--  命令执行结果显示出来 
cat this_file_not_exist 
cat: this_file_not_exist: No such file or directory 
make: *** [all] Error 1 
########################################################### 
# Makefile  内容  (前缀  @) 
all: 
        @echo "没有前缀" 
        @cat this_file_not_exist 
        @echo "错误之后的命令"              <--  这条命令不会被执行 

# bash 中执行  make 
$ make 
没有前缀                                                  <--  只有命令执行的结果,  不显示命令本身 
cat: this_file_not_exist: No such file or directory 
make: *** [all] Error 1 
########################################################### 
# Makefile  内容  (前缀  -) 
all: 
        -echo "没有前缀" 
        -cat this_file_not_exist 
-echo "错误之后的命令"              <--  这条命令会被执行 
 
# bash 中执行  make 
$ make 
echo "没有前缀"                          <--  命令本身显示出来 
没有前缀                                        <--  命令执行结果显示出来 
cat this_file_not_exist 
cat: this_file_not_exist: No such file or directory 
make: [all] Error 1 (ignored) 
echo "错误之后的命令"              <--  出错之后的命令也会显示 
错误之后的命令                            <--  出错之后的命令也会执行 

2.4 伪目标

伪目标并不是一个"目标(target)",不像真正的目标那样会生成一个目标文件。
典型的伪目标是 Makefile 中用来清理编译过程中中间文件的 clean 伪目标, 一般格式如下:

.PHONY: clean   <-- 这句没有也行, 但是最好加上 
clean: 
    -rm -f *.o 

2.5 引用其他的 Makefile

语法: include <filename> (filename 可以包含通配符和路径),最好在 include 前面加一个“-”

# Makefile  内容 
all: 
        @echo "主  Makefile begin" 
        @make other-all 
        @echo "主  Makefile end" 
 
include ./other/Makefile 
 
# ./other/Makefile  内容 
other-all: 
        @echo "other makefile begin" 
        @echo "other makefile end" 
 
# bash 中执行  make 
$ ll 
total 20K 
-rw-r--r-- 1 wangyubin wangyubin    125 Sep 23 16:13 Makefile
-rw-r--r-- 1 wangyubin wangyubin    11K Sep 23 16:15 makefile.org      <--  这个文件不用管 
drwxr-xr-x 2 wangyubin wangyubin 4.0K Sep 23 16:11 other 
$ ll other/ 
total 4.0K 
-rw-r--r-- 1 wangyubin wangyubin 71 Sep 23 16:11 Makefile 
 
$ make 
主  Makefile begin 
make[1]: Entering directory `/path/to/test/makefile' 
other makefile begin 
other makefile end 
make[1]: Leaving directory `/path/to/test/makefile' 
主  Makefile end 

2.6 查看 C 文件的依赖关系

  写 Makefile 的时候,需要确定每个目标的依赖关系。GNU 提供一个机制可以查看 C 代码文件依赖那些文件,这样我们在写 Makefile 目标的时候就不用打开 C 源码来看其依赖那些文件了。前面已经有所涉及。 比如, 下面命令显示内核源码中 virt/kvm/kvm_main.c 中的依赖关系

$ cd virt/kvm/ 
$ gcc -MM kvm_main.c   
kvm_main.o:  kvm_main.c  iodev.h  coalesced_mmio.h  async_pf.h      <--  这句就可以加到  Makefile  中作为编译 
kvm_main.o  的依赖关系 

2.7 make 退出码

Makefile 的退出码有以下 3 种:

  • 0 :: 表示成功执行
  • 1 :: 表示 make 命令出现了错误
  • 2 :: 使用了 “-q” 选项, 并且 make 使得一些目标不需要更新

2.8 指定 Makefile, 指定特定目标

  默认执行 make 命令时,GNU make 在当前目录下依次搜索下面 3 个文件 “GNUmakefile”, “makefile”, “Makefile”。找到对应文件之后,就开始执行此文件中的第一个目标(target)。如果找不到这 3 个文件就报错.
非默认情况下, 可以在 make 命令中指定特定的 Makefile 和特定的 目标.

$ make    –f    anyfile    clean 

2.9 make参数介绍

make的参数很多, 可以通过 make –h/–help 去查看, 下面只介绍几个我认为比较有用的.

参数含义
--debug[=FLAGS]输出 make 的调试信息, FLAGS可以是 a, b, v
-j [N]同时运行的命令的个数, 也就是多线程执行 Makefile
-r禁止使用任何隐含规则
-R禁止使用任何作用于变量上的隐含规则
-B假设所有目标都有更新, 即强制重编译
-f指定任意文件执行
-C [directory]改变当前目录,用于嵌套执行 make
-w打印出 make 过程中的当前目录,用具记录嵌套执行的过程

2.10 Makefile 隐含规则

这里只列一个和编译 C 相关的。编译 C 时,<n>.o 的目标会自动推导为 <n>.c

# Makefile  中 
main : main.o 
        gcc -o main main.o 
 
#会自动变为: 
main : main.o 
        gcc -o main main.o 
 
main.o: main.c        <-- main.o  这个目标是隐含生成的 
        gcc -c main.c 

2.11 隐含规则中的 命令变量 和 命令参数变量

2.11.1 命令变量, 书写 Makefile 可以直接写 shell 时用这些变量.

下面只列出一些 C 相关的,最好还是自己重新定义

变量名含义
RMrm -f
ARar
CCcc
CXXg++

2.11.2 命令参数变量

最好还是自己重新定义

变量名含义
ARFLAGSAR 命令的参数
CFLAGSC 语言编译器的参数
CXXFLAGSC++语言编译器的参数

2.12 自动变量

Makefile 中很多时候通过自动变量来简化书写, 各个自动变量的含义如下:

自动变量含义
$@目标集合
$%当目标是函数库文件时, 表示其中的目标文件名
$<第一个依赖目标. 如果依赖目标是多个, 逐个表示依赖目标
$?比目标新的依赖目标的集合
$^所有依赖目标的集合, 会去除重复的依赖目标
$+所有依赖目标的集合, 不会去除重复的依赖目标
$*这个是 GNU make 特有的, 其它的 make 不一定支持

3. Makefile 高级语法

3.1 嵌套 Makefile

  在 Makefile 初级语法中已经提到过引用其它 Makefile 的方法。 这里有另一种写法,且可以向引用的其它
Makefile 传递参数。
示例: (不传递参数, 只是调用子文件夹 other 中的 Makefile)

# Makefile  内容 
all: 
        @echo "主  Makefile begin" 
        @cd ./other && make 
        @echo "主  Makefile end" 
 
# ./other/Makefile  内容 
other-all: 
        @echo "other makefile begin" 
        @echo "other makefile end" 
 
# bash 中执行  make 
$ ll 
total 28K 
-rw-r--r-- 1 wangyubin wangyubin    104 Sep 23 20:43 Makefile 
-rw-r--r-- 1 wangyubin wangyubin    17K Sep 23 20:44 makefile.org      <--  这个文件不用管 
drwxr-xr-x 2 wangyubin wangyubin 4.0K Sep 23 20:42 other 
$ ll other/ 
total 4.0K 
-rw-r--r-- 1 wangyubin wangyubin 71 Sep 23 16:11 Makefile 
 
$ make 
主  Makefile begin 
make[1]: Entering directory `/path/to/test/makefile/other' 
other makefile begin 
other makefile end 
make[1]: Leaving directory `/path/to/test/makefile/other' 
主  Makefile end 

示例: (用 export 传递参数)

# Makefile  内容 
export VALUE1 := export.c        <--  用了  export,  此变量能够传递到  ./other/Makefile  中 
VALUE2 := no-export.c            <--  此变量不能传递到  ./other/Makefile  中 
 
all: 
        @echo "主  Makefile begin" 
        @cd ./other && make 
        @echo "主  Makefile end"
# ./other/Makefile  内容 
other-all: 
        @echo "other makefile begin" 
        @echo "VALUE1: " $(VALUE1) 
        @echo "VALUE2: " $(VALUE2) 
        @echo "other makefile end" 
 
# bash 中执行  make 
$ make 
主  Makefile begin 
make[1]: Entering directory `/path/to/test/makefile/other' 
other makefile begin 
VALUE1:    export.c                <-- VALUE1  传递成功 
VALUE2:                                    <-- VALUE2  传递失败 
other makefile end 
make[1]: Leaving directory `/path/to/test/makefile/other' 
主  Makefile end 

补充 export 语法格式如下:
• export variable = value
• export variable := value
• export variable += value

3.2 定义命令包

命令包有点像是个函数, 将连续的命令合成一条, 减少 Makefile 中的代码量, 便于以后维护.
语法:
define <command-name>
command
...
endef

# Makefile  内容 
define run-hello-makefile 
@echo -n "Hello" 
@echo " Makefile!" 
@echo "这里可以执行多条  Shell  命令!" 
endef 
 
all: 
        $(run-hello-makefile) 
 
# bash  中运行 make 
$ make 
Hello Makefile! 
这里可以执行多条  Shell  命令! 

3.3 条件判断

条件判断的关键字主要有 ifeq ifneq ifdef ifndef

<conditional-directive> 
<text-if-true> 
endif 
 
#  或者 
<conditional-directive> 
<text-if-true> 
else 
<text-if-false> 
endif 

示例: ifeq 的例子, ifneq 和 ifeq 的使用方法类似, 就是取反

# Makefile  内容 
all: 
ifeq ("aa", "bb") 
        @echo "equal" 
else 
        @echo "not equal" 
endif 
 
# bash  中执行  make 
$ make 
not equal 

示例: ifdef 的例子, ifndef 和 ifdef 的使用方法类似, 就是取反

# Makefile  内容 
SRCS := program.c 
 
all: 
ifdef SRCS 
        @echo $(SRCS) 
else 
        @echo "no SRCS" 
endif 
 
# bash  中执行  make 
$ make 
program.c 

3.4 Makefile 中的函数

Makefile 中自带了一些函数, 利用这些函数可以简化 Makefile 的编写.
函数调用语法如下:
$(<function> <arguments>)

或者

${<function> <arguments>}

  • <function> 是函数名
  • <arguments> 是函数参数

3.4.1 字符串函数

字符串替换函数: $(subst <from>,<to>,<text>)
功能: 把字符串<text> 中的 <from> 替换为 <to>
返回: 替换过的字符串

# Makefile  内容 
all: 
        @echo $(subst t,e,maktfilt)    <--  将 t 替换为 e 
 
# bash  中执行  make 
$ make 
makefile 

模式字符串替换函数: $(patsubst <pattern>,<replacement>,<text>)
功 能 : 查 找<text> 中 的 单 词 ( 单 词 以 " 空 格 ", “tab”, " 换 行 " 来 分 割 ) 是 否 符 合 <pattern>, 符 合 的 话 , 用 <replacement> 替代.
返回: 替换过的字符串

# Makefile  内容 
all: 
        @echo $(patsubst %.c,%.o,programA.c programB.c) 
 
# bash  中执行  make 
$ make 
programA.o programB.o 

去空格函数:$(strip <string>)
功能: 去掉 <string> 字符串中开头和结尾的空字符
返回: 被去掉空格的字符串值

# Makefile  内容 
VAL := "              aa    bb    cc " 
 
all: 
        @echo "去除空格前: " $(VAL) 
        @echo "去除空格后: " $(strip $(VAL)) 
 
# bash  中执行  make 
$ make 
去除空格前:                  aa    bb    cc   
去除空格后:      aa bb cc 

查找字符串函数: $(findstring <find>,<in>)
功能: 在字符串 <in> 中查找 <find> 字符串
返回: 如果找到, 返回 <find> 字符串, 否则返回空字符串

# Makefile  内容 
VAL := "              aa    bb    cc " 
 
all: 
        @echo $(findstring aa,$(VAL)) 
        @echo $(findstring ab,$(VAL)) 
 
# bash  中执行  make 
$ make 
aa 

过滤函数:$(filter <pattern...>,<text>)
功能: 以 <pattern> 模式过滤字符串 <text>, 保留 符合模式 <pattern> 的单词, 可以有多个模式
返回: 符合模式 <pattern> 的字符串

# Makefile  内容 
all: 
        @echo $(filter %.o %.a,program.c program.o program.a) 
 
 
# bash  中执行  make 
$ make 
program.o program.a 

反过滤函数: $(filter-out <pattern...>,<text>)
功能: 以 <pattern> 模式过滤字符串 <text>, 去除 符合模式 <pattern> 的单词, 可以有多个模式
返回: 不符合模式 <pattern> 的字符串

# Makefile  内容 
all: 
        @echo $(filter-out %.o %.a,program.c program.o program.a) 
 
# bash  中执行  make 
$ make 
program.c 

排序函数: $(sort <list>)
功能: 给字符串 <list> 中的单词排序 (升序)
返回: 排序后的字符串

# Makefile  内容 
all: 
        @echo $(sort bac abc acb cab) 
 
# bash  中执行  make 
$ make 
abc acb bac cab 

取单词函数: $(word <n>,<text>)
功能: 取字符串 <text> 中的 第<n>个单词 (n 从 1 开始)
返回: <text> 中的第<n>个单词, 如果<n><text> 中单词个数要大, 则返回空字符串

# Makefile  内容 
all: 
        @echo $(word 1,aa bb cc dd) 
        @echo $(word 5,aa bb cc dd) 
        @echo $(word 4,aa bb cc dd) 
 
# bash  中执行  make 
$ make 
aa 
 
dd 

取单词串函数: $(wordlist <s>,<e>,<text>)
功能: 从字符串<text>中取从<s>开始到<e>的单词串.<s><e>是一个数字.
返回: 从<s><e>的字符串

# Makefile  内容 
all: 
        @echo $(wordlist 1,3,aa bb cc dd) 
        @echo $(word 5,6,aa bb cc dd) 
        @echo $(word 2,5,aa bb cc dd) 
 
 
# bash  中执行  make 
$ make 
aa bb cc 
 
bb 

单词个数统计函数: $(words <text>)
功能: 统计字符串 <text> 中单词的个数
返回: 单词个数

# Makefile  内容 
 
all: 
        @echo $(words aa bb cc dd) 
        @echo $(words aabbccdd) 
        @echo $(words ) 
 
# bash  中执行  make 
$ make
4 
1
0 

首单词函数: $(firstword <text>)
功能: 取字符串<text> 中的第一个单词
返回: 字符串 <text> 中的第一个单词

# Makefile  内容 
all: 
        @echo $(firstword aa bb cc dd) 
        @echo $(firstword aabbccdd) 
        @echo $(firstword ) 
 
# bash  中执行  make 
$ make 
aa 
aabbccdd 

3.4.2 文件名函数

取目录函数: $(dir <names...>)
功能: 从文件名序列 <names> 中取出目录部分
返回: 文件名序列 <names> 中的目录部分

# Makefile  内容 
all: 
        @echo $(dir /home/a.c ./bb.c ../c.c d.c) 
 
# bash  中执行  make 
$ make 
/home/ ./ ../ ./ 

取文件函数:$(notdir <names...>)
功能: 从文件名序列 <names> 中取出非目录部分
返回: 文件名序列 <names> 中的非目录部分

# Makefile  内容 
all: 
        @echo $(notdir /home/a.c ./bb.c ../c.c d.c) 
 
# bash  中执行  make 
$ make 
a.c bb.c c.c d.c 

取后缀函数: $(suffix <names...>)
功能: 从文件名序列 <names> 中取出各个文件名的后缀
返回: 文件名序列 <names> 中各个文件名的后缀, 没有后缀则返回空字符串

# Makefile  内容 
all: 
        @echo $(suffix /home/a.c ./b.o ../c.a d) 
 
# bash  中执行  make 
$ make 
.c .o .a 

取前缀函数: $(basename <names...>)
功能: 从文件名序列 <names> 中取出各个文件名的前缀
返回: 文件名序列 <names> 中各个文件名的前缀, 没有前缀则返回空字符串

# Makefile  内容 
all: 
        @echo $(basename /home/a.c ./b.o ../c.a /home/.d .e) 
 
# bash  中执行  make 
$ make 
/home/a ./b ../c /home/ 

加后缀函数: $(addsuffix <suffix>,<names...>)
功能: 把后缀 <suffix> 加到 <names> 中的每个单词后面
返回: 加过后缀的文件名序列

# Makefile  内容 
all: 
        @echo $(addsuffix .c,/home/a b ./c.o ../d.c) 
 
 
# bash  中执行  make 
$ make 
/home/a.c b.c ./c.o.c ../d.c.c 

加前缀函数: $(addprefix <prefix>,<names...>)
功能: 把前缀 <prefix> 加到 <names> 中的每个单词前面
返回: 加过前缀的文件名序列

# Makefile  内容 
all: 
        @echo $(addprefix test_,/home/a.c b.c ./d.c) 
 
# bash  中执行  make 
$ make 
test_/home/a.c test_b.c test_./d.c 

连接函数: $(join <list1>,<list2>)
功能: <list2> 中对应的单词加到 <list1> 后面
返回: 连接后的字符串

# Makefile  内容 
all: 
        @echo $(join a b c d,1 2 3 4) 
        @echo $(join a b c d,1 2 3 4 5) 
        @echo $(join a b c d e,1 2 3 4) 
 
# bash  中执行  make 
$ make 
a1 b2 c3 d4 
a1 b2 c3 d4 5 
a1 b2 c3 d4 e 

3.4.3 foreach

语法: $(foreach <var>,<list>,<text>)

# Makefile  内容 
targets := a b c d 
objects := $(foreach i,$(targets),$(i).o) 
 
all: 
        @echo $(targets) 
        @echo $(objects) 
 
# bash  中执行  make 
$ make 
a b c d 
a.o b.o c.o d.o 

3.4.4 if

这里的 if 是个函数, 和前面的条件判断不一样, 前面的条件判断属于 Makefile 的关键字
语法:
$(if <condition>,<then-part>)
$(if <condition>,<then-part>,<else-part>)

# Makefile  内容 
val := a 
objects := $(if $(val),$(val).o,nothing) 
no-objects := $(if $(no-val),$(val).o,nothing) 
 
all: 
        @echo $(objects) 
        @echo $(no-objects) 
 
# bash  中执行  make 
$ make 
a.o 
nothing 

3.4.5 origin - 判断变量的来源

语法:$(origin <variable>)
返回值有如下类型:

类型含义
undefined没有定义过
default是个默认的定义, 比如 CC 变量
environment是个环境变量, 并且 make 时没有使用 -e 参数
file定义在 Makefile 中
command line定义在命令行中
override被 override 重新定义过
automatic自动化变量
# Makefile  内容 
val-in-file := test-file 
override val-override := test-override 
 
all: 
        @echo $(origin not-define)        # not-define  没有定义 
        @echo $(origin CC)                        # CC  是 Makefile 默认定义的变量 
        @echo $(origin PATH)                  # PATH  是  bash  环境变量 
        @echo $(origin val-in-file)        #  此 Makefile 中定义的变量 
        @echo $(origin val-in-cmd)        #  这个变量会加在  make  的参数中 
        @echo $(origin val-override) #  此 Makefile 中定义的 override 变量 
        @echo $(origin @)                          #  自动变量,  具体前面的介绍 
 
# bash  中执行  make 
$ make val-in-cmd=val-cmd 

undefined 
default 
environment 
file 
command line 
override 
automatic 

3.4.7 shell

语法: $(shell <shell command>)
它的作用就是执行一个 shell 命令, 并将 shell 命令的结果作为函数的返回.

3.4.8 make 控制函数

产生一个致命错误: $(error <text ...>)
功能: 输出错误信息, 停止 Makefile 的运行

# Makefile  内容 
all: 
        $(error there is an error!) 
        @echo "这里不会执行!" 
 
# bash  中执行  make 
$ make 
Makefile:2: *** there is an error!.    Stop. 

输出警告: $(warning <text ...>)
功能: 输出警告信息, Makefile 继续运行

# Makefile  内容 
all: 
        $(warning there is an warning!) 
        @echo "这里会执行!" 
 
# bash  中执行  make 
$ make 
Makefile:2: there is an warning! 
这里会执行! 

3.5 Makefile 中一些 GNU 约定俗成的伪目标

如果有过在 Linux 上从源码安装软件的经历的话, 就会对 make clean, make install 比较熟悉.
像 clean, install 这些伪目标, 广为人知, 不用解释就大家知道是什么意思了.
下面列举一些常用的伪目标, 如果在自己项目的 Makefile 合理使用这些伪目标的话, 可以让我们自己的 Makefile
看起来更专业, 呵呵 

伪目标含义
all所有目标的目标,其功能一般是编译所有的目标
clean删除所有被 make 创建的文件
install安装已编译好的程序,其实就是把目标可执行文件拷贝到指定的目录中去
print列出改变过的源文件
tar把源程序打包备份. 也就是一个 tar 文件
dist创建一个压缩文件, 一般是把 tar 文件压成 Z 文件. 或是 gz 文件
TAGS更新所有的目标, 以备完整地重编译使用
check 或 test一般用来测试 makefile 的流程

七、 一个通用 Makefile 的实现

  实现的功能描述如下:假设一个工程的工根目录中存在多个不同深度的子目录,子目录用 subxxxx 表示,
Subx 为第一级子目录,subxx 为第二集子目录,以此类推。比如,第一级子目录包括 sub1、sub2、sub3,sub1
下面的子目录为 sub1x,sub2 下面的子目录为 sub2x,sub3 下面的子目录为 sub3x;同理,第三级子目录 sub231
表示 sub2 子目录底下的 sub23 子目录下的子目录。只要工程所有的源文件都放在这些目录中(即所有源文件都
包含在工程目录内),则就可以使用该 makefile 进行处理。
第一版的代码如下(若要在处理过程中显示各种信息,可以通过 echo 指令实现):

# shell/dos 命令 
MKDIR = -mkdir 
RM = rm 
RMFLAGS = -fr 
# NM_TARGET 为最终的目标名,NM_MAP 为 map 文件名 
NM_TARGET = a_default 
############################################################################### 
#  编译链接用目录的设置:分别为链接前的.o 目录,链接后的二进制目标文件目录,依赖文件目录 
DIR_OBJS = objs 
DIR_BINS = bins 
DIR_DEPS = deps 
DIR_CLEAN = $(DIR_OBJS) $(DIR_BINS) $(DIR_DEPS) 
############################################################################### 
#  获取工程目录下的所有目录项到 CHILD_DIRS。 
# INC_PATH 为 gcc 的头文件搜索选项的一部分 
NOTHING = 
CHILD_DIRS = $(shell ls -R | grep :$) 
CHILD_DIRS := $(patsubst %:,%$(NOTHING),$(CHILD_DIRS)) 
INC_PATH := $(patsubst .%,-I.%,$(CHILD_DIRS)) 
CHILD_DIRS := $(filter ./%,$(CHILD_DIRS)) 
CHILD_DIRS := $(patsubst ./%,$(NOTHING)%,$(CHILD_DIRS)) 
    # vpath 或者 VPATH 指定的路径仅适用于 makefile 的(模式)规则搜寻文件的情况 
    #  对于 gcc 工具链的.h 和.a(静态库),则需要另外指定包含路径,并将该路径加入到 gcc 命令行中 
#VPATH = $(CHILD_DIRS) 
vpath %.h $(CHILD_DIRS) 
vpath %.c $(CHILD_DIRS) 
vpath %.d $(DIR_DEPS) 
vpath %.o $(DIR_OBJS) 
vpath %.out $(DIR_BINS) 
vpath %.bin $(DIR_BINS) 
vpath %.hex $(DIR_BINS) 
vpath %.elf $(DIR_BINS) 
############################################################################### 
# SRCS 用户输入的.c 源文件,不输入的情况下就是指工程的全部.c 文件 
    # SRCS_EXCLUDE 为全编译时需要排除在外的.c 文件 
    #  如果用户输入的是路径+名称,则需要去掉路径 
SRCS_EXCLUDE = 
SRCS := $(shell ls -R | grep .c$) 
SRCS := $(filter %.c,$(SRCS)) 
SRCS := $(filter-out $(SRCS_EXCLUDE),$(SRCS)) 
#  中间代码.o、最终目标、依赖项的名称及路径。默认情况下,最终代码为 elf 格式 
OBJS = $(notdir $(SRCS:.c=.o)) 
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) 
BINS = $(NM_TARGET).elf 
BINS :=$(addprefix $(DIR_BINS)/, $(BINS)) 
DEPS = $(notdir $(SRCS:.c=.d)) 
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS)) 
MAPS := $(NM_TARGET).map 
MAPS := $(addprefix $(DIR_BINS)/, $(MAPS)) 
############################################################################### 
# PATH_TOOLCHAIN 的路径最后需要为"/" 
PATH_TOOLCHAIN = 
CROSS_COMPILE ?= arm-none-eabi- 
OPTS_CPU ?= -mcpu=cortex-m3 -mthumb -nostartfiles 
OPTS_DIAG ?= -Wall -Wextra 
OPTS_CLIB ?= --specs=nano.specs 
OPTS_LDS ?= -T sections.lds 
OPTS_INC1 ?= 
OPTS_INC := $(INC_PATH) $(OPTS_INC1) 
OPTS_LIB ?= 
OPTS_DEF ?= 
debug ?= y 
ifeq ($(debug), y) 
OPTS_DEBUG ?= -g2 
OPTS_OPTIM ?= -O2 
else 
OPTS_DEBUG ?= 
OPTS_OPTIM ?= -Os 
endif 
 
USER_CCFLAGS = 
USER_LDFLAGS = -Wl,-Map,$(MAPS) 
#  交叉编译工具链 
TOOL_CHAIN = $(PATH_TOOLCHAIN)$(CROSS_COMPILE) 
CC := $(TOOL_CHAIN)gcc 
LD := $(TOOL_CHAIN)g++ 
OBJCP := $(TOOL_CHAIN)objcopy 
OBJDP := $(TOOL_CHAIN)objdump 
RDELF := $(TOOL_CHAIN)readelf 
OBJSZ := $(TOOL_CHAIN)size 
CCFLAGS  :=  $(OPTS_CPU)  $(OPTS_DIAG)  $(OPTS_CLIB)  $(OPTS_DEBUG)  $(OPTS_OPTIM)  $(OPTS_INC) 
$(OPTS_DEF) $(USER_CCFLAGS) 
LDFLAGS  :=  $(OPTS_CPU)  $(OPTS_DIAG)  $(OPTS_CLIB)  $(OPTS_DEBUG)  $(OPTS_OPTIM)  $(OPTS_INC) 
$(OPTS_DEF) $(OPTS_LDS) $(OPTS_LIB) $(USER_LDFLAGS) 
IHEX = $(NM_TARGET).hex 
IHEX := $(addprefix $(DIR_BINS)/, $(IHEX)) 
IBIN = $(NM_TARGET).bin 
IBIN := $(addprefix $(DIR_BINS)/, $(IBIN)) 
DISASSEM = $(NM_TARGET).s 
DISASSEM := $(addprefix $(DIR_BINS)/, $(DISASSEM)) 
############################################################################### 
# make all .c or .cpp 
.PHONY: ALL just_compile just_link rebuild clean 
ALL: $(BINS) 
  @echo "All needed have been created!" 
############################################################################### 
#  如果包含时,不存在 
ifeq ("$(wildcard $(DIR_DEPS))", "") 
DEPS_DIR_DEPS := $(DIR_DEPS) 
endif   
ifneq ($(MAKECMDGOALS), clean) 
-include $(DEPS) 
endif 
#  三个文件夹 objs、bins 和 deps,只有需要时才创建(展开后为三个目标): 
    #    调用目标$(BINS)时,会先查看目录 bins 是否存在,不存在时会调用该条规则 
    #    调用目标$(DIR_OBJS)/%.o 时,会先查看目录 objs 是否存在,不存在时会调用该条规则 
    #    调用目标$(DIR_DEPS)/%.d 时,会先查看目录 deps 是否存在,不存在时会调用该条规则 
$(DIR_CLEAN): 
  @echo "Creating directory $@ ..." 
  $(MKDIR) $@ 
  @echo $(SRCS) 
############################################################################### 
    #  生成 elf 目标文件的规则(因为目标只有一个所以这样写) 
$(BINS): $(DIR_BINS) $(OBJS) 
  $(LD) $(LDFLAGS) -pipe -o $@ $(filter %.o, $^) 
    #  由.c 文件生成.o 文件的规则,最好不要把目标写成$(OBJS)。只有%才是模式规则 
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c 
#  @echo $(CHILD_DIRS) $^3 
  $(CC) $(CCFLAGS) -pipe -o $@ -c $(filter %.c, $^) 
#  先决条件为:1)DIR_DEPS 目录存在;2)寻找对应的.c 文件。生成的.d 文件与对应的.c 文件重名    
$(DIR_DEPS)/%.d: $(DEPS_DIR_DEPS) %.c 
  @echo "Making $@ ..." 
  @set -e;\ 
  $(RM) $(RMFLAGS) $@ ; \ 
  $(CC) -MM $(INC_PATH) $(filter %.c, $^) > $@.$$$$ ; \ 
  sed 's,\($*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.$$$$ > $@ ; \ 
  $(RM) $(RMFLAGS) $@.$$$$   
###############################################################################   
just_compile: $(OBJS) 
  @echo "Just compiling $(filter %.o, $^) ..." 
###############################################################################   
just_link: $(BINS) 
  @echo "Just linking $(filter %.o, $^) ..." 
############################################################################### 
mk_ihex: $(IHEX) 
  @echo intel hex format file : $^ created...   
$(IHEX): $(DIR_BINS) $(BINS) 
  $(OBJCP) -g -S -O ihex $(filter %.elf, $^) $@ 
############################################################################### 
mk_bin: $(IBIN) 
  @echo intel hex format file : $^ created...   
$(IBIN): $(DIR_BINS) $(BINS) 
  $(OBJCP) -g -S -O binary $(filter %.elf, $^) $@ 
############################################################################### 
mk_size: $(DIR_BINS) $(BINS) 
  $(OBJSZ) -B $(BINS) 
############################################################################### 
mk_deassem: $(DIR_BINS) $(BINS) 
  $(OBJDP) -D -S $(BINS) > $(DISASSEM) 
############################################################################### 
clean: 
  $(RM) $(RMFLAGS) $(DIR_CLEAN) 
  

可以实现以下几种功能:

  1. 对单个.c 源文件进行编译操作;
  2. 对多个.c 源文件进行编译操作;
  3. 对整个工程所有的.c 源文件进行编译操作;
  4. 仅对整个工程进行链接操作;
  5. 对整个工程进行增量式编译、链接操作,也就是只编译修改了的源文件,然后进行链接(rebuild modified);
  6. 对整个工程进行强制编译、链接操作,而不管源文件是否已经改动(rebuild All);
  7. 清除整个编译、链接过程生成的所有文件。
  8. 生成 hex 或者 bin 文件,可以是编译、链接,然后调用生成 hex/bin,也可以直接调用生成 hex/bin
  9. 反汇编
  10. 代码跟踪

在不考虑配置的情况下,实现以上功能所需要的命令行调用如下:

编号功能调用方式(强制编译时启用[-B]选项)
1编译 LED.c 文件make just_compile SRCS=LED.c [-B]
2编译 LED.c 和 Cordic.cmake just_compile SRCS=”LED.c Cordic.c” [-B]
3编译整个工程的*.c 文件make just_compile [-B]
4仅链接整个工程make just_link [-B]
5增量式编译、链接整个工程make
6强制编译、链接整个工程make –B
7清理make clean
8生成 hex 或者 bin 文件make mk_ihex/mk_bin [-B] #-B 表示重新编译链接然后生成 hex/bin
9反汇编make mk_deassem [-B]
100x2000000E 在那个源文件的什么地方make adr2line P_ADDR=2000000E [-B]

以上功能中,8~10 功能有点特殊,如果在编译链接之前直接执行它们,则会先编译、链接,然后再对生成
的目标文件执行 8-10 的功能。
对于存在非工程目录下的库(源代码方式提供)的情况,这里提供两个办法:

  1. 在库的下面也写一个 makefile,然后用工程的 makefile 去调用库下面的 makefile。
  2. 将库先通过 arm-none-eabi-ar 打包成.a(静态库)或者.so(动态库),然后通过-L 和-l 选项将库包含到链接
    时的命令行中
 类似资料: