Bingkai tumpukan rusak GDB - Bagaimana cara melakukan debug?


113

Saya memiliki jejak tumpukan berikut. Apakah mungkin untuk mengetahui sesuatu yang berguna dari ini untuk debugging?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

Di mana untuk mulai melihat kode ketika kita mendapatkan Segmentation fault, dan pelacakan tumpukan tidak begitu berguna?

CATATAN: Jika saya memposting kode, maka pakar SO akan memberi saya jawabannya. Saya ingin mengambil panduan dari SO dan menemukan jawabannya sendiri, jadi saya tidak memposting kodenya di sini. Permintaan maaf.


Mungkin program Anda melompat ke rumput liar - dapatkah Anda memulihkan apa pun dari penunjuk tumpukan?
Carl Norum

1
Hal lain yang perlu dipertimbangkan adalah jika penunjuk bingkai disetel dengan benar. Apakah Anda membangun tanpa pengoptimalan atau mengoper bendera seperti itu -fno-omit-frame-pointer? Juga, untuk kerusakan memori, valgrindmungkin alat yang lebih tepat, jika itu pilihan untuk Anda.
FatalError

Jawaban:


155

Alamat palsu tersebut (0x00000002 dan sejenisnya) sebenarnya adalah nilai PC, bukan nilai SP. Sekarang, ketika Anda mendapatkan SEGV semacam ini, dengan alamat PC palsu (sangat kecil), 99% dari waktu itu karena panggilan melalui penunjuk fungsi palsu. Perhatikan bahwa panggilan virtual di C ++ diimplementasikan melalui pointer fungsi, sehingga masalah apa pun dengan panggilan virtual dapat bermanifestasi dengan cara yang sama.

Instruksi panggilan langsung hanya mendorong PC setelah panggilan ke stack dan kemudian menetapkan PC ke nilai target (palsu dalam kasus ini), jadi jika ini adalah apa yang terjadi, Anda dapat dengan mudah membatalkannya secara manual bermunculan PC dari tumpukan . Dalam kode x86 32-bit Anda cukup melakukan:

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

Dengan kode 64-bit x86 yang Anda butuhkan

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

Kemudian, Anda harus bisa melakukan btdan mencari tahu di mana kode sebenarnya.

1% lainnya dari waktu, kesalahan akan terjadi karena menimpa tumpukan, biasanya dengan melimpahkan array yang disimpan di tumpukan. Dalam kasus ini, Anda mungkin bisa mendapatkan kejelasan lebih lanjut tentang situasi tersebut dengan menggunakan alat seperti valgrind


5
@George: gdb executable corefileakan membuka gdb dengan file yang dapat dieksekusi dan inti, pada titik mana Anda dapat melakukannya bt(atau perintah di atas diikuti oleh bt) ...
Chris Dodd

2
@ mk .. ARM tidak menggunakan stack untuk alamat pengirim - melainkan menggunakan link register. Jadi umumnya tidak memiliki masalah ini, atau jika ya, biasanya karena beberapa korupsi tumpukan lainnya.
Chris Dodd

2
Bahkan di ARM, saya pikir, semua register tujuan umum dan LR disimpan dalam tumpukan sebelum fungsi yang dipanggil mulai dijalankan. Setelah fungsi selesai, nilai LR dimasukkan ke PC dan karenanya fungsi tersebut kembali. Jadi jika stack rusak, kita bisa melihat nilai PC yang salah kan? Dalam kasus ini mungkin menyesuaikan penunjuk tumpukan akan mengarah ke tumpukan yang sesuai dan membantu untuk men-debug masalah. Bagaimana menurut anda? tolong beri tahu saya pemikiran Anda. Terima kasih.
mk ..

1
Apa artinya palsu?
Danny Lo

5
ARM bukan x86 - penunjuk tumpukannya dipanggil sp, bukan espatau rsp, dan instruksi panggilannya menyimpan alamat pengirim di lrregister, bukan di stack. Jadi untuk ARM, yang benar-benar Anda perlukan untuk membatalkan panggilan adalah set $pc = $lr. Jika $lrtidak valid, Anda memiliki masalah yang jauh lebih sulit untuk bersantai.
Chris Dodd

44

Jika situasinya cukup sederhana, jawaban Chris Dodd adalah yang terbaik. Itu terlihat seperti itu melompat melalui pointer NULL.

Namun, mungkin saja program menembak dirinya sendiri di kaki, lutut, leher, dan mata sebelum menabrak — menimpa tumpukan, mengacaukan penunjuk bingkai, dan kejahatan lainnya. Jika demikian, maka mengungkap hash tidak akan menunjukkan kepada Anda kentang dan daging.

Solusi yang lebih efisien akan menjalankan program di bawah debugger, dan melangkahi fungsi hingga program macet. Setelah fungsi crashing diidentifikasi, mulai lagi dan masuk ke fungsi itu dan tentukan fungsi mana yang dipanggil yang menyebabkan crash. Ulangi sampai Anda menemukan satu baris kode yang menyinggung. 75% dari waktu, perbaikannya akan terlihat jelas.

Dalam 25% situasi lainnya, yang disebut baris kode yang menyinggung adalah red herring. Ini akan bereaksi terhadap kondisi (tidak valid) yang mengatur banyak baris sebelumnya — mungkin ribuan baris sebelumnya. Jika demikian, kursus terbaik yang dipilih bergantung pada banyak faktor: sebagian besar pemahaman Anda tentang kode dan pengalaman dengannya:

  • Mungkin menetapkan watchpoint debugger atau memasukkan diagnostik printfpada variabel kritis akan menghasilkan A ha!
  • Mungkin mengubah kondisi pengujian dengan input berbeda akan memberikan lebih banyak wawasan daripada debugging.
  • Mungkin sepasang mata kedua akan memaksa Anda untuk memeriksa asumsi Anda atau mengumpulkan bukti yang terlewat.
  • Terkadang, yang diperlukan hanyalah makan malam dan memikirkan bukti yang terkumpul.

Semoga berhasil!


13
Jika sepasang mata kedua tidak tersedia maka bebek karet terbukti sebagai alternatif.
Matt

2
Menuliskan akhir buffer juga dapat melakukannya. Ini mungkin tidak macet di mana Anda menghapus akhir buffer, tetapi ketika Anda keluar dari fungsi, maka itu mati.
phyatt


28

Dengan asumsi bahwa penunjuk tumpukan valid ...

Mungkin tidak mungkin untuk mengetahui dengan tepat di mana SEGV terjadi dari backtrace - saya pikir dua frame tumpukan pertama sepenuhnya ditimpa. 0xbffff284 sepertinya alamat yang valid, tetapi dua alamat berikutnya tidak. Untuk melihat tumpukan lebih dekat, Anda dapat mencoba yang berikut ini:

gdb $ x / 32ga $ rsp

atau varian (ganti 32 dengan nomor lain). Itu akan mencetak beberapa kata (32) mulai dari penunjuk tumpukan ukuran raksasa (g), diformat sebagai alamat (a). Ketik 'bantuan x' untuk info lebih lanjut tentang format.

Menginstruksikan kode Anda dengan beberapa sentinel 'printf' mungkin bukan ide yang buruk, dalam kasus ini.


Sangat membantu, terima kasih - Saya memiliki tumpukan yang hanya kembali tiga frame dan kemudian tekan "Backtrace berhenti: frame sebelumnya identik dengan frame ini (stack korup?)"; Saya telah melakukan sesuatu yang persis seperti ini dalam kode di pengendali pengecualian CPU sebelumnya, tetapi tidak dapat mengingat selain info symbolbagaimana melakukan ini di gdb.
leander

22
FWIW pada perangkat ARM 32-bit: x/256wa $sp =)
lihat

2
@leander Bisakah Anda memberi tahu saya apa itu X / 256wa? Saya membutuhkannya untuk ARM 64-bit. Secara umum akan sangat membantu jika Anda dapat menjelaskan apa itu.
mk ..

5
Sesuai jawaban, 'x' = memeriksa lokasi memori; itu mencetak sejumlah 'w' = kata (dalam hal ini, 256), dan menafsirkannya sebagai 'a' = alamat. Ada info lebih lanjut di manual GDB di sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory .
leander

7

Lihat beberapa register Anda yang lain untuk melihat apakah salah satunya memiliki penunjuk tumpukan yang di-cache di dalamnya. Dari sana, Anda mungkin bisa mengambil tumpukan. Juga, jika ini disematkan, cukup sering tumpukan didefinisikan di alamat yang sangat khusus. Dengan menggunakan itu, terkadang Anda juga bisa mendapatkan tumpukan yang layak. Ini semua mengasumsikan bahwa ketika Anda melompat ke hyperspace, program Anda tidak muntah di seluruh memori sepanjang jalan ...


3

Jika itu adalah tumpukan yang ditimpa, nilainya mungkin sesuai dengan sesuatu yang dapat dikenali dari program.

Misalnya, saya baru saja melihat diri saya sendiri di tumpukan

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

dan 0x342d13357, yang ternyata menjadi node-id ketika saya memahami log aplikasi untuk itu. Itu segera membantu mempersempit situs kandidat tempat penimpaan tumpukan mungkin terjadi.

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.