Ko iesākt ar try/catch bloku ražošanas vidē?
Šis bloga ieraksts būs no sērijas "hardcore debugging" :)
Šodien risinot dažāda rakstura problēmas ar ražošanas vidi uzdūros ļoti interesantai un varbūt arī brīžiem izplatītai problēmai.
Neliels ievads: par cik darba izejas kodu es nevaru publicēt, tādēl mēģināšu nodemonstrēt situāciju un demonstrācijas projektiem. Eksistē web serviss, kas nodarbojas ar kaut kādas operāicjas izpildi. Servisa operācijas laikā tiek izsauktas dažādas citas operācijas no dažādām citām komponentēm, klasēm vai pat nedod dievs, trešās puses moduļiem, kuriem nav pat pieejams izejas kods.
Ir citreiz situācijas, kad sastopies ar kodu, kas ir rakstīts nenormālā steigā relīzes naktī īsi pirms tam, kad sienas pulkstenis nositīs 4 no rīta.
Ar līdzīgu kodu man šodien nācās strādāt:
[WebMethod]
public ServiceResponse DoOperation()
{
ServiceResponse response = new ServiceResponse();
Operation op = new Operation();
try
{
// perform method will fail because there is no test on String.IsNullOrEmpty()
string result = op.Perform (null);
response.Result = result;
response.Successful = true;
}
catch (Exception ex)
{
response.Error = ex.Message;
response.Successful = false;
}
return response;
}
Šeit ir arī kļūdainā operācija:
public class Operation
{
public string Perform (string param)
{
// will fail here because of demo null parameter
int lenght = param.Length;
return param.Substring (lenght - 5);
}
}
Viss ir jauki, tikai pievēršam uzmanību try/catch bloka konstrukcijai. Response klase arī ir konstruēta tikai demonstrācijai.
public class ServiceResponse
{
public string Result { get; set; }
public bool Successful { get; set; }
public string Error { get; set; }
}
Servisa atbilde kļūdā situācijās nebūs ļoti izsmeļoša:
Jautājums ir ko darīt šādās situācijās un no kura gala ķerties klāt, lai vismaz stacktrace ieraudzītu kļūdas situācijai un mēģinātu lokalizēt kļūdaino kodu. Atbilde ir: "WinDbg will be your ultimate friend on production servers." (jālieto ir labais vecais WinDbg rīks, kas paredzēts tieši šādām situācijām).
Tātad, vienīgais, kas mums atliek, ir aizkopēt windbg.exe, dbgeng.dll un dbghelp.dll failus uz ražošanas serveri. Pēc tam varam palaist pašu windbg.exe. Pirmais, kas mums ir jāveic ir nemenedžētajam atkļūdotājam jāsniedz īsa lekcija par to, kas ir .Net Framework. To var izdarīt ielādējot SOS moduli no mscorwks.dll ar komandu:
| 0:000> .loadby sos mscorwks |
Ja mums kļūda sistēmā jau ir notikusi, tad uzzināt, kas īsti ir noticis ir iespējams ar komandu:
0:000> .lastevent
(ff4.b80): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00000000 ebx=0081f15c ecx=00dfe684 edx=00000000 esi=0016f410 edi=00000000 eip=049902f0 esp=0081f0c0 ebp=0081f0d8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 049902f0 8b4008 mov eax,dword ptr [eax+8] ds:0023:00000008=???????? |
Ir situācijas, kad pēdējais kļūdas ziņojums tiek parādīts, kā Break instruction exception.
0:000> .lastevent
Last event: ff4.720: Break instruction exception - code 80000003 (first chance) debugger time: Mon Jun 2 20:46:45.712 2008 (GMT+3) |
Tad ir nepieciešams atkārtot operācijas sistēmā, lai modelētu kļūdas situāciju no jauna.
Ja mums ir iespēja atkārtot kļūdas situāciju, tad pirms atkārtošanas nepieciešams izpildīt sekojošu komandu, lai WinDbg programma pārtrauktu aplikācijas darbību pie jebkuras CLR kļūdas situācijas:
Pēc šīs komandas izpildes un situācijas atkārtošanas sistēmā, WinDbg rīks auromātiski apturēs sistēmas darbību un piedāvās kļūdas situācijas kontektu. Ir iespējams pēc SOS moduļa ielādes izmantot !printexcpetion komandu, bet ir arī situācijas, kad var saskarties ar sekojošu situāciju:
0:000> !printexception There is no current managed exception on this thread |
Tas nozīmē, ka kļūdas situācija nav notikusi CLR kontekstā, bet gan unmanaged code slānī.
Tomēr, momentā, kad WinDbg rīks ir paturējis sistēmas darbību, mums ir pieejams CLR steks, jeb pēdējās izsauktās metodes izsaukuma ķēde:
0:000> !clrstack
OS Thread Id: 0xbd8 (1) ESP EIP 0081f0c0 04b21150 ProdTryCatch.Operation.Perform(System.String) 0081f0e0 04b21025 ProdTryCatch.Service.DoOperation() 0081f3a8 79e71b8c [CustomGCFrame: 0081f3a8] 0081f370 79e71b8c [GCFrame: 0081f370] 0081f38c 79e71b8c [GCFrame: 0081f38c] 0081f570 79e71b8c [HelperMethodFrame_1OBJ: 0081f570] System.RuntimeMethodHandle._InvokeMethodFast(System.Object, System.Object[], System.SignatureStruct ByRef, System.Reflection.MethodAttributes, System.RuntimeTypeHandle) 0081f5e0 792859c8 System.RuntimeMethodHandle.InvokeMethodFast(System.Object, System.Object[], System.Signature, System.Reflection.MethodAttributes, System.RuntimeTypeHandle) 0081f630 792857cf System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo, Boolean) 0081f66c 7928565e System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo) 0081f68c 65dd912b System.Web.Services.Protocols.LogicalMethodInfo.Invoke(System.Object, System.Object[]) 0081f6b0 65e1096e System.Web.Services.Protocols.WebServiceHandler.Invoke() 0081f6f0 65e10645 System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest() 0081f720 65e10fd7 System.Web.Services.Protocols.SyncSessionlessHandler.ProcessRequest(System.Web.HttpContext) 0081f734 6dddfb5e System.Web.Script.Services.ScriptHandlerFactory+HandlerWrapper.ProcessRequest(System.Web.HttpContext) 0081f73c 660a7ed6 System.Web.HttpApplication+CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() 0081f770 6608647c System.Web.HttpApplication.ExecuteStep(IExecutionStep, Boolean ByRef) 0081f7b0 66091f13 System.Web.HttpApplication+ApplicationStepManager.ResumeSteps(System.Exception) 0081f800 660859fc System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext, System.AsyncCallback, System.Object) 0081f81c 66088e5c System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest) 0081f850 66088b03 System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest) 0081f860 66087cfc System.Web.Hosting.ISAPIRuntime.ProcessRequest(IntPtr, Int32) 0081f864 79efd59b [InlinedCallFrame: 0081f864] 0081faa8 79efd59b [GCFrame: 0081faa8] 0081fc04 79efd59b [ComMethodFrame: 0081fc04] |
Pirmais ieraksts arī norāda uz kļūdaino metodi, jeb vismaz vietu, kur ir notikusi problēma.
Ja nav pieejami sistēmas debug simboli, tad atrast precīzi rindiņas numuru, kurā ir notikusi kļūda ir pagrūti. Talkā var nākt !analyze -v komanda:
0:000> !analyze -v
FAULTING_IP: +4b21150 04b21150 8b4008 mov eax,dword ptr [eax+8]
EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff) ExceptionAddress: 04b21150 ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 00000008 Attempt to read from address 00000008
FAULTING_THREAD: 00000bd8
DEFAULT_BUCKET_ID: NULL_INSTRUCTION_PTR
PROCESS_NAME: aspnet_wp.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory at "0x%08lx". The memory could not be "%s".
READ_ADDRESS: 00000008
FAILED_INSTRUCTION_ADDRESS: +4b21150 04b21150 8b4008 mov eax,dword ptr [eax+8]
NTGLOBALFLAG: 0
APPLICATION_VERIFIER_FLAGS: 0
IP_ON_HEAP: 04b21025
MANAGED_STACK: (TransitionMU) 0081F0C0 04B21150 ProdTryCatch!ProdTryCatch.Operation.Perform(System.String)+0x28 0081F0E0 04B21025 ProdTryCatch!ProdTryCatch.Service.DoOperation()+0x8d (TransitionUM) (TransitionMU) 0081F5E0 792859C8 mscorlib_ni!System.RuntimeMethodHandle.InvokeMethodFast(System.Object, System.Object[], System.Signature, System.Reflection.MethodAttributes, System.RuntimeTypeHandle)+0x48 0081F630 792857CF mscorlib_ni!System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo, Boolean)+0x15f 0081F66C 7928565E mscorlib_ni!System.Reflection.RuntimeMethodInfo.Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)+0x1e 0081F68C 65DD912B System_Web_Services_ni!System.Web.Services.Protocols.LogicalMethodInfo.Invoke(System.Object, System.Object[])+0x8b 0081F6B0 65E1096E System_Web_Services_ni!System.Web.Services.Protocols.WebServiceHandler.Invoke()+0x156 0081F6F0 65E10645 System_Web_Services_ni!System.Web.Services.Protocols.WebServiceHandler.CoreProcessRequest()+0x28d 0081F720 65E10FD7 System_Web_Services_ni!System.Web.Services.Protocols.SyncSessionlessHandler LAST_CONTROL_TRANSFER: from 04b21025 to 04b21150
PRIMARY_PROBLEM_CLASS: NULL_INSTRUCTION_PTR
BUGCHECK_STR: APPLICATION_FAULT_NULL_INSTRUCTION_PTR
FRAME_ONE_INVALID: 1
STACK_TEXT: 04b21150 ProdTryCatch!ProdTryCatch.Operation.Perform 04b21025 ProdTryCatch!ProdTryCatch.Service.DoOperation
FOLLOWUP_IP: +4b21150 04b21150 8b4008 mov eax,dword ptr [eax+8]
SYMBOL_STACK_INDEX: 0
SYMBOL_NAME: ProdTryCatch!ProdTryCatch.Operation.Perform+4b21150
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: ProdTryCatch
IMAGE_NAME: ProdTryCatch.DLL
DEBUG_FLR_IMAGE_TIMESTAMP: 0
STACK_COMMAND: dt ntdll!LdrpLastDllInitializer BaseDllName ; dt ntdll!LdrpFailureData ; dds 81f0c0 ; kb
FAILURE_BUCKET_ID: NULL_INSTRUCTION_PTR_c0000005_ProdTryCatch.DLL!ProdTryCatch.Operation.Perform
BUCKET_ID: APPLICATION_FAULT_NULL_INSTRUCTION_PTR_BAD_IP_ProdTryCatch!ProdTryCatch.Operation.Perform+4b21150 |
Rakstīšanas laikā man tomēr neizdevās piedabūt PDB failus strādāt, lai WinDbg parādītu precīzu koda rindiņu, kurā notika kļūdas situācija, bet es mēģināšu vēlreiz :)
Cerams, ka noderēs!