在嵌入式 Linux 或裸机开发中,除了写 C/汇编代码,还必须处理 编译、链接和内存布局
这其中,链接脚本 (Linker Script)Makefile 是两个非常关键的工具。

  • 链接脚本:决定程序中代码和数据在内存中的布局。

  • Makefile:让整个编译、链接过程自动化。

本文结合 i.MX6ULL 的实际开发案例,带大家从零理解链接脚本和 Makefile 的用法。


一、为什么需要链接脚本?

在 PC 程序开发中,编译器和操作系统会自动安排内存布局,开发者几乎不用关心。
但在嵌入式开发(尤其是裸机环境)下,没有操作系统帮忙,我们必须明确告诉编译器:

  • 代码放到哪里?

  • 已初始化的全局变量放哪里?

  • 未初始化的变量放哪里?

  • 常量数据又该放哪?

这就需要 链接脚本 来描述程序在内存中的分布。

它的作用就像一张 内存地图,指导链接器 ld 把各段内容放到合适的位置。


二、常见的内存段与作用

在 C/汇编编译结果中,目标文件通常包含以下几个段:

段名内容示例
.text程序代码(函数、指令)main(), 各种函数体
.data已初始化的全局变量 / 静态变量int a = 5;
.bss未初始化的全局变量 / 静态变量int b;static int c=0;
COMMON未初始化的非静态全局变量(类似 .bss)int g;
.rodata只读数据(常量、字符串、跳转表)"hello", const int x=10

其中 .bss 段必须在启动代码中清零,否则变量会带随机值。


三、示例链接脚本(imx6ull.lds)

下面是一份适用于 i.MX6ULL 的链接脚本:

SECTIONS
{
    . = 0x87800000;    /* 程序入口地址 (SDRAM 起始位置) */

    .text : {
        obj/start.o    /* 启动代码必须放在最前面 */
        *(.text)       /* 其他目标文件的代码 */
    }

    .rodata ALIGN(4) : {
        *(.rodata*)    /* 只读常量、字符串、跳转表 */
    }

    .data ALIGN(4) : {
        *(.data)       /* 已初始化的全局变量、静态变量 */
    }

    /* 定义 bss 段起止符号,启动代码会用来清零 */
    __bss_start = .;
    .bss ALIGN(4) : {
        *(.bss)        /* 未初始化的变量 */
        *(.COMMON)     /* COMMON 段 */
    }
    __bss_end = .;
}

📌 要点:

  • 程序入口地址指定为 0x87800000(常见的 DDR 入口)。

  • start.o 必须放在最前面,因为它包含复位向量和启动代码。

  • __bss_start__bss_end 是符号,在 start.S 里会用循环清零。


四、编译过程所需工具

整个编译 → 链接 → 转换 → 调试的过程涉及多个工具:

工具作用
gcc.c 源文件编译为目标文件 .o
as.S 汇编文件编译为 .o
ld链接器,使用 .lds 把多个 .o 拼成 .elf
objcopy格式转换:ELF → 纯二进制 .bin
objdump反汇编 ELF,生成 .dis 调试文件

最终我们得到 .bin 文件,烧录到开发板即可运行。


五、创建的 Makefile

在实际项目中,我们使用 Makefile 来自动化管理编译过程。
下面是一份带详细注释的示例:

# ================== 基本配置 ==================
target = led                          # 目标文件名,不带扩展名
cross_compiler = arm-linux-gnueabihf- # 交叉编译器前缀

cc      = $(cross_compiler)gcc        # C 编译器
ld      = $(cross_compiler)ld         # 链接器
objcopy = $(cross_compiler)objcopy    # ELF → BIN
objdump = $(cross_compiler)objdump    # 反汇编工具

# 头文件目录
incdirs = bsp imx6ull stdio/include

# 源码目录
srcdirs = bsp project stdio/lib

# 库路径与库配置
pathlib = -lgcc -L/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4/

# 将头文件目录转成 -I 选项
include = $(patsubst %, -I%, $(incdirs))

# ================== 文件收集 ==================
# 搜索 .c 和 .S 文件
cfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.c))
sfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.S))

# 去掉路径,只保留文件名
cfilenodir = $(notdir $(cfiles))
sfilenodir = $(notdir $(sfiles))

# 对应生成 obj/xxx.o
cobjs = $(patsubst %, obj/%, $(cfilenodir:.c=.o))
sobjs = $(patsubst %, obj/%, $(sfilenodir:.S=.o))

objs = $(cobjs) $(sobjs)   # 所有目标文件
VPATH = $(srcdirs)         # 告诉 make 在这些目录下找源文件

# ================== 规则部分 ==================
# 生成 bin 文件
$(target).bin : $(objs)
	$(ld) -Timx6ull.lds -o$(target).elf $^ $(pathlib)   # 链接成 ELF
	$(objcopy) -O binary -S -g $(target).elf $@         # 转换为 BIN
	$(objdump) -D $(target).elf > $(target).dis         # 生成反汇编

# 编译汇编文件
obj/%.o : %.S
	@mkdir -p obj
	$(cc) -Wall -Wa,-mimplicit-it=thumb -fno-builtin -nostdlib -c $(include) -o $@ $<

# 编译 C 文件
obj/%.o : %.c
	@mkdir -p obj		
	$(cc) -Wall -Wa,-mimplicit-it=thumb -fno-builtin -nostdlib -c $(include) -o $@ $<

# ================== 辅助规则 ==================
.PHONY : clean
clean:
	rm -rf $(objs) $(target).elf $(target).bin $(target).dis

# 下载规则(根据自己工具修改)
load:
	./../imxdownload ./$(target).bin /dev/sdb

📌 核心流程:

  1. .c/.S → .o:编译生成目标文件。

  2. ld:使用 imx6ull.lds 链接成 led.elf

  3. objcopy:转换成 led.bin

  4. objdump:生成 led.dis(反汇编文件)。

  5. make clean:一键清理。

  6. make load:下载到开发板。


六、整体编译流程图

源码 (C / ASM)
     │
     ▼
   gcc/as
     │
     ▼
 目标文件 .o
     │
     ▼
   ld (用 .lds)
     │
     ▼
 ELF 文件 .elf
     │
 ┌───┴─────┐
 │         │
objcopy   objdump
 │         │
 ▼         ▼
BIN 文件   反汇编 .dis
(烧录)    (调试)

七、总结

  • 链接脚本 就是一张 “内存地图”,控制各段放在哪里。

  • Makefile 让整个编译流程自动化,更加高效。

  • 嵌入式开发中,理解 .text / .data / .bss / .rodata 的区别非常重要。

  • 熟悉 gcc/ld/objcopy/objdump 的使用,可以帮助我们更好地调试与优化程序。

只要理解了这些工具链与脚本的工作原理,嵌入式开发过程就会更加清晰、可控。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐