Bagaimana cara menentukan kesalahan di dalam kode yang menyebabkan kesalahan segmentasi ?
Bisakah kompiler saya ( gcc
) menunjukkan lokasi kesalahan dalam program?
Bagaimana cara menentukan kesalahan di dalam kode yang menyebabkan kesalahan segmentasi ?
Bisakah kompiler saya ( gcc
) menunjukkan lokasi kesalahan dalam program?
Jawaban:
GCC tidak bisa melakukan itu tetapi GDB ( debugger ) pasti bisa. Kompilasi program Anda menggunakan -g
sakelar, 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.
bt
sebagai singkatan untuk backtrace
.
Anda juga dapat valgrind
mencoba: jika Anda menginstal valgrind
dan 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.
--leak-check=full
tidak akan membantu men-debug segfault. Ini berguna hanya untuk men-debug kebocoran memori.
Anda juga bisa menggunakan dump inti dan kemudian memeriksanya dengan gdb. Untuk mendapatkan informasi yang berguna, Anda juga perlu mengkompilasi dengan -g
bendera.
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.
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.
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.
ulimit -c unlimited
untuk mengaktifkan dump inti di tempat pertama.
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)