背景
前些日子在 github 闲逛时发现了一个 sdcc+binutils 的工具链,这个工具链很有意思,它使用 sdcc 作为前端将 .c 转换为优化后的 .asm
然后使用 binutils 中的 gas 将 .asm 编译成 .o 文件,最后使用 ld 链接为 elf, 然后使用 objcopy 生成 hex
我突然意识到,这个组合可以完美解决 sdcc 一直以来不支持的 “死代码消除”功能。
何为 “死代码消除”? SDCC 的链接器无法删除掉不使用的函数,比如你有一个 foo.c 文件,里面有 100 个函数,即使你的程序只使用了其中的1个,SDCC 在链接时也会将
剩下的 99 的不使用的函数也链接到最终的 hex 文件中,这样生成的代码尺寸就会很大。
而 binutils 中的 gas 支持 .section 伪指令(这样可以为每一个函数生成一个 .section),ld 是支持 --gc-sections 命令的,这个参数可以用来移除 .o 中未被使用的函数。于是乎开始折腾。。。
折腾过程
当时我浏览的源码仓库是这个:https://github.com/volumit/sdcc_aurix_scr_42
这个仓库包含了一个经过修改的 sdcc 和 binutils 并且提供了编译好的二进制
但是我尝试了一下下载下来的二进制好像用不了,然后我自行编译,也编译失败。而且我检查了一下它使用的是 sdcc 4.2.0 + binutils 2.2 这是很老的版本
竟然这么多坑?想了想还是自己再移植一遍,于是乎开干,经过2个星期的折腾,终于搞定,而且我编译了可用了二进制版本
源码基于 SDCC 4.5.0 最新 + binutils 2.38 最新 版修改
最终的源码仓库:github0null/sdcc-binutils-mcs51: use sdcc+as+ld to build your mcs51 project
可用的二进制:Release sdcc-with-binutils v0.2.1 · github0null/sdcc-binutils-mcs51
效果
使用原版的 SDCC 和 改进后的 SDCC 版本编译 10 个文件,主函数为:
#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);
}
}
代码大小如下:

可以看到效果达到预期,而且由于它使用了 ld 因此生成的 .map 文件更容易阅读,这比 SDCC 的 linker 生成的 map 清晰多了

测试套件也全部通过了,证明应该没什么大问题,应该可以正常使用

用法
1.使用Makefile
基本用法跟 sdcc 差不多,这里有一个示例的 Makefile 片段
需要注意的是 --stack-auto --nooverlay
这两个选项是必选的,而且现在仅支持 --small
和 large
model
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 $@ $<
2.使用VSCode插件
使用最新的 EIDE 插件可以无缝切换到该工具链
此处还提供了一个项目模板以便快速启动一个 STC8 项目
EIDE Project Templates


与原版SDCC不兼容的地方
由于链接器的不同,因此某些功能可能与 sdcc 不太一样
- 不支持 overlay:所有的局部变量保存到栈,因此必须启用
--stack-auto --nooverlay
参数,否则将编译失败
- 不支持 medium, huge 模型:目前仅支持
small
和 large
模型
- 不支持大于 64K 的地址:不支持引用大于的
0xFFFF
的地址,因此也不支持 SFR32
- 不支持 xstack: 栈只能位于
iram
中
- 绝对地址定位:不能使用
__at(xxx)
对 data idata xdata code
进行绝对地址定位,SFR寄存器除外,因为 ld 使用 linker script 进行地址分配。要使用该功能参考下面的 使用方法 小节,或者使用 -Wl,--defsym=
直接定义符号绝对位置
- 栈空间使用:由于不支持 overlay 功能,因此所有的局部变量和函数参数存储于栈中,这对于 mcs51 是一个挑战,因为即使使用 large 模型,stack 最大剩余大小为
256 - 32 = 224 bytes
,因此对于函数调用,不要使用太多的函数参数和嵌套调用,这会加速栈的溢出。