Sytuacja kobiet w IT w 2024 roku
21.04.20204 min
Jamie Liu

Jamie Liu

Problemy z kompilowaniem: Seqfault i Illegal Instruction

Zobacz jak debugguje się błędy kompilacji w C i do jakich wniosków może to doprowadzić, a to wszystko na przykładzie dwóch błędów: segfault i illegal instruction.

Problemy z kompilowaniem: Seqfault i Illegal Instruction

Ostatnio reorganizowałam i rekompilowałam wszystkie zewnętrzne zależności Nebula Graph - open-sourcowej rozproszonej grafowej bazy danych. Natknęłam się wtedy na dwie interesujące kwestie, którymi chciałabym się w Wami podzielić.

Segfault we Flex -- Segmentation fault (core dumped)

Segfault wystąpiło przy kompilowaniu Flex:

make[2]: Entering directory '/home/dutor/flex-2.6.4/src'
./stage1flex   -o stage1scan.c ./scan.l
make[2]: *** [Makefile:1696: stage1scan.c] Segmentation fault (core dumped)


Sprawdziłam coredump za pomocą gdb:

Core was generated by `./stage1flex -o stage1scan.c ./scan.l'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  flexinit (argc=4, argv=0x7ffd25bea718) at main.c:976
976             action_array[0] = '\0';
(gdb) disas
Dump of assembler code for function flexinit:
   0x0000556c1b1ae040 <+0>:     push   %r15
   0x0000556c1b1ae042 <+2>:     lea    0x140fd(%rip),%rax        # 0x556c1b1c2146
   ...
   0x0000556c1b1ae20f <+463>:   callq  0x556c1b1af460 <allocate_array> # Allocate buffer
   ...
=> 0x0000556c1b1ae24f <+527>:   movb   $0x0,(%rax) # Write to buffer[0], failed due to illegal address
   ...
(gdb) disas allocate_array
Dump of assembler code for function allocate_array:
   0x0000556c1b1af460 <+0>:     sub    $0x8,%rsp
   0x0000556c1b1af464 <+4>:     mov    %rsi,%rdx
   0x0000556c1b1af467 <+7>:     xor    %eax,%eax
   0x0000556c1b1af469 <+9>:     movslq %edi,%rsi
   0x0000556c1b1af46c <+12>:    xor    %edi,%edi
   0x0000556c1b1af46e <+14>:    callq  0x556c1b19a100 <reallocarray@plt> # Allocate buffer
   0x0000556c1b1af473 <+19>:    test   %eax,%eax  # Check if the result pointer is NULL
   0x0000556c1b1af475 <+21>:    je     0x556c1b1af47e <allocate_array+30># Jump to error handler if NULL 
   0x0000556c1b1af477 <+23>:    cltq   # Extend eax to rax, truncated
   0x0000556c1b1af479 <+25>:    add    $0x8,%rsp
   0x0000556c1b1af47d <+29>:    retq
   ...
End of assembler dump.


Widzimy z powyższego kodu asemblera, że problem został spowodowany przez funkcję alocate_array. reallocarray zwrócił wskaźnik, który należy zapisać w 64-bitowym rejestrze rax. Jednak allocate_array wywołało reallocarray i zwróciło 32-bitowy rejestr eax. W międzyczasie użyło instrukcji cltq, aby rozszerzyć eax do rax.

Powodem może być to, że prototyp reallocarray z punktu widzenia allocate_array był inny niż prawdziwy prototyp. Patrząc na log kompilacji, znalazłam takie ostrzeżenie, jak implicit declaration of function reallocarray'. Problem ten można rozwiązać, dodając CFLAGS = -D_GNU_SOURCE na etapie konfiguracji. Ten problem nie pojawi się za każdym razem. Jednak właczenie opcji -pie i parametru kernel.randomize_va_space sprawia, że dużo łatwiej zreprodukować ten problem. 

Czego się nauczyłam:

  • Zwracany typ funkcji zadeklarowanej niejawnie to int w C.
  • Zwróć uwagę na ostrzeżenia kompilatora z włączoną opcją -Wall i -Wextra. Lepiej włącz -Werror w trybie deweloperskim.

GCC Illegal Instruction -- Internal Compiler Error: Illegal Instruction

Jakiś czas temu otrzymałam feedback od użytkowników Nebula Graph, że pojawił się błąd kompilatora: illegal instruction. Zobacz szczegóły w tym pull request

Oto błąd:

Scanning dependencies of target base_obj_gch
[ 0%] Generating Base.h.gch
In file included from /opt/nebula/gcc/include/c++/8.2.0/chrono:40,
from /opt/nebula/gcc/include/c++/8.2.0/thread:38,
from /home/zkzy/nebula/nebula/src/common/base/Base.h:15:
/opt/nebula/gcc/include/c++/8.2.0/limits:1599:7: internal compiler error: Illegal instruction
min() _GLIBCXX_USE_NOEXCEPT { return FLT_MIN; }
^~~
0xb48c5f crash_signal
../.././gcc/toplev.c:325
Please submit a full bug report,
with preprocessed source if appropriate.

Ponieważ jest to wewnętrzny błąd kompilatora, zakładam, że nielegalna instrukcja została napotkana w samym g++. Aby zlokalizować określony zestaw tych instrukcji i komponent, do którego on należy, musimy zreproduktować błąd.

Na szczęście, poniższy fragment kodu dokonuje cudów:  

#include <thread>
int main() 
{
    return 0;
}

Illegal instruction z pewnością wywoła SIGIL. Ponieważ g++ działa tylko jako wejście kompilatora, prawdziwym kompilatorem jest cc1plus. Możemy użyć gdb do wykonania kompilacji i wyłapania illegal instruction.

$ gdb --args /opt/nebula/gcc/bin/g++ test.cpp
gdb> set follow-fork-mode child
gdb> run
Starting program: /opt/nebula/gcc/bin/g++ test.cpp
[New process 31172]
process 31172 is executing new program: /opt/nebula/gcc/libexec/gcc/x86_64-pc-linux-gnu/8.2.0/cc1plus
Thread 2.1 "cc1plus" received signal SIGILL, Illegal instruction.
[Switching to process 31172]
0x00000000013aa0fb in __gmpn_mul_1 ()
gdb> disas
...
0x00000000013aa086 <+38>: mulx (%rsi),%r10,%r8

Bingo!

mulx należy do zestawu instrukcji BMI2, a procesor maszyny, w której wystąpił błąd, nie obsługuje tego zestawu instrukcji. Po gruntownym dochodzeniu odkryłam, że to GMP (który jest jedną z zależności GCC) wprowadził ten zestaw instrukcji. Domyślnie GMP wykrywa typ procesora komputera hosta na etapie konfiguracji, aby wykorzystać najnowsze zestawy instrukcji, co poprawia wydajność przy jednoczesnym ograniczeniu przenośności binarki.

Aby rozwiązać ten problem, możesz spróbować nadpisać dwa pliki w drzewie źródłowym GMP, tj. config.guess i config.sub odpowiednio z _configfsf.guess_ i configfsf.sub przed etapem konfiguracji.

Podsumowanie

  • GCC domyślnie nie przyjmuje nowego zestawu instrukcji z powodu problemu z kompatybilnością.
  • Aby zrównoważyć kompatybilność i wydajność, musisz wykonać dodatkową pracę.


Jeśli chcesz skompilować kod źródłowy Nebula Graph, to zapoznaj się z jego dokumentacją.

<p>Loading...</p>