Katra programma, ko veido cilvēks, jau piedzimst ar kļūdām :) Tāds ir cilvēks un viņa daba. Šoreiz stāsts par to, kā efektīvāk ķert šāda veida kļūdas, pēc tam, kad sistēma jau ir atdota klientam.
Noteikti kādreiz ikviens no mums ir redzējis savā monitorā šāda veida dialoga logu:
Šoreiz mēģināsim ātri pārskriet pāri galvenajiem aspektiem, lai pašu spēkiem varētu izveidot šāda veida feedback mehānismu pašu radītajām sistēmām.
Mēģināsim radīt kļūdas situāciju parastā Windows Forms aplikācijā:
private void button1_Click(object sender, EventArgs e)
{
var bc = new BuggyClient();
bc.BuggyMethod();
}
public class BuggyClient
{
public void BuggyMethod()
{
var dbc = new DeeperBuggyClient();
dbc.DeeperBuggyMethod();
}
}
public class DeeperBuggyClient
{
public void DeeperBuggyMethod()
{
var bc = new BuggyComponent();
bc.TheBuggyMethod();
}
}
public class BuggyComponent
{
public void TheBuggyMethod()
{
var z = NullProperty;
z.Substring(5); // Null pointer exception should occur.
}
public string NullProperty
{
get
{
return null;
}
}
}
Tātad no ausgtāk redzamā koda ir saprotams, ka BuggyClient objekta BuggyMethod izsaukumam vajadzētu beigties ar null reference kļūdu.
Palaižot aplikāciju par to varam pārliecināties:
Tātad, lai pievienotu aplikācijai pilnvērtīgu feedback mehānismu vispirms ir nepeiciešams sagatavot sistēmu kļūdas situāciju apstrādei:
AppDomain.CurrentDomain.UnhandledException += ApplicationUnhandledException;
Application.ThreadException += ApplicationThreadException;
Ideālā gadījumā būtu jauki, ja kļūdu apstrādes metode būtu viena, bet delegāta signature atšķirības dēļ nākas izveidot divas metodes:
public static void ApplicationUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject != null)
{
HandleException((Exception)e.ExceptionObject);
}
}
private static void ApplicationThreadException(object sender, ThreadExceptionEventArgs e)
{
if (e.Exception != null)
{
HandleException(e.Exception);
}
}
HandleException metode tad arī ir abildīga par kļūdas apstrādi un kļūdas ziņojuma izveidi un nosūtīšanu izstrādātājiem.
private static void HandleException(Exception e)
{
if (e == null)
{
return;
}
DumpWriter.Write(Process.GetCurrentProcess().Id, "c:\\dump.dmp");
MessageBox.Show(
"Unhandled exception occurred in your application. See application event log for more details.");
Application.Exit();
}
Šoreiz nenodarbosimies ar tik garlaicīgām lietām, kā kļūdas ziņojuma nosūtīšanu izstrādātājiem, t.i., mums pašiem :), bet apskatīsim, ko dara DumpWriter klase.
public static class DumpWriter
{
public static void Write(int processId, string outputFilePath)
{
using (FileStream stream = new FileStream(outputFilePath, FileMode.Create, FileAccess.ReadWrite))
{
using (Process proc = Process.GetProcessById(processId))
{
int dumpType = 0x00000306;
MiniDumpWriteDump(proc.Handle, proc.Id, stream.Handle,
dumpType, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
}
}
}
[DllImport("DbgHelp.dll", SetLastError = true)]
private static extern bool MiniDumpWriteDump(
IntPtr hProcess, int processId, IntPtr fileHandle,
int dumpType, IntPtr excepInfo,
IntPtr userInfo, IntPtr extInfo);
}
DumpWriter klase izmanto importēto metodi MiniDumpWriteDump no DbgHelp.dll bibliotēkas, kas atrodama "debugging tools for windows" pakotnē un ir brīvi distribūtējama kopā ar aplikāciju.
dumpType vērtība nosaka cik daudz un ko vēlamies iekļaut atmiņas izrakstā (vairāk info šeit).
Palaižot aplikāciju ar jaunajām izmaiņām iegūstam elegantu kļūdas paziņojuma ekrānu.
Uz servera C:\ diska saknes direktorijā ir izveidots "dump.dmp" fails, kas satur izstrādātājiem nepeiciešamo informāciju. Faila nogādāšanu līdz pareizajiem cilvēkiem šoreiz skiposim un uzreiz kāpsim izstrādātāja/testētāja čībiņās.
Pirmais, kas ir jāveic esot kā izstrādātājam, kas saņēmis ir Crash dump failu, ir ieladēt no WinDbg programmā.
Pēc sekmīgas crash dump faila ielādes esam gatavi jārlūkot mūsu situāciju, kas notika aplikācijā un kāpēc aplikācija pārstāja darboties.
Ielādējam Son Of Strike (SOS) moduli.
| 0:000> .loadby sos mscorwks |
Ir iespējams aplūkot visus pavedienus (!threads), kas bija aktīvi kļūdas situācijas momentā:
0:000> !threads
ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 d38 001611b0 6020 Enabled 013130f0:01313fe8 00158e88 0 STA System.NullReferenceException (012ff0dc) 2 2 394 001644b8 b220 Enabled 00000000:00000000 00158e88 0 MTA (Finalizer)
|
Komanda !pe (print exception) dod iespēju aplūkot pēdējo kļūdu, kas notika pašreizējā pavedienā. Nepieciešams piefiksēt IP adresi metodei, kas izmeta kļūdu, jo tā noderēs mums vēlāk.
0:000> !pe
Exception object: 012ff0dc Exception type: System.NullReferenceException Message: Object reference not set to an instance of an object. InnerException: <none> StackTrace (generated): SP IP Function 0012EFD4 00DD0648 VeryBuggyApp!VeryBuggyApp.BuggyComponent.TheBuggyMethod()+0x38 0012EFE8 00DD05BC VeryBuggyApp!VeryBuggyApp.DeeperBuggyClient.DeeperBuggyMethod()+0x44 0012EFFC 00DD0524 VeryBuggyApp!VeryBuggyApp.BuggyClient.BuggyMethod()+0x44 0012F010 00DD048F VeryBuggyApp!VeryBuggyApp.Form1.button1_Click(System.Object, System.EventArgs)+0x47 0012F02C 7B194170 System_Windows_Forms_ni!System.Windows.Forms.Control.OnClick(System.EventArgs)+0x70 0012F044 7B18F55A System_Windows_Forms_ni!System.Windows.Forms.Button.OnClick(System.EventArgs)+0x4a 0012F054 7B7341E9 System_Windows_Forms_ni!System.Windows.Forms.ButtonBase.OnKeyUp(System.Windows.Forms.KeyEventArgs)+0x95 0012F064 7B6F5AB1 System_Windows_Forms_ni!System.Windows.Forms.Control.ProcessKeyEventArgs(System.Windows.Forms.Message ByRef)+0x401 0012F0B4 7B6F5B55 System_Windows_Forms_ni!System.Windows.Forms.Control.ProcessKeyMessage(System.Windows.Forms.Message ByRef)+0x35 0012F0C4 7B6F7241 System_Windows_Forms_ni!System.Windows.Forms.Control.WmKeyChar(System.Windows.Forms.Message ByRef)+0x15 0012F0D4 7BA29ABE System_Windows_Forms_ni!System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)+0x86146e 0012F12C 7B1C25D6 System_Windows_Forms_ni!System.Windows.Forms.ButtonBase.WndProc(System.Windows.Forms.Message ByRef)+0x66 0012F170 7B1C2550 System_Windows_Forms_ni!System.Windows.Forms.Button.WndProc(System.Windows.Forms.Message ByRef)+0x20 0012F17C 7B1C8640 System_Windows_Forms_ni!System.Windows.Forms.Control+ControlNativeWindow.OnMessage(System.Windows.Forms.Message ByRef)+0x10 0012F184 7B1C85C1 System_Windows_Forms_ni!System.Windows.Forms.Control+ControlNativeWindow.WndProc(System.Windows.Forms.Message ByRef)+0x31 0012F198 7B1C849A System_Windows_Forms_ni!System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)+0x5a
StackTraceString: <none> HResult: 80004003
|
!dumpstack komanda savukārt parāda visu izsaukumu kopu (kopā ar unmanaged metodēm) prety daudz stuffa, bet kopsummā ir jāmeklē "====> Exception cxr@" vērtība un jāpiefiksē ir tās atgriešanās adrese.
0:000> !dumpstack
OS Thread Id: 0xd38 (0) Current frame: ntdll!KiFastSystemCallRet ChildEBP RetAddr Caller,Callee 0012da2c 59a8de20 dbghelp!MiniDumpWriteDump+0x2470, calling dbghelp!StackWalk64+0x60a6 0012dae8 7c911538 ntdll!wcsncpy+0xaa9, calling ntdll!wcsncpy+0x13d ...
0012e1f8 00dd0864 (MethodDesc 0x976570 +0xc4 VeryBuggyApp.DumpWriter.Write(Int32, System.String)), calling 0097c138 0012e218 00dd0864 (MethodDesc 0x976570 +0xc4 VeryBuggyApp.DumpWriter.Write(Int32, System.String)), calling 0097c138 0012e280 00dd0772 (MethodDesc 0x973048 +0x5a VeryBuggyApp.Program.HandleException(System.Exception)), calling (MethodDesc 0x976570 +0 VeryBuggyApp.DumpWriter.Write(Int32, System.String)) 0012e298 00dd0701 (MethodDesc 0x97303c +0x59 VeryBuggyApp.Program.ApplicationThreadException(System.Object, System.Threading.ThreadExceptionEventArgs)), calling (MethodDesc 0x973048 +0 VeryBuggyApp.Program.HandleException(System.Exception)) 0012e2b4 7b6eee1b (MethodDesc 0x7b08b790 +0x8b System.Windows.Forms.Application+ThreadContext.OnThreadException(System.Exception)) 0012e2f0 7b6f77f6 (MethodDesc 0x7afea818 +0x16 System.Windows.Forms.Control.WndProcException(System.Exception)), calling 7b15af94 0012e2fc 7b6fa27c (MethodDesc 0x7b08889c +0xc System.Windows.Forms.Control+ControlNativeWindow.OnThreadException(System.Exception)), calling 7b1596b0 0012e300 7b1c84b2 (MethodDesc 0x7afebc50 +0x72 System.Windows.Forms.NativeWindow.Callback(IntPtr, Int32, IntPtr, IntPtr)) 0012e31c 79e88826 mscorwks!DllUnregisterServerInternal+0xc80a, calling mscorwks!DllUnregisterServerInternal+0xc787 ...
0012efd4 00dd0647 (MethodDesc 0x9764b4 +0x37 VeryBuggyApp.BuggyComponent.TheBuggyMethod()) ====> Exception cxr@12ed08 ...
0012efe0 00dd05bc (MethodDesc 0x97644c +0x44 VeryBuggyApp.DeeperBuggyClient.DeeperBuggyMethod()), calling (MethodDesc 0x9764b4 +0 VeryBuggyApp.BuggyComponent.TheBuggyMethod()) 0012eff4 00dd0524 (MethodDesc 0x9763e4 +0x44 VeryBuggyApp.BuggyClient.BuggyMethod()), calling (MethodDesc 0x97644c +0 VeryBuggyApp.DeeperBuggyClient.DeeperBuggyMethod()) 0012f008 00dd048f (MethodDesc 0x975a10 +0x47 VeryBuggyApp.Form1.button1_Click(System.Object, System.EventArgs)), calling (MethodDesc 0x9763e4 +0 VeryBuggyApp.BuggyClient.BuggyMethod()) 0012f020 7b194170 (MethodDesc 0x7afe9dd8 +0x70 System.Windows.Forms.Control.OnClick(System.EventArgs)) 0012f03c 7b18f55a (MethodDesc 0x7b085f70 +0x4a System.Windows.Forms.Button.OnClick ...
0012fef0 79ef9473 mscorwks!ClrCreateManagedInstance+0x51e5, calling mscorwks+0x23b5 0012ff18 79ef009f mscorwks!CorExeMain+0x168, calling mscorwks!GetPrivateContextsPerfCounters+0xf5a0 0012ff68 79eeffcf mscorwks!CorExeMain+0x98, calling mscorwks!CorExeMain+0x11f 0012ffc0 7c816fd7 kernel32!RegisterWaitForInputIdle+0x49
|
Kad ir zināms slikās metodes adrese un return adrese kļūdas situācija, varam veikt unassemblēšanu (!u), lai noskaidrotu kļūdaino rindu, kas izsauc kļūdu:
0:000> !u 00DD0648
Normal JIT generated code VeryBuggyApp.BuggyComponent.TheBuggyMethod() Begin 00dd0610, size 44 00dd0610 55 push ebp 00dd0611 8bec mov ebp,esp 00dd0613 83ec0c sub esp,0Ch 00dd0616 894dfc mov dword ptr [ebp-4],ecx 00dd0619 833d142e970000 cmp dword ptr ds:[972E14h],0 00dd0620 7405 je 00dd0627 00dd0622 e85a9e2f79 call mscorwks!CorLaunchApplication+0xec6b (7a0ca481) (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE) 00dd0627 33d2 xor edx,edx 00dd0629 8955f8 mov dword ptr [ebp-8],edx 00dd062c 90 nop 00dd062d 8b4dfc mov ecx,dword ptr [ebp-4] 00dd0630 ff15c8649700 call dword ptr ds:[9764C8h] (VeryBuggyApp.BuggyComponent.get_NullProperty(), mdToken: 06000013) 00dd0636 8945f4 mov dword ptr [ebp-0Ch],eax 00dd0639 8b45f4 mov eax,dword ptr [ebp-0Ch] 00dd063c 8945f8 mov dword ptr [ebp-8],eax 00dd063f 8b4df8 mov ecx,dword ptr [ebp-8] 00dd0642 ba05000000 mov edx,5 00dd0647 3909 cmp dword ptr [ecx],ecx 00dd0649 e8529f4f78 call mscorlib_ni+0x20a5a0 (792ca5a0) (System.String.Substring(Int32), mdToken: 0600015a) 00dd064e 90 nop 00dd064f 90 nop 00dd0650 8be5 mov esp,ebp 00dd0652 5d pop ebp 00dd0653 c3 ret
|
Un esam atraduši, ka vainīgais kods ir string apakškopas iegūšana, jo strings, ar kuru operē aplikācija nav inicializēts (null).
Tātad izmantojot DbgHelp.dll bibliotēku ir iespējams izveidot crash memory dump failus un vēlāk ar Windbg rīka palīdzību jau offline no aplikācijas ir iespējams izanalizēt radušos situāciju un labot izejas kodu pēc nepieciešamības.
Cerams, ka noderēs!