Warum verursacht eine leere Schleife einen Busfehler, wenn ein C-Programm mit clang -O2 auf macOS kompiliert wird?

Warum verursacht eine leere Schleife einen Busfehler, wenn ein C-Programm mit clang -O2 auf macOS kompiliert wird?


Ich verwende macOS High Sierra.


$ uname -v
Darwin Kernel Version 17.2.0: Fri Sep 29 18:27:05 PDT 2017; root:xnu-4570.20.62~3/RELEASE_X86_64

Ich habe das folgende synthetisierte Programm.


void nop1() {
for (;;);
}
void nop2() {
while (1);
}
void nop3() {
int i = 0;
while(1) {
i++;
}
}
void nop4() {
static int i = 0;
while(1) {
i++;
};
}
int main() {
nop1();
return 0;
}

Änderung 2: Ich habe jetzt in den folgenden Beispielen explizit mit clang kompiliert.


Wenn ich das folgende C-Programm mit clang -O2 kompiliere und ausführe Ich erhalte einen Busfehler bei main() ruft nop1() auf , nop2() , nop3() aber nicht für nop4() .


$ ./a.out
[1] 93029 bus error (core dumped) ./a.out

Beim Kompilieren ohne -O2 alle Versionen laufen ohne Busfehler. Ich denke, der Optimierer transformiert nop3() bis nop2() . Ich würde gerne verstehen, was den Busfehler in jedem Fall verursacht und warum eine statische Variable in nop4() verwendet wird verursacht keinen Busfehler.


Dies ist meine Clang-Version:


$ clang -v
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin17.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

Ich habe auch mit gcc unter Linux getestet:


$ uname -a
Linux trygger 4.4.0-112-generic #135-Ubuntu SMP Fri Jan 19 11:48:36 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

, und die Programme laufen für alle nop einwandfrei -Funktionen, sowohl mit als auch ohne -O2 .


Dies ist meine gcc-Version unter Linux.


$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.6' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.6)

Bearbeiten 4


Vielleicht ist die Ausgabe von otool einfacher zu analysieren. Zuerst mit -O2 .


$ clang -O2 segfault.c
$ otool -vt a.out
a.out:
(__TEXT,__text) section
_nop1:
0000000100000f30 pushq %rbp
0000000100000f31 movq %rsp, %rbp
0000000100000f34 nopw %cs:(%rax,%rax)
0000000100000f40 jmp 0x100000f40
0000000100000f42 nopw %cs:(%rax,%rax)
_nop2:
0000000100000f50 pushq %rbp
0000000100000f51 movq %rsp, %rbp
0000000100000f54 nopw %cs:(%rax,%rax)
0000000100000f60 jmp 0x100000f60
0000000100000f62 nopw %cs:(%rax,%rax)
_nop3:
0000000100000f70 pushq %rbp
0000000100000f71 movq %rsp, %rbp
0000000100000f74 nopw %cs:(%rax,%rax)
0000000100000f80 jmp 0x100000f80
0000000100000f82 nopw %cs:(%rax,%rax)
_nop4:
0000000100000f90 pushq %rbp
0000000100000f91 movq %rsp, %rbp
0000000100000f94 nopw %cs:(%rax,%rax)
0000000100000fa0 jmp 0x100000fa0
0000000100000fa2 nopw %cs:(%rax,%rax)
_main:
0000000100000fb0 pushq %rbp
0000000100000fb1 movq %rsp, %rbp

Und ohne -O2 .


$ clang segfault.c
$ otool -vt a.out
a.out:
(__TEXT,__text) section
_nop1:
0000000100000f30 pushq %rbp
0000000100000f31 movq %rsp, %rbp
0000000100000f34 jmp 0x100000f39
0000000100000f39 jmp 0x100000f39
0000000100000f3e nop
_nop2:
0000000100000f40 pushq %rbp
0000000100000f41 movq %rsp, %rbp
0000000100000f44 jmp 0x100000f49
0000000100000f49 jmp 0x100000f49
0000000100000f4e nop
_nop3:
0000000100000f50 pushq %rbp
0000000100000f51 movq %rsp, %rbp
0000000100000f54 movl $0x0, -0x4(%rbp)
0000000100000f5b movl -0x4(%rbp), %eax
0000000100000f5e addl $0x1, %eax
0000000100000f61 movl %eax, -0x4(%rbp)
0000000100000f64 jmp 0x100000f5b
0000000100000f69 nopl (%rax)
_nop4:
0000000100000f70 pushq %rbp
0000000100000f71 movq %rsp, %rbp
0000000100000f74 jmp 0x100000f79
0000000100000f79 movl 0x81(%rip), %eax
0000000100000f7f addl $0x1, %eax
0000000100000f82 movl %eax, 0x78(%rip)
0000000100000f88 jmp 0x100000f79
0000000100000f8d nopl (%rax)
_main:
0000000100000f90 pushq %rbp
0000000100000f91 movq %rsp, %rbp
0000000100000f94 subq $0x10, %rsp
0000000100000f98 movl $0x0, -0x4(%rbp)
0000000100000f9f callq 0x100000f40
0000000100000fa4 xorl %eax, %eax
0000000100000fa6 addq $0x10, %rsp
0000000100000faa popq %rbp
0000000100000fab retq

Bearbeiten 3


Wie von @Olaf angefordert, habe ich die von clang -S generierte Assembly hinzugefügt.


 .section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 12
.globl _nop1
.p2align 4, 0x90
_nop1: ## @nop1
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
jmp LBB0_1
LBB0_1: ## =>This Inner Loop Header: Depth=1
jmp LBB0_1
.cfi_endproc
.globl _nop2
.p2align 4, 0x90
_nop2: ## @nop2
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp3:
.cfi_def_cfa_offset 16
Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp5:
.cfi_def_cfa_register %rbp
jmp LBB1_1
LBB1_1: ## =>This Inner Loop Header: Depth=1
jmp LBB1_1
.cfi_endproc
.globl _nop3
.p2align 4, 0x90
_nop3: ## @nop3
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp6:
.cfi_def_cfa_offset 16
Ltmp7:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp8:
.cfi_def_cfa_register %rbp
movl $0, -4(%rbp)
LBB2_1: ## =>This Inner Loop Header: Depth=1
movl -4(%rbp), %eax
addl $1, %eax
movl %eax, -4(%rbp)
jmp LBB2_1
.cfi_endproc
.globl _nop4
.p2align 4, 0x90
_nop4: ## @nop4
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp9:
.cfi_def_cfa_offset 16
Ltmp10:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp11:
.cfi_def_cfa_register %rbp
jmp LBB3_1
LBB3_1: ## =>This Inner Loop Header: Depth=1
movl _nop4.i(%rip), %eax
addl $1, %eax
movl %eax, _nop4.i(%rip)
jmp LBB3_1
.cfi_endproc
.globl _main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp12:
.cfi_def_cfa_offset 16
Ltmp13:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp14:
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
callq _nop1
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
.zerofill __DATA,__bss,_nop4.i,4,2 ## @nop4.i
.subsections_via_symbols

Antworten:


Dies ist ein bekannter Fehler in LLVM. Das Verhalten, das Sie sehen, gilt für C++, aber nicht für C.


Siehe Fehlerbericht Nr. 965 von 2006 hier.


Kürzlich trat dieses Problem erneut auf, weil Rust davon betroffen war.


Es gibt hier einen Patch mit einem Fix, der im November 2017 zusammengeführt wurde, aber ich weiß nicht, in welcher Version er veröffentlicht wird.


Siehe auch eine Diskussion in der Mailingliste hier.