Segmentasyon hatasına neden olan kod satırını belirle?


157

Bölümleme hatasına neden olan hatanın kodda nerede olduğu nasıl belirlenir ?

Derleyicim ( gcc) programdaki hatanın yerini gösterebilir mi?


5
Hiçbir gcc / gdb yapamaz. Segfault'un nerede oluştuğunu öğrenebilirsiniz , ancak gerçek hata tamamen farklı bir yerde olabilir.

Yanıtlar:


230

GCC bunu yapamaz ama GDB (bir hata ayıklayıcı ) kesinlikle yapabilir. Şunun -ggibi anahtarı kullanarak programınızı derleyin :

gcc program.c -g

Sonra gdb'yi kullanın:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

İşte GDB ile başlamanız için güzel bir öğretici.

Segfault'un meydana geldiği yer genellikle "neden olan hatanın" kodda nerede olduğuna dair bir ipucudur. Verilen konum, sorunun bulunduğu yer olmayabilir.


30
Segfault'un nerede oluştuğuna, genellikle sadece "neden olan hatanın" kodda nerede olduğuna dair bir ipucu olduğuna dikkat edin. Önemli bir ipucu, ancak sorunun tam olarak nerede olduğu anlamına gelmez.
mpez0

9
Daha fazla ayrıntı almak için (bt full) da kullanabilirsiniz.
ant2009


2
İçin btbir kısaltma olarak kullanın backtrace.
rustyx

48

Ayrıca şunu valgrinddeneyebilirsiniz: kurup valgrindçalıştırırsanız

valgrind --leak-check=full <program>

daha sonra programınızı çalıştırır ve herhangi bir segment hatası için yığın izlerini, ayrıca geçersiz bellek okuma veya yazma işlemlerini ve bellek sızıntılarını görüntüler. Gerçekten oldukça kullanışlıdır.


2
+1, Valgrind bellek hatalarını tespit etmek için çok daha hızlı / daha kolay. Hata ayıklama sembollerine sahip optimize edilmemiş derlemelerde, size bir segfault'un tam olarak nerede olduğunu ve neden olduğunu söyler .
Tim Post

1
Ne yazık ki segfault'um -g -O0 ile derlendiğinde ve valgrind ile birleştirildiğinde kayboluyor.
JohnMudd

2
--leak-check=fullsegfault'larda hata ayıklamaya yardımcı olmayacaktır. Yalnızca bellek sızıntılarında hata ayıklamak için kullanışlıdır.
ks1322

@JohnMudd Bir segfault'um var, test edilen giriş dosyalarının sadece% 1'i görünüyor, eğer başarısız girişi tekrarlarsanız başarısız olmayacak. Sorunum çoklu iş parçacığından kaynaklanıyordu. Şimdiye kadar bu soruna neden olan kod satırını bulamadım. Şimdilik bu sorunu çözmek için yeniden denemeyi kullanıyorum. -G seçeneği kullanılırsa, hata ortadan kalkar!
Kemin Zhou

18

Ayrıca bir çekirdek dökümü kullanabilir ve ardından gdb ile inceleyebilirsiniz. Yararlı bilgiler elde etmek için ayrıca -gbayrakla birlikte derlemeniz gerekir .

Mesajı ne zaman alırsanız:

 Segmentation fault (core dumped)

geçerli dizininize bir çekirdek dosya yazılır. Ve bunu komutla inceleyebilirsiniz

 gdb your_program core_file

Dosya, program çöktüğünde belleğin durumunu içerir. Yazılımınızın konuşlandırılması sırasında bir çekirdek dökümü faydalı olabilir.

Sisteminizin çekirdek döküm dosyası boyutunu sıfır olarak ayarlamadığından emin olun. Aşağıdakilerle sınırsız olarak ayarlayabilirsiniz:

ulimit -c unlimited

Yine de dikkatli olun! bu çekirdek çöplükler çok büyük olabilir.


Geçenlerde arch-linux'a geçtim. Mevcut dizinim çekirdek döküm dosyasını içermiyor. Nasıl oluşturabilirim?
Abhinav

Onu siz üretmiyorsunuz; Linux yapar. Çekirdek dökümler, farklı Linuces'te farklı yerlerde depolanır - Google çevresinde. Arch Linux için, bu wiki.archlinux.org/index.php/Core_dump'ı
Mawg, Monica'nın yeniden etkinleştirildiğini söylüyor

7

Segmentasyon hatalarının ayıklanmasına yardımcı olan bir dizi araç vardır ve en sevdiğim aracı listeye eklemek istiyorum: Adres Temizleyicileri (genellikle ASAN olarak kısaltılır) .

Modern derleyiciler kullanışlı bir -fsanitize=addressbayrakla birlikte gelir , biraz derleme süresi ve çalışma süresi ek yükü daha fazla hata denetimi yapar.

Belgelere göre bu kontroller, varsayılan olarak bölümleme hatalarının yakalanmasını içerir. Buradaki avantaj, gdb'nin çıktısına benzer bir yığın izleme elde etmenizdir, ancak programı bir hata ayıklayıcı içinde çalıştırmadan. Bir örnek:

int main() {
  volatile int *ptr = (int*)0;
  *ptr = 0;
}
$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

Çıktı, gdb'nin çıkardığından biraz daha karmaşık, ancak bazı artıları var:

  • Yığın izleme almak için sorunu yeniden oluşturmaya gerek yoktur. Geliştirme sırasında bayrağı etkinleştirmek yeterlidir.

  • ASAN'lar, segmentasyon hatalarından çok daha fazlasını yakalar. Bu bellek alanına işlem için erişilebilir olsa bile, sınır dışı erişimlerin çoğu yakalanacaktır.


¹ Yani Clang 3.1+ ve GCC 4.8+ .


Bu bana çok yardımcı oluyor. Yaklaşık% 1 sıklıkta rastgele gerçekleşen çok ince bir hatam var. Çok sayıda girdi dosyasını (16 ana adım; her biri farklı bir C veya C ++ ikili dosyasıyla yapılır) işliyorum. Sonraki bir adım, çoklu iş parçacığı nedeniyle yalnızca rastgele olarak segmentasyon hatasını tetikleyecektir. Hata ayıklamak zor. Bu seçenek hata ayıklama bilgisi çıktısını tetikledi, en azından hatanın yerini bulmak için kod incelemesi için bana bir başlangıç ​​noktası verdi.
Kemin Zhou

2

Lucas'ın temel dökümler hakkındaki cevabı güzel. .Cshrc dosyamda:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

'çekirdek' girerek geri izlemeyi görüntülemek için. Ve doğru dosyaya baktığımdan emin olmak için tarih damgası :(.

Eklendi : Yığın bozulması hatası varsa, çekirdek dökümüne uygulanan geri izleme genellikle gereksizdir. Bu durumda, programı gdb içinde çalıştırmak, kabul edilen cevaba göre daha iyi sonuçlar verebilir (hatanın kolayca tekrarlanabilir olduğunu varsayarak). Ayrıca çekirdeği eşzamanlı olarak boşaltan birden fazla işlemden de sakının; bazı işletim sistemleri PID'yi çekirdek dosyanın adına ekler.


4
ve ulimit -c unlimiteden başta temel dökümleri etkinleştirmeyi unutmayın .
James Morris

@James: Doğru. Lucas bundan daha önce bahsetmişti. Ve hala csh'de sıkışmış olanlarımız için 'limit' kullanın. Ve CYGWIN yığın dökümlerini hiç okuyamadım (ama 2 veya 3 yıldır denemedim).
Joseph Quinsey

2

Yukarıdaki cevapların tümü doğru ve tavsiye edilir; Bu yanıt, yukarıda bahsedilen yaklaşımlardan hiçbirinin kullanılamaması durumunda, yalnızca son çare olarak düşünülmüştür.

Her şey başarısız olursa, programınızı her zaman fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);kodunuzun ilgili kısımları olduğuna inandığınız şeyler boyunca serpiştirilmiş çeşitli geçici hata ayıklama-yazdırma ifadeleriyle (örn. ) Yeniden derleyebilirsiniz . Ardından programı çalıştırın ve çökme meydana gelmeden hemen önce yazdırılan en son hata ayıklama yazısının ne olduğunu gözlemleyin - programınızın bu kadar ileri gittiğini biliyorsunuz, bu nedenle çökme bu noktadan sonra olmuş olmalı. Hata ayıklama baskılarını ekleyin veya kaldırın, yeniden derleyin ve tek bir kod satırına daraltıncaya kadar testi tekrar çalıştırın. Bu noktada hatayı düzeltebilir ve tüm geçici hata ayıklama baskılarını kaldırabilirsiniz.

Oldukça sıkıcı, ancak hemen hemen her yerde çalışma avantajına sahip - bunun olmayabileceği tek durum, herhangi bir nedenle stdout veya stderr'e erişiminizin olmaması veya düzeltmeye çalıştığınız hata bir yarışsa olabilir. -Programın zamanlaması değiştiğinde davranışı değişen koşul (çünkü hata ayıklama baskıları programı yavaşlatacak ve zamanlamasını değiştirecektir)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.