ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

gmake报错不是根因:CCS编译失败的正确排查思路

gmake报错不是根因:CCS编译失败的正确排查思路 1. 先看清楚这条报错在整条编译链里的位置很多朋友第一次在CCS里遇到gmake: Target all not remade because of errors这条报错时第一反应是去搜索引擎里原样粘贴这句话然后发现搜出来的答案五花八门照着试了半天却一点用都没有。我当年在调DSP28335的工程时也一样差点把CCS卸载重装一遍。这里要先说一个非常关键的认知这条gmake报错本身不是根因它只是一个“连锁反应”的结果。你真正要处理的问题在编译日志的更上方。为了把这件事讲透我们得先搞清楚gmake的工作机制。CCS使用的构建系统核心是gmake也就是GNU Make在命令行下的实现。Make的基本逻辑是一个目标target能否被生成取决于它的依赖prerequisite是否全部成功生成。如果任何一个依赖步骤失败make就不会去执行生成这个目标的命令并给出类似“not remade because of errors”的提示。拿一个典型的DSP工程来举例最终生成的.out文件是顶层目标all它的依赖包括各个.obj目标文件而这些.obj又依赖对应的.c源文件。整个编译过程是分层的第一步把每个.c文件编译成.obj文件这一步是语法检查、预处理、生成目标代码由C编译器完成。第二步把所有.obj文件和库文件链接成最终的.out文件由链接器完成。当其中一个.c文件编译失败时它对应的.obj就没有生成。gmake发现all的依赖树中有一个环节断了于是直接跳过链接和后续步骤告诉你“all这个目标没法重新生成”。但这只是结果不是原因。真正的原因可能是某个源文件里有语法错误可能是某个头文件找不到也可能是某个选项配置不对。理解这一点之后你再看这条报错心态就完全不一样了。它不是在告诉你问题出在哪里而是在告诉你“你的工程里有一步挂了自己往上翻日志去。” 所以下一步的关键动作不是去搜索这条报错怎么解而是学会看CCS的Build Console里的完整日志找到第一个真正的错误。我在实际带新人的过程中发现最容易让人误判的场景是控制台窗口比较小只显示了最后几行大家看到的就是这个gmake的“总结性报错”前面的流水信息被滚屏冲掉了。加上CCS默认的Console字体小、信息密扫一眼全是长长的编译命令行很容易眼睛发花。因此学会正确查看和筛选日志才是解决这一类问题的第一步。2. 第一现场永远在日志上方如何快速定位真正的编译错误先把结论放在最前面当看到“not remade because of errors”时直接忽视它。你的任务是往日志上方翻找到第一条带有 error 字样、且带有具体文件路径和行号的信息。这才是排错的唯一正确起点。CCS的编译日志其实是比较有规律的正常的编译行大致长这样makefile: /workspace/my_dsp_project/Debug/objects.mk: 完成 subdir_rules: 目标 my_dsp_project/Debug/main.obj 的规则如果某个步骤失败错误信息会以明显的标识出现比如C:/ti/ccs1281/ccs/tools/compiler/ti-cgt-c2000_22.6.1.LTS/bin/cl2000 -v28 -ml -g ... --obj_directoryDebug ../main.c WARNING: 文件 ../config.h 不存在 ERROR: 无法打开源文件 config.h gmake: *** [makefile:117: Debug/main.obj] Error 1注意这里最后一行gmake: *** [makefile:117: Debug/main.obj] Error 1这行信息同样不是根因它的作用是告诉你“这个obj文件对应的make规则以失败告终”。真正的根因是它上面那几行 ERROR: 无法打开源文件 config.h。为了方便排查我在CCS里会做几件日常设置建议你照着配一次把Build Console的字体调大一点路径在Window - Preferences - General - Appearance - Colors and Fonts - Debug - Console字号建议调到14以上长时间盯日志眼睛会舒服很多。构建时只显示错误和警告减少无关输出。在Window - Preferences - CCS - Build - Console里有一个Console output when building的下拉选项可以选Show only errors and warnings。这样一旦编译有错控制台会非常干净第一条就是真正的错误。遇到报错但不清楚定位时用Problem视图。CCS底部的Problems标签页会把当前工程的错误、警告汇总成一张表格双击某一条可以直接跳到对应的源文件行非常方便。接下去你要养成一个习惯往下看错误行之前先盯住它描述的对象。例如无法打开源文件指的是include路径或文件不存在#1965 cannot open source file是同一类问题的另一种格式undefined symbol说明是链接阶段找不到符号问题通常在库文件或内存分配space placement相关报错则指向了cmd文件里的内存段分配。我见过不少新手在这个阶段消耗大量时间原因就是看到了gmake: Error 1就去搜索搜出一堆“清缓存”“重装”的建议然后挨个试。实际上只要在CCS里打开Problems视图双击错误条目跳到具体位置多数问题在几十秒内就能定位。学会区分“根因错误”和“结果错误”是这个报错排解的核心分水岭。2.1 把编译日志按照错误类型归档事半功倍我在长期的使用中把CCS编译错误大致分成了三类遇到not remade because of errors时我会直接根据错误首行的特征归入某一类错误特征典型提示根源阶段排查方向找不到头文件cannot open source file / #1965 / 无法打开源文件预处理阶段include搜索路径、文件是否真实存在语法或类型错误expected a ; / identifier is undefined / #20编译阶段对应源文件的语法和类型声明链接阶段失败undefined symbol / cannot find library / placement fails链接阶段cmd内存分配、库路径、函数实现缺失这个分类解决了“我到底该改哪里”的迷茫。你是文件路径问题是代码语法问题还是内存分配问题处理手法完全不同。不要一上来就Clean工程、改编译选项先把错误对号入座再动手。3. 高频根因逐个过头文件、链接、cmd内存分配和编译器选型3.1 头文件路径与include顺序的坑在CCS工程里头文件找不到是最常见的第一类根因尤其是从别人的机器上拷过来的工程。DSP工程往往不是把所有源文件放在同一个目录下而是分成src、include、common等若干目录。CCS不会自动扫描所有目录你必须把头文件所在的目录明确加入到编译器的include搜索路径里。具体操作路径是右键工程 -Properties-C/C General - Paths and Symbols - Includes在GNU C或对应编译器选项卡下添加目录。也可以直接将路径写在Build - C2000 Compiler - Include Options里的--include_path参数中。一个特别容易忽略的点是CCS在include路径里填写相对路径时基准目录不一定是工程根目录而是编译输出目录通常是 Debug 目录。这意味着如果工程根目录是C:/workspace/proj在Debug目录下编译那么../include和../../include是有本质区别的。路径少写了一层或写多了一层都会直接导致头文件无法打开。我建议所有DSP工程采用统一的做法要么全部使用绝对路径要么全部使用以$(PROJECT_LOC)为基点的相对路径不要混用。$(PROJECT_LOC)是CCS内置的变量代表工程所在的绝对路径用它可以彻底避开“编译目录不同导致相对路径失效”的问题。3.2 链接器报错与.cmd内存段分配如果日志里出现undefined symbol或placement fails for object那么问题往往不在编译器而在链接器也就是.cmd文件里。undefined symbol指的是链接器找不到某个函数或变量的实现。这种情况有三个可能一是你没有把对应的库文件添加到工程里二是配置了条件编译导致某个函数没有被编译进去三是函数声明和实现的名字不一致。而placement fails背后的逻辑更好玩。DSP的内存资源是分段的.cmd文件里定义了各个段放在哪块内存区域。拿TMS320F28335来说它内部有RAM、Flash等存储空间链接器负责把代码段、数据段放到指定的空间里。如果你把RAM段的长度设置得比实际需要小或者一个段被分配到了已经被占用的区域就会报放置错误。这时候的排错手法是先看整体内存占用在CCS的编译日志里找.map文件的位置然后打开map文件查看各段的使用情况。比如ramfuncs段占了多少字节stack段是否溢出哪个段超出了设定长度。然后回到28335_RAM_lnk.cmd这类链接命令文件里调整段地址或长度。我还想特别提醒一点很多cmd文件里的内存区域定义是固定死的比如从某一地址开始的长度是0x2000如果你新增了一个大数组超出这个长度就会报placement错误。此时不要盲目扩大长度先确认相邻的内存区域是否空闲如果紧挨着是另一个已用的段那样扩下去只会把问题隐藏得更深。正确的做法是为大数组单独开一个段并把它放在一块足够大的连续空间里。3.3 编译器版本与代码生成工具链错位CCS的一个隐藏比较深的问题是工程默认使用的编译器版本和你机器上实际安装的版本对不上。当你在别人的工程文件上直接开发时CCS会尝试用本地安装的编译器打开工程但如果版本差异过大某些编译选项、内建宏定义就变了代码就可能在预处理阶段报出一堆莫名其妙的错误。CCS中每个工程都有独立的编译器配置。你是可以同时安装多个版本的工具链的在工程属性的Build - C2000 Compiler - Processor Options里可以看到当前使用的编译器版本。建议所有成员统一编译器小版本号否则排错时你会被“同一个工程不同人编译结果不同”的情况折磨。另一个常见问题是编译器的浮点支持选项。C2000系列编译器的--float_supportfpu32或fpu64如果和芯片实际型号不匹配代码也能编过但运行结果不对甚至在某些优化级别下编译阶段就报错。这类问题排查起来比较隐蔽建议在拿到一个全新工程后第一时间核对三个配置目标芯片型号例如28335还是28379D编译器版本工具链的具体小版本号浮点支持选项fpu32 / fpu64 / 不启用这三个配置和工程代码的匹配度直接决定了编译和运行是否稳定。我在带新人时会让对方把这三项截图存在工程目录下的README里省得每次换电脑都重新踩一遍配置坑。4. 工程环境里的另类致命细节路径、工作区与并行编译4.1 中文或空格路径导致的“玄学问题”CCS对工程路径的容忍度说实话不算高。如果你的工程放在类似D:/我的代码/DSP项目或C:/Program Files (x86)/work这种路径下编译时各种诡异问题都可能出现。其中比较典型的有两种。第一种是路径里的中文或特殊字符在Makefile解析时变成乱码明明文件就在那里gmake却提示找不到文件第二种是路径中的空格导致make把一条命令拆成了两段编译器收到的是半截参数直接换行报错。如果不清楚是不是这个问题有个很简单的验证办法把工程整体复制到C:/ti_workspace/dsp_test这种纯英文、纯短路径下重新编译一次。如果报错消失那就说明问题确实在路径上。这里我多说一句很多嵌入式工程师习惯把Workspace放在同步盘或者桌面方便备份。但云同步盘的路径往往带着随机字符串甚至特殊字符加上CCS频繁读写构建文件轻则拉低编译速度重则触发文件锁和路径错误。把Workspace独立一个本地磁盘目录别放在同步盘、桌面和中文路径下这一条经验能替你在后续几年的开发里省下大量无意义的时间。4.2 工作区路径漂移和索引器失真CCS有一个比较特殊的行为它会在工程文件里记录一些绝对路径信息比如.cproject和.project文件里。当整个工程被移动到新位置CCS有时能自动识别并更新有时不会。如果工程在移动后还能打开但点击编译时报告file not found就要考虑路径信息没有刷新的问题。解决办法通常是执行一下Project - Clean再关闭并重新导入工程。在导入时注意选择Copy projects into workspace选项这样CCS会重建一套基于新位置的工程配置把旧的绝对路径信息彻底清掉。另一个容易被误解的现象是Indexer。CCS的代码索引器负责提供代码跳转、自动补全、语法高亮等服务但Indexer看到的“代码状态”和磁盘上真实代码状态偶尔会不同步。于是你会遇到一种情况代码里明明有语法错误高亮但编译却通过了或者相反代码看起来没问题编译却报错。碰到这种情况优先做一次Project - C/C Index - Rebuild重建索引后再看。如果还是不对再Clean工程。记住一个判断原则以编译日志为准Indexer的显示仅供参考。很多新人在Indexer红色波浪线和编译报错之间来回折腾其实只需要关注gmake输出里的错误行就够了。4.3 并行编译的假失败与Clean的时机CCS支持多核并行编译在Window - Preferences - CCS - Build里可以设置并行编译的Job数量。并行编译确实能明显提升大型工程的构建速度但它也有代价多个编译任务同时读写共享文件时偶尔会触发偶发性的“假失败”。什么叫假失败就是你什么都没改Clean之后重新编译却过了或者编译失败说的是cannot open file xxx.obj但你打开对应目录那个obj文件根本不存在完完全全是make的依赖关系错乱。这种问题通常是并行编译时两个任务竞争同一个目标文件导致的触发概率不高但一旦遇到非常迷惑人。我的建议是先确认根因再进行Clean操作。很多新手的习惯是编译报错后第一时间Clean重新Build有时候确实碰巧解决了问题但下一次遇到同样的报错还是不知道怎么定位。真正的排错顺序应该是看日志定位第一条真正的错误根据错误类型对应好根因范围只有确认是缓存类问题时才做Clean另外如果你怀疑是并行编译的不稳定问题可以临时把并行Job数改成1即串行编译。串行编译速度会慢一些但它可以排除大量依赖竞争的干扰。特别是在排疑难杂症时串行编译的结果更可复现。5. 我踩过几次坑之后沉淀的排查清单与预防措施我把自己多次处理gmake: Target all not remade because of errors报错的完整思路整理成了一份清单现在每次遇到这类问题都按这个顺序来基本上能在十分钟内定位到根因。第一步忽略gmake总结性报错不看最后三行。直接把控制台输出切到“仅显示错误和警告”或者打开Problem视图。找出第一条error或 ERROR开头的行看它到底在说什么。第二步根据错误内容分类处理。如果是头文件打不开检查include路径和文件是否存在如果是语法错误跳到对应源文件行如果是undefined symbol检查库文件和函数实现如果是内存布局问题打开map文件看段占用。第三步如果错误信息指向的文件路径可疑先检查工程路径是否为纯英文且不包含空格。是的话再检查工程是否移动过位置必要时执行Clean和重新导入。第四步确认不是编译器版本问题。核对当前工程使用的编译器版本和本机安装的工具链是否匹配版本差异过大时直接切换工具链或重新创建对应版本工程。第五步排除并行编译干扰。将并行Job数改为1重新build如果问题消失再调整回并行编译。这套流程执行下来无论问题出在哪一层都绕不开“先看日志、再定位、后处理”的核心逻辑。真正值钱的不是某一条具体报错的解法而是这种分而治之的排错思路。最后再分享两个我在长期使用CCS过程中养成的小习惯。第一个是定期Clean工程不是在出错时才Clean而是每完成一轮较大改动后主动Clean一次把增量编译中可能积累的脏依赖清理掉。第二个是不建议所有代码工程共用一个Workspace我习惯按项目类别分多个Workspace防止工程数量太多导致索引变慢、误报增多。这两个习惯看起来不起眼却在长期使用中极大地减少了无谓的排错时间。
返回列表