NO BUILTIN!
(尝试在遍地 AI 的时代古法炼丹)
# 前言
做毕设的实验阶段,在移植 CoreMark 时,遇到了测试 elf 文件运行爆栈的错误。
经过定位,发现是 memset 函数生成的汇编代码死循环了。
# 问题描述
问题 C 代码如下:
void* memset(void* s, int c, size_t n) { | |
unsigned char* p = (unsigned char*)s; | |
while (n--) { | |
*p++ = (unsigned char)c; | |
} | |
return s; | |
} |
经过默认参数,开启 -O2 的 gcc 编译后,生成了如下代码:
80001d84 <memset>:
80001d84: ff010113 addi sp,sp,-16
80001d88: 00812423 sw s0,8(sp)
80001d8c: 00112623 sw ra,12(sp)
80001d90: 00050413 mv s0,a0
80001d94: 00060663 beqz a2,80001da0 <memset+0x1c>
80001d98: 0ff5f593 zext.b a1,a1
80001d9c: fe9ff0ef jal 80001d84 <memset> # ❌ 递归调用自身
80001da0: 00c12083 lw ra,12(sp)
80001da4: 00040513 mv a0,s0
80001da8: 00812403 lw s0,8(sp)
80001dac: 01010113 addi sp,sp,16
80001db0: 00008067 ret
# 原因分析
为什么 C 函数中没有调用自身的指令,生成的汇编代码却会出现那一条递归指令呢?
问题出在 gcc 的优化中。以下是相关的编译流程:
- gcc 识别到循环为 memset 的语义;
- 由于没有
-fno-builtin参数,gcc 认为存在 memset 内置函数,尝试将函数体替换为内置函数; - 本函数名恰好为
memset,进行函数解析时即找到了当前函数的符号。
# 解决方法
最简洁的修复方式:
__attribute__((optimize("no-builtin"))) | |
void* memset(void* s, int c, size_t n) { | |
unsigned char* p = (unsigned char*)s; | |
while (n--) { | |
*p++ = (unsigned char)c; | |
} | |
return s; | |
} |
或者如果 GCC 版本较老(不支持 no_builtin):
__attribute__((optimize("no-tree-loop-distribute-patterns"))) | |
void* memset(void* s, int c, size_t n) { | |
unsigned char* p = (unsigned char*)s; | |
while (n--) { | |
*p++ = (unsigned char)c; | |
} | |
return s; | |
} |