📜 ⬆️ ⬇️

Debugging applications in the .NET Framework 2.0 and higher

I want to start a series of articles on debugging your .NET applications on the customer side, as well as optimizing your code. In this regard, you need to prepare a little your system. In this article we will look at various tools for debugging applications, a little delve into the description of the CLR, where it will be necessary.

Utilities


It is convenient for me when it is all installed on a separate virtual machine, and does not litter the main OS, which I use only for developing applications for .NET and entertainment.

Debugging .NET Applications


When developing applications, developers usually work on machines over which they have complete control, and they can use various utilities. For debugging, they have development environments, with the ability to step through the code, change it and compile it. Without any problems, you can pause the application at any time, start it again. However, on the customer's servers, suspending the application for step-by-step debugging is almost impossible without the fact that users of the system will be affected. In general, debugging applications on the customer’s server usually means that the problem should be solved as soon as possible. With a simple service, the customer loses money, customers go away, there is a simple production cycle, other problems are possible. In the event of a problem, it is highly desirable to take measures for its temporary solution. This may be a restart of the application, restriction of functionality and other solutions for the situation.
Several problems can happen: application lag, deadlocks, unhandled or fatal exceptions, data loss, performance problems, resource and memory leaks, incorrect functionality. All these situations can affect all users of the system, as well as specific users.
The main reasons that lead to problems in the application are the difference in environment (hardware, software, differences in configuration), as well as interaction with other systems.
Why is it so difficult to debug applications on the customer side? You cannot use standard tools, it is difficult or impossible to access the server, the problem may appear very rarely or only on the side of the customer.
Depending on the type of your application, you can perform the following steps before the application is installed at the customer.
Server application:

Client application:

To minimize risks, it is better to find and correct errors before they appear at the customer, to work out different situations, to do automated testing, both standard in Visual Studio and third-party - NUnit , NCover .
When a problem occurs, it is important to collect as much information as possible, which will be useful, to study and correct the error.

Managed Assembly Structure


I will not go into much detail. They are readily available in various ways, I’ll only dwell on what we will continue to use, and note additional sources of information.
The executable file for .NET begins with the usual MS-DOS and COFF header headers that are standard for all Windows applications.
In the first section of the data are CLR-header and data.
ildasm.exe
Note that the first section has the attributes CNT_CODE, MEM_EXECUTE, MEM_READ, indicating to the loader that the section contains the code that will be executed by the runtime. In the import table you can see the call “_CorExeMain” in case you have an application running. It is implemented in mscoree.dll - in the main module of the runtime environment.
mscroree.dll
For more information, see the Unmanaged API Reference .
These segments are followed by the IL code of your application itself. The result of the compilation is intermediate language code (MSIL). This is a processor independent language - a language of a higher level than the native code. Specifications you can find here .
')
The IL code is followed by metadata headers, the metadata itself, and additional segments containing application resources, native application code, if the application was developed with a managed c ++.
Information about the structure of the executable file can be obtained using the standard utility ildasm.exe .

Exploring WinDbg + SOS, adplus.vbs


At this stage, I hope you have already installed all the necessary applications and configured them (add another Debugging Tools directory to the PATH environment variable). I will do further research on this test program:

You can (and even should) write your own application that will perform some kind of nonsense. What is important in this program (oh, I am twisted by such thoughts), and what is important in it is that we create several objects in the heap, call several methods, and at the end randomly generate an exception. Let's create a debug version of the application with symbols.
Run our application. And enter this command in the directory with the application
adplus -quiet -crash -fullonfirst -pn ApplicationForWinDbg.exe
ApplicationForWinDbg.exe - your compiled exe.

In the console application, press any key. If you did everything correctly, then the folder where the Debugging Tools were installed will appear in the folder Crash_Mode__Date_ [DATE] __ Time_ [TIME] . Where [DATE] and [TIME] are the date and time of the dump.
We created a dump, try to explore it, and at the same time understand the insides of the CLR. I will not give detailed explanations in this article, the main thing for us is to learn how to use WinDbg + SOS. More details in other articles.
Open the dump in WinDbg. You should have something like this:
WinDbg
Download the WinDbg - SOS extension by executing the command:
0: 000> .loadby sos mscorwks
To check how the download went, run the following command:
0: 000> ! Eeversion
  2.0.50727.3053 retail
 Workstation mode
 SOS Version: 2.0.50727.3053 retail build 

We made sure that the extension is loaded, press ctrl + s and add the path to our application so that WinDbg loads the characters. Now we can proceed to the study. Let's look at the streams that we have in the application:
0: 000> ~ *
  .  0 Id: e8c.11bc Suspend: 1 Teb: 7ffde000 Unfrozen
       Start: ApplicationForWinDbg! COM + _Entry_Point <PERF> (ApplicationForWinDbg + 0x284e) (0011284e) 
       Priority: 0 Priority class: 32 Affinity: 3
    1 Id: e8c.13f0 Suspend: 1 Teb: 7ffdd000 Unfrozen
       Start: mscorwks! DebuggerRCThread :: ThreadProcStatic (7243237f) 
       Priority: 0 Priority class: 32 Affinity: 3
    2 Id: e8c.130c Suspend: 1 Teb: 7ffdc000 Unfrozen
       Start: mscorwks! Thread :: intermediateThreadProc (724c1fcf) 
       Priority: 2 Priority class: 32 Affinity: 3 

Let's look at managed threads:
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 11bc 00529188 a020 Disabled 01c903cc: 01c91fe8 00524c40 0 MTA
    2 2 130c 00537020 b220 Enabled 00000000: 00000000 00524c40 0 MTA (Finalizer) 

From this we can see that we have 2 managed threads in the application — the main managed thread in which our application runs and the Finalizer thread. And yet there is one unmanaged thread mscorwks! DebuggerRCThread :: ThreadProcStatic is a Debugger thread. The communication specifications are closed, it provides debugging information, step-by-step debugging capabilities, etc. This thread exists regardless of whether you are creating the Release version of your application or the Debug version.
Let's look at the AppDomains of our application:
0: 000> ! Dumpdomain
 -------------------------------------- System Domain: 728ed058 LowFrequencyHeap: 728ed07c HighFrequencyHeap: 728ed0c8 StubHeap: 728ed114 Stage: OPEN Name: None ---------------------------------------------- Shared Domain: 728ec9a8 LowFrequencyHeap : 728ec9cc HighFrequencyHeap: 728eca18 StubHeap: 728eca64 Stage: OPEN Name: None Assembly: 0051c920 ----------------------------------------- ----- Domain 1: 00524c40. LowFrequencyHeap: 00524c64. HighFrequencyHeap: 00524cb0. StubHeap: 00524cfc. .dll] ClassLoader: 0051c990 SecurityDescriptor: 005384a8 Module Name 70da1000 C: \ Windows \ assembly \ GAC_32 \ mscorlib \ 2.0.0.0 __b77a5c561934e089 \ mscorlib.dll Assembly: 0051ca70 [D: \ # Projects \ #Active \ TestApplication \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ as \ # \ as \ \ \ \ \ \ \ \ \ Debug \ ApplicationForWinDbg.exe] ClassLoader: 0051cae0 SecurityDescriptor: 0053d088 Module Name 00202c5c D: \ # Projects \ #Active \ Test  ApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe 

You can see that in addition to the domain in which our code runs, we also have a System Domain , Shared Domain . The first is responsible for loading the Shared Domain , as well as for loading and unloading all user domains. The second acts as a repository for domain-neutral assembly assemblies. The code is not executed in the Shared Domain , because the code can only be executed in user domains.
In the user domain we see modules - executable files dll, exe and others. Assembler assembly may contain several modules, it is not only executable files, but also resource files. Let's take from the previous example the address of our assembler assembly 0051ca70, and get additional information:
0: 000> ! Dumpassembly 0x0051ca70
  Parent Domain: 00524c40
 Name: D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe
 ClassLoader: 0051cae0
 SecurityDescriptor: 002bed20
   Module Name
 00202c5c D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe 

Let's look at the information about the 00202c5c module:
0: 000> ! Dumpmodule 0x00202c5c
  Name: D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe
 Attributes: PEFile 
 Assembly: 0051ca70
 LoaderHeap: 00000000
 TypeDefToMethodTableMap: 00200038
 TypeRefToMethodTableMap: 00200048
 MethodDefToDescMap: 002000a0
 FieldDefToDescMap: 002000b8
 MemberRefToDescMap: 002000bc
 FileReferencesMap: 00200118
 AssemblyReferencesMap: 0020011c
 MetaData start address: 001120e4 (1668 bytes) 

If we use the –mt parameter, we will also get information about the types in our module:
0: 000> ! Dumpmodule -mt 0x00202c5c
  Name: D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe
 Attributes: PEFile 
 Assembly: thodTableMap: 00200038
 TypeRefToMethodTableMap: 00200048
 MethodDefToDescMap: 002000a0
 FieldDefToDescMap: 002000b8
 MemberRefToDescMap: 002000bc
 FileReferencesMap: 00200118
 AssemblyReferencesMap: 0020011c
 MetaData start address: 001120e4 (1668 bytes)

 Types defined in this module

       MT TypeDef Name
 -------------------------------------------------- ----------------------------
 00203080 0x02000002 ApplicationForWinDbg.Class1
 0020300c 0x02000003 ApplicationForWinDbg.Program

 Types referenced in this module

       MT TypeRef Name
 -------------------------------------------------- ----------------------------
 71010508 0x01000001 System.Object
 710108ec 0x01000012 System.String
 71014258 0x01000013 System.Console
 71012b38 0x01000015 System.Int32
  0051ca70
 LoaderHeap: 00000000
 TypeDefToMe 

We found our types defined in the code, this is ApplicationForWinDbg.Class1 and ApplicationForWinDbg.Program . Information about these classes is stored in the internal structures of the EEClass . This structure contains information on the number of interfaces, methods, fields, as well as their structures and much more.
Let's see what is happening in our hip:
0: 000> ! Dumpheap
  Address MT Size
 01c71000 0052ccc8 12 Free
 01c7100c 0052ccc8 12 Free
 01c71018 0052ccc8 12 Free
 01c71024 71010b10 72     
 01c7106c 71010ba0 72     
 01c710b4 71010c30 72     
 01c710fc 71010cc0 72     
 01c71144 71010cc0 72     
 01c7118c 71010508 12     
 01c71198 710108ec 20     
 01c711ac 71010fb8 28     
 01c711c8 710108ec 160     
 01c71268 710108ec 216     
 01c71340 710110cc 100     
 01c713a4 710113d8 44     
 01c713d0 70fe40bc 80     
 01c71420 710108ec 28     
 01c7143c 710108ec 32     
 01c7145c 710108ec 20     
 01c71470 710108ec 52     
 01c714a4 710108ec 40     
 …………………………………………
 01c90398 7101151c 24     
 01c903b0 710108ec 28     
 02c71000 0052ccc8 16 Free
 02c71010 70fe40bc 4096     
 02c72010 0052ccc8 16 Free
 02c72020 70fe40bc 528     
 02c72230 0052ccc8 16 Free
 02c72240 70fe40bc 4096     
 02c73240 0052ccc8 16 Free
 total 5361 objects
 Statistics:
       MT Count TotalSize Class Name
 71013dc0 1 12 System.Text.DecoderExceptionFallback
 71013d7c 1 12 System.Text.EncoderExceptionFallback
 71013ae4 1 16 System.Text.DecoderReplacementFallback
 71013a94 1 16 System.Text.EncoderReplacementFallback
 71014808 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle
 710147b0 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
 71014724 1 20 System.Text.InternalEncoderBestFitFallback
 71012e50 1 20 System.Security.PermissionToken
 7100e8b8 1 20 Microsoft.Win32.SafeHandles.SafeFileHandle
 71014770 1 24 System.Text.InternalDecoderBestFitFallback
 710143c0 1 24 System.IO.TextWriter + SyncTextWriter
 710127cc 2 24 System.Security.Permissions.SecurityPermission
 71012510 1 24 System.OperatingSystem
 71014290 1 28 Microsoft.Win32.Win32Native + InputRecord
 71013f24 1 28 System.Text.EncoderNLS
 71013c34 1 28 System.IO.Stream + NullStream
 71013990 1 28 System.Text.UTF8Encoding
 71010fb8 1 28 System.SharedStatics
 71013ea4 1 32 System.Text.UTF8Encoding + UTF8Encoder
 71012efc 1 32 System.Security.PermissionTokenFactory
 710122cc 1 32 Microsoft.Win32.Win32Native + OSVERSIONINFO
 710142e4 1 36 System.IO .__ ConsoleStream
 71012eb0 1 36 System.Security.Util.TokenBasedSet
 71012d18 1 36 System.Security.Permissions.FileIOPermission
 710120c8 1 36 System.Int64 []
 710123d4 1 40 Microsoft.Win32.Win32Native + OSVERSIONINFOEX
 7101278c 1 44 System.Security.FrameSecurityDescriptor
 710113d8 1 44 System.AppDomainSetup
 71012874 3 48 System.Security.Permissions.FileIOAccess
 71012494 2 48 System.Version
 71011e38 2 48 System.Reflection.Assembly
 7100e6f4 4 48 System.UInt16
 71012c14 2 56 System.Collections.ArrayList + ArrayListEnumeratorSimple
 71010ec0 1 56 System.Threading.Thread
 71013714 1 68 System.Globalization.CultureTable
 710128bc 3 72 System.Security.Util.StringExpressionSet
 710125b0 2 72 System.Security.PermissionSet
 71010c30 1 72 System.ExecutionEngineException
 71010ba0 1 72 System.StackOverflowException
 71010b10 1 72 System.OutOfMemoryException
 71014608 1 76 System.Text.SBCSCodePageEncoding
 710137a8 5,100 System.Globalization.CultureTableItem
 710110cc 1 100 System.AppDomain
 0052ccc8 7 100 Free
 71012b38 9,108 System.Int32
 710140d4 2 112 System.IO.StreamWriter
 71010a28 6 120 System.Text.StringBuilder
 71010508 10 120 System.Object
 710136c4 3 144 System.Globalization.CultureTableRecord
 7101291c 6 144 System.Collections.ArrayList
 71010cc0 2,144 System.Threading.ThreadAbortException
 71011a6c 8 160 System.RuntimeType
 71011fe4 3 192 System.IO.UnmanagedMemoryStream
 710134e8 3 204 System.Globalization.CultureInfo
 71013850 2 256 System.Globalization.NumberFormatInfo
 71012f40 7 392 System.Collections.Hashtable
 7101335c 3 684 System.Byte []
 7101303c 7 1008 System.Collections.Hashtable + bucket []
 71012a88 14 1536 System.Int32 []
 70fe40bc 28 9564 System.Object []
 00203080 1000 12000 ApplicationForWinDbg.Class1
 7101151c 1022 25376 System.Char []
 710108ec 3160 82600 System.String
 Total 5361 objects 

Try to answer how many lines are in the heap now?
Get information about our ApplicationForWinDbg.Class1 class using a pointer to metadata:
0: 000> ! Dumpmt 0x00203080
  EEClass: 0020135c
 Module: 00202c5c
 Name: ApplicationForWinDbg.Class1
 mdToken: 02000002 (D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe)
 BaseSize: 0xc
 ComponentSize: 0x0
 Number of IFaces in IFaceMap: 0
 Slots in VTable: 7 

Now we have a pointer to its EEClass , with its description, look:
0: 000> ! Dumpclass 0x0020135c
  Class Name: ApplicationForWinDbg.Class1
 mdToken: 02000002 (D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe)
 Parent Class: 70da3ef0
 Module: 00202c5c
 Method Table: 00203080
 Vtable Slots: 4
 Total Method Slots: 5
 Class Attributes: 100,000  
 NumInstanceFields: 0
 NumStaticFields: 0 

Let's look at the list of all methods, in our class, by its pointer to the method table 00203080:
0: 000> ! Dumpmt -md 0x00203080
  EEClass: 0020135c
 Module: 00202c5c
 Name: ApplicationForWinDbg.Class1
 mdToken: 02000002 (D: \ # Projects \ #Active \ TestApplicationNet \ ApplicationForWinDbg \ bin \ Debug \ ApplicationForWinDbg.exe)
 BaseSize: 0xc
 ComponentSize: 0x0
 Number of IFaces in IFaceMap: 0
 Slots in VTable: 7
 --------------------------------------
 MethodDesc Table
    Entry MethodDesc JIT Name
 70f66a70 70de4934 PreJIT System.Object.ToString ()
 70f66a90 70de493c PreJIT System.Object.Equals (System.Object)
 70f66b00 70de496c PreJIT System.Object.GetHashCode ()
 70fd72f0 70de4990 PreJIT System.Object.Finalize ()
 004b0150 00203078 JIT ApplicationForWinDbg.Class1..ctor ()
 004b0188 00203060 JIT ApplicationForWinDbg.Class1.Method1 (System.String)
 004b01d8 0020306c JIT ApplicationForWinDbg.Class1.Method2 (Int32) 

And for example, look at the information about ApplicationForWinDbg.Class1.Method1 and its compiled code:
0: 000> ! Dumpmd 0x00203060
  Method Name: ApplicationForWinDbg.Class1.Method1 (System.String)
 Class: 0020135c
 MethodTable: 00203080
 mdToken: 06000001
 Module: 00202c5c
 IsJitted: yes
 CodeAddr: 004b0188 

0: 000> ! U 0x004b0188
  Normal JIT generated code
 ApplicationForWinDbg.Class1.Method1 (System.String)
 Begin 004b0188, size 3a
 >>> 004b0188 55 push ebp
 004b0189 8bec mov ebp, esp
 004b018b 83ec0c sub esp, 0Ch
 004b018e 894dfc mov dword ptr [ebp-4], ecx
 004b0191 8955f8 mov dword ptr [ebp-8], edx
 004b0194 833d142e200000 cmp dword ptr ds: [202E14h], 0
 004b019b 7405 je 004b01a2
 004b019d e8dfa21472 call mscorwks! JIT_DbgIsJustMyCode (725fa481)
 004b01a2 90 nop
 004b01a3 8b153020c702 mov edx, dword ptr ds: [2C72030h] ("$")
 004b01a9 8b4df8 mov ecword dword ptr [ebp-8]
 004b01ac e86feaaa70 call mscorlib_ni + 0x1bec20 (70f5ec20) (System.String.Concat (System.String, System.String), mdToken: 060001c9)
 004b01b1 8945f4 mov dword ptr [ebp-0Ch], eax
 004b01b4 8b4df4 mov ecword dword ptr [ebp-0Ch]
 004b01b7 e81c36fc70 call mscorlib_ni + 0x6d37d8 (714737d8) (System.Console.WriteLine (System.String), mdToken: 060007c8)
 004b01bc 90 nop
 004b01bd 90 nop
 004b01be 8be5 mov esp, ebp
 004b01c0 5d pop ebp
 004b01c1 c3 ret 

This is probably the end, as the article turned out long because of all the listings. Wait for the continuation. And more detailed explanations of the purpose of all fields, as well as in the next article we will talk about other commands.

Source: https://habr.com/ru/post/44781/


All Articles