Een groot gedeelte van de supportcalls bij premier support heeft te maken met crashes, memory leaks en andere van die voor velen ongrijpbare problemen. In veel gevallen is de oorzaak echter redelijk simpel te achterhalen, en zou je een support call en veel tijd en ergernis kunnen besparen. Het probleem is dat velen niet weten dat je geen die-hard debugger hoeft te zijn om een memory dump te analyseren (ok. Ok. Eerste stap daartoe J.), of een memory leak te traceren.
Je hebt alleen de juiste tools nodig een klein duwtje in de goede richting. Die ga ik je vandaag geven J
Tools
Er zijn een aantal tools die in mijn optiek niet mogen ontbreken in de gereedschapskist van een admin. Dat zijn:
De installatie spreekt voor zich, dus daar ga ik jullie niet mee vervelen, echter voor de debugger die we gaan gebruiken (WinDBG) is het aan te raden een symbolserver te configureren (http://www.microsoft.com/whdc/DevTools/Debugging/debugstart.mspx) . In principe is dat voor deze eerste stapjes in de wereld van debugging nog niet nodig, maar ik zou het niet laten, daar het je toch net even wat meer inzicht geeft in wat er precies gebeurd onder de motorkap. Ik prefereer de Environmental Variable, aangezien deze door veel debugtools automatisch wordt opgepakt.
Introductie
In deze post ga ik drie type scenario's behandelen. Dat zijn:
- Eerste analyse van een kerneldump, naar aanleiding van een crash/BSOD (Blue-Screen-Of-Death).
- Troubleshooten van usermode crashes.
- Troubleshooten van memorydumps.
Voordat we daaraan beginnen zal ik toch een klein stukje theorie moeten behandelen, om een en ander duidelijk te maken.
Moderne CPU architecturen hebben verschillende modi waarin zij opereren. Deze noemen we ook wel Rings (er zijn nog tig andere benamingen). Deze modi stellen een ontwikkelaar in staat bepaalde functionaliteit van de CPU architectuur niet toe te staan voor de code die op dat moment wordt uitgevoerd. Denk hierbij aan toegang tot bepaalde CPU registers of onderdelen van het geheugen. Dit om de stabiliteit en veiligheid van het systeem te waarborgen. In Windows NT besturingssystemen (Alles behalve 3.*, 9* en ME) maken we gebruik van 2 van deze modi, namelijk de modus waarin alles is toegestaan, welke wij 'Kernelmode' noemen (Ring 0) en een modus waarin toegang tot hardwareresources beperkt worden, welke wij 'Usermode' noemen (Ring 3; in x86 en x64 architecturen zijn het er 3 (Ring 1,2,3), maar Windows gebruikt alleen de meest beperkte). De structuur van Windows zorgt ervoor dat eigenlijk alleen code die hoort bij het operating system in Kernelmode mag draaien. Er zijn wat uitzonderingen, waarvan drivers de meest belangrijke zijn.
Code wat in Kernelmode draait heeft dus onbeperkt toegang tot systeemresources, en kan dus ook geheugen structuren veranderen van andere processen, wat kan leiden tot datacorruptie, dataloss en een instabiel systeem. Windows beschermt zich hiertegen door in dergelijke gevallen op de noodknop te drukken en het systeem te stoppen of bugchecken met een BSOD. Wij noemen dit een crash, maar het is eigenlijk Windows die je redt van nog grotere problemen. Windows zal dan op basis van de instellingen wel of niet een dump maken van het kernelgeheugen. Dit heb je niet voor usermode processen.
Code wat in Usermode draait heeft dus beperkte toegang tot systeemresources. Zo heeft het alleen toegang tot geheugenbereik dat het proces toegewezen heeft gekregen en dient het toegang tot hardware aan te vragen bij het operating system. Qua performance lever je hierdoor wel wat in, maar bijkomend voordeel is dat het ene proces niet zo makkelijk een ander proces om zeep kan helpen, laat staan een BSOD kan veroorzaken, en je een verhoogde mate van veiligheid hebt, doordat geheugenstructuren van het ene proces niet uit te lezen zijn door het andere proces.
Waarom is deze informatie nu belangrijk?
Voor deze post om twee redenen:
Enerzijds is het belangrijk om te weten dat een BSOD dus in bijna alle gevallen veroorzaakt wordt door OF een bug in het operating system, OF een driver.
9 van de 10 keer zal het gaan om een driver.
Anderzijds is het belangrijk om te begrijpen dat het analyseren van een usermode crash anders is dan het analyseren van een kernelmode crash. Bij een kernmode crash heb je vaak de beschikking over een volledige dump van het geheugen. Bij usermode crashes, moet je memorydumps zelf generen en verzamelen.
Blue Screen of Death – BSOD
Wanneer een systeem geplaagd wordt door BSODs is de eerste plaats waar je gaat kijken de memorydump. Ik ben van mening dat elk systeem geconfigureerd moet zijn om een volledige dump te maken, daar in de volledige dump de meeste informatie staat. Je kan met een MiniDump soms ook wel af, maar meestal zal een support engineer vragen om de volledige dump. Ik neem aan dat je jouw systeem niet nogmaals wilt laten crashen met alle gevolgen van dien voor de data en uiteindelijk de business. Opties voor het maken van een memorydump kan je vinden op http://support.microsoft.com/kb/254649/en-us
Verder adviseer ik mijn klanten altijd, dat als er een memorydump heeft plaatsgevonden, deze veilig te stellen. Een volgende dump overschrijft deze namelijk. Meerdere dumps is handig daar de oorzaak van de crash niet altijd meteen duidelijk is. Helaas is dit vaak het geval. Het kan namelijk zijn dat de veroorzaker van de crash allang uit het geheugen is verdwenen, voordat het systeem struikelt over het probleem dat deze code veroorzaakt heeft. Door meerdere dumps te analyseren kan je soms correlaties trekken, en zo tot een conclusie komen.
Goed… We hebben een crash gehad en we hebben een memory dump. What's next??
We starten WinDBG en openen de memory dump. Deze dump is standaard te vinden op %SystemRoot%\Memory.dmp. Je kan uiteraard de memory dump naar een andere machine kopiëren en hem daar analyseren:
Nu je de memorydump geopend hebt, zal WinDBG de dump snel analyseren. Dit levert al vaak meteen waardevolle informatie op, zoals je kan zien op de analyse van 1 van mijn recente dumps:
Probably caused by : rismcx64.sys … Schijnbaar geeft WinDBG rismcx64.sys de schuld. Een korte blik in mijn drivers, gaf aan dat het om een smartcard driver ging uit 2006. Even een bezoekje aan de Windows Update en de vendor website en voila.. Nieuwe drivers. Sindsdien heb ik die crash nooit meer gehad.
Nu kan je WinDBG een verbose output laten zien van deze analyse, waar je vaak ook wel wat aan hebt. Dit doe je door !analyze –v uit te voeren. De output laat dan bijvoorbeeld ook de stacktrace zien, die je een idee kan geven, van wat het systeem op dat moment aan het doen was:
Je leest een stacktrace altijd van onder naar boven. In mijn dump was windows duidelijk bezig met het afhandelen van een IO request richting mijn smartcard device, te zien aan termen als IopXxxControlFile, NTDeviceIoControlFile en andere IO routines. Verder zie je verwijzingen naar Wdf modules wat staat voor Windows Driver Foundation. Deze informatie kan ik zien doordat ik WinDBG geconfigureerd heb om de public symbols van de Microsoft SymbolServer te halen. Zonder symbols ziet de output er anders uit.
Een hele hoop van de functienamen zijn weggevallen. Hierdoor is het lastig in 1 oogopslag te bepalen of de modules behoren tot het besturingssysteem of niet. Ik kijk altijd even naar de stacktrace. Je kan bijvoorbeeld zien of er nog andere 3rd party modules zijn die rond die tijd acties aan het uitvoeren zijn geweest. Deze kan je dan vervolgens eveneens aan een grondig onderzoek onderwerpen.
Zo zie je… In een aantal simpele stappen kan je al snel wat zaken uitzoeken en proberen voordat je een case aanmeldt om een BSOD te laten onderzoeken.
User mode crash
Voor usermode crashes heb je standaard geen memory dumps. Echter je kan een debugger attachen aan een proces en een memory dump laten generen op het moment dat deze klapt. In de debugging tools kan dat op verschillende manieren, maar ik gebruik eigenlijk altijd DebugDiag, wat een schil rondom de debuggers uit de debugging tools is.
In mijn geval crashed een application pool regelmatig. Ik start dus debugdiag en kies voor de 'Crash' optie (een Hang is niet hetzelfde als een crash):
Vervolgens kan ik twee kanten op. Ik kan kiezen voor een proces, en selecteer gewoon het juiste w3wp.exe proces, of ik kies voor Application Pool. Dit is uiteraard eenvoudiger dus doe ik dat:
Vervolgens kies ik de application pool, en krijg ik de mogelijkheid om advanced opties mee te geven, zoals op welke Exceptions debugdiag moet reageren. Ik selecteer gewoon de standaard opties, daar dat in de meeste gevallen voldoende is.
Uiteindelijk kies ik Next > Next > Activate Rule.
Als dan vervolgens het proces klapt, maakt DebugDiag een memory dump, welke geanalyseerd kan worden. Na een paar keer mijn webapplicatie te hebben geprobeerd te starten, had ik 3 memory dumps:
Ik kan dan vervolgens kiezen om de data te laten analyseren door Analyze Data te selecteren. Na een tijdje door de dumps te hebben gewroet, laat DebugDiag een rapport zien met de bevindingen. In de meeste gevallen kan je dat rapport gebruiken om verder onderzoek te plegen. In mijn geval wist ik wat het probleem was, daar ik zelf een kapotte ISAPI filter had gebakken om demo's van DebugDiag te geven. Het rapport laat daarom ook netjes zien dat mijn UpCaseStackOverflow.dll filter het probleem heeft veroorzaakt.
Memory Leaks
Memory Leaks worden vaak gezien als zeer lastige problemen om te analyseren, maar met de juiste tools valt dat best mee. Ook hier gebruik ik meestal DebugDiag voor. Je start DebugDiag en kiest voor Memory and Handle Leak. Vervolgens kies je het proces waaraan je de debugger wilt koppelen. Het valt hier op dat we nu niet de keuze hebben om een Application pool te kiezen. Als je nu meerdere application pools hebt, en deze ook nog eens onder hetzelfde application pool account draait, lijkt het lastig deze processen te onderscheiden. Gelukkig is er een redelijk makkelijke manier om erachter te komen om welke application pool het gaat. Je scrolled helemaal naar rechts in het DebugDiag window en controlleert de commandline kolom. Dit bevat de naam van de application pool:

Je dient vervolgens aan te geven wanneer er precies een Memory dump gemaakt moet worden. Dit kan na een bepaalde tijd, maar vaak is het handiger een maximum aantal MB's aan te geven. Dit doe je door te kiezen voor Configure in de Userdump generation sectie:
Ik kies hier meestal voor Virtual Bytes, omdat Private Bytes daar een subset van is. Virtual Bytes is namelijk het aantal bytes dat het proces gealloceerd heeft binnen het Virtual Address Space van het proces. Private Bytes is het geheugen dat daadwerkelijk in gebruik is (commited). Ook hou je zo rekening met een een ander memory management fenomeen, namelijk page sharing. Wanneer een proces namelijk een module laadt met shareable data (code afhankelijk dus), die al geladen is door een ander proces, wordt deze niet nogmaals in het geheugen geladen, maar gedeeld door processen. Dit geheugen wordt gemapped naar het proces. De virtual bytes geven vervolgens aan dat het proces het aantal bytes voor die module gealloceerd heeft, maar fysiek is er geen extra geheugen gebruikt. Deze shareable bytes zie je dus ook niet terug in de Private Bytes.
Om te bepalen wanneer een memory dump te maken, zal je monitoring moeten inregelen. Dit kan door System Monitor (perfmon) te gebruiken en de Private Bytes en Virtual Bytes van het systeem en processen te monitoren.
Als laatste selecteer ik weer Next > Next > Activate Rule en kan het wachten beginnen.
Zodra er weer een memorydump is gemaakt, kies je wederom voor Analyze Data en Voila… een rapport.
Net als bij de Usermode Crash, was het in mijn geval wederom een zelf geschreven ISAPI filter, met ditmaal een memory leak. Het rapport laat ook netjes zien dat er een module UpCaseLeak.dll is in mijn w3wp.exe proces die aardig wat geheugen heeft gealloceerd. In dit geval heeft DebugDiag te kort gelopen om echt een harde analyse te doen, maar je snapt waar dit heengaat.
Zo zie je… Debugging is niet alleen voor code junkies, maar iedereen kan een aantal basis stappen zetten, die in veel gevallen al tot de oorzaak van het probleem kunnen leiden!