Uzun bir aradan sonra tekrardan merhaba arkadaşlar. Bir süredir şirkette yoğun geçirdiğim bir çalışma temposundan sonra, bir makale daha yazabilmeye fırsat bulabildim. 🙂
Bu makaleyi yazma ihtiyacım ise, bir kaç haftadan bu yana mevcut bir sistemin üzerine yerleştirmiş olduğum Asp.NET Web API’da, anlamsız bir şekilde Load Test‘ler sonucunda yeterli response time performansını alamıyor olmam yatıyor aslında. Tüm request’ler garip bir şekilde IIS üzerindeki Worker Process Queue’sunda garip bir şekilde yığılmaktaydı. Bu süreç boyunca her türlü Performance Profiling Tool’ları ile API’ı profile ettim, SQL tarafındaki profiling işlemlerini gerçekleştirdim ve dahası…
Günler geçiyor ve herhangi elle tutulur bir sonuç hala alamıyordum ve artık işin boyutu memory’den bir dump alıp, bunu WinDBG aracılığı ile analiz etmeye kalmıştı. Nerede ne kaçırıyor olabilirdim acaba?
Evet işin hikaye boyutunu geçtiğimizde ise nedir bu WinDBG olayı bir bakmaya başlayarak sizlere deneyimlerimi aktarmaya çalışacağım. 🙂
Nedir Bu Memory Dump?
Belleği (memory) tüketen uygulamaların, herhangi bir “t” anında alınan “görüntüsüdür” aslında.
Yukarıdaki tanımlamadan yola çıkarsak, biz bu görüntüye bakarak ortamda neler var?, neler ön plana çıkıyor?, neler arka planda kalıyor?, neler daha çok yer kaplarken, neler daha az yer kaplıyor gibi resmin bütününü görebilme şansına sahip oluyoruz aslında.
Şimdi makaleye ilk girişimdeki hikayeme tekrar baktığımızda ise; memory’de “t” anında neler olup olmadığını anlayabilmem, yani resmin tümüne bakabilmem için neden memory dump almaya ihtiyaç duyduğumu anlatabilmişimdir umarım.
WinDBG’a Başlangıç ve Kurulum
Öncelikle buraya girerek “Get debugging tools” sekmesinde bulunan Get Debugging Tools for Windows (WinDbg) (from the SDK) bağlantısına tıklayarak, indirelim ve kurulumunu istediğimiz bir path’e (en azından 3-5 gb yer olan) gerçekleştirelim.
Not: İndirme işlemi biraz uzun sürebilir. Bu adımda otomatik olarak Windows Software Development Kit parçaları indirilecektir.
Kurulumu gerçekleştirdikten sonra WinDBG tool’u açtığınızda, yukarıdaki ekran ile karşılaşacaksınız.
PssCor2 Nedir? ve Kurulumu
Bu eklenti bize, managed kod’ları debug edebilme yetisini kazandıracaktır. Çünkü default olarak WinDBG unmanaged kod’ları debug etmek için tasarlanmıştır ve win32 uygulamalarının debugging’i için çok kullanışlıdır bu komutlar. Default olarak gelen bu komutları .Net Framework ile geliştirilmiş uygulamaların debugging’i içinde kullanılabilir fakat oldukça zor ve zahmetli bir iştir. İşte tam bu noktada biz developer’ları düşünerek, bu işi kolaylaştıracak extension’lar çıkartılmıştır. 🙂
Bu extension’lardan bir tanesi .Net Framework ile birlikte gelen SOS.dll‘dir ve bize managed kodları ekstra komutlar ile nispeten daha kolay debug edebilmemizi sağlamaktadır. Her güzel şeyin bir eksiği olduğu gibi SOS.dll’de debugging işlemlerinde bazı noktalarda eksik kalmaktadır. İşte bu noktada PssCor2 extension’u kullanılarak, .Net uygulamalarının debugging işlemleri ekstra fonksiyonaliteler ile dahada kolaylaştırılmış ve SOS.dll’e göre olan gap‘leri giderilmiştir.
PssCor2 aynı zamanda beraberinde managed thread‘leri ve managed object heap‘i izleme, CLR stack‘i görebilme gibi ekstra özelliklerini de getirmektedir.
Ne için kullanmamız gerektiğini anladığımıza göre şimdi kurulum işlemine geçebiliriz. Öncelikle buraya tıklayarak Psscor2 Managed-Code Debugging Extension for WinDbg‘ı indirelim. İndirme işlemi bittikten sonra dosyayı unzip edelim. Unzip işlemini tamamladıktan sonra “psscor2” klasörünü, daha önceden kurulumunu yapmış olduğumuz WinDBG’ın, ilgili kurulum dosyasının içerisinde bulunan “Debug” dosyasının içerisine kopyalayalım.
Symbol Path’in Ayarlanması
Öncelikle neden Symbol dosyalarına ihtiyaç duyacağımıza geçmeden buraya tıklayarak, ilgili işletim sisteminize ve x86, x64 bit seçeneklerine dikkat ederek ilgili Symbol dosyalarını indirmeye başlayalım.
Not: Symbol dosyalarını indirme işlemi zorunlu olmamakla beraber daha hızlı işlem yapabilmeniz adına indirmenizi tavsiye edip, makalenin ilerleyen bölümlerinde WinDBG içerisinde nasıl tanımlayabileceğimizi göreceğiz.
Symbol dosyalarını kabaca anlatmak gerekirse: Bu dosyalar uygulamanın compile sırasında üretilirler. Bildiğimiz gibi compile işlemi sırasında ise kodlar, IL(Intermediate Language) tarafından makine diline çevrilir. Visual Studio ile derlediğimizde de farkederseniz “bin/debug” dosyası altında “.PDB” uzantılı dosyalar oluşmaktadır. İşte bu dosya assembly için debugging symbol’lerini içermektedir. İçerisinde ise identifier‘lar ve bu identifier’ların assembly içerisinde bulunduğu yerler ve aynı zamanda attribute‘ler yer almaktadır.
Dump içerisinde ise sorununu araştırdığımız uygulamanın stack’i, hangi bellek adresinde bulunduğu hexadecimal olarak tutulmaktadır ve o sırada ne yapmaya çalıştığını anlayabilmemiz için gerekli bu tüm bilgiler symbol dosyalarıyla şekillenmektedir.
Symbol dosyalarının indirme işlemi tamamlandıktan sonra, PssCor2 kurulumunda da yaptığımız gibi WinDBG’ı kurmuş olduğumuz dosyanın içerisindeki “Debug” dosyasının içerisine kurulumunu gerçekleştirelim.
Sonunda asıl kısmımıza geçebiliriz. Debugging işlemlerini yapabilmemiz için gerekli olan her şey hazır durumda. Şimdi bize problem yaratacak küçük bir uygulama yazalım. 🙂
using System; namespace WinDbgTestApplication { class Program { static void Main(string[] args) { while (true) { Console.WriteLine("Doğum yılınızı giriniz:"); int birthYear = Convert.ToInt32(Console.ReadLine()); int currentYear = DateTime.Today.Year; int result = currentYear - birthYear; Console.WriteLine("{0} yaşındasınız.", result); } } } }
Ben basit bir Console uygulaması tercih ettim. Bu kodda olası hataya açık olan kısımlar convert işlemleri sırasında herhangi bir kontrolün yapılmamasından kaynaklanarak, runtime sırasında kullanıcı bazlı oluşabilecek olan hatalardır. Zaten en çok bu tarz hatalardan dolayı production ortamlarında gol yemiyor muyuz?
Neyse konumuza geri dönelim 🙂 Şimdi hazırlamış olduğumuz bu Console uygulamasını bir iyi kullanıcı gözünden kullanalım, birde kötü kullanıcı rolünde kullanıp doğum tarihimizi yazı ile yazalım. 🙂
ve tada… uygulamamız çöker.
Buradaki hatayı herhangi bir try-catch bloğu ile kaybedede bilirdik ve işte ozaman bizim için sıkıntılı günler başlardı. Hangi durumlar karşısında ne gibi hatalar oluşuyor? Ben bu problemi nasıl yakalayacağım? Her yere loglama koyma çabaları ve dahası. Kullanıcı kaynaklı olmayan bir hata da olabilirdi. Developer kaynaklı bir hataya ne derdiniz? Açık unutulmuş ve dispose edilmemiş bir obje? Belli bir yük altında memory leak‘lere sebep olabilir ve anlamsız bir şekilde uygulamanızın crash olmasına kadar sebep verebilirdi ve dahası ve dahası. İşte bu gibi durumlar karşısında her ne kadar debugging işlemi zor olsa da imdadımıza WinDBG yetişiyor.
Dump Alma
Uygulamamız çalışır durumda iken dump nasıl alabiliriz bir bakalım. “Ctrl+Alt+Delete” tuş kombinasyonu ile “Görev Yöneticisini” açarak arka planda çalışan process’lere bir bakalım. Açılan process listesinde uygulamamızın adını görmekteyiz “WinDbgTestApplication” olarak.
Not: Dump alacağınız uygulama bir windows form veya console uygulaması olmayabilir. Bir web uygulaması ise yine process’ler üzerinden örneğin ilgili “w3wp.exe” veya localhost üzerinde ise “iisexpress” in dump’larını da alabilmek mümkündür. (Aynı zamanda çalışan bir process’i attach edebilmekte mümkündür.) Fakat bu şekilde alınan dump’ları okumakta bi hayli zorlanabiliriz. Bunun yerine IIS üzerinden ilgili Application Pool’unu seçerek, “Debug Diagnostic Tool” gibi araçlar ile daha kolay full memory dump’ları alabilmek mümkündür. Bu konuyu farklı bir makalemde detaylı olarak aktaracağım. Bu makale kapsamında herhangi bir dump nasıl yorumlanabilir’i göreceğiz.
Görev Yöneticisi üzerindeki process listesinde görmüş olduğumuz uygulamamızın adına sağ tıklayarak, ben windows 10 kullandığım için “Döküm dosyası oluştur” seçeneğine basıyorum. Eğer farklı bir windows versiyonu kullanıyor iseniz, “Dump dosyası oluştur” seçeneğine basabilirsiniz.
Yukarıdaki resimde görebildiğimiz gibi dump dosyamızı, “AppData/Local/Temp” klasörü altında “WinDbgTestApplication.DMP” olarak oluşturmuş durumdadır. Fakat bu şekilde oluşan dump’lar default olarak minidump‘dır. Bizim ise daha detaylı olarak full memory dumplarına ihtiyacımız vardır.
Process Explorer
Full memory dumpları alabilmek için öncelikle buraya tıklayarak, “Process Explorer” ı indirelim. İndirme işlemini tamamladıktan sonra unzip ederek içerisinde bulunan uygulamayı çalıştıralım. Bu işlem sonunda tüm process’lerin daha detaylı bir şekilde görünümünün ekrana geldiğini görebiliriz. Şimdi listeden “WinDbgTestApplication” uygulamasını bulalım ve ve sağ tıklayarak “Create Dump” menüsünden “Create Full Dump” ı seçelim.
Create Full Dump adımından sonra istediğimiz bir path’e dump dosyamızı kaydedelim. Artık elimizde WinDbgTestApplication uygulamasına dair full memory dump’ı bulunmaktadır.
WinDBG’ı Keşfetmeye Başlayalım
Uygulamalarımız kısmından WinDbg (x86)’yı seçelim ve çalıştıralım. Uygulamamız çalıştıktan sonra ilk önce indirmiş olduğumuz Symbol dosyasını tanımlayalım. Bunu gerçekleştirebilmek için sol üstte bulunan “File” sekmesine tıklayarak ardından “Symbol File Path” seçeneğine tıklayalım.
Açılan pencerede “Symbol path” kısmına symbol yolunu belirtme syntax’ı şu şekildedir:
srv*[kurulum yapılan local symbol path'i]*http://msdl.microsoft.com/download/symbols
Ben kurulumumu WinDBG içerisinde bulunan “Debug” klasörü altında yaptığım için şu şekilde path bilgisini giriyorum.
srv*f:\wdk\debug\symbols*http://msdl.microsoft.com/download/symbols
Şimdi sıra almış olduğumuz dump’ın import işleminde. Bunun içinde yine WinDBG uygulamasının sol üstünde bulunan “File” menüsüne tıklayarak ardından “Open Crash Dump” seçeneğine tıklıyoruz. Ardından oluşturmuş olduğumuz dump dosyamızı burada import ediyoruz. (Ben dosyaya daha kolay erişebilmek adına masaüstüme taşıdım.)
Dump dosyasının import işlemi sonrasında ekranda karşımıza şu komut satırları gelecektir:
Microsoft (R) Windows Debugger Version 10.0.10586.567 X86 Copyright (c) Microsoft Corporation. All rights reserved. Loading Dump File [C:\Users\GÖKALP\Desktop\WinDbgTestApplication-Full.dmp] User Mini Dump File with Full Memory: Only application data is available ************* Symbol Path validation summary ************** Response Time (ms) Location Deferred srv*f:\wdk\debug\symbols*http://msdl.microsoft.com/download/symbols Symbol search path is: srv*f:\wdk\debug\symbols*http://msdl.microsoft.com/download/symbols Executable search path is: Windows 10 Version 10586 MP (4 procs) Free x86 compatible Product: WinNt, suite: SingleUserTS Personal Built by: 10.0.10586.0 (th2_release.151029-1700) Machine Name: Debug session time: Sun May 8 16:35:28.000 2016 (UTC + 3:00) System Uptime: 3 days 16:21:05.267 Process Uptime: 0 days 0:01:01.000 .................................. eax=00000000 ebx=00000000 ecx=00000005 edx=00000000 esi=00000003 edi=00000003 eip=7773718c esp=0018e3e8 ebp=0018e578 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!NtWaitForMultipleObjects+0xc: 7773718c c21400 ret 14h
Şimdi WinDBG aracımız için indirmiş olduğumuz PssCor2 extension’ını dahil edelim. PssCor2 indirme kısmında da bahsettiğimiz gibi bu extension sayesinde .Net ile geliştirmiş olduğumuz uygulamamızı bize sağlayacağı ek fonksiyonaliteler ile daha kolay debug işlemlerini gerçekeleştirecek idik.
WinDBG içerisine PssCor2’yi dahil edebilmek için aşağıdaki komut’u, WinDBG uygulamasının alt bölümünde bulunan komut satırı kısmına girmemiz yeterli olacaktır.
.load f:\wdk\debug\psscor2\x86\psscor2.dll
Not: PssCor2 yükleme işlemi sırasında uygulamanızın bit’ine göre ilgili PssCor2’yi seçmeniz önemlidir. Console uygulamamız 32 bit olduğu için “x86” seçeneğini yüklüyorum.
Yükleme işleminin ardından PssCor2’nin başarılı bir şekilde yüklenip yüklenilmediğini anlayabilmek için komut kısmında “!help” komutunu girerek çalıştıralım.
Yükleme işlemi başarılı ise yukarıdaki şekilde yardım komutları ekrana gelecektir.
Şimdi sıra “mscordacwks.dll” i yüklemeye geldi. Mscordacwks kabaca tıpkı “SOS.dll” de olduğu gibi WinDBG aracımızda debugging işlemlerimiz için CLR ayrıntıları üzerinde bize data-access abstraction’ı sağlamaktadır. Bu ayrıntıya takılmamıza gerek yoktur.
Yükleme işlemini aşağıdaki komut ile gerçekleştirebiliriz:
.cordll -ve -u -l
Başarılı ile yüklendiğinde aşağıdaki komut satırı ekrana gelecektir.
0:000> .cordll -ve -u -l Automatically loaded SOS Extension CLRDLL: Loaded DLL C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll CLR DLL status: Loaded DLL C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscordacwks.dll
Not: “mscordacwks.dll” yükleme sırasında eğer “SOS does not support the current target architecture.” gibi bir hata alırsanız, uygulamanızın bit değerlerine göre doğru PssCor2’yi yüklediğinizden emin olmalısınız.
CLR Stack’i İnceleme Zamanı
Evet her şey hazır olduğuna göre artık CLR Stack’i inceleyebiliriz. Aşağıdaki komut satırı ile managed stack’i listeleyebiliriz.
!clrstack
Bu işlem sonucunda aşağıdaki gibi bir çıktı ile karşılaşacağız:
0:000> !clrstack OS Thread Id: 0x314c (0) Child SP IP Call Site 0018f1f0 7773718c [HelperMethodFrame: 0018f1f0] 0018f2a0 720acc3a System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean) 0018f2c8 7156fe79 System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo) 0018f378 71bfd34a System.Convert.ToInt32(System.String) 0018f384 00d804ab *** WARNING: Unable to verify checksum for WinDbgTestApplication.exe WinDbgTestApplication.Program.Main(System.String[]) [C:\Users\GÖKALP\Documents\Visual Studio 2015\Projects\WinDbgTestApplication\WinDbgTestApplication\Program.cs @ 13] 0018f538 72671376 [GCFrame: 0018f538]
Ekran görüntüsü ise aşağıdaki gibidir.
tada… artık managed stack karşımızda. Evet şimdi bunları nasıl yorumlayacağız kısmına gelelim.
Uygulamamızın dump alma işlemi sırasında çalışan thread’lere bir bakalım öncelikle. Bunun için aşağıdaki komut satırını girmemiz yeterlidir.
!threads
Bu işlemin sonucunda aşağıdaki gibi thread’ler ekrana gelmektedir.
Ekrana gelen bu managed thread’lere baktığımızda:
0:000> !threads ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 314c 00be98a8 2a020 Preemptive 02613E9C:00000000 00be3ed8 0 MTA System.FormatException 0260b2c4 5 2 2168 00bf77a8 2b220 Preemptive 00000000:00000000 00be3ed8 0 MTA (Finalizer)
“1” id’li olan managed thread’in exception başlığı altında “System.FormatException” verdiğini açıkça görebilmekteyiz. Şimdi bu hatanın detayına biraz daha inmek istersek eğer aşağıdaki komut satırını girmemiz yeterli olacaktır.
!printexception
Bu komutun sonucunda ise exception detayı aşağıdaki gibi karşımıza çıkacaktır. 🙂
0:000> !printexception Exception object: 0260b2c4 Exception type: System.FormatException Message: Giriş dizesi doğru biçimde değildi. InnerException: <none> StackTrace (generated): SP IP Function 0018F2A0 720ACC39 mscorlib_ni!System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)+0xb3cd59 0018F2C8 7156FE79 mscorlib_ni!System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)+0x79 0018F378 71BFD34A mscorlib_ni!System.Convert.ToInt32(System.String)+0x32 0018F384 00D804AB WinDbgTestApplication!WinDbgTestApplication.Program.Main(System.String[])+0x63 StackTraceString: <none> HResult: 80131537
Evet uygulamamızın crash olmasına sebep olan exception detaylarına git gide yaklaşmaktayız. İlk başta “!clrstack” komutu ile karmaşık gelen managed stack’den yola çıkarak git gide stack’i anlamlandırmaya başladık. StackTrace kısmında görebildiğimiz gibi “System.Number.StringToNumber” fonksiyonu ile string’den int’e convert işlemi sırasında bir hatanın meydana geldiğini görmekteyiz.
Dilersek bu işlem sırasında bu fonksiyonların hangi parametreler ve ilgili değişkenleri ile beraber çağrıldığını da aşağıdaki kod bloğu ile alabilmek mümkündür.
!clrstack -a
Bu işlem sonucunda ise aşağıdaki gibi bir kod ekranı karşımıza gelecektir.
Hatanın “0018f378 71bfd34a System.Convert.ToInt32(System.String)” kısmında meydana geldiğini görebilmekteyiz. Uygulamamızı açtığımızda ise “Program.cs @ 13” kısmında belirtilen 13. satırdaki koda bir bakalım:
13. satırda gördüğümüz gibi tamda stack’de belirtilen “int birthYear = Convert.ToInt32(Console.ReadLine);” satırı bulunmaktadır. Hatalı doğum yılını girdiğimiz satır! 🙂
Size burada crash durumlarındaki dump’ları nasıl okuyabileceğimiz hakkındaki deneyimlerimi aktarmaya çalıştım. Her zaman bazı spesifik hataları dump üzerinden bulabilmek pekte kolay olmayacaktır. Özellikle memory leak veya aşırı zaman alan uygulamalarınızın sebebini araştırma durumlarında. WinDBG konsolundan ayrıca, “!dumpheap” komutu ile heap’de yer alan objeleri ve bunların adetleri gibi detaylı bilgilere ulaşabilirken, bir başka daha kolay analiz yöntemi ise DebugDiag Analysis aracı ile performans analizleri yaptırıp, hangi thread’in ne kadar vakit harcadığı gibi daha detaylı raporlama bilgilerine de ulaşabilmek mümkündür.
Bu makalemin devamı niteliğinde bir başka makale serisinde bu tarz problemlerdeki deneyimlerime de aktarmaya çalışacağım. Umarım faydalı bir yazı olmuştur.
Vakit ayırıp okuduğunuz için teşekkür ederim. 🙂 Sevgiyle kalın.
Merhaba Gökhan bey ,
sizin bu yönlendirdiğiniz sitede windbg olarak bulamadım sdk kur falan diyor ben microsoftun kendi sitesnden indirdim o da sizinkinden çok farklı yardımcı olabilir misiniz
Merhaba, üzerinden uzun zaman geçti. Sanırım o süre zarfında Windbg bir güncelleme aldı ama çoğu süreç hala aynı. Sanırım şuradan indirebilirsiniz. https://learn.microsoft.com/en-gb/windows-hardware/drivers/debugger/ Windows ortamım olmadığı için şuan deniyemiyorum maalesef.