Tentukan baris kode yang menyebabkan kesalahan segmentasi?


151

Bagaimana cara menentukan kesalahan di dalam kode yang menyebabkan kesalahan segmentasi ?

Bisakah kompiler saya ( gcc) menunjukkan lokasi kesalahan dalam program?


5
Tidak ada gcc / gdb tidak bisa. Anda dapat mengetahui di mana segfault terjadi, tetapi kesalahan sebenarnya bisa berada di lokasi yang sama sekali berbeda.

Jawaban:


218

GCC tidak bisa melakukan itu tetapi GDB ( debugger ) pasti bisa. Kompilasi program Anda menggunakan -gsakelar, seperti ini:

gcc program.c -g

Kemudian gunakan gdb:

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

Berikut ini adalah tutorial yang bagus untuk memulai dengan GDB.

Di mana segfault terjadi umumnya hanya petunjuk tentang di mana "kesalahan yang menyebabkan" itu dalam kode. Lokasi yang diberikan tidak harus di mana masalahnya berada.


28
Perhatikan bahwa di mana segfault terjadi umumnya hanya petunjuk tentang di mana "kesalahan yang menyebabkan" itu ada dalam kode. Sebuah petunjuk penting, tetapi itu tidak selalu di mana masalahnya berada.
mpez0

9
Anda juga dapat menggunakan (bt full) untuk mendapatkan detail lebih lanjut.
ant2009


2
Gunakan btsebagai singkatan untuk backtrace.
rustyx

43

Anda juga dapat valgrindmencoba: jika Anda menginstal valgrinddan menjalankan

valgrind --leak-check=full <program>

maka itu akan menjalankan program Anda dan menampilkan jejak tumpukan untuk segfault, serta memori yang tidak valid membaca atau menulis dan kebocoran memori. Ini sangat berguna.


2
+1, Valgrind jauh lebih cepat / lebih mudah digunakan untuk menemukan kesalahan memori. Pada build yang tidak dioptimalkan dengan simbol debugging, ia memberi tahu Anda dengan tepat di mana segfault terjadi dan mengapa.
Tim Post

1
Sayangnya segfault saya hilang ketika dikompilasi dengan -g -O0 dan dikombinasikan dengan valgrind.
JohnMudd

2
--leak-check=fulltidak akan membantu men-debug segfault. Ini berguna hanya untuk men-debug kebocoran memori.
ks1322

@JohnMudd Saya memiliki segfault hanya muncul sekitar 1% dari file input yang diuji, jika Anda mengulangi input yang gagal itu tidak akan gagal. Masalah saya disebabkan oleh multithreading. Sejauh ini saya belum menemukan garis kode yang menyebabkan masalah ini. Saya menggunakan coba lagi untuk menutupi masalah ini untuk saat ini. Jika menggunakan opsi -g, kesalahan hilang!
Kemin Zhou

18

Anda juga bisa menggunakan dump inti dan kemudian memeriksanya dengan gdb. Untuk mendapatkan informasi yang berguna, Anda juga perlu mengkompilasi dengan -gbendera.

Setiap kali Anda menerima pesan:

 Segmentation fault (core dumped)

file inti ditulis ke direktori Anda saat ini. Dan Anda bisa memeriksanya dengan perintah

 gdb your_program core_file

File berisi status memori saat program macet. Dump inti dapat berguna selama penyebaran perangkat lunak Anda.

Pastikan sistem Anda tidak menetapkan ukuran file dump inti ke nol. Anda dapat mengaturnya menjadi tidak terbatas dengan:

ulimit -c unlimited

Hati-hati! inti pembuangan bisa menjadi besar.


Saya beralih ke arch-linux baru-baru ini. Direktori saya saat ini tidak mengandung file dump inti. Bagaimana saya bisa menghasilkannya?
Abhinav

Anda tidak menghasilkannya; Linux melakukannya. Core dumps disimpan di lokasi berbeda di beda Linuces - Google around. Untuk Arch Linux, baca ini wiki.archlinux.org/index.php/Core_dump
Mawg mengatakan mengembalikan Monica

7

Ada sejumlah alat yang tersedia yang membantu men-debug kesalahan segmentasi dan saya ingin menambahkan alat favorit saya ke dalam daftar: Address Sanitizers (sering disingkat ASAN) .

Kompiler modern¹ dilengkapi dengan berguna -fsanitize=address flag , menambahkan beberapa waktu kompilasi dan menjalankan overhead waktu yang melakukan lebih banyak pengecekan kesalahan.

Menurut dokumentasi cek ini termasuk menangkap kesalahan segmentasi secara default. Keuntungannya di sini adalah Anda mendapatkan jejak stack yang mirip dengan output gdb, tetapi tanpa menjalankan program di dalam debugger. Sebuah contoh:

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

Outputnya sedikit lebih rumit daripada gdb apa yang akan dihasilkan tetapi ada sisi positifnya:

  • Tidak perlu mereproduksi masalah untuk menerima jejak tumpukan. Cukup mengaktifkan bendera selama pengembangan sudah cukup.

  • ASAN menangkap lebih dari sekadar kesalahan segmentasi. Banyak akses di luar batas akan ditangkap bahkan jika area memori itu dapat diakses oleh proses.


¹ Yaitu Dentang 3.1+ dan GCC 4.8+ .


Ini sangat membantu saya. Saya memiliki bug yang sangat halus yang terjadi secara acak dengan frekuensi sekitar 1%. Saya memproses sejumlah besar file input dengan (16 langkah utama; masing-masing dilakukan oleh biner C atau C ++ yang berbeda). Satu langkah kemudian akan memicu kesalahan segmentasi hanya secara acak karena multi-threading. Sulit untuk di-debug. Opsi ini memicu keluaran informasi debug setidaknya memberi saya titik awal untuk tinjauan kode untuk menemukan lokasi bug.
Kemin Zhou

2

Jawaban Lucas tentang dump inti adalah baik. Di .cshrc saya, saya punya:

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

untuk menampilkan jejak balik dengan memasukkan 'inti'. Dan cap tanggal, untuk memastikan saya melihat file yang tepat :(.

Ditambahkan : Jika ada bug korupsi stack , maka backtrace yang diterapkan ke dump inti sering sampah. Dalam hal ini, menjalankan program dalam gdb dapat memberikan hasil yang lebih baik, sesuai jawaban yang diterima (dengan asumsi kesalahan mudah direproduksi). Dan juga waspadai beberapa proses dumping core secara bersamaan; beberapa OS menambahkan PID ke nama file inti.


4
dan jangan lupa ulimit -c unlimiteduntuk mengaktifkan dump inti di tempat pertama.
James Morris

@ James: Benar. Lucas sudah menyebutkan ini. Dan bagi kita yang masih terjebak dalam csh, gunakan 'batas'. Dan saya tidak pernah bisa membaca stackdumps CYGWIN (tapi saya belum mencoba selama 2 atau 3 tahun).
Joseph Quinsey

2

Semua jawaban di atas adalah benar dan direkomendasikan; jawaban ini dimaksudkan hanya sebagai upaya terakhir jika tidak ada pendekatan yang disebutkan di atas yang dapat digunakan.

Jika semuanya gagal, Anda selalu dapat mengkompilasi ulang program Anda dengan berbagai pernyataan debug-cetak sementara (mis fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__); ) Yang disebarkan sepanjang apa yang Anda yakini sebagai bagian yang relevan dari kode Anda. Kemudian jalankan program, dan amati apa yang terakhir dicetak-cetak dicetak tepat sebelum crash terjadi - Anda tahu program Anda sejauh itu, jadi pasti crash terjadi setelah titik itu. Tambahkan atau hapus debug-cetakan, kompilasi ulang, dan jalankan tes lagi, sampai Anda mempersempitnya menjadi satu baris kode. Pada titik itu Anda dapat memperbaiki bug dan menghapus semua cetakan debug sementara.

Ini cukup membosankan, tetapi memiliki keuntungan untuk bekerja di mana saja - satu-satunya waktu yang mungkin tidak adalah jika Anda tidak memiliki akses ke stdout atau stderr karena alasan tertentu, atau jika bug yang Anda coba perbaiki adalah balapan -kondisi yang perilakunya berubah ketika waktu program berubah (karena debug-cetakan akan memperlambat program dan mengubah waktunya)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.