GDB bozuk yığın çerçevesi - Nasıl hata ayıklanır?


113

Aşağıdaki yığın izlemesine sahibim. Bundan hata ayıklama için yararlı bir şey çıkarmak mümkün mü?

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) 

Bir aldığımızda koda bakmaya nereden başlamalıyız Segmentation faultve yığın izleme o kadar kullanışlı değil?

NOT: Kodu postalarsam, SO uzmanları bana cevabı verecektir. SO'dan rehberlik almak ve cevabı kendim bulmak istiyorum, bu yüzden kodu buraya göndermiyorum. Özür.


Muhtemelen programınız yabani otların içine atladı - yığın işaretçisinden herhangi bir şey kurtarabilir misiniz?
Carl Norum

1
Dikkate alınması gereken bir diğer husus, çerçeve işaretçisinin doğru ayarlanıp ayarlanmadığıdır. Optimizasyon yapmadan mı yoksa beğenerek bir bayrak mı inşa ediyorsunuz -fno-omit-frame-pointer? Ayrıca, sizin için bir seçenekse, bellek bozulması valgrindiçin daha uygun bir araç olabilir.
FatalError

Yanıtlar:


155

Bu sahte adresler (0x00000002 ve benzeri) aslında SP değerleri değil, PC değerleridir. Şimdi, sahte (çok küçük) bir PC adresiyle bu tür bir SEGV aldığınızda, zamanın% 99'u sahte bir işlev işaretçisi aracılığıyla aramaya bağlıdır. C ++ 'daki sanal çağrıların işlev işaretçileri aracılığıyla uygulandığını unutmayın, bu nedenle sanal çağrı ile ilgili herhangi bir sorun aynı şekilde ortaya çıkabilir.

Dolaylı bir çağrı talimat sadece yığını üzerine çağrısından sonra PC iter ve daha sonra hedef değeri (bu durumda sahte) Bu takdirde, yani PC'yi ayarlar olduğunu olanları, kolayca el yığın kapalı PC kahpeden onu geri alabilirsiniz . 32 bit x86 kodunda yapmanız gerekenler:

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

64 bit x86 koduyla ihtiyacınız olan

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

Ardından, bir yapıp btkodun gerçekte nerede olduğunu bulabilmelisiniz .

Zamanın diğer% 1'inde hata, genellikle yığında depolanan bir dizinin taşmasıyla yığının üzerine yazılmasından kaynaklanır. Bu durumda, valgrind gibi bir araç kullanarak durum hakkında daha fazla netlik elde edebilirsiniz.


5
@George: gdb'yi gdb executable corefileçalıştırılabilir ve çekirdek dosyayla açacak, bu noktada yapabilirsiniz bt(veya yukarıdaki komutları takip ederek bt) ...
Chris Dodd

2
@mk .. ARM, yığını dönüş adresleri için kullanmaz - bunun yerine bağlantı kaydını kullanır. Dolayısıyla, genellikle bu sorunu yoktur veya varsa, genellikle başka bir yığın bozulması nedeniyledir.
Chris Dodd

2
ARM'de bile, bence, tüm Genel amaçlı yazmaçlar ve LR, çağrılan işlev yürütülmeye başlamadan önce yığın halinde saklanır. İşlev bittiğinde, LR değeri PC'ye açılır ve dolayısıyla işlev geri döner. Yani yığın bozuksa, PC'nin yanlış bir değer olduğunu görebilir miyiz? Bu durumda, yığın işaretçisi ayarlanıyor olabilir, uygun yığına yol açar ve sorunu gidermeye yardımcı olur. Ne düşünüyorsun? lütfen düşüncelerinizi bildirin. Teşekkür ederim.
mk ..

1
Sahte ne demek?
Danny Lo

5
ARM, x86 değil - yığın işaretçisi çağrılır sp, espveya değil rspve çağrı komutu, dönüş adresini lryığın üzerinde değil , kayıtta saklar . Yani ARM için, aramayı gerçekten geri almanız gereken tek şey set $pc = $lr. Eğer $lrgeçersiz, sen dinlenmenizi bir çok zor bir sorun var.
Chris Dodd

44

Durum oldukça basitse, Chris Dodd'un cevabı en iyisidir. NULL göstericiden atlamış gibi görünüyor.

Bununla birlikte, programın çökmeden önce kendini ayak, diz, boyun ve gözle vurması mümkündür - yığının üzerine yazmış, çerçeve işaretçisini bozmuş ve diğer kötülükleri bozmuştur. Eğer öyleyse, esrarın çözülmesinin size patates ve et göstermesi pek olası değildir.

Daha verimli çözüm, programı hata ayıklayıcı altında çalıştırmak ve program çökene kadar işlevlerin üzerinden geçmek olacaktır. Bir çökme işlevi tanımlandıktan sonra, yeniden başlayın ve bu işleve adım atın ve hangi işlevi çağırdığının çökmeye neden olduğunu belirleyin. Tek sorunlu kod satırını bulana kadar tekrarlayın. Zamanın% 75'inde, düzeltme açık olacaktır.

Durumların diğer% 25'inde, sözde rahatsız edici kod satırı kırmızı ringa balığıdır. Daha önce çok sayıda satır (belki daha önce binlerce satır) ayarlanmış (geçersiz) koşullara tepki verecektir. Durum buysa, seçilen en iyi kurs birçok faktöre bağlıdır: çoğunlukla kodu anlayışınız ve onunla deneyiminiz:

  • Belki de bir hata ayıklayıcı izleme noktası ayarlamak veya printfkritik değişkenler üzerine teşhisler eklemek gerekli A ha!
  • Belki farklı girdilerle test koşullarını değiştirmek, hata ayıklamadan daha fazla bilgi sağlayacaktır.
  • Belki ikinci bir çift göz sizi varsayımlarınızı kontrol etmeye veya gözden kaçan kanıtları toplamaya zorlar.
  • Bazen tek gereken yemeğe gitmek ve toplanan kanıtları düşünmektir.

İyi şanslar!


13
İkinci bir çift göz yoksa, lastik ördekler alternatif olarak kanıtlanmıştır.
Matt

2
Bir tamponun sonunu yazmak da bunu yapabilir. Arabelleğin sonunu yazdığınız yerde çökmeyebilir, ancak işlevden çıktığınızda ölür.
phyatt

Faydalı olabilir: GDB: Otomatik 'Sonraki'
user202729

28

Yığın işaretçisinin geçerli olduğunu varsayarsak ...

Geri izlemeden SEGV'nin tam olarak nerede oluştuğunu bilmek imkansız olabilir - ilk iki yığın çerçevesinin tamamen üzerine yazıldığını düşünüyorum. 0xbffff284 geçerli bir adres gibi görünüyor, ancak sonraki ikisi değil. Yığına daha yakından bakmak için aşağıdakileri deneyebilirsiniz:

gdb $ x / 32ga $ rsp

veya bir varyant (32'yi başka bir sayı ile değiştirin). Bu, adresler (a) olarak biçimlendirilmiş dev (g) boyutundaki yığın göstericisinden başlayarak birkaç kelimeyi (32) yazdıracaktır. Biçimle ilgili daha fazla bilgi için 'yardım x' yazın.

Bu durumda, kodunuzu bazı nöbetçi "printf" ler ile kullanmak kötü bir fikir olmayabilir.


İnanılmaz derecede yardımcı oldu, teşekkür ederim - Sadece üç kare geriye giden ve sonra "Backtrace durdu: önceki kare bu kareyle aynı (bozuk yığın?)"; Daha önce bir CPU istisna işleyicisinde kodda tam olarak böyle bir şey yaptım, ancak bunu info symbolgdb'de nasıl yapacağımı hatırlayamadım .
leander

23
32 bit ARM cihazlarda FWIW: x/256wa $sp =)
2013

2
@leander X / 256wa'nın ne olduğunu söyleyebilir misiniz? 64 bit ARM için ihtiyacım var. Genel olarak ne olduğunu açıklayabilirseniz yardımcı olacaktır.
mk ..

5
Cevaba göre, 'x' = hafıza konumunu inceleyin; bir dizi 'w' = kelime (bu durumda, 256) yazdırır ve bunları 'a' = adresleri olarak yorumlar. GDB kılavuzunda sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory adresinde daha fazla bilgi bulunmaktadır .
Leander

7

Diğer yazmaçlarınızdan birinde yığın işaretçisinin önbelleğe alınmış olup olmadığını görmek için bazılarına bakın. Oradan bir yığını geri getirebilirsin. Ayrıca, eğer bu katıştırılmışsa, genellikle yığın çok özel bir adreste tanımlanır. Bunu kullanarak, bazen iyi bir yığın da elde edebilirsiniz. Tüm bunlar, hiperuzaya atladığınızda, programınızın yol boyunca tüm hafızayı kusmadığını varsayar ...


3

Yığın üzerine yazılmışsa, değerler program tarafından tanınan bir şeye karşılık gelebilir.

Örneğin, kendimi yığına bakarken buldum

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

ve 0x342d13357, bunun için uygulama günlüklerini açtığımda bir düğüm kimliği olduğu ortaya çıktı. Bu, yığın üzerine yazma işleminin gerçekleşmiş olabileceği aday sitelerin hemen daraltılmasına yardımcı oldu.

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.