Dinamik Bağlanma (Dynamic Linking) ve Hata Çözümleme
Önceki yazımda verdiğim örnek programı yazının başlığından da gelen ipucuyla bir iki arkadaşım çözmeyi başardı. Ama sorun kokeksibir’in yorumundakinden daha farklı. (Onu da anlatacağım ama sonra) Programı derleyip çalıştırdığınızda 1022 defa “Unable to connect to database” hatası vererek sonlandığını göreceksiniz. (Denemeyenler şimdi ortaya çıkacak
) İlginç, hiç bir yerde döngü yok aslında. (İlk ipucu, iç içe çağrılar var ama nerede?) Programa baktığımıza hiç sorun görünmüyor. Bir şeyleri yanlış yapıp yapmadığımızı anlamak için yine de bir iki sefer kodu kontrol ediyoruz.
Sonucun değişmemesi, çalışma zamanında yanlış giden bir şeylerin olduğu konusunda şüphelendiriyor. Hemen GDB’yi çalıştırıyoruz.
gdb ./mysql_bugbreak connect
connect() metodunun çıktısını gördüğümüzden oraya bir breakpoint ekledim. run diyerek çalıştırıyoruz ve breakpoint’e takılıyor. Adım adım işletelim.
next n <enter>
O da ne?? Tekrar connect()’e geldi. hmm, bt ile bir stack trace alalım.
(gdb) bt #0 connect () at mysql_bug.c:16 #1 0xb7e6f766 in my_connect () from /usr/lib/libmysqlclient.so.15 #2 0xb7e70338 in mysql_real_connect () from /usr/lib/libmysqlclient.so.15 #3 0x080485f0 in connect () at mysql_bug.c:16 #4 0xb7e6f766 in my_connect () from /usr/lib/libmysqlclient.so.15 #5 0xb7e70338 in mysql_real_connect () from /usr/lib/libmysqlclient.so.15 #6 0x080485f0 in connect () at mysql_bug.c:16 #7 0x08048644 in main () at mysql_bug.c:34
İlginç bir şekilde mysql_real_connect()’ten sonra, tekrar connect() metodumuza geliyor. MySQL kütüphanesi bizim yazdığımız bir fonksiyonu neden çağırsın? Artık mesele anlaşılmış gibi olsa da biraz daha debug etmeye devam edelim. “ignore 1 1010″ komutuyla GDB’nin breakpoint’imizi 1010 kez gözardı etmesini sağlıyor ve devam (cont) ediyoruz. Tekrar breakpoint’e takıldığımızda bir bt komutuna bakın.. Artık problem daha da kendini belli ediyor. Tüm stack tekrar tekrar çağırılmaktan dolmuş:
#3034 0xb7e6f766 in my_connect () from /usr/lib/libmysqlclient.so.15 #3035 0xb7e70338 in mysql_real_connect () from /usr/lib/libmysqlclient.so.15 #3036 0x080485f0 in connect () at mysql_bug.c:16 #3037 0xb7e6f766 in my_connect () from /usr/lib/libmysqlclient.so.15 #3038 0xb7e70338 in mysql_real_connect () from /usr/lib/libmysqlclient.so.15 #3039 0x080485f0 in connect () at mysql_bug.c:16 ---Type <return> to continue, or q <return> to quit--- #3040 0x08048644 in main () at mysql_bug.c:34
Dinamik Bağlanma (Dynamic Linking)
Artık bulmacanın cevabını verebiliriz. Bir şekilde fonksiyonumuza verdiğimiz masum “connect” ismi, MySQL kütüphanesinde de tanımlanmış ve mysql_real_connect() fonksiyonu tarafından kullanılıyor. Biz mysql_real_connect’i çağırdıkça o da bizi tekrar çağırıyor. Bu iç içe durum 1022 kez tekrarladıktan sonra mysql_real_connect hata dönüyor ve o ana kadar çağırılan tüm metodlar hata vererek tek tek çıkıyor.
Peki, bizim kendi yazdığımız bir fonksiyon nasıl olur da MySQL kütüphanesi tarafından çağırılabilir? (Bence esas yanıtlanması gereken soru bu, yoksa problemi deneme yanılma yaparak çözmek ve bir daha arkanıza bakmadan kaçmak mümkün. Ama gerçek bir coder kaçmaz, üzerine gider.)
Programımız MySQL kütüphanesine dinamik olarak bağlanarak derleniyor. Dinamik bağlanma yönteminde çalıştırılan program hangi kütüphaneleri kullandığını bir tabloda ve bu kütüphanede kullandığı metodları da başka bir tabloda tutar. Bu listeyi görmek için “ldd” komutunu kullanabiliriz. Mesela bizim programımız için ldd çıktısı şu şekilde olmaktadır:
# ldd mysql_bug
linux-gate.so.1 => (0xb8092000) libmysqlclient.so.15 => /usr/lib/libmysqlclient.so.15 (0xb7e96000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7d33000) libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7d19000) libcrypt.so.1 => /lib/tls/i686/cmov/libcrypt.so.1 (0xb7ce7000) libnsl.so.1 => /lib/tls/i686/cmov/libnsl.so.1 (0xb7cce000) libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0xb7ca8000) libz.so.1 => /lib/libz.so.1 (0xb7c92000) /lib/ld-linux.so.2 (0xb8093000)
Programımızı çalıştırdığımızda Linux altında ld-linux.so kütüphanesi bu tablodaki kütüphaneleri bağlamaya başlar ve programımızı çalıştırır. (Aynı shell scriptlerinin ilk satırında hangi interpreter tarafından çalıştırılacağının yazması gibi, tüm çalıştırılabilir dosyaları da aslında ld-linux.so “çalıştırır”. /lib/ld-linux.so’ya programınızın adını parametre verin ve olanları görün
) Öncelikle tüm kütüphaneler araştırılır. ld-linux, /etc/ld.so.config dosyasındaki sıralamaya göre kütüphaneleri arar. Bulunamayan bir kütüphane olduğunda program çalışması hata ile sonlanır (hatta teknik olarak hiç başlamaz..) Genelde aksi belirtilmedikçe fonksiyonlar tembel (LAZY) bağlanır. Yani bir fonksiyon kullanılmadıkça performans açısında bu arama/bağlama işlemi masraflı bir işlem olduğu için bağlanmaz.
Program çalışması sırasında daha önce bağlantısı sağlanmamış bir fonksiyon kullanımı ile karşılaşıldığında hemen bir dinamik bağlanma işlemi başlar. Aşağıdaki sırayla fonksiyon bulunmaya çalışılır:
- mevcut program içinde
- LD_LIBRARY_PATH çevre değişkeniyle verilmiş olan kütüphaneler arasında
- programın dinamik bağlı olduğu kütüphaneler arasında (ld.so.cache’e bakarak)
- programın dinamik bağlı olduğu kütüphanelerin önce /lib’te sonra /usr/lib’te bulunanları arasında
Gördüğümüz gibi, bilinmeyen bir fonksiyon hangi kütüphaneye ait olursa olsun, bu sırayla araştırılıyor. MySQL kütüphanesine ait mysql_real_connect fonksiyonu bağlandıktan sonra, çalıştırıldığında içindeki connect() fonksiyonu için arama başlatılıyor ve ilk sırada bizim programımız içinde bir aday bulunduğu için bağlantı burada sonlanıyor. Ondan sonrası yukarıda karşılaştığımız duruma neden oluyor.
Yukarda kendi programımızı debug edebilmiştik. Peki, bu dinamik bağlantı işlemini nasıl görebiliriz ve hataları tespit edebiliriz? Kütüphanenin çevre değişkenleri sayesinde çalışmasının etkilenebildiğini LD_LIBRARY_PATH’ten biliyoruz. Aynı şekilde LD_DEBUG çevre değişkeniyle de debug çıktısını açabilmek mümkün. Burada kullanılabilecek çeşitli parametreleri man ld-linux ile veya LD_DEBUG=help <herhangi_bir_komut> ile görebilirsiniz. Şimdilik LD_DEBUG=all ./mysql_bug diyerek programımızı çalıştıralım ve yukardaki paragrafta anlattığımız süreci canlı canlı görebilirsiniz. Çok uzun bir çıktı veriyor ama ilk satırlarını okumak fikri anlamanız açısından çok öğretici ve faydalı.
MySQL kütüphanesinde neden böyle çok yaygın kullanılabilir bir fonksiyon ismi vardır, kim bilir? Ama özensiz alınan kararlar maalesef böyle sorunlara neden olabiliyor. Fonksiyonlara, değişkenlere isim vermek sorumluluk ister.
Aslında bu yöntem çok yaygın olarak programların hafıza tahsis (memory allocation) hatalarını tespitte kullanılabilmekte. Örneğin malloc/realloc/free fonksiyonlarını yeniden tanımladığımız bir kütüphane yazarak, diğer programları test etmek mümkün (üstelik tekrar derlemek gerekmeksizin..)
Sonuç: Ufak gibi görünen bir sorun, üzerine düşüldüğünde farklı kavramların tekrar üzerinden geçmemizi ve hafiften paslanmış debugging marifetlerimizi hatırlamamızı sağladı.
Bunun üzerine fonksiyon ismini my_connect() yaparak tekrar denedim. Bahtsızlığın bu kadarı, bu seferde derleme aşamasında bir hatayla karşılaştım.
Bu fonksiyon yine (!!) MySQL tarafından tanımlanmış. Başlık dosyalarında daha farklı tanımlandığı için daha derleme aşamasında hatayı görmek mümkün olabildi. Bu sorun kokeksibir’in yorumunda bahsettiği durum..) Neyse bunu geçebilmek en azından daha kolay.
Keyifli kodlamalar..
Bu kategori altındaki diğer yazılar: Yazılım, debian
30 Haziran 2009 1:14 pm
peki fonksiyon ismini tiktak_connect() olarak degistirip, kod icerisinde connect() adinda baska bir fonksiyon daha yarattigimiz zaman herhangi bir hatayla karsilasmiyoruz. cunki mysql_real_connect() bizim tanimladigimiz o connect() fonksiyonunu kullanmiyor. neden bu bug sadece mysql_real_connec() fonkisyonunun cagirildigi fonksiyonun adi connect() oldugu zaman ortaya cikiyor ???
30 Haziran 2009 1:45 pm
Denememde aynı sonucu alamadım. tiktak_connect() mysql_real_connect()’i çağırıyor ve sadece girildiğini belli eden bir connect() fonksiyonu tanımladım. connect() kodda kullanılmıyor. Ama çalıştırıldığında connect()’e geliyor. LD_DEBUG’ı açarak baktığımda beklendiği gibi hareket etmiş:
13866: symbol=connect; lookup in file=./mysql_bug
13866: binding file /usr/lib/libmysqlclient.so.14 to ./mysql_bug: normal symbol `connect’ [GLIBC_2.0]
Kodu gönderirsen belki neler döndüğünü anlayabiliriz..
1 Temmuz 2009 10:16 am
ehe, simdi ben de ayni sonucu alamadim
sanirim ilk denememde typo yapmisim.