Da jeg holdt indlæg om WinDbg for ONUG, var der en, der spurgte, om man ikke kunne sætte applikationen til selv at lave et memory dump i tilfælde af fejl. Det er et meget relevant spørgsmål, og timingen var lidt pudsig, for jeg skulle have møde med en kollega den efterfølgende dag om netop dette emne.
Jeg svarede, at jeg endnu ikke havde gransket mulighederne og begrænsningerne, men at min mavefornemmelse fortalte mig, at det næppe ville være mulig at gøre det på en brugbar måde i alle tilfælde. Det viste sig, at holde stik. De interesserede kan læse John Robbins detaljerede gennemgang af sit mislykkedes forsøg på at implementere denne feature. Den vil jeg ikke gentage, men blot konstatere at hvis han ikke kan, er vilkårene ikke særlig gunstige for os andre. I stedet vil jeg prøve at belyse noget af problematikken med en analogi.
Hjerneblødning
Forestil dig, at du får en hjerneblødning. Det er en forfærdelig situation, og jo før en læge får set på det desto bedre. Du har to muligheder. Enten kan du lade lægen stå på spring, så hun er klar til at slå til, så snart symptomerne konstateres, eller også kan du bide smerterne i dig, hoppe ned i bilen, drøne mod hospitalet, sidde i kø på motorvejen, fortsætte i endnu højere fart mod hospitalet, parkere ulovligt på en nærliggende mark, fordi parkeringspladsen ved hospitalet allerede er fuld, løbe hele vejen til receptionen, vente de obligatoriske tre kvarter eller mere for derefter at blive tilset af en læge.
I første tilfælde har lægen alle muligheder for at inspicere ikke alene den konkrete blødning men også tilstanden af andre vitale organer. Jeg ved ikke, hvad der er relevant at se på i den situation, men det ved lægen forhåbentlig. Pointen er, at relevante detaljer risikerer at forsvinde med tiden, og derfor er det ideelt at kunne komme til med det samme. Givet omstændighederne har hun altså de bedste muligheder for at finde ud af, hvad der er galt og hvorfor.
Det er ikke tilfældet i vores andet tilfælde. Blødningen kan have bredt sig, og det kan derfor være svært at se, hvor den opstod. Der kan være følgevirkninger. Måske har trykket påvirket dit syn, så du er løbet ind i samtlige dørkarme og faldet ned af alle trapperne på vej til hospitalet. Lægens første indtryk kan derfor meget vel være en meget forslået m/k, og derfor kan det være, at hun indledningsvis forsøger med plaster og bandager. Jeg kan gudskelov ikke tale af erfaring her, men ikke desto mindre står det klart, at lægen kommer til at arbejde under langt fra ideelle forhold i andet tilfælde.
Lad os prøve at se på, hvordan min lettere morbide analogi hænger sammen med debugging. Men måske er det på sin plads at lægge ud med at understrege, at jeg ikke anser applikationsnedbrud som en livstruende situation.
Gør det selv
Hvad skal der til, for at få vores applikation til at tage et memory dump i tilfælde af en uhåndteret exception? I første omgang er vi nødt til at sørge for, at der ikke er en uhånderet exception. Vi er jo nødt til at kunne reagere, og vi er således ikke interesseret i, at Windows overtager styringen på dette punkt. Ergo, er vi nødt til at fange alle exceptions på det yderste niveau for alle tråde. Det er ikke uden sine udfordringer, men lad os antage, at det kan lade sig gøre. Når vi så står med en exception, er det sådan set bare, at kalde de relevante metoder i dbghelp.dll, men det er ikke her problemet ligger.
Generering af dumpet svarer til lægens indledende undersøgelse af patienten. Som illustreret er det perioden fra blødningens opståen til lægens undersøgelse, der er problemet. Jo længere tid der går, desto dårligere bliver mulighederne for indsamling af brugbar information.
For det første har vores applikation jo fanget den uhåndterede exception, og derfor er der per definition ikke en uhåndteret exception! Det betyder, at der ikke ligger en exception på den fejlende tråds stak, og relevante stakvariable vil højst sandsynlig ligeledes være væk. Vi vil muligvis kunne finde den relevante Exception-instans på heapen, men allerede her er der en forskydning mellem fejlsituationen og rapporteringen, og relevante detaljer er muligvis gået tabt.
Håndtering af exceptions sker som bekendt per tråd. Det vil sige, at mens vi er ved at konstatere, om vi har en – under andre forhold – uhåndteret exception på en tråd, fortsætter alle andre tråde i applikationen ufortrødent. Vi kan forsøge at stoppe dem tidligt i forløbet, men det vil i bedste fald kun reducere men ikke eliminere omfanget af skaderne.
Vi kan ikke med sikkerhed sige, om de andre tråde sletter vigtige spor i forhold til undersøgelse af problemet. Når vi inspicerer det genererede dump, vil disse tråde være i en vilkårlig tilstand i forhold til problemet. Deres kaldestakke vil være ændret mange gange, og de kan have påvirket heapen, finalizerkøen, indlæsning af moduler og meget andet.
Ganske som i analogien er vores muligheder for at undersøge problemet altså begrænset i forhold til det, vi kunne ønske os.
Lægen på spring
Hvordan ser det ud, hvis vi har lægen stående klar? Det svarer til, at vi f.eks. sætter adplus op til at tage et crash dump i tilfælde af en uhåndteret exception. Håndtering af exceptions i .NET bygger på Windows’ SEH, som er en mekanisme i selve operativsystemet til håndtering af exceptions. Det vil sige, at fra Windows’ synspunkt er der intet specielt ved en .NET exception. Det er blot en exception af en bestemt type, men håndteringen er ikke specifik for .NET.
Så når vi sætter adplus til at lave et crash dump, fortæller vi blot Windows, at vi ønsker at bruge CDB som JIT debugger. Ydermere sørger adplus for, at bruge denne debugger til at generere det ønskede dump, så snart Windows konstaterer problemet.
Hvordan er det forskelligt fra ovenstående situation? Som analogien illustrerer, træder lægen til så snart problemet opstår, og det er præcis, hvad der sker, når vi bruger adplus eller lignende. Når Windows konstaterer, at applikationen har en exception, der ikke vil blive håndteret, sætter den processen på hold og overlader kontrollen af denne til den registrerede debugger.
Det vil altså sige, at CDB kommer til i det øjeblik problemet opstår (altså ”just in time”). Ydermere er vores applikation, kaldet debuggee processen stoppet, og det er debuggeren, der har kontrollen. Kaldestakken for den fejlende tråd er korrekt i forhold til den ikke uhåndterede exception, og alle andre tråde er sat på hold. De ødelægger altså ikke noget, når problemet først er konstateret. Det vil sige, at vi har de bedst mulige forhold til at lave et retvisende dump af processens hukommelse. Det er stadig mulighed for at information er gået tabt, men det er de bedste forhold, vi kan håbe på.
Adplus er tilmed smart nok til ikke blot at tage et dump i tilfælde af et crash. Med mindre den instrueres i at skippe first chance exceptions, laver adplus faktisk både et dump, når vores exception smides (first chance), og derefter når den konstaterer, at den ikke er blevet håndteret (second chance). Derved kan vi inspicerer begge de aktuelle tilstande, hvilket vi ikke har mulighed for med gør det selv-metoden, eftersom at vi ikke kan få afviklet kode, hver gang en vilkårlig exception bliver smidt.
Som min analogi forhåbentlig har illustreret, er der således stor forskel på vores muligheder for at skabe brugbare dumpfiler afhængig af, om vi gør det via debugging grænsefladen eller ej, og derfor er svaret på det indledende spørgsmål i bedste fald, ”det er næsten mulig”. Da dump debugging allerede er en noget kryptisk affære, er det næppe tilrådeligt, at gøre det sværere ved at lave næsten brugbare dumpfiler.