Introduction
One
of the main problems in our applications is exceptions handling.
Right
from the planning stage, when architecting a new solution, we are designing an 'Error
Handling' mechanism, which naturally effects on the application reliability
& stability.
Quite
often (especially in large scale solutions) we are encountering lots of
different exceptions in our logging systems, and sometimes even experiencing exceptions
that crashes our application.
There
are 2 types of exceptions:
1. First
Chance Exception (Non-fatal):
An exception was thrown, however the process wasn't crashed.
2. Second
Chance Exception (Fatal):
An exception was thrown and the process was crashed.
We
could use debugging tools & debugging techniques to seize 'Crash Dumps',
analyze these exceptions and find the root cause of these crashes in our code.
In
this post I'll demonstrate how to use debugging extensions in WinDbg (from:
'debugging tools for windows') to find exceptions in our application's Dump
file.
Demo – planning
1.
Create a simple application that crashes.
2.
Run the application.
3.
Generate a Dump file.
4.
Analyze the Dump file.
4. 1
Print all thrown exceptions.
4. 2
Review the managed Call Stack.
4. 3
Find the exception in the method disassembly.
4. 4
Save the source code.
5.
Summary & Conclusions.
Demo
1. Create a simple application that crashes:
I've
created a simple application with the following objects and logic:
-
Person business object (BO):
namespace
DebuggingExceptionsExample.BOs
{
public class Person
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
|
-
PersonException object:
using System;
using
DebuggingExceptionsExample.BOs;
namespace
DebuggingExceptionsExample.ErrorHandling
{
public class PersonException : Exception
{
private Person _person;
public PersonException(string message,
Exception innerException = null,
Person person = null)
: base(message,
innerException)
{
_person = person;
}
// TODO: Implement
logic.
}
}
|
-
Main method:
using DebuggingExceptionsExample.BOs;
using
DebuggingExceptionsExample.ErrorHandling;
namespace
DebuggingExceptionsExample
{
class Program
{
static void Main(string[] args)
{
DebuggingPersonExceptionExample();
}
private static void DebuggingPersonExceptionExample()
{
// 1. Create a person.
Person person = new Person {FirstName = "Yonatan",
LastName = "Fedaeli",
Id =
1234};
// 2. Validate person details.
if (!ValidatePerson(person))
{
// 3. Throw a PersonException in case failed validation.
throw new PersonException("Debugging exceptions example.",
person: person);
}
}
private static bool ValidatePerson(Person person)
{
return false; // For demo purposes.
}
}
}
|
2. Run the application:
After
compiling & running the application we received the expected PersonException,
and the application crashed:
The
exception message:
Unhandled
Exception: DebuggingExceptionsExample.ErrorHandling.PersonException: D
ebugging
exceptions example.
at
DebuggingExceptionsExample.Program.DebuggingPersonExceptionExample()
in
c:\Users\Yonatan\Documents\Visual Studio
2013\Projects\DebuggingExceptionsExample\
DebuggingExceptionsExample\Program.cs:line
24
at
DebuggingExceptionsExample.Program.Main(String[] args) in c:\Users\Yonatan
\Documents\Visual
Studio 2013\Projects\DebuggingExceptionsExample\DebuggingExcep
tionsExample\Program.cs:line
10
|
From the
error message above we could observe the following:
-
Type of exception: DebuggingExceptionsExample.ErrorHandling.PersonException
-
The exception message: Debugging exceptions
example.
-
The call stack and method in which the exception was
thrown: DebuggingPersonExceptionExample()
3. Generate a Dump file:
There are
several ways and tools to generate a Dump file:
1. Windows Task Manager
2. DebugDiag
3. ADPlus
4. Visual Studio
5. ProcDump
6. Etc.
In this
example, where the application crashes at startup, I'll demonstrate the
DebugDiag (Debug Diagnostic) Tool.
3.1. Open DebugDiag and add new Rule…
3.2. Select Target Type:
3.3. Select Target:
I'll
type our application name: DebuggingExceptionsExample.exe
3.4. Advanced Configuration:
In
our case, we'll select the Exceptions configuration:
3.5. Configure Exception:
In
this window we'll set the following:
-
Exception Code: CLR (.Net) 4.x Exception
-
Exception Type Equals…: DebuggingExceptionsExample.ErrorHandling.PersonException
-
Action Type: Full Userdump (so we could perform proper
debugging)
3.6. Save & Close:
3.7. Select Dump Location And Rule Name:
3.8. Activate the rule now, and finish:
3.9. Now we could observe the Rule we created:
-
Rule Type
-
Rule Name
-
Status
-
Userdump Count
-
Userdump Path
The
next time we'll run our application DebugDiag tool would detect it, and based
on the rule we just created, would create the Crash Dump file.
4. Analyze the Dump file:
After
generating the Dump file, we could perform different debugging commands to
locate the exception and find the root cause of the error in our code.
The basic
idea is to explore the application's Threads, their corresponding Call
Stacks (Managed & Native) and to review the exceptions that were
thrown.
When
opening the Dump file in WinDbg we could observe the following:
Remark: For brevity, I omitted some of the
irrelevant information.
Microsoft
(R) Windows Debugger Version 6.3.9600.17200 X86
Copyright
(c) Microsoft Corporation. All rights reserved.
Loading
Dump File
[C:\Program Files\DebugDiag\Logs\Crash rule for all instances of
DebuggingExceptionsExample.exe\DebuggingExceptionsExample__PID__9748__Date__01_18_2017__Time_06_14_33PM__148__Second_Chance_Exception_E0434352.dmp]
User
Mini Dump File with Full Memory: Only application data is available
Comment:
'Dump created by DbgHost. Second_Chance_Exception_E0434352'
Symbol
search path is: *** Invalid ***
.
. .
This
dump file has an exception of interest stored in it.
The
stored exception information can be accessed via .ecxr.
(2614.1dcc):
CLR exception - code e0434352 (first/second chance not available)
.
. .
eax=0024ef24
ebx=00000005 ecx=00000005 edx=00000000 esi=0024efdc edi=00000001
eip=7538c6e3
esp=0024ef24 ebp=0024ef74 iopl=0
nv up ei pl nz ac pe nc
cs=001b ss=0023
ds=0023 es=0023 fs=003b
gs=0000 efl=00000216
KERNELBASE!RaiseException+0x54:
7538c6e3
c9 leave
0:000>
.symfix
0:000>
.reload
...........................
0:000>
.load psscor4
|
-
We could clearly see that WinDbg alerts that this Dump
file contains an exception.
-
Next, I fixed my symbol path, and loaded the PSSCOR4
(psscor4.dll) debugging extension.
4.1.
Print
all thrown exceptions:
Command: !PrintException
Output:
0:000>
!PrintException
Exception
object: 015a3258
Exception
type: DebuggingExceptionsExample.ErrorHandling.PersonException
Message: Debugging exceptions example.
InnerException:
StackTrace
(generated):
SP
IP Function
0024F0E0 002E0560
DebuggingExceptionsExample!DebuggingExceptionsExample.Program.DebuggingPersonExceptionExample()+0xe0
0024F104 002E0464
DebuggingExceptionsExample!DebuggingExceptionsExample.Program.Main(System.String[])+0x1c
StackTraceString:
HResult:
80131500
|
We could
clearly see the:
-
Type of the exception: DebuggingExceptionsExample.ErrorHandling.PersonException
-
The call stack and relevant method:
DebuggingPersonExceptionExample()
Remark: Inner Exception
In this
case we don't have an inner exception, however, in cases we have inner
exceptions; we should investigate all of them up to the root exception, since
it probably caused the outer exceptions, and could help resolving this issue.
Remark: Thread Exception
We could
print all exceptions in the current thread, since WinDbg opened the Dump file
in the thread that caused the crash. We could observe its information and code
also in the opening message (presented at section 4 above - Analyze the Dump
file).
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 1dcc
00442b70 2a020 Preemptive 015AAD74:00000000 0043b3e0 0 MTA
DebuggingExceptionsExample.ErrorHandling.PersonException 015a3258
2
2 1840 004500d0 2b220 Preemptive 00000000:00000000 0043b3e0 0 MTA (Finalizer)
|
4.2.
Review
the managed Call Stack:
Command: !CLRStack
–a
Output:
0:000>
!CLRStack –a
OS
Thread Id: 0x1dcc (0)
Child
SP IP Call Site
0024f030
7538c6e3 [HelperMethodFrame: 0024f030]
0024f0e0
002e0560 *** WARNING:
Unable to verify checksum for DebuggingExceptionsExample.exe
DebuggingExceptionsExample.Program.DebuggingPersonExceptionExample()
[c:\Users\Yonatan\Documents\Visual Studio
2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 27]
LOCALS:
0x0024f0f0 = 0x015a3240
0x0024f0ec = 0x015a3240
0x0024f0f8 = 0x00000000
0024f104
002e0464 DebuggingExceptionsExample.Program.Main(System.String[])
[c:\Users\Yonatan\Documents\Visual Studio
2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 10]
PARAMETERS:
args (0x0024f104) = 0x015a31fc
0024f280
703d1396 [GCFrame: 0024f280]
|
We could
see that the 'DebuggingPersonExceptionExample()' method has an issue.
4.3.
Find
the exception in the method disassembly
Command: !U
002e0560
Output:
0:000>
!U 002e0560
Normal
JIT generated code
DebuggingExceptionsExample.Program.DebuggingPersonExceptionExample()
Begin
002e0480, size e5
c:\Users\Yonatan\Documents\Visual
Studio
2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 14:
002e0480
55 push ebp
002e0481
8bec mov ebp,esp
002e0483
83ec1c sub esp,1Ch
002e0486
33c0 xor eax,eax
.
. .
c:\Users\Yonatan\Documents\Visual
Studio
2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 24:
002e0528
b96c4e2900 mov ecx,294E6Ch (MT:
DebuggingExceptionsExample.ErrorHandling.PersonException)
002e052d
e8962bfaff call 002830c8 (JitHelp: CORINFO_HELP_NEWSFAST)
002e0532
8945e8 mov dword ptr [ebp-18h],eax
002e0535
b921000000 mov ecx,21h
002e053a
babc3f2900 mov edx,293FBCh
002e053f
e869832870 call clr!JIT_StrCns (705688ad)
002e0544
8945e4 mov dword ptr [ebp-1Ch],eax
002e0547
6a00 push 0
002e0549
ff75f4 push dword ptr [ebp-0Ch]
002e054c
8b55e4 mov edx,dword ptr [ebp-1Ch]
002e054f
8b4de8 mov ecx,dword ptr [ebp-18h]
002e0552
ff15544e2900 call dword ptr ds:[294E54h]
(DebuggingExceptionsExample.ErrorHandling.PersonException..ctor(System.String,
System.Exception, DebuggingExceptionsExample.BOs.Person), mdToken: 06000008)
002e0558
8b4de8 mov ecx,dword ptr [ebp-18h]
002e055b
e8f50a1970 call clr!IL_Throw (70471055)
c:\Users\Yonatan\Documents\Visual
Studio 2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 27:
>>> 002e0560 90 nop
002e0561
8be5 mov esp,ebp
002e0563
5d pop ebp
002e0564
c3 ret
|
-
We could use the !U command to present the
method disassembly in order to focus on the root cause of the exception.
-
The !U command expect a 'Code Address', in this
case I provided the 'Instruction Pointer Register (EIP)' value, and we could
review the exact line in the output using the >>> sign.
4.4.
Save
the source code:
In a case
we'd like to use the !SaveModule command and to generate the
source code from this Dump file (e.g. we don't necessarily possess the source
code), we need to first extract the correct executable name of the method that
caused the exception.
Most of
the time it's like the method namespace, but for cases that the developer
didn't follow this convention and wrote a different namespace, we could perform
the following:
0:000>
!CLRStack
OS
Thread Id: 0x1dcc (0)
Child
SP IP Call Site
0024f030
7538c6e3 [HelperMethodFrame: 0024f030]
0024f0e0
002e0560
DebuggingExceptionsExample.Program.DebuggingPersonExceptionExample()
[c:\Users\Yonatan\Documents\Visual Studio 2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 27]
0024f104
002e0464 DebuggingExceptionsExample.Program.Main(System.String[])
[c:\Users\Yonatan\Documents\Visual Studio
2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 10]
0024f280
703d1396 [GCFrame: 0024f280]
0:000>
!ip2md 002e0560
MethodDesc: 00294cf8
Method
Name:
DebuggingExceptionsExample.Program.DebuggingPersonExceptionExample()
Class: 002913e8
MethodTable: 00294d18
mdToken: 0600000a
Module: 00293fbc
IsJitted: yes
CodeAddr: 002e0480
Transparency:
Critical
Source
file:
c:\Users\Yonatan\Documents\Visual Studio
2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\Program.cs
@ 27
0:000>
!DumpMT 00294d18
EEClass: 002913e8
Module: 00293fbc
Name:
DebuggingExceptionsExample.Program
mdToken: 02000004
File: C:\Users\Yonatan\Documents\Visual
Studio 2013\Projects\DebuggingExceptionsExample\DebuggingExceptionsExample\bin\Debug\DebuggingExceptionsExample.exe
BaseSize: 0xc
ComponentSize: 0x0
Slots
in VTable: 8
Number
of IFaces in IFaceMap: 0
|
We could
see the executable name and path: DebuggingExceptionsExample.exe
Explanation:
1. From the
!CLRStack command: We
extracted the Instruction Pointer Register of the
DebuggingPersonExceptionExample() method.
2. From the !IP2MD command: We extracted the Method Table
address.
3. From the !DumpMT
command: We extracted
the executable full name.
Now we
could save the module to a file:
0:000>
!SaveModule DebuggingExceptionsExample.exe C:\MyTemp\DebuggingExceptionsExample_CodeFromDump.exe
3
sections in file
section
0 - VA=2000, VASize=cb4, FileAddr=200, FileSize=e00
section
1 - VA=4000, VASize=5d0, FileAddr=1000, FileSize=600
section
2 - VA=6000, VASize=c, FileAddr=1600, FileSize=200
|
Now we
could explore the code using a de-compiler such as ILSpy.
Remark: Alternate way to save the source
code:
We could
also use the WinDbg 'lm' command to first list all the Loaded Modules, and then
provide the module 'Start Address' as a parameter to the !SaveModule command:
0:000>
lm
start end
module name
00010000 00018000 DebuggingExceptionsExample C (pdb
symbols)
C:\ProgramData\dbg\sym\DebuggingExceptionsExample.pdb\9476FA2B9F554AFDA3BC32F124B3CD463\DebuggingExceptionsExample.pdb
0f750000
0f824000 diasymreader (deferred)
69380000
6a4ca000 mscorlib_ni (deferred)
6e2f0000
6e32f000 snxhk (deferred)
70250000
702ce000 clrjit (deferred)
702d0000
703c5000 MSVCR120_CLR0400 (deferred)
703d0000
70a81000 clr (pdb symbols)
.
. .
77240000
77259000 sechost (deferred)
77260000
7732c000 msctf (deferred)
0:000>
!SaveModule 00010000
C:\MyTemp\DebuggingExceptionsExample_CodeFromDump_2.exe
3
sections in file
section
0 - VA=2000, VASize=cb4, FileAddr=200, FileSize=e00
section
1 - VA=4000, VASize=5d0, FileAddr=1000, FileSize=600
section
2 - VA=6000, VASize=c, FileAddr=1600, FileSize=200
|
Summary &
Conclusions
-
We learned & demonstrated how to find exceptions
in our application using a Dump file.
-
We generated the Dump file using a scheduled Rule in
DebugDiag tool.
-
We analyzed the Dump file in WinDbg using the PSSCOR4
debugging extension.
-
We performed commands to analyze the process's Threads
and CLR Stack.
-
Finally, we were able to save the source code to a
file and decompile it using a de-compiler, in order to explore the code and provide
a proper fix.
The End
Hope you enjoyed!
Appreciate your
comments…
Yonatan Fedaeli
No comments:
Post a Comment