UPDATE3:Clang issue submitted因为我现在确信这是一个以前没有报告的编译器错误.(LLVM bug跟踪器中有许多类似但不同的问题.感谢所有真心想帮忙的人.
UPDATE2:原来这个bug确实需要使用-n
(--nmagic
)选项.在没有使用C库(-nostdlib
Clang选项)的情况下构建的二进制文件上也会发生同样的崩溃.因此,构建NMAGIC二进制文件显然与这个问题无关.
UPDATE:LLVM自己的链接器LLD支持-n
(--nmagic
)选项,所以我安装了它并try 了一下.发生完全相同的segfault.由于LLVM自己的链接器支持构建NMAGIC二进制文件,这强烈表明它should在使用他们的编译器(Clang)时也可以工作.我会提交一份bug报告.
Original Post:
I've come across a C++ issue where instantiating some objects in programs compiled by Clang segfaults. Thanks in advance to anyone who can help shed light on the issue.
Just建议Clang正在生成SSE movaps
指令来初始化一些char数组,而正是这些指令在某些情况下似乎会导致segfault.
我已经在多个Linux系统上使用binutils链接器与Clang 16和Clang 17进行了测试,结果相同.我不确定是否在其他x86—64操作系统或其他链接器下也会出现同样的问题.当使用GCC编译器的版本而不是Clang时,问题确实发生了not个.
在以下最小条件集下,某些对象会发生段故障:
- x86—64代码生成
- 启用优化(—O1或以上)
- [EDIT—这是错误的segfault do not需要
-n
(--nmagic
)链接器选项.所有必要的是构建一个不使用标准C库的二进制文件(Clang的-nstdlib
选项).在这里,我还应该指出,使用-fno-builtin
选项没有什么区别.
这里是要复制的最小代码.在x86—64 Linux系统上编译以下代码[EDIT:移除不必要的链接器选项]:
$ clang -O1 -nostdlib -fno-stack-protector -static ClangSegv.s clang_segv.cc -o clang_segv
clang_segv. cc:
struct SegV
{
void set(const char *s) { char *b = buf; while ( *s ) { *b++ = *s++; } *b = '\0'; }
char buf[128] = "";
char *cursor = buf; // needed for segfault
};
int
main()
{
SegV v;
v.set("aa");
return 0;
}
ClangSegv.s
.intel_syntax noprefix
.global _start
_start:
xor rbp,rbp # Zero stack base pointer
xor r9,r9
pop rdi # Pop argc off stack -> rdi for 1st arg to main()
mov rsi,rsp # Argv @top of stack -> rsi for 2nd arg to main()
call main # Call main()... return result ends up in rax
xor r9,r9
mov rdi,rax # Move main()'s return to 1st argument for exit()
mov rax,231 # exit_group() syscall
syscall # Tell kernel to exit program
这个例子是我能想出的最小的再现器,与原始代码没有相似之处,我注意到问题,即两者都有字符数组的对象.更改代码可以掩盖或揭开问题,这通常意味着一个编码错误,但我在这个简单的例子中找不到一个.
我的调试器似乎认为问题是Clang生成的初始化'buf'字符数组的指令: Image of debugger suggesting issue with movaps instructions.
我的progrger说Clang生成了以下代码来初始化char buf[128]
:
0x400171 xorps %xmm0,%xmm0
0x400174 movaps %xmm0,-0x10(%rsp)
0x400179 movaps %xmm0,-0x20(%rsp)
0x40017e movaps %xmm0,-0x30(%rsp)
0x400183 movaps %xmm0,-0x40(%rsp)
0x400188 movaps %xmm0,-0x50(%rsp)
0x40018d movaps %xmm0,-0x60(%rsp)
0x400192 movaps %xmm0,-0x70(%rsp)
0x400197 movaps %xmm0,-0x80(%rsp)
0x40019c lea -0x80(%rsp),%rax
并且SegFault由第一Movapps指令生成.
显然,我希望数组的初始化不应该segfault.
不管是像我在这个例子中所做的那样使用类内初始化,还是使用初始化器列表初始化.这两种方法都有同样的问题.
我认为问题可能是clang thinks生成的代码对象成员的对齐方式与成员的对齐方式不匹配.我可能是错的,但是如果我在 struct 或char数组中添加alignas(32),问题就消失了.我不知道为什么我需要在32字节对齐.令人困惑的是(对我来说),在16字节对齐并不能掩盖问题.
如果我只是告诉Clang不要生成任何带有-mno-sse
的SSE指令,问题也会消失,但我不希望失go 这些优化.在这种情况下,Clang使用movq
指令来初始化数组,而不是movaps
.
如果我放弃初始化char数组成员,而在构造函数中手动执行,问题也会消失,但当然,这会降低效率.
在我看来,这看起来像是编译器的bug.还是我只是用错了?
谢谢!