WinDBG ile Hata Ayıklama

    Hata ayıklama makale serisi ile uygulamalarımızı geliştirirken alabileceğiniz önlemlerden başlayarak, son kullanıcıda oluşan hataların kaynağını bulabilme, .net framework 4.0 ile geliştirilen uygulamanıza ait bir hafıza dökümü üzerinden hata ayıklama işlemi yapabilmeye kadar  pek çok konuya değinmeye çalıştım. Visual Studio ile bir hafıza döküm (dump) dosyası üzerinden hata ayıklama yapmayı anlatırken hatırlayacaksınız, özellikle not düşmüştüm; bu özelliği kullanabilmeniz için hafıza dökümünü aldığınız uygulamanın mutlaka .net framework 4.0 ile derlenmiş olması gerekmekte. Peki bu durumda önceki sürümlerle geliştirilmiş uygulamalarda hafıza dökümünü kullanamıyor muyuz? Tabi ki hayır; kesinlikle kullanabilirsiniz, sadece mevcut sürümüyle henüz Visual Studio ile bir hata ayıklama oturumu başlatmanız desteklenmemekte. Alternatif olarak bu gibi durumlarda WindDBG uygulaması ile hafıza  döküm dosyasını incelemeniz  mümkün; fakat yaşayacağınız şey aynı Windows ortamından MS-DOS ortamına geçiş gibi olacaktır 🙂 WinDBG oldukça güçlü bir uygulamadır; fakat neredeyse tüm işler sadece komutlarla yapılabildiği için iyi bir bilgi birikimine ihtiyacınız olacaktır. Bu makalemde Visual Studio içerisinde bahsetmiş olduğum hata ayıklama oturumu özelliğinin bulunmadığını/kullanamadığımızı varsayarak WinDBG uygulamasını kullanarak hatanın kaynağına nasıl inebileceğimizi paylaşıyor olacağım.

    Öncelikle, makalemde bahsedeceğim WinDBG uygulamasını kullanabilmek için Debugging Tools for Windows bilgisayarınızda kurulu olmalıdır. Kurulum sonrasında başlat menüsünden erişebileceğiniz WinDBG uygulaması açıldığında aşağıdaki ekranla karşılaşacaksınız;

WinDBG uygulaması ana ekranı

    WinDBG uygulamasının kullanımını örneklerken, daha fazla detay verebilmek adına, çalışan bir uygulamada nasıl hata yakalama oturumunun başlatabileceğimizden bahsedeceğim. WinDBG’yi kullanırken çalışan bir uygulama üzerinden işlem yapmak ile bir hafıza dökümü üzerinde işlem yapma arasında çok fazla fark olmamasına karşın, doğası gereği hafıza dökümlerinden süreci devam ettirme, break point bırakma, adım atlama, ilerleme v.b. işlemleri kullanabilme olanağı bulunmamaktadır.

WinDBG ile bir işleme bağlanma

    Çalışan bir uygulama üzerinde hata ayıklama işlemine başlayabilmek için  File->Attach to a Process.. menüsünü ya da F6 kısa yolunu kullanabilirsiniz. Bu menü karşımıza yukarıda ekran görüntüsünü bulabileceğiniz "Attach to Process" diyalogunu getirecektir. Hali hazırda işletim sistemi üzerinde çalışan işlemlerin listelendiği bu ekranda hata ayıklama oturumunu başlatmak istediğimiz işlemi seçerek ok tuşuyla oturumu başlatabiliriz.

WinDBG ile bir  işleme bağlandıktan sonra alınan ilk hata : BreakPoint

    WinDBG uygulaması ile çalışan bir işlem üzerinde hata ayıklama oturumu başlattığında ilk önce bir break point oluşturularak uygulamanın tüm işleyişi işletim sistemince durdurulacaktır. Bir Win32 uygulaması için debugger geliştirilmesini sizlerle paylaştığım makalemi hatırlayacak olursanız oluşan bu ilk break point size tanıdık gelecektir. İşletim sistemi açısından break point’te bir hata olarak ele alınarak yönetilmesi için işlem durdurularak debugger’e bildirilmektedir. Makalemde, daha önceki hata ayıklama makalelerimde de olduğu gibi, örnek olarak CRMUygulamasını kullanacağım. CRM uygulamasını başlattıktan sonra WinDBG uygulamasını açarak F6 tuşu ile gelen diyalog üzerinden bu uygulamayı seçtiğinizde yukarıdaki ekran görüntüsü ile karşı karşıya kalacaksınız. İsterseniz öncelikle bu ekranın bize neler anlattığını ve WinDBG uygulamasını nasıl kullanmaya başlayabileceğimizi görelim.

    Hata ayıklama oturumunun başlaması ile birlikte WinDBG uygulaması bize işlemde yüklü olan modülleri listeleyecektir. Hemen ardından da "Break instruction exception – code 80000003 (first chance)" mesajıyla, yukarıda da bahsetmiş olduğum, ilk break point hatasını yakalayacaktır.  WinDBG uygulaması her hata bildiriminde otomatik olarak hata bilgisi ile birlikte işlemci kayıtçılarını (registery’lerini) ve sahip oldukları değerleri listeleyecektir. Mesajın son satırında ise assembly seviyesinde işletilen son satırı göreceksiniz; ki bizim örneğimizde bu "776b3574 cc int 3" ifadesidir.

    Ekranın en alt bölümü komut girişi için ayrılmıştır. Bu alanda aynı anda bir ya da birkaç komutun belirtilmesi mümkündür. Birden fazla komut verilmesi durumunda komutları ; (noktalı virgül) ile ayrılması gerekmektedir. Bu bölümün solunda ise aktif olan iş parçacığının (thread)  id’si gösterilmektedir. Bu sayede, yazılım geliştirici hangi iş parçacığı üzerinde işlem yaptığını kolayca görebilmektedir. İş parçacıklarından bahsedince, sanırım ilk komutumuzu öğrenmenin zamanı geldi. WinDBG ile başlatılan bir hata ayıklama oturumunda aktif iş parçacıklarını ~ komutu ile görebilirsiniz. Bu komut ile gelen tablonun ilk sütunu ilgili iş parçacığının id’sini belirtmektedir. Aktif iş parçacığından farklı bir iş parçacığında işlem yapabilmek için ~[iş parçacığı id’si]s komutu kullanılabilir; örnek vermek gerekirse, 1 id’li iş parçacığında aktif olarak işlem yapmak için ~1s komutu kullanılmalıdır. Aktif iş parçacığının her değişiminde WinDBG otomatik olarak bu iş parçacığı için işlemci kayıtçılarını ve değerlerini, ardından da işletilen son komutu gösterecektir. Uygulamanın durdurulduğu anda aktif olan iş parçacığından farklı bir iş parçacığında çalışılırken ~ komutu ile tüm iş parçacıklarını listeleyecek olursanız, bu son aktif iş parçacığının başında # karakteri ile diğerlerinden ayırt edebilirsiniz. Benzer şekilde geçiş yapılmış olan ve üzerinde çalışılan iş parçacığının hemen yanında da . (nokta) bulunacaktır.

    WinDBG içerisinde komutlar üç temel grupta toplanmıştır; komutlar, meta komutlar ve genişletme komutları. komutların başında herhangi bir ön ek bulunmazken, meta komutlar . (nokta), genişletme komutları ise ! (ünlem) ile başlamaktadırlar.

    Eğer bir yazılım geliştirici olarak assembly ve/veya C deneyiminiz yok/az ise muhtemelen şimdiye kadar paylaştığım bilgiler sizin açınızdan çok fazla faydalı olmayacaktır 🙂 İsterseniz biz .net framework ailesi yazılım geliştiricilerinin daha fazla aşına olduğu konulara doğru ilerleyelim. .Net framework kurulumu ile birlikte Microsoft biz yazılım geliştiricileri düşünerek WinDBG kullanımımızı kolaylaştıracak bir grup komut setini kullanımına sunmuştur (sos.dll). .Net framework 4 ile birlikte bu komut setini aşağıdaki komutu kullanarak yükleyebilmeniz mümkün;

.Net uygulamalarını debug edebilmek için SOS.dll yüklenmeli

.loadby sos clr

    Canlı bir hata ayıklama oturumunda yukarıdaki şekilde yüklenebilen SOS komut setini, alternatif olarak (örneğin bir hafıza döküm dosyasını incelerken) aşağıdaki komut yardımıyla da komut setinin yüklenmesi de mümkündür;

.load C:\Windows\Microsoft.NET\Framework\v4.0.30128\sos.dll

    Bu noktada eğer takip ettiğiniz işlem .net framework 4.0 altındaki bir framework ile yazılmış ise SOS komut setini aşağıdaki şekilde yükleyebilirsiniz;

.loadby sos mscorwks

    .Net framework komut setinin yüklenmesi sonrasındaki adımlarımız (zorunlu olmamakla birlikte) uygulama sembol ve kaynak kodlarının bulunduğu dizinleri belirtmek olmalıdır. Bu iki işlemi File menüsü altındaki girdilerle yapabiliyor olmamıza karşın, aşına olmanız adına, aşağıdaki komutlarla yapabilirsiniz;

sympath komutu ile sembol dosyalarınızın nerede bulunduğunu belirtebilirsiniz

.sympath C:\temp\semboller;srv*http://msdl.microsoft.com/download/symbols

    Sembol dosyalarının bulunacağı dizinlerin/sunucuların belirtildiği .sympath komutunda, noktalı virgül ile ayırmak koşulu ile, istenildiği kadar adres verilmesi mümkündür. Yukarıdaki örneğimizde c:\temp\semboller klasörü ilk bakılacak sembol klasörü olarak belirlenirken, devamında da http://msdl.microsoft.com/download/symbols adresi eklenmiştir. srv* ile bir sembol sunucusu adresi belirtildiği belirtilmektedir. http://msdl.microsoft.com/download/symbols adresi Microsoft’un geliştirdiği ürünlerin açık sembollerinin (Public Symbols) bulunduğu adrestir.

srcpath komutu ile kaynak kodlarınızın nerede bulunduğunu belirtebiliriz

.srcpath C:\temp\HataAyiklama\CRMUygulamasi

    Yukarıda örneklediğim .srcpath komutu ise WinDBG’ye kaynak kodlarını hangi dizinlerde bulabileceğini belirtmektedir. Noktalı virgül ile ayırmak koşulu ile birden fazla adres belirtilmesi mümkündür.

    Öncede belirttiğim gibi, .sympath ve .srcpath komutları kesinlikle çalıştırılması zorunlu komutlar olmamasına karşın (örneğin pdb ve/veya kaynak kodlarının elinizde bulunmadığı senaryolarda), daha detaylı hata ayıklama yapabilmeniz, gerçek hata kaynağına hızlıca ulaşabilmeniz adına önemlidir.

    Gerekli ön hazırlığımızı tamamladıktan sonra artık hata ayıklamaya başlayabiliriz. WinDBG’de işlemin çalışması hata ya da break point nedeniyle duraklatıldığı herhangi bir zamanda işlemin sürdürmek için (Visual Studio’da ki F5) g (Go) komutu kullanılmaktadır. Yine benzer şekilde işleyişin sadece bir adım ileri gitmesi için p komutu kullanılabilir. Windows Debug API’sini ve debugger’ların işleyiş mantığını hatırlayacak olursanız, bir hata durumunda işleyiş iki şekilde devam edebilmekteydi; oluşan hata debugger tarafından ele alınabilir ya da işlem hiç debugger yokmuş gibi kaldığı yerden hata işlemeye devam ederek işlemin bu hatayı yakalaması beklenebilirdi. Bu süreç benzer şekilde WinDBG içerisinde de yönetilebilmektedir. Hata durumu yazılım geliştirici tarafından ele alınmış ve uygulama hata olmamış gibi kaldığı yerden devam etsin isteniyorsa gH (Go exception handled), hatanın işlem tarafından yönetilmesi isteniyorsa da gN (Go not handled) komutları kullanılabilir.

Debuggee is running

     İsterseniz hata durumunda oluşan hata hakkında bilgiye nasıl ulaşabileceğimizi görmek için uygulamamızı g komutu ile devam ettirelim. Bu noktada iş parçacığı id’si alanında "*BUSY*", komut girdi alanında ise "Debuggee is running…" yazacaktır. Açılan uygulamamızda herhangi bir veri girişi yapmadan kaydet butonuna basacak olursanız bir hata oluşacaktır.

Hata oluşması durumunda uygulama durdurularak komut işletilmesine olanak verilir

    Hata oluşması durumunda, yukarıdaki ekran görüntüsünde de görüleceği üzere, WinDBG işlemin çalışmasını duraklatarak üzerinden komut çalıştırılmasına olanak sunmakta. Ekran görüntüsünde hata hakkında bilgi veriliyor olmasına karşın, görülen "CLR exception – code e0434352 (first chance)" hata mesajı maalesef ki bizim için hata hakkında yeterli bir bilgi sunmamaktadır.

    Bu noktada, isterseniz hata alındığı sırada çalışan managed iş parçacıkları hakkında bilgi alalım. Bu amaçla, yazımın başında belirtmiş olduğum ~ komutunu çalıştırılacak olursa, maalesef ki işimize yarayan detaylı bir bilgiye ulaşamamaktayız. Bu komut yerine .net komut setiyle gelen !threads komutu kullanılabilir;

managed iş parçacıklarının listesi !Threads komutu ile görülebilir

    Gördüğünüz gibi bu komut bize daha tanıdık ve detaylı bilgiler sunacaktır. ~ komutundan farklı olarak burada sadece iki iş parçacığı görülmekte, sebebi ise bu sırada çalışan sadece iki managed  iş parçacığı bulunmasıdır. İki iş parçacığından 0 id’li olan STA (Single Threaded Apartment) iş parçacığında System.FormatException türünden bir hata oluşmuş. Tanıdık bir hata mesajı, değil mi!

    Bir sonraki adımda hata mesajı hakkında daha detaylı bilgi alalım. Bunun için kullanılabilecek ilk komut !printexception (ya da kısaca !pe) komutudur;

!printexception komutu ile hata bilgisine ulaşılabilir

    Alınan hata konusunda bir yerlere varmaya başlıyoruz; string türünden bir ifade sayıya dönüştürülürken hata oluşmuş. Hata, call stack’e baktığımızda, en son  CRMUygulamasi assembly’si içerisindeki Com.Enterprisecoding.HataAyiklama.CRMUygulamasi.AnaEkran sınıfında yer alan kaydet_Click fonksiyonumuz çalıştırılmış; fakat call stack içerisinde managed ve unmanaged kodlar karışık şekilde verilmiş ve takip edilmesi zor olabiliyor.

    !clrstack komutu bu görünümü biraz daha temiz hale getirecek ve bize sadece managed kodlar hakkında bilgi verecektir. !clrstack komutu aldığı parametrelerle bize daha da detaylı bilgiler verebilmekte. Örneğin; !clrstack -p komutunu çalıştırdığınızda call stack bilgisine ek olarak fonksiyonların hangi parametrelerle çağrıldığı bilgisini de verecektir;

!clrstack -p komutu yardımıyla parametre bilgileri ile birlikte call stack görülebilir

    Bir başka güzel örnek ise; !clrstack –l komutunu çalıştırdığınızda call stack bilgisine ek olarak fonksiyonların yerel değişkenlerinin bilgisini de verecektir;

!clrstack -l komutu yardımıyla yerel değişkenlerin bilgileri ile birlikte call stack görülebilir

    Son olarak; !clrstack -a komutu ise yukarıdaki iki parametreyi birleştirerek bize daha detaylı bilgi sunmaktadır;

!clrstack -a komutu yardımıyla hem parametre hem de yerel değişkenlerin bilgileri ile birlikte call stack görülebilir

    Yukarıdaki ekran görüntüsüne bakacak olursak hatanın kaynağı daha da netleşecektir. "0024e978 6ff8d535 System.Convert.ToInt32(System.String)" satırının hemen altında bu fonksiyonun value parametresi görülebilir. Girilen ifade tam sayıya dönüştürülürken alınan bu hata string ifadenin boş (null) olması kaynaklıdır.

     Takip ettiğimiz yukarıdaki adımlar sonrasında artık hatamızı tespit etmiş durumdayız; fakat fonksiyonumuzun içerisini inceleyecek olursak bu durumun oluşabileceği bir kaç nokta bulunmakta. Peki hata tam olarak hangi satırda?

private void kaydet_Click(object sender, EventArgs e) {
    Debug.WriteLine("Müşteri kayıt işlemi başlatılıyor");
    var musteriAd = ad.Text;
    var musteriSoyad = soyad.Text;
    var musteriYas = yas.Text;
    var musteriTCKimlik = tckimlik.Text;

    var musteri = new Musteri();
    musteri.Ad = musteriAd;
    musteri.Soyad = musteriSoyad;
    musteri.Yas = Convert.ToInt32(musteriYas);
    musteri.TCKimlik = Convert.ToInt32(musteriTCKimlik);

    musteriKaydet(musteri);
}

    Hata satırının tespit edebilmek için yukarıdaki gibi satır numaralarının görülmediği durumlarda .reload komutu kullanılabilir. Tüm modüllerin sembol dosyalarının yeniden yüklenmesi sağlayan bu komut ardından artık satır numaralarını da görebiliyor olacaksınız. İsterseniz .reload CRMUygulamasi.exe gibi bir kullanımla sadece CRMUygulaması’nın sembollerinin yeniden yüklenmesini de sağlamanız mümkün.

.reload komutunun çalıştırılması sonrası kaynak dosya ve satır numarası bilgisi görülmekte

    Bu son adım sonrasında, hatanın C:\temp\HataAyiklama\CRMUygulamasi\AnaEkran.cs dosyası 31. satırda oluştuğunu ve bunun sebebinin de yas.Text içerisinde bir değer olmaması olduğunu bulabilmiş oluyoruz.

 

    Aşağıda hata ayıklama sırasında kullanabileceğiniz bir kaç faydalı komut bulabilirsiniz;

  • ~*e[Komut] komutunu kullanarak istediğiniz herhangi bir komutu tüm iş parçacıklarında çalıştırmanın mümkündür. Örneğin; hatanın hangi iş parçacığında oluştuğunu bilmiyorsanız ya da bir de fazla iş parçacığında hata oluştu ise ~*e!clrstack komutunu kullanarak tüm iş parçacıklarında call stack bilgisine ulaşmanız mümkün.

  • Heap’te yer alan nesneleri ve bu nesnelerde ne kadar bulunduğunu listelemek için !dumpheap komutunu kullanabilirsiniz. Özellikle beklenenden fazla hafıza kullanımına sahip uygulamalarda bu komutu kullanarak hangi nesnenin hafızanın şişmesine neden olduğu bulunabilir. Bu komuta geçilecek type parametresi ile de gelen liste içerisinde filtreleme yapılması mümkün. Örneğin;  !dumpheap -type Exception komutu bize heap’te yer alan nesneler arasından sadece adında Exception geçenlerin listelenmesi sağlanacaktır.

  • Arka arkaya çalıştırılan komutlar sonrasında WinDBG penceresi uzayıp giden bilgilerle dolacaktır. Bu gibi durumlarda, MS-DOS’takine benzer şekilde .cls komutu yardımıyla ekranı temizlemeniz mümkün.

  • Eğer bir hafıza dökümü dosyası ile işlem yapıyorsanız, .time komutu yardımıyla dökümün alındığı zaman, işletim sisteminin ne kadar zamandır açık olduğu gibi bilgilere ulaşabilirsiniz.

  • Hata ayıklama oturumu başlatılan bir uygulamayı takip etmekten çıkmak için .detach komutu kullanılabilir. Benzer şekilde .restart komutu ile işlem baştan başlatılabilir.

  • Stack’te yer alan tüm nesnelerin bilgisi !dso komutu ile alınabilir.

  • İş parçacıklarının ne kadar işlemci zamanı kullandığı !runaway komutu ile öğrenilebilir.

Fatih Boy

Ankara'da yaşayan Fatih, bir kamu kurumunda danışman olarak çalışmaktadır. ALM süreçleri, kurumsal veri yolu sistemleri, kurumsal altyapı ve yazılım geliştirme konularında destek vermektedir. Boş zamanlarında açık kaynak kodlu projeler geliştirmeyi ve bilgisini yazdığı makalelerle paylaşmayı seven Fatih, aynı zamanda Visual C# ve Visual Studio teknolojileri konusundan Microsoft tarafından altı yıl üst üste MVP (En Değerli Profesyonel) ödülüne layık görülmüştür. İş hayatı boyunca masaüstü uygulamaları, web teknolojileri, akıllı istemciler gibi konularda Asp.Net, Php, C#, Java programlama dilleri ve MySql, MsSql ve Oracle gibi veritabanı yönetim yazılımları ile çalışmıştır. İngilizce ve Türkçe olarak yayınlanan makalelerini gerek İngilizce bloğunda, gerekse de Türkçe bloğunda bulabileceğiniz gibi web sitesinden de açık kaynak kodlu geliştirdiği yazılımlarına ulaşabilirsiniz. vCard - Twitter - Facebook - Google+

3 yorum

  1. emin   •  

    merhaba fatih bey bu exe için sanırım ;
    web içinde microsoft tun remote debug uygulaması ile visual studio içine process dan PID i attachment ederek yapılıyor
    ben iis publish edilmiş asp.net projesini debug etmek istiyorum iis de çalışarak canlıdaki projeyi debug yapacak bir uygulama varmı
    teşekkürler

    • fatih   •     Yazar

      Merhaba Emin,
      Canlı ortamda debug işleminin iyi bir fikir olduğundan emin değilim. Sebebine gelince; debug işleminin doğası gereği öncelikle hedef process’e attach olmalısın. Devamında, bir hata olduğunda ya da bir break point’te takıldığında hedef uygulama yeni bir talep kabul edemeyecektir; çünkü teknik olarak donmuş durumda olacaktır. Böylesi bir durumda da canlı ortamdaki uygulaman sen debug oturumunu sonlandırana kadar istemcilere yanıt veremeyecektir.

      Öte yandan, sadece canlı ortamda üretebildiğin hataları çözüme kavuşturmak için alternatifler de var. Aklıma ilk gelen Debug Diagnostic Tool. Bu uygulama yardımıyla, IIS’te anlık ya da hata oluşması durumunda dump alarak Visual Studio içerisinde inceleyebilirsin. İçerik olarak geçerli olmakla birlikte, geçen zamanda bu makalede paylaştıklarıma alternatif ürünlerde oluştu. Çalışan uygulama kaynak kodları ve debug sembolleri elinizde ise Visual Studio rahatlığıyla hatayı analiz edebilirsiniz.

  2. emin   •  

    ilginiz için teşekkür ederim

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir