ソースを参照

raw: 扩充 i.MX6ULL 裸机 Makefile 逐行解析

OpenCode 9 時間 前
コミット
23f5952579

+ 323 - 4
X-Knowledge-Base/raw/Joplin/Linux+C+C++技术体系梳理/3. 编译调试与驱动预留/2. Makefile.md

@@ -424,9 +424,328 @@ all:
 	@echo "CC = $(CC)"
 ```
 
-## 九、完整项目示例
+## 九、深度实例:i.MX6ULL ARM 裸机 Makefile 逐行解析
 
-### 9.1 嵌入式 LED 控制项目
+以下是一个真实的嵌入式裸机项目 Makefile,涵盖了多目录编译、交叉编译、链接脚本、反汇编等生产级特性。
+
+### 9.1 项目目录结构
+
+```
+project/
+├── imx6ul/                  # 芯片级头文件(寄存器定义等)
+│   ├── imx6ul.h
+│   └── fsl_common.h
+├── bsp/                     # 板级支持包
+│   ├── clk/                 # 时钟驱动
+│   │   ├── bsp_clk.h
+│   │   └── bsp_clk.c
+│   ├── led/                 # LED 驱动
+│   │   ├── bsp_led.h
+│   │   └── bsp_led.c
+│   └── delay/               # 延时驱动
+│       ├── bsp_delay.h
+│       └── bsp_delay.c
+├── project/                 # 应用入口
+│   └── main.c
+├── imx6ul.lds               # 链接脚本(定义内存布局)
+├── obj/                     # 编译输出目录(.o 文件)
+└── Makefile
+```
+
+### 9.2 完整 Makefile
+
+```makefile
+CROSS_COMPILE 	?= arm-linux-gnueabihf-
+TARGET		  	?= bsp
+
+CC 				:= $(CROSS_COMPILE)gcc
+LD				:= $(CROSS_COMPILE)ld
+OBJCOPY 		:= $(CROSS_COMPILE)objcopy
+OBJDUMP 		:= $(CROSS_COMPILE)objdump
+
+INCDIRS 		:= imx6ul \
+				   bsp/clk \
+				   bsp/led \
+				   bsp/delay
+
+SRCDIRS			:= project \
+				   bsp/clk \
+				   bsp/led \
+				   bsp/delay
+
+
+INCLUDE			:= $(patsubst %, -I %, $(INCDIRS))
+
+SFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
+CFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
+
+SFILENDIR		:= $(notdir  $(SFILES))
+CFILENDIR		:= $(notdir  $(CFILES))
+
+SOBJS			:= $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
+COBJS			:= $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
+OBJS			:= $(SOBJS) $(COBJS)
+
+VPATH			:= $(SRCDIRS)
+
+.PHONY: 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 $@ $<
+
+clean:
+	rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
+```
+
+### 9.3 逐段深度解析
+
+#### 第一段:交叉编译工具链定义
+
+```makefile
+CROSS_COMPILE 	?= arm-linux-gnueabihf-
+TARGET		  	?= bsp
+
+CC 				:= $(CROSS_COMPILE)gcc
+LD				:= $(CROSS_COMPILE)ld
+OBJCOPY 		:= $(CROSS_COMPILE)objcopy
+OBJDUMP 		:= $(CROSS_COMPILE)objdump
+```
+
+| 变量            | 赋值方式 | 作用                                                                         |
+| --------------- | -------- | ---------------------------------------------------------------------------- |
+| `CROSS_COMPILE` | `?=`     | 条件赋值,允许外部覆盖。`make CROSS_COMPILE=aarch64-linux-gnu-` 可切换工具链 |
+| `TARGET`        | `?=`     | 同上,默认目标名为 `bsp`,可外部指定                                         |
+| `CC`            | `:=`     | 立即展开,派生出 `arm-linux-gnueabihf-gcc`                                   |
+| `LD`            | `:=`     | 链接器,用于将 `.o` 链接为 `.elf`                                            |
+| `OBJCOPY`       | `:=`     | 格式转换工具,将 `.elf` 转为裸二进制 `.bin`                                  |
+| `OBJDUMP`       | `:=`     | 反汇编工具,生成 `.dis` 文件供调试分析                                       |
+
+> **为什么用 `?=` 而不是 `:=`?** `?=` 允许用户在命令行覆盖,例如 `make CROSS_COMPILE=arm-linux-gnueabihf-`,方便在同一台机器上切换不同工具链。
+
+#### 第二段:多目录源文件与头文件收集
+
+```makefile
+INCDIRS 		:= imx6ul \
+				   bsp/clk \
+				   bsp/led \
+				   bsp/delay
+
+SRCDIRS			:= project \
+				   bsp/clk \
+				   bsp/led \
+				   bsp/delay
+```
+
+`INCDIRS` 和 `SRCDIRS` 分别列出所有头文件目录和源文件目录。裸机项目通常按功能模块拆分目录,每个模块包含自己的 `.c` 和 `.h` 文件。
+
+#### 第三段:生成 `-I` 编译选项
+
+```makefile
+INCLUDE			:= $(patsubst %, -I %, $(INCDIRS))
+```
+
+**`patsubst` 执行过程:**
+
+```
+输入: imx6ul bsp/clk bsp/led bsp/delay
+模式: % → -I %
+输出: -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
+```
+
+最终 `INCLUDE` 变量展开为编译器的头文件搜索路径参数。
+
+#### 第四段:递归收集所有源文件
+
+```makefile
+SFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
+CFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
+```
+
+**展开过程(以 CFILES 为例):**
+
+```
+SRCDIRS = project bsp/clk bsp/led bsp/delay
+
+$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
+
+→ $(wildcard project/*.c)      → project/main.c
+  $(wildcard bsp/clk/*.c)      → bsp/clk/bsp_clk.c
+  $(wildcard bsp/led/*.c)      → bsp/led/bsp_led.c
+  $(wildcard bsp/delay/*.c)    → bsp/delay/bsp_delay.c
+
+最终: CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
+```
+
+`foreach` + `wildcard` 组合是多目录项目自动收集源文件的标准模式。
+
+#### 第五段:剥离路径、生成目标文件列表
+
+```makefile
+SFILENDIR		:= $(notdir  $(SFILES))
+CFILENDIR		:= $(notdir  $(CFILES))
+
+SOBJS			:= $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
+COBJS			:= $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
+OBJS			:= $(SOBJS) $(COBJS)
+```
+
+**逐步展开:**
+
+```
+CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c
+
+① notdir: 去掉路径前缀
+CFILENDIR = main.c bsp_clk.c bsp_led.c bsp_delay.c
+
+② 后缀替换: .c → .o
+CFILENDIR:.c=.o → main.o bsp_clk.o bsp_led.o bsp_delay.o
+
+③ patsubst: 加上 obj/ 前缀
+COBJS = obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o
+```
+
+所有 `.o` 文件统一输出到 `obj/` 目录,保持源码目录干净。
+
+#### 第六段:VPATH — 源文件搜索路径
+
+```makefile
+VPATH			:= $(SRCDIRS)
+```
+
+**这是本 Makefile 最关键的设计之一。**
+
+`VPATH` 告诉 make:当在当前目录找不到某个依赖文件时,依次去 `project`、`bsp/clk`、`bsp/led`、`bsp/delay` 目录下搜索。
+
+**为什么需要 VPATH?**
+
+目标文件统一放在 `obj/` 目录(如 `obj/main.o`),但源文件分散在多个子目录。模式规则写的是:
+
+```makefile
+$(COBJS) : obj/%.o : %.c
+```
+
+make 看到 `obj/main.o` 需要 `main.c`,但 `main.c` 不在当前目录。`VPATH` 让 make 去 `project/` 目录下找到它,然后执行:
+
+```bash
+arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk ... -o obj/main.o project/main.c
+```
+
+**没有 VPATH 会怎样?** make 会报错 `No rule to make target 'main.c', needed by 'obj/main.o'`。
+
+#### 第七段:静态模式规则 — 分离 .S 和 .c 编译
+
+```makefile
+$(SOBJS) : obj/%.o : %.S
+	$(CC) -Wall -nostdlib -c -O2  $(INCLUDE) -o $@ $<
+
+$(COBJS) : obj/%.o : %.c
+	$(CC) -Wall -nostdlib -c -O2  $(INCLUDE) -o $@ $<
+```
+
+**静态模式规则语法:**
+
+```makefile
+$(targets): pattern: prereqs
+	command
+```
+
+- `$(SOBJS)` 限定此规则只对 SOBJS 列表中的目标生效
+- `obj/%.o : %.S` 是模式部分,`%` 匹配文件名词干(如 `start`)
+- `$@` = `obj/start.o`(当前目标)
+- `$<` = `start.S`(第一个依赖,由 VPATH 解析)
+
+**`-nostdlib` 的含义:** 不链接标准库(libc、libgcc 等),因为裸机环境没有操作系统提供的 C 运行时库。
+
+#### 第八段:链接与输出 — 三步生成最终产物
+
+```makefile
+$(TARGET).bin : $(OBJS)
+	$(LD) -Timx6ul.lds -o $(TARGET).elf $^
+	$(OBJCOPY) -O binary -S $(TARGET).elf $@
+	$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
+```
+
+| 步骤 | 命令                                   | 作用                                        |
+| ---- | -------------------------------------- | ------------------------------------------- |
+| 1    | `ld -Timx6ul.lds -o bsp.elf $^`        | 用链接脚本将所有 `.o` 链接为 ELF 可执行文件 |
+| 2    | `objcopy -O binary -S bsp.elf bsp.bin` | 从 ELF 提取裸二进制,用于烧录到 Flash       |
+| 3    | `objdump -D -m arm bsp.elf > bsp.dis`  | 反汇编全部内容,生成 `.dis` 文件供调试分析  |
+
+**链接脚本 `imx6ul.lds` 的作用:** 定义内存布局——代码段放哪里、数据段放哪里、栈的起始地址等。裸机项目没有操作系统的内存管理,必须手动指定。
+
+**为什么需要 `.bin`?** ELF 是 Linux 可执行文件格式,包含元数据。烧录到裸机 Flash 时需要纯二进制 `.bin`(不含 ELF 头、段表等)。
+
+#### 第九段:清理目标
+
+```makefile
+clean:
+	rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
+```
+
+删除所有生成物,恢复到干净状态。
+
+### 9.4 构建流程图
+
+```
+                    ┌─────────────────────────────────┐
+                    │          make 执行流程            │
+                    └─────────────────────────────────┘
+                                    │
+                    ┌───────────────▼───────────────┐
+                    │  1. 默认目标: bsp.bin          │
+                    │     依赖: $(OBJS)              │
+                    └───────────────┬───────────────┘
+                                    │
+                    ┌───────────────▼───────────────┐
+                    │  2. 检查 OBJS 中每个 .o 文件   │
+                    │     obj/main.o → main.c (VPATH)│
+                    │     obj/bsp_clk.o → bsp/clk/  │
+                    │     obj/bsp_led.o → bsp/led/  │
+                    │     ...                        │
+                    └───────────────┬───────────────┘
+                                    │
+                    ┌───────────────▼───────────────┐
+                    │  3. 模式规则编译               │
+                    │     .c → obj/*.o (gcc -c)     │
+                    │     .S → obj/*.o (gcc -c)     │
+                    └───────────────┬───────────────┘
+                                    │
+                    ┌───────────────▼───────────────┐
+                    │  4. 链接                       │
+                    │     ld → bsp.elf              │
+                    └───────────────┬───────────────┘
+                                    │
+                    ┌───────────────▼───────────────┐
+                    │  5. 格式转换                   │
+                    │     objcopy → bsp.bin (烧录用) │
+                    │     objdump → bsp.dis (调试用) │
+                    └───────────────────────────────┘
+```
+
+### 9.5 关键技巧总结
+
+| 技巧                   | 本例用法                   | 解决的问题                     |
+| ---------------------- | -------------------------- | ------------------------------ |
+| `?=` 条件赋值          | `CROSS_COMPILE ?=`         | 允许命令行覆盖工具链           |
+| `foreach` + `wildcard` | 自动收集多目录源文件       | 无需手动列出每个文件           |
+| `notdir` + `patsubst`  | 生成 `obj/` 前缀的目标列表 | 统一输出目录,保持源码整洁     |
+| `VPATH`                | 指定源文件搜索路径         | 解决目标与源文件路径分离的问题 |
+| 静态模式规则           | 分别处理 `.S` 和 `.c`      | 汇编和 C 用不同编译选项        |
+| 链接脚本 `-T`          | `imx6ul.lds`               | 裸机项目的内存布局定义         |
+| `objcopy`              | ELF → bin                  | 生成可烧录的裸二进制           |
+| `objdump -D`           | 生成反汇编                 | 调试时对照源码分析             |
+
+## 十、完整项目示例
+
+### 10.1 嵌入式 LED 控制项目
 
 ```makefile
 # ============================================
@@ -466,7 +785,7 @@ deploy: $(TARGET)
 .PHONY: all clean deploy
 ```
 
-## 十、注意事项
+## 十、注意事项
 
 1. **Tab 缩进是强制的**:命令行前必须用 Tab,不能用空格(最常见的错误)
 2. **`@` 前缀**:`@echo` 不打印命令本身,只打印输出
@@ -476,7 +795,7 @@ deploy: $(TARGET)
 6. **`make` 默认执行第一个目标**:将 `all` 放在最前面
 7. **依赖自动生成**:用 `-MMD -MP` 避免手动维护头文件依赖
 
-## 十、面试要点
+## 十、面试要点
 
 ### Q1: Make 的核心机制是什么?