用AI生成Makefile和链接脚本,哪些能信

简介: 本文以实战经验揭示AI辅助编写嵌入式构建脚本的边界:Makefile可由AI高效完成七八成(如结构、变量、规则),而链接脚本仅宜用其生成骨架,向量表位置、栈顶符号、段对齐等关键细节必须人工核验。核心在于——“已有约束下改”AI可靠,“从零定约束”须亲为。(239字)

先说结论,Makefile 我敢让 AI 写个七八成,链接脚本我只敢让它写个开头,剩下的必须自己抠。

这不是拍脑袋,是最近两个项目里实打实试出来的。一个是在 imx6ull 上给一个采集板子加新驱动,另一个是给一块国产 Cortex-M 的板子从零搭裸机工程。两个场景我都把 AI 拉进来干活,结果差别挺大,今天把过程摊开讲讲,顺便把我用的提示词贴出来,你可以直接抄。

一张对比示意图,左侧是Makefile被AI覆盖七八成的进度条,右侧是链接脚本只

一、先说我到底让 AI 干了什么

1.1 两个场景的差异

场景一,老工程。Makefile 是前人留下的,几百行,交叉编译、多目录、条件编译混在一起。我要往里加一个新的 sensor 驱动目录,得改 Makefile。

场景二,裸机工程,从零开始。我要一个能编译、能链接、能生成 bin 和 hex 的 Makefile,外加一个匹配这颗芯片内存布局的链接脚本。

这两个场景的区别很关键,一个是「在已有约束里改」,一个是「从零定约束」。AI 在前者表现明显更好,后者就开始飘了。

为什么会有这种差异,想清楚这一点对后面怎么用 AI 很重要。已有约束意味着上下文是完整的,AI 只需要在给定的变量名、目录结构、编译选项里做局部推理,它犯错的空间被压缩了。从零定约束则相反,AI 要自己「发明」一套约束,它会默认套用训练语料里最常见的模板,而这个模板未必匹配你手上这颗芯片。约束越少,AI 的自由度越高,跑偏的概率也越高。这个规律不止适用于 Makefile 和链接脚本,所有「让 AI 从零生成工程骨架」的任务都成立。

1.2 为什么这个区分重要

如果你把这两个场景混为一谈,很容易得出「AI 写构建系统不靠谱」或者「AI 写构建系统真香」这种一刀切的结论。实际情况是分层的,改得动的地方大胆用,定不下来的地方自己上。后面几节就是按这个思路展开的。

二、Makefile 怎么让 AI 写

我用的工具是 Claude,也试过 ChatGPT,结论差不多,这里不比较谁强,只说用法。

2.1 第一步,把约束喂清楚

别上来就让它写。先把约束喂清楚。我的提示词大概长这样。

我在做一个嵌入式C工程,需要你帮我写Makefile。
约束如下:
- 交叉编译器:arm-linux-gnueabihf-gcc
- 源文件在 src/ 下,分 src/driver、src/app 两个子目录
- 头文件在 include/
- 输出目录 build/,中间文件.o也要放进去
- 目标文件名 sensor_app
- 编译选项:-Wall -O2 -std=c99
- 需要生成 .bin,用 arm-linux-gnueabihf-objcopy
请给出完整Makefile,并逐段解释每个变量的作用。

这一步出来的东西,说实话,能用。变量定义、wildcard 收集源文件、patsubst 换后缀、vpath 找头文件,套路是对的。我基本改了两处就跑了,一处是它把 -I 路径写成了绝对路径,得换成相对;一处是 .bin 的规则它漏了依赖顺序,第一次 make 会报找不到 elf。

为什么绝对路径是个问题,值得说透。绝对路径在你的机器上能编,换一台机器、换一个 CI 环境就断,而且断的时候报错信息往往是「找不到头文件」,排查方向容易跑偏到源码上。Makefile 里凡是涉及工程内路径的,一律用相对于 Makefile 所在目录的路径,配合 $(CURDIR) 或者 $(dir $(lastword $(MAKEFILE_LIST))) 定位,这是硬规矩。

依赖顺序那处更典型。.bin 由 .elf 经 objcopy 生成,.elf 又由 .o 链接生成,这条链在 Makefile 里必须显式写清依赖。AI 漏掉依赖时,make -j 并发编译下第一次构建经常失败,第二次就好了,这种「第二次能过」的 bug 最阴,因为它会让人误以为是环境抖动。

2.2 第二步,让它做增量修改

加驱动目录的时候我这么说。

在上面的Makefile基础上,新增一个目录 src/driver/sensor,
里面的.c文件也要参与编译,其他规则不变,只告诉我需要改哪几行。

这个用法很舒服。它不会把整个文件重写一遍,只给你 diff 级别的改动,我核对起来快。记住一点,让 AI 改,别让它重写。重写容易把它上一版对的东西又改错。

增量修改为什么更可靠,从信息论角度看很清楚。重写意味着 AI 要重新生成几百行,每一行都是一次新的采样,出错概率随行数线性累积。增量修改只生成几行 diff,出错面被限制在这几行里,你核对的工作量也同步缩小。工程越大,这个差别越明显。几百行的 Makefile 重写一次,你要逐行比对,跟手写一遍的工夫差不多,AI 省的时间全还回去了。

2.3 翻车的地方

翻车的地方也有。有一次我让它加一个「根据宏定义切换两颗不同芯片」的条件编译,它给我写了个 ifeq 嵌套,逻辑上没错,但变量展开的时机搞错了,导致 CFLAGS 在递归 make 里丢了一段。这个坑我自己盯了半天才看出来。所以涉及 make 的变量展开、递归调用、$(shell ...) 这类,AI 给的代码必须逐行读,不能信。

变量展开时机这个问题值得单独展开。make 里有立即展开(:=)和延迟展开(=)两种赋值,还有 override、export 对作用域的影响。递归 make 时子 make 拿到的是环境变量,父 make 里用 = 定义的变量如果没 export,子 make 根本看不到。AI 生成的代码经常把 = 和 := 混用,单层 make 下看不出问题,一旦进了递归就丢变量。这类错误的现象是「顶层编过了,子目录里的编译选项不对」,报错信息又指向别处,排查成本很高。

下面这段是修正后的写法示例,把条件编译和导出都写明确,可以直接跑。

# 顶层 Makefile 片段,演示条件编译与变量导出的正确姿势
CHIP ?= imx6ull

# 用 := 立即展开,避免递归 make 时重复求值
ifeq ($(CHIP),imx6ull)
    CROSS_COMPILE := arm-linux-gnueabihf-
    CHIP_DEFS     := -DCHIP_IMX6ULL
else ifeq ($(CHIP),stm32f4)
    CROSS_COMPILE := arm-none-eabi-
    CHIP_DEFS     := -DCHIP_STM32F4
else
    $(error 未知的 CHIP 取值 $(CHIP),请指定 imx6ull 或 stm32f4)
endif

CC      := $(CROSS_COMPILE)gcc
CFLAGS  := -Wall -O2 -std=c99 $(CHIP_DEFS)
LDFLAGS := -T link.lds -nostartfiles

# 关键一步,把子 make 需要的变量显式导出
export CC CFLAGS LDFLAGS

SUBDIRS := src/driver src/app

.PHONY: all $(SUBDIRS) clean

all: $(SUBDIRS)

$(SUBDIRS):
    $(MAKE) -C $@

clean:
    for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done

这段里 $(error ...) 那行是刻意加的,AI 生成的版本通常没有这种兜底,CHIP 拼错时它会静默走 else 分支,编出来的固件跑在错误的芯片上。加一行显式报错,把「编过了但不对」变成「编不过」,是构建脚本里性价比最高的防御手段之一。

三、链接脚本,这里我开始谨慎了

3.1 骨架能出,细节得补

链接脚本我一开始也想偷懒,直接让它生成。提示词是这样的。

帮我写一个GNU ld链接脚本,目标芯片是Cortex-M4,
Flash起始0x08000000,大小512K;
RAM起始0x20000000,大小128K;
需要定义 .isr_vector、.text、.data、.bss 段,
.data要从Flash加载到RAM运行,
提供 _estack、_sidata、_sdata、_ebss 符号。

出来的脚本,结构是对的,段名、AT> 加载地址、>RAM AT>FLASH 这些写法都在。但问题出在细节。

第一,.isr_vector 它放的位置对,但没保证是文件里第一个段,实际链接时向量表可能不在 0x08000000,板子一上电就跑飞。这个必须自己加 KEEP 和排序约束。

第二,栈顶 _estack 它直接写成 0x20000000 + 128K,看着没问题,但如果后面 RAM 要划一块给别的用途,这个值就得跟着改,它不会替你考虑。

第三,对齐。.data 的加载地址它没做 4 字节对齐,个别情况下 objcopy 出来的 bin 会有坑。

所以我的实际做法是,让 AI 出骨架,自己补三样东西,向量表位置、栈顶符号、段对齐。骨架省了我查语法的时间,这三样省不了。

image.png

3.2 用真实数字算一遍

把上面那组参数代入算一遍,能看清几个符号到底指什么。

Flash 起始 0x08000000,大小 512K,结束地址是 0x08000000 + 0x80000 = 0x08080000。RAM 起始 0x20000000,大小 128K,结束地址是 0x20000000 + 0x20000 = 0x20020000。栈顶 _estack 就取 RAM 结束地址 0x20020000。

.data 段在 Flash 里的加载地址(LMA)从 .text 结束处接着排,假设 .text 结束在 0x0800A000,那 _sidata 就是 0x0800A000 向上对齐到 4 字节边界。.data 在 RAM 里的运行地址(VMA)从 0x20000000 开始,_sdata 就是 0x20000000,_ebss 在 .data 和 .bss 之后。启动文件里的拷贝循环,就是把 [_sidata, _sidata + (_edata - _sdata)) 拷到 [_sdata, _edata),再把 [_bss_start, _ebss) 清零。

这几个数字的意义在于,只要有一个符号算错,现象就是「程序进了 main 但全局变量初值全是 0」或者「一进中断就 HardFault」。这类问题用调试器单步能看出来,但不知道往哪看的话,很容易怀疑到编译器或者芯片上。

3.3 一段可运行的补丁代码

下面这段 C 代码是启动文件里配套上面符号的拷贝逻辑,可以直接放进 Reset_Handler,注释标了每个符号对应的段。

/* startup.c 片段,配合链接脚本里的 _sidata/_sdata/_edata/_sbss/_ebss 符号使用 */
#include <stdint.h>

/* 这些符号由链接脚本提供,声明成外部符号,取地址即为边界 */
extern uint32_t _sidata;   /* .data 在 Flash 中的加载起始地址 (LMA) */
extern uint32_t _sdata;    /* .data 在 RAM 中的运行起始地址 (VMA) */
extern uint32_t _edata;    /* .data 在 RAM 中的运行结束地址 */
extern uint32_t _sbss;     /* .bss 起始地址 */
extern uint32_t _ebss;     /* .bss 结束地址 */
extern uint32_t _estack;   /* 栈顶,通常等于 RAM 结束地址 */

extern int main(void);
extern void SystemInit(void);

void Reset_Handler(void)
{
   
    uint32_t *src = &_sidata;
    uint32_t *dst = &_sdata;

    /* 1. 把 .data 从 Flash 拷贝到 RAM,初值不为 0 的全局变量靠这一步 */
    while (dst < &_edata) {
   
        *dst++ = *src++;
    }

    /* 2. 清零 .bss,未初始化或初值为 0 的全局变量靠这一步 */
    dst = &_sbss;
    while (dst < &_ebss) {
   
        *dst++ = 0;
    }

    /* 3. 时钟、外设等芯片相关初始化,一般由厂商库提供 */
    SystemInit();

    /* 4. 进 main,正常不返回 */
    (void)main();
    for (;;) {
   
        /* main 返回说明有 bug,停在这里方便调试器捕获 */
    }
}

/* 向量表,放在 .isr_vector 段,链接脚本必须用 KEEP 保证它不被回收 */
__attribute__((section(".isr_vector"), used))
void (* const g_vectors[])(void) = {
   
    (void (*)(void))&_estack,   /* 初始栈顶,硬件上电后第一个加载的值 */
    Reset_Handler,              /* 复位向量 */
    /* 其余异常和中断向量按芯片手册顺序补齐 */
};

这段代码里有两个地方是 AI 生成版本经常漏的。一个是 __attribute__((section(".isr_vector"), used)) 里的 used,防止编译器认为这个数组没被引用而优化掉。另一个是 g_vectors 必须声明成 const,否则它会被放进 .data 而不是 Flash 里的只读区,链接脚本里 .isr_vector 段的放置就失去意义。这两点不看手册很难想到,但错了就是上电跑飞。

四、常见误区

4.1 误区一,AI 生成的 Makefile 能编过就等于对

错误认知是把「make 成功」当作验收标准。正确做法是编过之后还要看三样,编译选项是否真的传到了每个子目录、.bin 和 .hex 的大小是否和预期量级一致、make clean 之后重新构建是否仍然成功。第一项对应变量导出问题,第二项能抓出段被误链接,第三项能抓出隐式依赖缺失。三项都过,才算这个 Makefile 及格。

4.2 误区二,链接脚本让 AI 从零生成能省事

错误认知是既然语法是标准的,AI 生成的和手写的等价。正确做法是把它当成草稿,向量表位置、栈顶符号、段对齐这三处必须逐行核对,并且和启动文件里的符号名一一对上。链接脚本和启动文件是一对耦合件,任何一方改了符号名,另一方必须同步改,AI 不会替你维护这个耦合关系。

4.3 误区三,让 AI 边写边解释就等于学会了

错误认知是把 AI 的逐段解释当作学习材料。正确做法是拿它的解释当索引,回到 GNU make 手册和 ld 手册里找对应章节读原文。AI 的解释在常见用法上基本对,在边界条件上经常含糊,而嵌入式构建出问题恰恰都出在边界条件上。学的时候用它指路,验证的时候以手册为准。

五、参数对照表

下面这张表把本文涉及的主要参数和典型取值列出来,方便对照自己工程里的设置。

参数 典型值 选小了会怎样 选大了会怎样 可突破场景
Flash 大小 256K 到 1M 段放不下,链接报 region overflow 一般无害,但地址空间可能和别的映射冲突 外扩 QSPI Flash 时可超过片内
RAM 大小 64K 到 256K 栈溢出,现象是随机 HardFault 无害,但栈顶符号要同步改 外扩 SDRAM 时按实际划分
栈大小 1K 到 8K 深递归或大局部变量直接踩穿 挤占堆空间 中断嵌套深时可临时加大
堆大小 0 到 32K malloc 失败返回 NULL 挤占栈空间 裸机不用 malloc 时可设 0
.data 对齐 4 字节 objcopy 出的 bin 长度异常 浪费几个字节 有 DMA 要求时按 32 字节对齐
编译优化等级 -O2 -O0 代码大、调试友好 -O3 可能改变浮点行为 调试期用 -O0 -g
并行编译 -j CPU 核数 构建慢 内存吃紧时可能 OOM CI 机器按内存调

六、排查清单

构建出问题时,按下面几条顺序过一遍,能覆盖大部分现象。

  • [ ] make -n 看实际执行的命令行,确认 -I 路径和 -D 宏都传到位了
  • [ ] make clean && make 完整重建一次,排除增量构建的隐式依赖问题
  • [ ] 用 $(info $(CFLAGS)) 在子目录 Makefile 里打印变量,确认递归 make 没丢变量
  • [ ] arm-none-eabi-objdump -h 看各段地址,确认 .isr_vector 在 Flash 起始处
  • [ ] 对比 .data 的 LMA 和 VMA,确认启动文件里的拷贝源地址和链接脚本一致
  • [ ] arm-none-eabi-size 看 text/data/bss 三段大小,和预期量级对一遍
  • [ ] 上电后用调试器读 0x08000000 头两个字,确认是栈顶值和复位向量

七、选型对照与使用边界

7.1 哪些能信,哪些别信

用下来我大概划了条线。

能信的,Makefile 的模板结构、变量命名、常见的模式规则、.PHONY 这类规范写法、链接脚本的段定义骨架、AT> 这种语法。这些是「查手册能查到、但查起来烦」的东西,AI 帮你省时间,值。

别全信的,make 的变量展开时机、递归 make、条件编译的组合逻辑;链接脚本里的内存布局细节、对齐、符号地址计算、启动文件和脚本的配合。这些错一个,现象往往是「编译过了、跑起来不对」,排查成本比手写还高。

还有一类我基本不让它碰,和具体芯片手册强相关的地址、寄存器、启动流程。这些它给的数值可能是从别的芯片抄来的,看着像模像样,实际是错的。手册还是得自己翻。

7.2 什么情况下别用它

说几个我踩过或者想明白的场景。

一是你本身不熟 Makefile 和链接脚本的时候,别拿 AI 的输出去「学」。它会给你一个看起来对的答案,但你看不出哪里错了,最后是带着错误认知往下走。这种时候先看手册和官方例程。

二是老工程里语法很「野」的,比如大量自定义函数、include 嵌套、非标准的目录结构,AI 容易按通用套路给你改,改完编译过了但行为变了。这种我宁可自己动手。

三是时间紧、又要一次成功的场合。AI 省的是打字时间,不省核对时间,急着上线的时候,核对成本反而更突出。

最后说句实在的,这两个东西,AI 现在的位置是「帮你把八成的体力活干了,剩两成脑力活还得自己上」。Makefile 那八成比较实,链接脚本那八成有点虚。你要是刚开始用,建议先从改 Makefile 试起,跑通了再往链接脚本上靠,别一上来就让它从零生成整个裸机工程,容易给自己挖坑。

相关文章
|
3月前
|
人工智能
Qwen3.8抢先体验!正式版即将发布并开源!
千问Qwen3.8即将开源,参数达2.4T,进化速度以“天”计,实力媲美Fable 5。预览版Qwen3.8-Max已上线阿里Token Plan等平台,限时优惠:日间Credits低至1折,夜间更优,个人/团队版月付仅35元起!
6046 216
|
10月前
|
监控 安全 Unix
iOS 崩溃排查不再靠猜!这份分层捕获指南请收好
从 Mach 内核异常到 NSException,从堆栈遍历到僵尸对象检测,阿里云 RUM iOS SDK 基于 KSCrash 构建了一套完整、异步安全、生产可用的崩溃捕获体系,让每一个线上崩溃都能被精准定位。
3322 174
|
10月前
|
网络协议 安全 数据挖掘
阿里云服务器九代实例c9i/g9i/r9i与c9a/g9a/r9a对比,性能、价格、适用场景对比与选择参考
目前在阿里云的活动中,除了入门级的轻量应用服务器和经济型e实例,主打高性价比的通用算力型实例云服务器之外,对于需要高性能云服务器的用户,可选择活动内的九代云服务器c9i/g9i/r9i实例和c9a/g9a/r9a实例,本文为大家介绍c9i/g9i/r9i实例和c9a/g9a/r9a实例的架构、处理器、适用场景与价格,以供大家对比和选择参考。
1861 9
|
10月前
|
人工智能 安全 API
上手教程:聊聊怎么用阁下AI的API
上手教程:聊聊怎么用阁下AI的API
|
4月前
|
编解码 自然语言处理 文字识别
HiDream-O1开源:8B参数像素级统一Transformer
HiDream-O1-Image是HiDream.ai开源的8B参数像素级统一生成模型,摒弃VAE与分离文本编码器,首创UiT架构实现文本、图像、任务条件在共享token空间端到端联合建模。支持2048×2048高清生成、多镜头/多语言渲染、指令编辑与主体个性化,在GenEval等基准刷新SOTA。含50步未蒸馏版与28步Dev加速版,并集成推理驱动提示代理。
1075 0
|
人工智能
如何把自己“建”出来( Prompt大模型的自我描述 系列四)
本文讲述了一个人工智能体如何逐步构建自身认知结构的过程。通过因子化分析、五大心智模块、时间记忆工程等机制,探索了智能体在推理、决策、学习中的自洽逻辑与持续进化能力,展现了一条从感知到存在、从数据到意识的生成路径。
573 11
|
7月前
|
人工智能 自然语言处理 供应链
2026年的企业级Agent解决方案,打破数据孤岛实现跨部门智能协同
2026年,瓴羊Agent One作为阿里云企业级智能体平台,以“智能中枢”角色打破数据孤岛:统一语义层、跨系统编排任务、主动协同推送。联动Dataphin、Quick Audience、Quick BI小Q与Quick Service,实现全域智能协同,赋能企业高效决策与敏捷运营。(239字)
|
机器学习/深度学习 人工智能 自然语言处理
迁移学习:让小数据也能驱动AI大模型
迁移学习:让小数据也能驱动AI大模型
585 99
|
人工智能 开发框架 搜索推荐
AI Agent构建强大外部工具调用能力不足,MCP Server怎样应对?MCP Serve在企业级Agent系统中的关键意义
本文AI产品专家三桥君探讨了MCP Server在企业级AI Agent系统中的关键作用,通过标准化工具接口实现AI与外部服务的无缝集成。三桥君重点阐述了分布式系统中的会话管理、状态持久化等实践方案,强调MCP Server在降低AI决策风险、提升系统可靠性方面的企业价值,为AI产品经理提供了架构设计与优化策略的实践指导。
916 0
|
11月前
|
存储 安全 开发者
阿里云4核8G云服务器收费标准与活动价格解析:u1/u2i/c9i多规格价格参考
阿里云4核8G服务器价格是多少?当前,经济型e实例的按量付费最低标准为每小时0.45元,包月费用为159.84元,而包年费用则为1477.44元。在阿里云的活动中,通用算力型u1实例的包年价格低至955.58元起,u2i实例的包年价格为1170.26元起,而计算型c9i实例的包年价格则为3136.81元起。不同实例类型的4核8G云服务器,其价格存在差异,且日常购买价格与活动期间价格也有所不同。以下是阿里云4核8G云服务器的最新价格概览。
2105 4
阿里云4核8G云服务器收费标准与活动价格解析:u1/u2i/c9i多规格价格参考

热门文章

最新文章