分享一个支持 "死代码消除" 的 SDCC 工具链
<h2>背景</h2><p>前些日子在 github 闲逛时发现了一个 sdcc+binutils 的工具链,这个工具链很有意思,它使用 sdcc 作为前端将.c 转换为优化后的 .asm</p>
<p>然后使用 binutils 中的 gas 将 .asm 编译成 .o 文件,最后使用ld 链接为 elf, 然后使用 objcopy 生成 hex</p>
<p>我突然意识到,这个组合可以完美解决 sdcc 一直以来不支持的 “死代码消除”功能。</p>
<p>何为 “死代码消除”? SDCC 的链接器无法删除掉不使用的函数,比如你有一个 foo.c 文件,里面有 100 个函数,即使你的程序只使用了其中的1个,SDCC 在链接时也会将</p>
<p>剩下的 99 的不使用的函数也链接到最终的 hex 文件中,这样生成的代码尺寸就会很大。</p>
<p>而 binutils 中的 gas 支持 .section 伪指令(这样可以为每一个函数生成一个 .section),ld 是支持--gc-sections 命令的,这个参数可以用来移除 .o 中未被使用的函数。于是乎开始折腾。。。</p>
<h2>折腾过程</h2>
<p>当时我浏览的源码仓库是这个:https://github.com/volumit/sdcc_aurix_scr_42</p>
<p>这个仓库包含了一个经过修改的 sdcc 和 binutils 并且提供了编译好的二进制</p>
<p>但是我尝试了一下下载下来的二进制好像用不了,然后我自行编译,也编译失败。而且我检查了一下它使用的是 sdcc 4.2.0 + binutils 2.2 这是很老的版本</p>
<p>竟然这么多坑?想了想还是自己再移植一遍,于是乎开干,经过2个星期的折腾,终于搞定,而且我编译了可用了二进制版本</p>
<p>源码基于 SDCC 4.5.0 最新 + binutils 2.38 最新 版修改</p>
<p>最终的源码仓库:github0null/sdcc-binutils-mcs51: use sdcc+as+ld to build your mcs51 project</p>
<p>可用的二进制:Release sdcc-with-binutils v0.2.1 · github0null/sdcc-binutils-mcs51</p>
<h2>效果</h2>
<p>使用原版的 SDCC 和 改进后的 SDCC 版本编译 10 个文件,主函数为:</p>
<pre><code>#include "config.h"
#include "delay.h"
#include "soft_uart.h"
#define LED_Toggle() P34 = !P34
void main(void)
{
while (1)
{
LOG("Hello, STC15F104W !");
LED_Toggle();
delay_ms(200);
}
}
</code></pre>
<p>代码大小如下:</p>
<p><img src="data/attachment/forum/202508/30/000717rgvi7rgvhwpp0vli.png" alt="image.png" title="image.png" /></p>
<p>可以看到效果达到预期,而且由于它使用了 ld 因此生成的 .map 文件更容易阅读,这比 SDCC 的 linker 生成的 map 清晰多了</p>
<p><img src="data/attachment/forum/202508/30/001712p0s1dd2n1cv51hmd.png" alt="image.png" title="image.png" /></p>
<p>测试套件也全部通过了,证明应该没什么大问题,应该可以正常使用</p>
<p><img src="data/attachment/forum/202508/30/000953fk46rvkv6422cffb.png" alt="image.png" title="image.png" /></p>
<h2>用法</h2>
<h4>1.使用Makefile</h4>
<p>基本用法跟 sdcc 差不多,这里有一个示例的 Makefile 片段</p>
<p>需要注意的是 <code>--stack-auto --nooverlay</code> 这两个选项是必选的,而且现在仅支持 <code>--small</code> 和 <code>large</code> model</p>
<pre><code>main.hex: main.o delay.o foo.o
@echo "link $@"
$(CC) -mmcs51 --model-small --stack-auto --nooverlay --out-fmt-ihx -Wl,--print-memory-usage -o $@ $^
%.o: %.c | Makefile
@echo "CC $<"
@$(CC) -c -mmcs51 --model-small --stack-auto --nooverlay -o $@ $<
</code></pre>
<h4>2.使用VSCode插件</h4>
<p>使用最新的 EIDE 插件可以无缝切换到该工具链</p>
<p>此处还提供了一个项目模板以便快速启动一个 STC8 项目</p>
<p>EIDE Project Templates</p>
<p><img src="data/attachment/forum/202508/30/001447txxu436b458gkxzi.png" alt="image.png" title="image.png" /></p>
<p><img src="data/attachment/forum/202508/30/001549mimif6kjjij1fqaj.png" alt="image.png" title="image.png" /></p>
<h3>与原版SDCC不兼容的地方</h3>
<p>由于链接器的不同,因此某些功能可能与 sdcc 不太一样</p>
<ul>
<li><strong>不支持 overlay</strong>:所有的局部变量保存到栈,因此必须启用 <code>--stack-auto --nooverlay</code> 参数,否则将编译失败</li>
<li><strong>不支持 medium, huge 模型</strong>:目前仅支持 <code>small</code> 和 <code>large</code> 模型</li>
<li><strong>不支持大于 64K 的地址</strong>:不支持引用大于的 <code>0xFFFF</code> 的地址,因此也不支持 SFR32</li>
<li><strong>不支持 xstack</strong>: 栈只能位于 <code>iram</code> 中</li>
<li><strong>绝对地址定位</strong>:不能使用 <code>__at(xxx)</code> 对 <code>data idata xdata code</code> 进行绝对地址定位,<strong>SFR寄存器除外</strong>,因为 ld 使用 linker script 进行地址分配。要使用该功能参考下面的 <strong>使用方法</strong> 小节,或者使用 <code>-Wl,--defsym=</code> 直接定义符号绝对位置</li>
<li><strong>栈空间使用</strong>:由于不支持 overlay 功能,因此所有的局部变量和函数参数存储于栈中,这对于 mcs51 是一个挑战,因为即使使用 large 模型,stack 最大剩余大小为 <code>256 - 32 = 224 bytes</code>,因此对于函数调用,不要使用太多的函数参数和嵌套调用,这会加速栈的溢出。</li>
</ul>
为你的“折腾”点赞{:4_167:}谢谢分享{:4_197:}
页:
[1]