Bir program, kendisine ayrılan alanın dışındaki belleğe erişmeye çalıştığında bir segmentasyon hatası oluşur.
Bu durumda, deneyimli bir C programcısı, sorunun sprintf
çağrılan satırda meydana geldiğini görebilir . Ancak, segmentasyon hatasının nerede meydana geldiğini söyleyemiyorsanız veya anlamaya çalışmak için kodu okumak istemiyorsanız , programınızı hata ayıklama sembolleri ile oluşturabilirsiniz (ile gcc
, -g
bayrak bunu yapar ) ve ardından bir hata ayıklayıcı üzerinden çalıştırın.
Kaynak kodunuzu kopyaladım ve adını verdiğim bir dosyaya yapıştırdım slope.c
. Sonra böyle inşa ettim:
gcc -Wall -g -o slope slope.c
(Bu -Wall
isteğe bağlıdır. Yalnızca daha fazla durum için uyarı üretmesini sağlamaktır. Bu, neyin yanlış olabileceğini anlamaya da yardımcı olabilir.)
Daha sonra gdb
ilk olarak programla gdb ./slope
başlamak için gdb
çalışarak hata ayıklayıcıda programı çalıştırdım ve sonra hata ayıklayıcıda bir kez hata ayıklayıcıya run
komut verdim:
ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!
Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
(Do benim dert you have broken Linux kernel i386 NX
... support
mesajın, aynı engellemez gdb
bu programı hata ayıklamak için etkin bir kullanılmaktan.)
Bu bilgiler son derece şifreli ... ve libc için hata ayıklama sembolleri yüklü değilse, sembolik işlev adı yerine onaltılık bir adrese sahip daha da şifreli bir mesaj alırsınız _IO_default_xsputn
. Neyse ki, önemli değil, çünkü gerçekten bilmek istediğimiz şey, programınızda sorunun nerede olduğu .
Bu nedenle, çözüm, geriye doğru bakmak, SIGSEGV
sinyalin nihayet tetiklendiği bir sistem kütüphanesinde o belirli işlev çağrısına giden işlev çağrılarını görmek .
gdb
(ve herhangi bir hata ayıklayıcı) yerleşik bu özelliğe sahiptir: buna yığın izleme veya geri izleme denir . bt
Hata ayıklama komutunu bir geri izleme oluşturmak için kullanın gdb
:
(gdb) bt
#0 0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1 0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2 0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3 0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5 0x08048578 in main () at slope.c:52
(gdb)
Sen görebilirsiniz main
işlevi çağırır calc_slope
(amaçladığınız) işlevini ve sonra calc_slope
çağırır sprintf
diğer ilgili kütüphane fonksiyonları bir çift için çağrıları ile uygulanan (bu sistemde) olan.
Genel olarak ilgilendiğiniz şey, programınızdaki , programınızın dışındaki bir işlevi çağıran işlev çağrısıdır . Kullandığınız bir kütüphanede hata / kütüphaneler kendilerini olmadığı sürece (bu durumda, standart C kütüphanesi libc
kütüphane dosyası tarafından sağlanan libc.so.6
), çarpışma programınızda ve neden hata genellikle ya da yakınında olacak programınızdaki son çağrı .
Bu durumda, bu:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
Programınız buradan çağırır sprintf
. Bunu biliyoruz çünkü sprintf
bir sonraki adım. Ama bunu belirtmeden bile, bunu biliyorsunuz çünkü 26. satırda olan budur ve şöyle diyor:
... at slope.c:26
Programınızda, satır 26 şunları içerir:
sprintf(s,"%d",curr);
(Her zaman en azından şu anda bulunduğunuz satır için satır numaralarını otomatik olarak gösteren bir metin düzenleyici kullanmalısınız. Bu, hem derleme zamanı hatalarını hem de hata ayıklayıcı kullanırken ortaya çıkan çalışma zamanı sorunlarını yorumlamak için çok yararlıdır.)
Dennis Kaarsemaker'ın cevabında tartışıldığı gibi , s
tek baytlık bir dizidir. (Sıfır değil, çünkü atadığınız değer ""
bir bayt uzunluğunda, yani eşittir { '\0' }
, aynı şekilde "Hello, world!\n"
eşittir { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Peki, neden olabilir bazı platformda bu hala iş (ve görünüşe göre Windows için VC9 ile derlenmiş ne zaman)?
İnsanlar genellikle bellek ayırdığınızda ve sonra belleğe erişmeye çalıştığınızda bunun bir hata oluşturduğunu söylerler. Ama bu gerçekten doğru değil. C ve C ++ teknik standartlarına göre, bunun gerçekten ürettiği tanımlanmamış davranıştır.
Başka bir deyişle, her şey olabilir!
Yine de, bazı şeyler diğerlerinden daha olasıdır. Neden yığındaki küçük bir dizi, bazı uygulamalarda, yığındaki daha büyük bir dizi gibi görünür?
Bu, platformdan platforma değişmesine izin verilen yığın tahsisinin nasıl uygulandığı ile ilgilidir. Yürütülebilir dosya, yığınına gerçekte herhangi bir zamanda kullanılması amaçlanandan daha fazla bellek ayırabilir. Bazen bu, kodunuzda açıkça hak talebinde bulunmadığınız bellek konumlarına yazmanıza izin verebilir . VC9'da programınızı oluştururken olan şey budur.
Ancak, VC9'da bile bu davranışa güvenmemelisiniz. Farklı Windows sistemlerinde bulunabilecek kitaplıkların farklı sürümlerine bağlı olabilir. Ancak, daha fazla yığın alanının gerçekte kullanılması amacıyla tahsis edilmesi sorunu daha da olasıdır ve bu yüzden de kullanılabilir.Sonra "tanımsız davranış" tam kabusu yaşarsınız, burada, bu durumda, birden fazla değişken aynı yerde depolanabilir, burada birine yazma üzerine diğerinin üzerine yazılır ... ama her zaman değil, çünkü bazen değişkenlere yazar kayıtlarda önbelleğe alınır ve gerçekte hemen gerçekleştirilmez (veya değişkenlere yapılan okumalar önbelleğe alınabilir veya bir değişkenin öncekiyle aynı olduğu varsayılabilir, çünkü derleyiciye ayrılan belleğin yazılmadığı bilinir değişkenin kendisi).
Ve bu da beni VC9 ile oluşturulduğunda programın neden çalıştığına dair diğer olası olasılığa getiriyor. Tek baytlık diziden sonraki alanı kullanmak için, programınız tarafından bazı dizilerin veya başka bir değişkenin gerçekte (programınızın kullandığı bir kütüphane tarafından tahsis edilmesini de içerebilir) tahsis edilmesi mümkündür s
. Bu nedenle, s
bir bayttan daha uzun bir dizi olarak işlem yapmak , o / bu değişkenlerin / dizilerin içeriğine erişim etkisi de yaratacaktır, bu da kötü olabilir.
Sonuç olarak, böyle bir hatayla karşılaşırsanız, "Segmentasyon hatası" veya "Genel koruma hatası" gibi bir hata almak şanslı . Ne zaman yok buna sahip çok geç program olmasıdır kadar, dışarı bulamayabilir tanımsız davranış.