160516 .NET Application Debugging
160516 .NET Application Debugging
황현동 노트북
.net
c#
debugging
study
windbg
english
Summary
I'll introduce the debugging solution of .NET Application that is variety and pratical.
- Global exception handling
- Most frequently used.
- Use as the after-death debugging.
- Usually upload the exception log include the callstack information, and analyze it at the global exception routine.
- By this callstack information, about 80% ~ 90% errors can be anaylized.
- But this cannot analyze the exact variables's value.
- At runtime, attach the VS debugger.
- Connect DevPC( or remote pc with debugging env ) and debug based on the break point solution.
- Very strong solution because it is based on the break point solution.
- But it is not possible as after-death debugging, demanded to effort setting as dev environment.
- After creating dump file, and analyze with windbg.
- At the runtime or at the crash time, debug with windbg by the dump file.
- Callstack inforamtion, memory information also can check.
- Can use all the strong functions provided by windbg.
- But should know windbg's commands very well.
- And also windbg don't support the convinient GUI.
- In debugging, visualization is very important, so weakness at this is very big disadvantage.
- After creating dumpfiles, analyze with VS debugger.
- Has same advantages as the analyzing dump file solution.
- As using VS debugger, don't need difficult commands, and supported convinient GUI!
Simple application for test
- At AppDomain.CurrentDomain.UnhandledException , it handles global exceptions.
- Added initializing codes for local variable, static variable.
- Q, D, E keys are acts as quit, dump, exception.
class Program
{
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += _AppDomain_UnhandledException;
var p = new MyPerson();
p.id = 101;
p.name = "william";
MySingleton.Current.id = 12345;
MySingleton.Current.name = "william";
MySingleton.Current.friends = new List<string> { "brandon", "kevin" };
Console.WriteLine($@"
commands
----------------
q : exit program
d : create dump
e : create exception
enter command :
");
while (true)
{
var key = Console.ReadKey();
if (key.Key == ConsoleKey.D)
{
Console.WriteLine("create dump...");
MiniDumpWriter.Write();
}
else if (key.Key == ConsoleKey.Q)
{
Console.WriteLine("exit program...");
break;
}
else if (key.Key == ConsoleKey.E)
{
Console.WriteLine("create exception...");
throw new Exception("my exception is occured!!!");
break;
}
}
}
private static void _AppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine($@"e : {e}");
Console.WriteLine($@"e.ExceptionObject : {e.ExceptionObject}");
}
}
public class MyPerson
{
public int id { get; set; }
public string name { get; set; }
}
public class MySingleton
{
public int id { get; set; }
public string name { get; set; }
public List<string> friends { get; set; }
#region 싱글톤
static MySingleton _Current;
static public MySingleton Current
{
get
{
if (_Current == null)
{
_Current = new MySingleton();
}
return _Current;
}
}
MySingleton()
{
}
#endregion
}
At occuring an excption, print the exception and callstack information.(like a usual .NET application)
- The callstack information are printed very well!!!
- If it is enough to analyze error situation, you don't need to read this article more.
commands
----------------
q : exit program
d : create dump
e : create exception
enter command :
e
create exception...
e : System.UnhandledExceptionEventArgs
e.ExceptionObject : System.Exception: my exception is occured!!!
위치: MySimpleConApp.Program.Main(String[] args) 파일 C:\temp\MySimpleConApp\
Program.cs:줄 53
Create dump file manually
- As the easiest way to create dump file, open the task manager, and find the exact process, and right click, and create dump file.
- This dump file that is created by task manager is full-dump file. This contains callstack, variables(=memory) informations.
- Instead of task manager, you can use ProcessExplorer. This can create mini-dump, full-dump files separatelly. At this time you can analyze callstack information only by mini-dump file.
- Of course, by this reason, full-dump file has very big volume.
PS C:\Users\Hyundong\AppData\Local\Temp> ls *.dmp
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2016-05-16 오후 3:45 97156600 MySimpleConApp.DMP
Download windbg
- Download here : https://msdn.microsoft.com/en-us/windows/hardware/hh852365.aspx
- Install with WDK10
- C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe
PS C:\Users\Hyundong\AppData\Local\Temp> windbg -z .\MySimpleConApp.DMP
Analyze dump file
- Load windbg
- Load MySimpleConApp.DMP
- Specify the properties of 'User Mini Dump File with Full Memory'
Microsoft (R) Windows Debugger Version 10.0.10586.567 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
Loading Dump File [C:\Users\Hyundong\AppData\Local\Temp\MySimpleConApp.DMP]
User Mini Dump File with Full Memory: Only application data is available
Symbol search path is: srv*
Executable search path is:
Windows 10 Version 10586 MP (4 procs) Free x64
Product: WinNt, suite: SingleUserTS
Built by: 10.0.10586.0 (th2_release.151029-1700)
Machine Name:
Debug session time: Mon May 16 15:45:27.000 2016 (UTC + 9:00)
System Uptime: 0 days 5:39:46.399
Process Uptime: 0 days 0:04:06.000
........................................
Loading unloaded module list
.
ntdll!NtDeviceIoControlFile+0x14:
00007ffd`734351c4 c3 ret
0:000>
- Load sos.dll clr.dll.
- At windbg, .NET application commands are included this dlls.
.loadby sos clr
0:000> .loadby sos clr
- Print callstack informations.
- All debuggings are begin with printing callstack information!
!clrstack -a
0:000> !clrstack -a
OS Thread Id: 0x1dc4 (0)
Child SP IP Call Site
0000003a9acfed28 00007ffd734351c4 [InlinedCallFrame: 0000003a9acfed28] Microsoft.Win32.Win32Native.ReadConsoleInput(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
0000003a9acfed28 00007ffd416893a1 [InlinedCallFrame: 0000003a9acfed28] Microsoft.Win32.Win32Native.ReadConsoleInput(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
0000003a9acfecf0 00007ffd416893a1 DomainNeutralILStubClass.IL_STUB_PInvoke(IntPtr, InputRecord ByRef, Int32, Int32 ByRef)
PARAMETERS:
<no data>
<no data>
<no data>
<no data>
0000003a9acfee00 00007ffd4177e8d9 System.Console.ReadKey(Boolean)
PARAMETERS:
intercept (0x0000003a9acfef08) = 0x0000000000000000
LOCALS:
<no data>
<no data>
<no data>
<no data>
<no data>
0x0000003a9acfee40 = 0x000001963d847c98
<no data>
<no data>
<no data>
0000003a9acfef00 00007ffce4f7063c *** WARNING: Unable to verify checksum for MySimpleConApp.exe
MySimpleConApp.Program.Main(System.String[]) [C:\project\160516_DotNetDebugging\MySimpleConApp\Program.cs @ 35]
PARAMETERS:
args (0x0000003a9acfefd0) = 0x000001963d8443f8
LOCALS:
0x0000003a9acfef68 = 0x000001963d844640
0x0000003a9acfefa0 = 0x0000000000000000
0x0000003a9acfef9c = 0x0000000000000000
0x0000003a9acfef98 = 0x0000000000000000
0x0000003a9acfef94 = 0x0000000000000000
0x0000003a9acfef90 = 0x0000000000000001
0000003a9acff200 00007ffd445d4073 [GCFrame: 0000003a9acff200]
- At above result, if you click 0x000001963d844640 (Program.Main's local variable), you can print object's dump.
- This variable is 'var p = new MyPerson();'
- As 'p.id = 101;, you can check this value.
!DumpObj /d 000001963d844640
0:000> !DumpObj /d 000001963d844640
Name: MySimpleConApp.MyPerson
MethodTable: 00007ffce4e65b10
EEClass: 00007ffce4fb1068
Size: 32(0x20) bytes
File: C:\project\160516_DotNetDebugging\MySimpleConApp\bin\Debug\MySimpleConApp.exe
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd4115af60 4000001 10 System.Int32 1 instance 101 <id>k__BackingField
00007ffd41158538 4000002 8 System.String 0 instance 000001963d844410 <name>k__BackingField
- Trying to check p's name, print dump 000001963d844410.
- It can be checked text value 'william'.
!DumpObj /d 000001963d844410
0:000> !DumpObj /d 000001963d844410
Name: System.String
MethodTable: 00007ffd41158538
EEClass: 00007ffd40aa4ab8
Size: 40(0x28) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: william
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd4115af60 4000243 8 System.Int32 1 instance 7 m_stringLength
00007ffd411596e8 4000244 c System.Char 1 instance 77 m_firstChar
00007ffd41158538 4000248 80 System.String 0 shared static Empty
>> Domain:Value 000001963b9500e0:NotInit <<
- In addtion to this, if you want to know the value of all the memroy allocated to the heap, recklessly…
- Sorted by address and types are printed clearly as with statistical.
- But the result is longer because of enormous internal system variables, even though the short program. :(
!dumpheap
0:000> !dumpheap
Address MT Size
000001963d841000 000001963b9560a0 24 Free
000001963d841018 000001963b9560a0 24 Free
000001963d841030 000001963b9560a0 24 Free
000001963d841048 00007ffd41158768 160
000001963d8410e8 00007ffd41158950 160
000001963d841188 00007ffd411589c8 160
000001963d841228 00007ffd41158a40 160
...
Statistics:
MT Count TotalSize Class Name
00007ffd4117a140 1 24 System.Reflection.Missing
00007ffd41179ff8 1 24 System.__Filters
00007ffd41179890 1 24 System.IntPtr
00007ffd4115fea0 1 24 System.Security.HostSecurityManager
...
Total 417 objects
- Check the value of the MySingleton as a static variable.
- Once you see the code for the intializing again…
MySingleton.Current.id = 12345;
MySingleton.Current.name = "william";
MySingleton.Current.friends = new List<string> { "brandon", "kevin" };
- Dump the heap variable to specify the type Singleton.
!dumpheap -type MySingleton
0:000> !dumpheap -type MySingleton
Address MT Size
000001963d844660 00007ffce4e65c78 40
Statistics:
MT Count TotalSize Class Name
00007ffce4e65c78 1 40 MySimpleConApp.MySingleton
Total 1 objects
0:000> !DumpObj /d 000001963d844660
Name: MySimpleConApp.MySingleton
MethodTable: 00007ffce4e65c78
EEClass: 00007ffce4fb10e0
Size: 40(0x28) bytes
File: C:\project\160516_DotNetDebugging\MySimpleConApp\bin\Debug\MySimpleConApp.exe
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd4115af60 4000003 18 System.Int32 1 instance 12345 <id>k__BackingField
00007ffd41158538 4000004 8 System.String 0 instance 000001963d844410 <name>k__BackingField
00007ffd40abd6c8 4000005 10 ...tring, mscorlib]] 0 instance 000001963d844688 <friends>k__BackingField
00007ffce4e65c78 4000006 8 ...onApp.MySingleton 0 static 000001963d844660 _Current
- Once you determine the name attribute.
!DumpObj /d 000001963d844410
0:000> !DumpObj /d 000001963d844410
Name: System.String
MethodTable: 00007ffd41158538
EEClass: 00007ffd40aa4ab8
Size: 40(0x28) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: william
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd4115af60 4000243 8 System.Int32 1 instance 7 m_stringLength
00007ffd411596e8 4000244 c System.Char 1 instance 77 m_firstChar
00007ffd41158538 4000248 80 System.String 0 shared static Empty
>> Domain:Value 000001963b9500e0:NotInit <<
- Friends will take a few steps because List type.
- Once dump the friends.
!DumpObj /d 000001963d844688
0:000> !DumpObj /d 000001963d844688
Name: System.Collections.Generic.List`1[[System.String, mscorlib]]
MethodTable: 00007ffd40abd6c8
EEClass: 00007ffd40b6b310
Size: 40(0x28) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd41147288 4001820 8 System.__Canon[] 0 instance 000001963d8446c8 _items
00007ffd4115af60 4001821 18 System.Int32 1 instance 2 _size
00007ffd4115af60 4001822 1c System.Int32 1 instance 2 _version
00007ffd41158b18 4001823 10 System.Object 0 instance 0000000000000000 _syncRoot
00007ffd41147288 4001824 0 System.__Canon[] 0 shared static _emptyArray
>> Domain:Value dynamic statics NYI 000001963b9500e0:NotInit <<
- Again dump _items.
- At dumping _items, use dumparray, not dumpobject.
!DumpArray /d 000001963d8446c8
0:000> !DumpArray /d 000001963d8446c8
Name: System.String[]
MethodTable: 00007ffd41159880
EEClass: 00007ffd40b624a8
Size: 56(0x38) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 00007ffd41158538
[0] 000001963d844438
[1] 000001963d844460
[2] null
[3] null
- It is confirmed that 2 arrays are assigned only.
- If you check 0th value, you can find value of brandon.
- If you check 1th value, you can find value of kevin.
!DumpObj /d 000001963d844438
!DumpObj /d 000001963d844460
0:000> !DumpObj /d 000001963d844438
Name: System.String
MethodTable: 00007ffd41158538
EEClass: 00007ffd40aa4ab8
Size: 40(0x28) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: brandon
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd4115af60 4000243 8 System.Int32 1 instance 7 m_stringLength
00007ffd411596e8 4000244 c System.Char 1 instance 62 m_firstChar
00007ffd41158538 4000248 80 System.String 0 shared static Empty
>> Domain:Value 000001963b9500e0:NotInit <<
0:000> !DumpObj /d 000001963d844460
Name: System.String
MethodTable: 00007ffd41158538
EEClass: 00007ffd40aa4ab8
Size: 36(0x24) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: kevin
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd4115af60 4000243 8 System.Int32 1 instance 5 m_stringLength
00007ffd411596e8 4000244 c System.Char 1 instance 6b m_firstChar
00007ffd41158538 4000248 80 System.String 0 shared static Empty
>> Domain:Value 000001963b9500e0:NotInit <<
Load at VS debugger
- Compared to the windbg it is very simple!
- It should open the dump file in VS debugger
- Just dmp file open in Visual Studio
- Load the dump file path, a dump file creation time, process information, may be collected based on a variety of information such as OS information.
Debug Managed Only
Click the Start Debugging
- Go looking for the main thread in the thread list, and click the last routine function of expanding this Program.Main
- Program.Main source code of the function is displayed on the screen
- A yellow arrow appears on the stopping point of the source code.
- It is possible to access the local variables p, Singleton.Current static variable.
Note that when using VS debugger
- Pdb file for the application that created the dmp file must exist in the same directory in the analysis.
- It shall have the same version of the source code exists. VS does not find it, to find the source code dialog box is derived.
- Pdb file version is also exactly the same as the dmp file. However, the Fair is the modified source code other than analysis.
- Good to use it practically every connected prior to full-scale tests such as CBT, OBT, put together by the pdb version, if necessary.
To create dump files from within .NET applications
- The application crash situation, a point in time of the execution, requires a way to perform an immediate dump.
- Crash-after debugging
- Debugging at the user's runtime
- Customer Experience Improvement Program(?)
- API functions are provided as well as create a dump file
- Unfortunately, so you must use Win32 API pinvoke.
namespace MySimpleConApp
{
public static class MiniDumpWriter
{
[Flags]
public enum MINIDUMP_TYPE
{
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
MiniDumpWithFullMemory = 0x00000002,
MiniDumpWithHandleData = 0x00000004,
MiniDumpFilterMemory = 0x00000008,
MiniDumpScanMemory = 0x00000010,
MiniDumpWithUnloadedModules = 0x00000020,
MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
MiniDumpFilterModulePaths = 0x00000080,
MiniDumpWithProcessThreadData = 0x00000100,
MiniDumpWithPrivateReadWriteMemory = 0x00000200,
MiniDumpWithoutOptionalData = 0x00000400,
MiniDumpWithFullMemoryInfo = 0x00000800,
MiniDumpWithThreadInfo = 0x00001000,
MiniDumpWithCodeSegs = 0x00002000
}
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
public static bool Write()
{
var currentProcess = Process.GetCurrentProcess();
var currentProcessHandle = currentProcess.Handle;
var currentProcessId = (uint)currentProcess.Id;
{
var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $@"{DateTime.Now.ToString("yyMMdd-HHmmss")}.mini.dmp");
var options = MINIDUMP_TYPE.MiniDumpNormal;
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
{
MiniDumpWriteDump(currentProcessHandle, currentProcessId, fs.SafeFileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
}
{
var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $@"{DateTime.Now.ToString("yyMMdd-HHmmss")}.full.dmp");
var options =
MINIDUMP_TYPE.MiniDumpNormal |
MINIDUMP_TYPE.MiniDumpWithFullMemory |
MINIDUMP_TYPE.MiniDumpWithHandleData |
MINIDUMP_TYPE.MiniDumpWithProcessThreadData |
MINIDUMP_TYPE.MiniDumpWithThreadInfo; ;
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
{
MiniDumpWriteDump(currentProcessHandle, currentProcessId, fs.SafeFileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
}
return true;
}
}
}
- Use MiniDumpWriteDump function of dbghelp.dll. It also made a mini dumps and full dumps.
- The mini dump will contain only callstack information. full dump includes both callstack and memory information.
- Code are as follows:
- Clicking d, it performs.
if (key.Key == ConsoleKey.D)
{
Console.WriteLine("create dump...");
MiniDumpWriter.Write();
}
이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요. |
댓글
댓글 쓰기