diff options
author | Mike McLaughlin <mikem@microsoft.com> | 2015-11-19 18:13:25 -0800 |
---|---|---|
committer | Mike McLaughlin <mikem@microsoft.com> | 2015-12-02 16:07:18 -0800 |
commit | aa78a2773ffe573c705caa11dd66467ae6c82781 (patch) | |
tree | 8da63f171dcf97bbdb796a3b961d53b40be00d65 /src | |
parent | b3a45c0a4db6989f9ba6e4cc9db849d0558e961f (diff) | |
download | coreclr-aa78a2773ffe573c705caa11dd66467ae6c82781.tar.gz coreclr-aa78a2773ffe573c705caa11dd66467ae6c82781.tar.bz2 coreclr-aa78a2773ffe573c705caa11dd66467ae6c82781.zip |
Some SOS fixes/cleanup (bpmd, setclrpath, setsostid).
Enable the module load notification when no module or methods are found.
setclrpath/setsostid print the current path/tid if no arguments. "setsostid -clear"
now clears the tid/os id mapping.
Added better exception hook to sos plugin to catch special CLRN exception. Before
multiple lldb "Exception breakpoints" where set each time bpmd added a new bp. Added
a "DoNotDeleteOrDisable" message to the exception breakpoint created.
Fix bpmd on release builds. RtlpRaiseException was being inlined and the ExceptionRecord
parameter was being optimized away causing the GetLastEventInformation api to failed
and the special CLRN exception to be ignored.
Add sos help support. "soshelp" displays help about a command.
Diffstat (limited to 'src')
-rw-r--r-- | src/ToolBox/SOS/Strike/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/ToolBox/SOS/Strike/exts.h | 19 | ||||
-rw-r--r-- | src/ToolBox/SOS/Strike/sosdocsunix.txt | 1374 | ||||
-rw-r--r-- | src/ToolBox/SOS/Strike/strike.cpp | 116 | ||||
-rw-r--r-- | src/ToolBox/SOS/Strike/util.cpp | 21 | ||||
-rw-r--r-- | src/ToolBox/SOS/Strike/util.h | 2 | ||||
-rw-r--r-- | src/ToolBox/SOS/lldbplugin/debugclient.cpp | 104 | ||||
-rw-r--r-- | src/ToolBox/SOS/lldbplugin/debugclient.h | 17 | ||||
-rw-r--r-- | src/ToolBox/SOS/lldbplugin/inc/dbgeng.h | 11 | ||||
-rw-r--r-- | src/ToolBox/SOS/lldbplugin/setclrpathcommand.cpp | 26 | ||||
-rw-r--r-- | src/ToolBox/SOS/lldbplugin/setsostidcommand.cpp | 16 | ||||
-rw-r--r-- | src/ToolBox/SOS/lldbplugin/soscommand.cpp | 9 | ||||
-rw-r--r-- | src/pal/src/exception/seh-unwind.cpp | 24 | ||||
-rw-r--r-- | src/pal/src/include/pal/seh.hpp | 26 |
14 files changed, 1641 insertions, 126 deletions
diff --git a/src/ToolBox/SOS/Strike/CMakeLists.txt b/src/ToolBox/SOS/Strike/CMakeLists.txt index 18fd0b1bb5..2825df5efa 100644 --- a/src/ToolBox/SOS/Strike/CMakeLists.txt +++ b/src/ToolBox/SOS/Strike/CMakeLists.txt @@ -131,4 +131,6 @@ target_link_libraries(sos ${SOS_LIBRARY}) install (TARGETS sos DESTINATION .) if(WIN32) install (FILES ${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/sos.pdb DESTINATION PDB) +else(WIN32) + install (FILES sosdocsunix.txt DESTINATION .) endif(WIN32) diff --git a/src/ToolBox/SOS/Strike/exts.h b/src/ToolBox/SOS/Strike/exts.h index ff9d4b2621..284430e3c7 100644 --- a/src/ToolBox/SOS/Strike/exts.h +++ b/src/ToolBox/SOS/Strike/exts.h @@ -191,15 +191,16 @@ public: inline void EENotLoadedMessage(HRESULT Status) { - ExtOut("Failed to find runtime DLL (clr.dll), 0x%08x\n",Status); - ExtOut("Extension commands need clr.dll in order to have something to do.\n"); + ExtOut("Failed to find runtime DLL (%s), 0x%08x\n", MAKEDLLNAME_A("coreclr"), Status); + ExtOut("Extension commands need it in order to have something to do.\n"); } inline void DACMessage(HRESULT Status) { - ExtOut("Failed to load data access DLL, 0x%08x\n",Status); + ExtOut("Failed to load data access DLL, 0x%08x\n", Status); +#ifndef FEATURE_PAL ExtOut("Verify that 1) you have a recent build of the debugger (6.2.14 or newer)\n"); - ExtOut(" 2) the file mscordacwks.dll that matches your version of clr.dll is \n"); + ExtOut(" 2) the file mscordacwks.dll that matches your version of coreclr.dll is \n"); ExtOut(" in the version directory or on the symbol path\n"); ExtOut(" 3) or, if you are debugging a dump file, verify that the file \n"); ExtOut(" mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path.\n"); @@ -213,7 +214,11 @@ inline void DACMessage(HRESULT Status) ExtOut("If that succeeds, the SOS command should work on retry.\n"); ExtOut("\n"); ExtOut("If you are debugging a minidump, you need to make sure that your executable\n"); - ExtOut("path is pointing to clr.dll as well.\n"); + ExtOut("path is pointing to coreclr.dll as well.\n"); +#else + ExtOut("You can run the debugger command 'setclrpath' to control the load of %s.\n", MAKEDLLNAME_A("mscordaccore")); + ExtOut("If that succeeds, the SOS command should work on retry.\n"); +#endif // FEATURE_PAL } HRESULT CheckEEDll(); @@ -259,12 +264,12 @@ HRESULT CheckEEDll(); g_clrData = NULL; \ if ((Status = CheckEEDll()) != S_OK) \ { \ - ExtOut("Failed to find runtime DLL (clr.dll), 0x%08x\n", Status); \ + ExtOut("Failed to find runtime DLL (%s), 0x%08x\n", MAKEDLLNAME_A("coreclr"), Status); \ ExtOut("Some functionality may be impaired\n"); \ } \ else if ((Status = LoadClrDebugDll()) != S_OK) \ { \ - ExtOut("Failed to load data access DLL, 0x%08x\n", Status); \ + ExtOut("Failed to load data access DLL (%s), 0x%08x\n", MAKEDLLNAME_A("mscordaccore"), Status); \ ExtOut("Some functionality may be impaired\n"); \ } \ else \ diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt new file mode 100644 index 0000000000..8a6218af40 --- /dev/null +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -0,0 +1,1374 @@ +------------------------------------------------------------------------------- +NOTE: THIS FILE CONTAINS SOS DOCUMENTATION. THE FORMAT OF THE FILE IS: + +<optional comments> +COMMAND: <cmd name, all lower case> +<descriptive text of the command> +\\ <these are two backslashes, immediately followed by a newline> + +<repeat the sequence above> + +The first command is "contents" which is the general help screen. The rest +correspond to SOS command names. This file is embedded as a resource in the SOS +binary. Be sure to list any new commands here. +------------------------------------------------------------------------------- + + + +COMMAND: contents. +SOS is a debugger extension DLL designed to aid in the debugging of managed +programs. Functions are listed by category, then roughly in order of +importance. Shortcut names for popular functions are listed in parenthesis. +Type "soshelp <functionname>" for detailed info on that function. + +Object Inspection Examining code and stacks +----------------------------- ----------------------------- +DumpObj (dumpobj) Threads (clrthreads) +DumpArray ThreadState +DumpStackObjects (dso) IP2MD (ip2md) +DumpHeap (dumpheap) CLRStack (clrstack) +DumpVC GCInfo +GCRoot (gcroot) EHInfo +PrintException (pe) bpmd (bpmd) + +Examining CLR data structures Diagnostic Utilities +----------------------------- ----------------------------- +DumpDomain VerifyHeap +EEHeap (eeheap) FindAppDomain +Name2EE (name2ee) DumpLog (dumplog) +DumpMT (dumpmt) +DumpClass +DumpMD (dumpmd) +Token2EE +DumpModule +DumpAssembly +DumpRuntimeTypes + +Other +----------------------------- +FAQ +Help (soshelp) +\\ + +COMMAND: faq. +>> Where can I get the right version of SOS for my build? + +If you are running a xplat version of coreclr, the sos module (exact name +is platform dependent) is installed in the same directory as the main coreclr +module. There is also an lldb sos plugin command that allows the path where +the sos, dac and dbi modules are loaded: + + "setsospath /home/user/coreclr/bin/Product/Linux.x64.Debug"" + +If you are using a dump file created on another machine, it is a little bit +more complex. You need to make sure the dac module that came with that install +is in the directory set with the above command. + +>> I have a chicken and egg problem. I want to use SOS commands, but the CLR + isn't loaded yet. What can I do? + +TBD + +>> I got the following error message. Now what? + + + (lldb) sos DumpStackObjects + The coreclr module is not loaded yet in the target process + (lldb) + +This means that the clr is not loaded yet, or has been unloaded. You need to +wait until your managed program is running in order to use these commands. If +you have just started the program a good way to do this is to type + + breakpoint set coreclr`EEStartup + +in the debugger, and let it run. After the function EEStartup is finished, +there will be a minimal managed environment for executing SOS commands. + +\\ + +COMMAND: dumpobj. +DumpObj [-nofields] <object address> + +This command allows you to examine the fields of an object, as well as learn +important properties of the object such as the EEClass, the MethodTable, and +the size. + +You might find an object pointer by running DumpStackObjects and choosing +from the resultant list. Here is a simple object: + + (lldb) dumpobj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type VT Attr Value Name + 009038ec 4000008 4 Customer 0 instance 00a79ce4 name + 009038ec 4000009 8 Bank 0 instance 00a79d2c bank + +Note that fields of type Customer and Bank are themselves objects, and you can +run DumpObj on them too. You could look at the field directly in memory using +the offset given. "dd a79d40+8 l1" would allow you to look at the bank field +directly. Be careful about using this to set memory breakpoints, since objects +can move around in the garbage collected heap. + +What else can you do with an object? You might run GCRoot, to determine what +roots are keeping it alive. Or you can find all objects of that type with +"dumpheap -type Customer". + +The column VT contains the value 1 if the field is a valuetype structure, and +0 if the field contains a pointer to another object. For valuetypes, you can +take the MethodTable pointer in the MT column, and the Value and pass them to +the command DumpVC. + +The arguments in detail: +-nofields: do not print fields of the object, useful for objects like String +\\ + +COMMAND: dumparray. +DumpArray + [-start <startIndex>] + [-length <length>] + [-details] + [-nofields] + <array object address> + +This command allows you to examine elements of an array object. +The arguments in detail: + -start <startIndex>: optional, only supported for single dimension array. + Specify from which index the command shows the elements. + -length <length>: optional, only supported for single dimension array. + Specify how many elements to show. + -details: optional. Ask the command to print out details + of the element using DumpObj and DumpVC format. + -nofields: optional, only takes effect when -details is used. Do + not print fields of the elements. Useful for arrays of + objects like String + + Example output: + + (lldb) sos DumpArray -start 2 -length 3 -details 00ad28d0 + Name: Value[] + MethodTable: 03e41044 + EEClass: 03e40fc0 + Size: 132(0x84) bytes + Array: Rank 1, Number of elements 10, Type VALUETYPE + Element Type: Value + [2] 00ad28f0 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (/home/user/bugs/225271/arraytest) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 2 x + 5b9a628c 4000002 4 System.Int32 instance 4 y + 5b9a628c 4000003 8 System.Int32 instance 6 z + [3] 00ad28fc + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (/home/user/bugs/225271/arraytest) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 3 x + 5b9a628c 4000002 4 System.Int32 instance 6 y + 5b9a628c 4000003 8 System.Int32 instance 9 z + [4] 00ad2908 + Name: Value + MethodTable 03e40f4c + EEClass: 03ef1698 + Size: 20(0x14) bytes + (/home/user/bugs/225271/arraytest.exe) + Fields: + MT Field Offset Type Attr Value Name + 5b9a628c 4000001 0 System.Int32 instance 4 x + 5b9a628c 4000002 4 System.Int32 instance 8 y + 5b9a628c 4000003 8 System.Int32 instance 12 z + + +\\ + +COMMAND: dumpstackobjects. +DumpStackObjects [-verify] [top stack [bottom stack]] + +This command will display any managed objects it finds within the bounds of +the current stack. Combined with the stack tracing commands like K and +CLRStack, it is a good aid to determining the values of locals and +parameters. + +If you use the -verify option, each non-static CLASS field of an object +candidate is validated. This helps to eliminate false positives. It is not +on by default because very often in a debugging scenario, you are +interested in objects with invalid fields. + +The abbreviation dso can be used for brevity. +\\ + +COMMAND: dumpheap. +DumpHeap [-stat] + [-strings] + [-short] + [-min <size>] + [-max <size>] + [-live] + [-dead] + [-thinlock] + [-startAtLowerBound] + [-mt <MethodTable address>] + [-type <partial type name>] + [start [end]] + +DumpHeap is a powerful command that traverses the garbage collected heap, +collection statistics about objects. With it's various options, it can look for +particular types, restrict to a range, or look for ThinLocks (see SyncBlk +documentation). Finally, it will provide a warning if it detects excessive +fragmentation in the GC heap. + +When called without options, the output is first a list of objects in the heap, +followed by a report listing all the types found, their size and number: + + (lldb) dumpheap + Address MT Size + 00a71000 0015cde8 12 Free + 00a7100c 0015cde8 12 Free + 00a71018 0015cde8 12 Free + 00a71024 5ba58328 68 + 00a71068 5ba58380 68 + 00a710ac 5ba58430 68 + 00a710f0 5ba5dba4 68 + ... + total 619 objects + Statistics: + MT Count TotalSize Class Name + 5ba7607c 1 12 System.Security.Permissions.HostProtectionResource + 5ba75d54 1 12 System.Security.Permissions.SecurityPermissionFlag + 5ba61f18 1 12 System.Collections.CaseInsensitiveComparer + ... + 0015cde8 6 10260 Free + 5ba57bf8 318 18136 System.String + ... + +"Free" objects are simply regions of space the garbage collector can use later. +If 30% or more of the heap contains "Free" objects, the process may suffer from +heap fragmentation. This is usually caused by pinning objects for a long time +combined with a high rate of allocation. Here is example output where DumpHeap +provides a warning about fragmentation: + + <After the Statistics section> + Fragmented blocks larger than 1MB: + Addr Size Followed by + 00a780c0 1.5MB 00bec800 System.Byte[] + 00da4e38 1.2MB 00ed2c00 System.Byte[] + 00f16df0 1.2MB 01044338 System.Byte[] + +The arguments in detail: + +-stat Restrict the output to the statistical type summary +-strings Restrict the output to a statistical string value summary +-short Limits output to just the address of each object. This allows you + to easily pipe output from the command to another debugger + command for automation. +-min Ignore objects less than the size given in bytes +-max Ignore objects larger than the size given in bytes +-live Only print live objects +-dead Only print dead objects (objects which will be collected in the + next full GC) +-thinlock Report on any ThinLocks (an efficient locking scheme, see SyncBlk + documentation for more info) +-startAtLowerBound + Force heap walk to begin at lower bound of a supplied address range. + (During plan phase, the heap is often not walkable because objects + are being moved. In this case, DumpHeap may report spurious errors, + in particular bad objects. It may be possible to traverse more of + the heap after the reported bad object. Even if you specify an + address range, DumpHeap will start its walk from the beginning of + the heap by default. If it finds a bad object before the specified + range, it will stop before displaying the part of the heap in which + you are interested. This switch will force DumpHeap to begin its + walk at the specified lower bound. You must supply the address of a + good object as the lower bound for this to work. Display memory at + the address of the bad object to manually find the next method + table (use DumpMT to verify). If the GC is currently in a call to + memcopy, You may also be able to find the next object's address by + adding the size to the start address given as parameters.) +-mt List only those objects with the MethodTable given +-type List only those objects whose type name is a substring match of the + string provided. +start Begin listing from this address +end Stop listing at this address + +A special note about -type: Often, you'd like to find not only Strings, but +System.Object arrays that are constrained to contain Strings. ("new +String[100]" actually creates a System.Object array, but it can only hold +System.String object pointers). You can use -type in a special way to find +these arrays. Just pass "-type System.String[]" and those Object arrays will +be returned. More generally, "-type <Substring of interesting type>[]". + +The start/end parameters can be obtained from the output of eeheap -gc. For +example, if you only want to list objects in the large heap segment: + + (lldb) eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00c32754 + generation 1 starts at 0x00c32748 + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 010443a8 005d33a8(6108072) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a75000 0x00004000(16384) + Total Size 0x5d73a8(6124456) + ------------------------------ + GC Heap Size 0x5d73a8(6124456) + + (lldb) dumpheap 1a71000 1a75000 + Address MT Size + 01a71000 5ba88bd8 2064 + 01a71810 0019fe48 2032 Free + 01a72000 5ba88bd8 4096 + 01a73000 0019fe48 4096 Free + 01a74000 5ba88bd8 4096 + total 5 objects + Statistics: + MT Count TotalSize Class Name + 0019fe48 2 6128 Free + 5ba88bd8 3 10256 System.Object[] + Total 5 objects + +Finally, if GC heap corruption is present, you may see an error like this: + + (lldb) dumpheap -stat + object 00a73d24: does not have valid MT + curr_object : 00a73d24 + Last good object: 00a73d14 + ---------------- + +That indicates a serious problem. See the help for VerifyHeap for more +information on diagnosing the cause. +\\ + +COMMAND: dumpvc. +DumpVC <MethodTable address> <Address> + +DumpVC allows you to examine the fields of a value class. In C#, this is a +struct, and lives on the stack or within an Object on the GC heap. You need +to know the MethodTable address to tell SOS how to interpret the fields, as +a value class is not a first-class object with it's own MethodTable as the +first field. For example: + + (lldb) sos DumpObj a79d98 + Name: Mainy + MethodTable: 009032d8 + EEClass: 03ee1424 + Size: 28(0x1c) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000010 4 VALUETYPE instance 00a79d9c m_valuetype + 009032d8 400000f 4 CLASS static 00a79d54 m_sExcep + +m_valuetype is a value type. The value in the MT column (0090320c) is the +MethodTable for it, and the Value column provides the start address: + + (lldb) sos DumpVC 0090320c 00a79d9c + Name: Funny + MethodTable 0090320c + EEClass: 03ee14b8 + Size: 28(0x1c) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type Attr Value Name + 0090320c 4000001 0 CLASS instance 00a743d8 signature + 0090320c 4000002 8 System.Int32 instance 2345 m1 + 0090320c 4000003 10 System.Boolean instance 1 b1 + 0090320c 4000004 c System.Int32 instance 1234 m2 + 0090320c 4000005 4 CLASS instance 00a79d98 backpointer + +DumpVC is quite a specialized function. Some managed programs make heavy use +of value classes, while others do not. +\\ + +COMMAND: gcroot. +GCRoot [-nostacks] <Object address> + +GCRoot looks for references (or roots) to an object. These can exist in four +places: + + 1. On the stack + 2. Within a GC Handle + 3. In an object ready for finalization + 4. As a member of an object found in 1, 2 or 3 above. + +First, all stacks will be searched for roots, then handle tables, and finally +the freachable queue of the finalizer. Some caution about the stack roots: +GCRoot doesn't attempt to determine if a stack root it encountered is valid +or is old (discarded) data. + +Because people often want to restrict the search to gc handles and freachable +objects, there is a -nostacks option. +\\ + +COMMAND: pe. +COMMAND: printexception. +PrintException [-nested] [-lines] [-ccw] [<Exception object address>] [<CCW pointer>] + +This will format fields of any object derived from System.Exception. One of the +more useful aspects is that it will format the _stackTrace field, which is a +binary array. If _stackTraceString field is not filled in, that can be helpful +for debugging. You can of course use DumpObj on the same exception object to +explore more fields. + +If called with no parameters, PrintException will look for the last outstanding +exception on the current thread and print it. This will be the same exception +that shows up in a run of clrthreads. + +PrintException will notify you if there are any nested exceptions on the +current managed thread. (A nested exception occurs when you throw another +exception within a catch handler already being called for another exception). +If there are nested exceptions, you can re-run PrintException with the +"-nested" option to get full details on the nested exception objects. The +clrthreads command will also tell you which threads have nested exceptions. + +PrintException can display source information if available, by specifying the +-lines command line argument. + +PrintException prints the exception object corresponding to a given CCW pointer, +which can be specified using the -ccw option. + +The abbreviation 'pe' can be used for brevity. +\\ + +COMMAND: threadstate. +ThreadState value + +The clrthreads command outputs, among other things, the state of the thread. +This is a bit field which corresponds to various states the thread is in. +To check the state of the thread, simply pass that bit field from the +output of clrthreads into ThreadState. + +Example: + (lldb) clrthreads + 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 250 0019b068 a020 Disabled 02349668:02349fe8 0015def0 0 MTA + 2 2 944 001a6020 b220 Enabled 00000000:00000000 0015def0 0 MTA (Finalizer) + 0:003> sos ThreadState b220 + Legal to Join + Background + CLR Owns + CoInitialized + In Multi Threaded Apartment + +Possible thread states: + Thread Abort Requested + GC Suspend Pending + User Suspend Pending + Debug Suspend Pending + GC On Transitions + Legal to Join + Yield Requested + Hijacked by the GC + Blocking GC for Stack Overflow + Background + Unstarted + Dead + CLR Owns + CoInitialized + In Single Threaded Apartment + In Multi Threaded Apartment + Reported Dead + Fully initialized + Task Reset + Sync Suspended + Debug Will Sync + Stack Crawl Needed + Suspend Unstarted + Aborted + Thread Pool Worker Thread + Interruptible + Interrupted + Completion Port Thread + Abort Initiated + Finalized + Failed to Start + Detached +\\ +COMMAND: threads. +COMMAND: clrthreads. +Threads [-live] [-special] + +Threads (clrthreads) lists all the mananaged threads in the process. + +-live: optional. Only print threads associated with a live thread. +-special: optional. With this switch, the command will display all the special + threads created by CLR. Those threads might not be managed threads + so they might not be shown in the first part of the command's + output. Example of special threads include: GC threads (in + concurrent GC and server GC), Debugger helper threads, Finalizer + threads, AppDomain Unload threads, and Threadpool timer threads. + +Each thread has many attributes, many of which can be ignored. The important +ones are discussed below: + +There are three ID columns: + +1) The debugger shorthand ID (When the runtime is hosted this column might + display the special string "<<<<" when this internal thread object is not + associated with any physical thread - this may happen when the host reuses + the runtime internal thread object) +2) The CLR Thread ID +3) The OS thread ID. + +If PreEmptiveGC is enabled for a thread, then a garbage collection +can occur while that thread is running. For example, if you break in while +a managed thread is making a PInvoke call to a Win32 function, that thread +will be in PreEmptive GC mode. + +The Domain column indicates what AppDomain the thread is currently executing +in. You can pass this value to DumpDomain to find out more. + +The APT column gives the COM apartment mode. + +Exception will list the last thrown exception (if any) for the thread. More +details can be obtained by passing the pointer value to PrintException. If +you get the notation "(nested exceptions)", you can get details on those +exceptions by switching to the thread in question, and running +"PrintException -nested". +\\ + +COMMAND: clrstack. +CLRStack [-a] [-l] [-p] [-n] +CLRStack [-a] [-l] [-p] [-i] [variable name] [frame] + +CLRStack attempts to provide a true stack trace for managed code only. It is +handy for clean, simple traces when debugging straightforward managed +programs. The -p parameter will show arguments to the managed function. The +-l parameter can be used to show information on local variables in a frame. +SOS can't retrieve local names at this time, so the output for locals is in +the format <local address> = <value>. The -a (all) parameter is a short-cut +for -l and -p combined. + +If the debugger has the option SYMOPT_LOAD_LINES specified (either by the +.lines or .symopt commands), SOS will look up the symbols for every managed +frame and if successful will display the corresponding source file name and +line number. The -n (No line numbers) parameter can be specified to disable +this behavior. + +When you see methods with the name "[Frame:...", that indicates a transition +between managed and unmanaged code. You could run IP2MD on the return +addresses in the call stack to get more information on each managed method. + +On x64 platforms, Transition Frames are not displayed at this time. To avoid +heavy optimization of parameters and locals one can request the JIT compiler +to not optimize functions in the managed app by creating a file myapp.ini +(if your program is myapp.exe) in the same directory. Put the following lines +in myapp.ini and re-run: + +[.NET Framework Debugging Control] +GenerateTrackingInfo=1 +AllowOptimize=0 + +The -i option is a new EXPERIMENTAL addition to CLRStack and will use the ICorDebug +interfaces to display the managed stack and variables. With this option you can also +view and expand arrays and fields for managed variables. If a stack frame number is +specified in the command line, CLRStack will show you the parameters and/or locals +only for that frame (provided you specify -l or -p or -a of course). If a variable +name and a stack frame number are specified in the command line, CLRStack will show +you the parameters and/or locals for that frame, and will also show you the fields +for that variable name you specified. Here are some examples: + clrstack -i -a : This will show you all parameters and locals for all frames + clrstack -i -a 3 : This will show you all parameters and locals, for frame 3 + clrstack -i var1 0 : This will show you the fields of 'var1' for frame 0 + clrstack -i var1.abc 2 : This will show you the fields of 'var1', and expand + 'var1.abc' to show you the fields of the 'abc' field, + for frame 2. + clrstack -i var1.[basetype] 0 : This will show you the fields of 'var1', and + expand the base type of 'var1' to show you its + fields. + clrstack -i var1.[6] 0 : If 'var1' is an array, this will show you the element + at index 6 in the array, along with its fields +The -i options uses DML output for a better debugging experience, so typically you +should only need to execute "clrstack -i", and from there, click on the DML +hyperlinks to inspect the different managed stack frames and managed variables. +\\ + +COMMAND: ip2md. +IP2MD <Code address> + +Given an address in managed JITTED code, IP2MD attempts to find the MethodDesc +associated with it. For example, this output from K: + + (lldb) bt + ... + frame #9: 0x00007fffffffbf60 0x00007ffff61c6d89 libcoreclr.so`MethodDesc::DoPrestub(this=0x00007ffff041f870, pDispatchingMT=0x0000000000000000) + 3001 at prestub.cpp:1490 + frame #10: 0x00007fffffffc140 0x00007ffff61c5f17 libcoreclr.so`::PreStubWorker(pTransitionBlock=0x00007fffffffc9a8, pMD=0x00007ffff041f870) + 1399 at prestub.cpp:1037 + frame #11: 0x00007fffffffc920 0x00007ffff5f5238c libcoreclr.so`ThePreStub + 92 at theprestubamd64.S:800 + frame #12: 0x00007fffffffca10 0x00007ffff04981cc + frame #13: 0x00007fffffffca30 0x00007ffff049773c + frame #14: 0x00007fffffffca80 0x00007ffff04975ad + ... + frame #22: 0x00007fffffffcc90 0x00007ffff5f51a0f libcoreclr.so`CallDescrWorkerInternal + 124 at calldescrworkeramd64.S:863 + frame #23: 0x00007fffffffccb0 0x00007ffff5d6d6dc libcoreclr.so`CallDescrWorkerWithHandler(pCallDescrData=0x00007fffffffce80, fCriticalCall=0) + 476 at callhelpers.cpp:88 + frame #24: 0x00007fffffffcd00 0x00007ffff5d6eb38 libcoreclr.so`MethodDescCallSite::CallTargetWorker(this=0x00007fffffffd0c8, pArguments=0x00007fffffffd048) + 2504 at callhelpers.cpp:633 + + (lldb) ip2md 0x00007ffff049773c + MethodDesc: 00007ffff7f71920 + Method Name: Microsoft.Win32.SafeHandles.SafeFileHandle.Open(System.Func`1<Int32>) + Class: 00007ffff0494bf8 + MethodTable: 00007ffff7f71a58 + mdToken: 0000000006000008 + Module: 00007ffff7f6b938 + IsJitted: yes + CodeAddr: 00007ffff04976c0 + Transparency: Critical + +We have taken a return address into Mainy.Main, and discovered information +about that method. You could run DumpMT, DumpClass, DumpMD, or +DumpModule on the fields listed to learn more. + +The "Source line" output will only be present if the debugger can find the +symbols for the managed module containing the given <code address>, and if the +debugger is configured to load line number information. +\\ + +COMMAND: ehinfo. +EHInfo (<MethodDesc address> | <Code address>) + +EHInfo shows the exception handling blocks in a jitted method. For each +handler, it shows the type, including code addresses and offsets for the clause +block and the handler block. For a TYPED handler, this would be the "try" and +"catch" blocks respectively. + +Sample output: + + (lldb) sos EHInfo 33bbd3a + MethodDesc: 03310f68 + Method Name: MainClass.Main() + Class: 03571358 + MethodTable: 0331121c + mdToken: 0600000b + Module: 001e2fd8 + IsJitted: yes + CodeAddr: 033bbca0 + Transparency: Critical + + EHHandler 0: TYPED catch(System.IO.FileNotFoundException) + Clause: [033bbd2b, 033bbd3c] [8b, 9c] + Handler: [033bbd3c, 033bbd50] [9c, b0] + + EHHandler 1: FINALLY + Clause: [033bbd83, 033bbda3] [e3, 103] + Handler: [033bbda3, 033bbdc5] [103, 125] + + EHHandler 2: TYPED catch(System.Exception) + Clause: [033bbd7a, 033bbdc5] [da, 125] + Handler: [033bbdc5, 033bbdd6] [125, 136] + +\\ + +COMMAND: gcinfo. +GCInfo (<MethodDesc address> | <Code address>) + +GCInfo is especially useful for CLR Devs who are trying to determine if there +is a bug in the JIT Compiler. It parses the GCEncoding for a method, which is a +compressed stream of data indicating when registers or stack locations contain +managed objects. It is important to keep track of this information, because if +a garbage collection occurs, the collector needs to know where roots are so it +can update them with new object pointer values. + +Here is sample output where you can see the change in register state. Normally +you would print this output out and read it alongside a disassembly of the +method. For example, the notation "reg EDI becoming live" at offset 0x11 of the +method might correspond to a "mov edi,ecx" statement. + + (lldb) sos GCInfo 5b68dbb8 (5b68dbb8 is the start of a JITTED method) + entry point 5b68dbb8 + preJIT generated code + GC info 5b9f2f09 + Method info block: + method size = 0036 + prolog size = 19 + epilog size = 8 + epilog count = 1 + epilog end = yes + saved reg. mask = 000B + ebp frame = yes + fully interruptible=yes + double align = no + security check = no + exception handlers = no + local alloc = no + edit & continue = no + varargs = no + argument count = 4 + stack frame size = 1 + untracked count = 5 + var ptr tab count = 0 + epilog at 002E + 36 D4 8C C7 AA | + 93 F3 40 05 | + + Pointer table: + 14 | [EBP+14H] an untracked local + 10 | [EBP+10H] an untracked local + 0C | [EBP+0CH] an untracked local + 08 | [EBP+08H] an untracked local + 44 | [EBP-04H] an untracked local + F1 79 | 0011 reg EDI becoming live + 72 | 0013 reg ESI becoming live + 83 | 0016 push ptr 0 + 8B | 0019 push ptr 1 + 93 | 001C push ptr 2 + 9B | 001F push ptr 3 + 56 | 0025 reg EDX becoming live + 4A | 0027 reg ECX becoming live + 0E | 002D reg ECX becoming dead + 10 | 002D reg EDX becoming dead + E0 | 002D pop 4 ptrs + F0 31 | 0036 reg ESI becoming dead + 38 | 0036 reg EDI becoming dead + FF | + +This function is important for CLR Devs, but very difficult for anyone else to +make sense of it. You would usually come to use it if you suspect a gc heap +corruption bug caused by invalid GCEncoding for a particular method. +\\ + +COMMAND: bpmd. +bpmd [-nofuturemodule] <module name> <method name> [<il offset>] +bpmd -md <MethodDesc> +bpmd -list +bpmd -clear <pending breakpoint number> +bpmd -clearall + +bpmd provides managed breakpoint support. If it can resolve the method name +to a loaded, jitted or ngen'd function it will create a breakpoint with "bp". +If not then either the module that contains the method hasn't been loaded yet +or the module is loaded, but the function is not jitted yet. In these cases, +bpmd asks the Windows Debugger to receive CLR Notifications, and waits to +receive news of module loads and JITs, at which time it will try to resolve +the function to a breakpoint. -nofuturemodule can be used to suppress +creating a breakpoint against a module that has not yet been loaded. + +Management of the list of pending breakpoints can be done via bpmd -list, +bpmd -clear, and bpmd -clearall commands. bpmd -list generates a list of +all of the pending breakpoints. If the pending breakpoint has a non-zero +module id, then that pending breakpoint is specific to function in that +particular loaded module. If the pending breakpoint has a zero module id, then +the breakpoint applies to modules that have not yet been loaded. Use +bpmd -clear or bpmd -clearall to remove pending breakpoints from the list. + +This brings up a good question: "I want to set a breakpoint on the main +method of my application. How can I do this?" + + 1) Stop after coreclr is loaded - TBD + + 2) Add the breakpoint with command such as: + bpmd myapp.exe MyApp.Main + 3) g + 4) You will stop at the start of MyApp.Main. If you type "bl" you will + see the breakpoint listed. + +To correctly specify explicitly implemented methods make sure to retrieve the +method name from the metadata, or from the output of the "dumpmt -md" command. +For example: + + public interface I1 + { + void M1(); + } + public class ExplicitItfImpl : I1 + { + ... + void I1.M1() // this method's name is 'I1.M1' + { ... } + } + + bpmd myapp.exe ExplicitItfImpl.I1.M1 + + +bpmd works equally well with generic types. Adding a breakpoint on a generic +type sets breakpoints on all already JIT-ted generic methods and sets a pending +breakpoint for any instantiation that will be JIT-ted in the future. + +Example for generics: + Given the following two classes: + + class G3<T1, T2, T3> + { + ... + public void F(T1 p1, T2 p2, T3 p3) + { ... } + } + + public class G1<T> { + // static method + static public void G<W>(W w) + { ... } + } + + One would issue the following commands to set breapoints on G3.F() and + G1.G(): + + bpmd myapp.exe G3`3.F + bpmd myapp.exe G1`1.G + +And for explicitly implemented methods on generic interfaces: + public interface IT1<T> + { + void M1(T t); + } + + public class ExplicitItfImpl<U> : IT1<U> + { + ... + void IT1<U>.M1(U u) // this method's name is 'IT1<U>.M1' + { ... } + } + + bpmd bpmd.exe ExplicitItfImpl`1.IT1<U>.M1 + +Additional examples: + If IT1 and ExplicitItfImpl are types declared inside another class, + Outer, the bpmd command would become: + + bpmd bpmd.exe Outer+ExplicitItfImpl`1.Outer.IT1<U>.M1 + + (note that the fully qualified type name for ExplicitItfImpl became + Outer+ExplicitItfImpl, using the '+' separator, while the method name + is Outer.IT1<U>.M1, using a '.' as the separator) + + Furthermore, if the Outer class resides in a namespace, NS, the bpmd + command to use becomes: + + bpmd bpmd.exe NS.Outer+ExplicitItfImpl`1.NS.Outer.IT1<U>.M1 + +bpmd does not accept offsets nor parameters in the method name. You can add +an IL offset as an optional parameter seperate from the name. If there are overloaded +methods, bpmd will set a breakpoint for all of them. + +In the case of hosted environments such as SQL, the module name may be +complex, like 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. +For this case, just be sure to surround the module name with single quotes, +like: + +bpmd 'price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' Price.M2 + +\\ + +COMMAND: dumpdomain. +DumpDomain [<Domain address>] + +When called with no parameters, DumpDomain will list all the AppDomains in the +process. It enumerates each Assembly loaded into those AppDomains as well. +In addition to your application domain, and any domains it might create, there +are two special domains: the Shared Domain and the System Domain. + +Any Assembly pointer in the output can be passed to DumpAssembly. Any Module +pointer in the output can be passed to DumpModule. Any AppDomain pointer can +be passed to DumpDomain to limit output only to that AppDomain. Other +functions provide an AppDomain pointer as well, such as clrthreads where it lists +the current AppDomain for each thread. +\\ + +COMMAND: eeheap. +EEHeap [-gc] [-loader] + +EEHeap enumerates process memory consumed by internal CLR data structures. You +can limit the output by passing "-gc" or "-loader". All information will be +displayed otherwise. + +The information for the Garbage Collector lists the ranges of each Segment in +the managed heap. This can be useful if you believe you have an object pointer. +If the pointer falls within a segment range given by "eeheap -gc", then you do +have an object pointer, and can attempt to run "dumpobj" on it. + +Here is output for a simple program: + + (lldb) eeheap -gc + Number of GC Heaps: 1 + generation 0 starts at 0x00a71018 + generation 1 starts at 0x00a7100c + generation 2 starts at 0x00a71000 + segment begin allocated size + 00a70000 00a71000 00a7e01c 0000d01c(53276) + Large object heap starts at 0x01a71000 + segment begin allocated size + 01a70000 01a71000 01a76000 0x00005000(20480) + Total Size 0x1201c(73756) + ------------------------------ + GC Heap Size 0x1201c(73756) + +So the total size of the GC Heap is only 72K. On a large web server, with +multiple processors, you can expect to see a GC Heap of 400MB or more. The +Garbage Collector attempts to collect and reclaim memory only when required to +by memory pressure for better performance. You can also see the notion of +"generations," wherein the youngest objects live in generation 0, and +long-lived objects eventually get "promoted" to generation 2. + +The loader output lists various private heaps associated with AppDomains. It +also lists heaps associated with the JIT compiler, and heaps associated with +Modules. For example: + + (lldb) eeheap -loader + Loader Heap: + -------------------------------------- + System Domain: 5e0662a0 + LowFrequencyHeap:008f0000(00002000:00001000) Size: 0x00001000 bytes. + HighFrequencyHeap:008f2000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:008fa000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x3000(12288)bytes + -------------------------------------- + Shared Domain: 5e066970 + LowFrequencyHeap:00920000(00002000:00001000) 03e30000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00922000(00008000:00001000) Size: 0x00001000 bytes. + StubHeap:0092a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x6000(24576)bytes + -------------------------------------- + Domain 1: 14f000 + LowFrequencyHeap:00900000(00002000:00001000) 03ee0000(00010000:00003000) Size: 0x00004000 bytes. + Wasted: 0x00001000 bytes. + HighFrequencyHeap:00902000(00008000:00003000) Size: 0x00003000 bytes. + StubHeap:0090a000(00002000:00001000) Size: 0x00001000 bytes. + Total size: 0x8000(32768)bytes + -------------------------------------- + Jit code heap: + Normal JIT:03ef0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Module Thunk heaps: + Module 5ba22410: Size: 0x00000000 bytes. + Module 001c1320: Size: 0x00000000 bytes. + Module 001c03f0: Size: 0x00000000 bytes. + Module 001caa38: Size: 0x00000000 bytes. + Total size: 0x0(0)bytes + -------------------------------------- + Module Lookup Table heaps: + Module 5ba22410:Size: 0x00000000 bytes. + Module 001c1320:Size: 0x00000000 bytes. + Module 001c03f0:Size: 0x00000000 bytes. + Module 001caa38:03ec0000(00010000:00002000) Size: 0x00002000 bytes. + Total size: 0x2000(8192)bytes + -------------------------------------- + Total LoaderHeap size: 0x15000(86016)bytes + ======================================= + +By using eeheap to keep track of the growth of these private heaps, we are +able to rule out or include them as a source of a memory leak. +\\ + +COMMAND: name2ee. +Name2EE <module name> <type or method name> +Name2EE <module name>!<type or method name> + +This function allows you to turn a class name into a MethodTable and EEClass. +It turns a method name into a MethodDesc. Here is an example for a method: + + (lldb) name2ee unittest.exe MainClass.Main + Module: 001caa38 + Token: 0x0600000d + MethodDesc: 00902f40 + Name: MainClass.Main() + JITTED Code Address: 03ef00b8 + +and for a class: + + (lldb) name2ee unittest!MainClass + Module: 001caa38 + Token: 0x02000005 + MethodTable: 009032d8 + EEClass: 03ee1424 + Name: MainClass + +The module you are "browsing" with Name2EE needs to be loaded in the process. +To get a type name exactly right, first browse the module with ILDASM. You +can also pass * as the <module name> to search all loaded managed modules. +<module name> can also be the debugger's name for a module, such as +mscorlib or image00400000. + +The <module>!<type> syntax is also supported. You can use an asterisk on the +left of the !, but the type on the right side needs to be fully qualified. + +If you are looking for a way to display a static field of a class (and you +don't have an instance of the class, so dumpobj won't help you), note that +once you have the EEClass, you can run DumpClass, which will display the +value of all static fields. + +There is yet one more way to specify a module name. In the case of modules +loaded from an assembly store (such as a SQL db) rather than disk, the +module name will look like this: + +price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + +For this kind of module, simply use price as the module name: + + 0:044> name2ee price Price + Module: 10f028b0 (price, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null) + Token: 0x02000002 + MethodTable: 11a47ae0 + EEClass: 11a538c8 + Name: Price + +Where are we getting these module names from? Run DumpDomain to see a list of +all loaded modules in all domains. And remember that you can browse all the +types in a module with DumpModule -mt <module pointer>. +\\ + +COMMAND: dumpmt. +DumpMT [-MD] <MethodTable address> + +Examine a MethodTable. Each managed object has a MethodTable pointer at the +start. If you pass the "-MD" flag, you'll also see a list of all the methods +defined on the object. +\\ + +COMMAND: dumpclass. +DumpClass <EEClass address> + +The EEClass is a data structure associated with an object type. DumpClass +will show attributes, as well as list the fields of the type. The output is +similar to DumpObj. Although static field values will be displayed, +non-static values won't because you need an instance of an object for that. + +You can get an EEClass to look at from DumpMT, DumpObj, Name2EE, and +Token2EE among others. +\\ + +COMMAND: dumpmd. +DumpMD <MethodDesc address> + +This command lists information about a MethodDesc. You can use ip2md to turn +a code address in a managed function into a MethodDesc: + + (lldb) dumpmd 902f40 + Method Name: Mainy.Main() + Class: 03ee1424 + MethodTable: 009032d8 + mdToken: 0600000d + Module: 001caa78 + IsJitted: yes + CodeAddr: 03ef00b8 + +You can call DumpClass, DumpMT, DumpModule on the Class, MethodTable and Module +fields above. +\\ + +COMMAND: token2ee. +Token2EE <module name> <token> + +This function allows you to turn a metadata token into a MethodTable or +MethodDesc. Here is an example showing class tokens being resolved: + + (lldb) sos Token2EE unittest.exe 02000003 + Module: 001caa38 + Token: 0x02000003 + MethodTable: 0090375c + EEClass: 03ee1ae0 + Name: Bank + (lldb) sos Token2EE image00400000 02000004 + Module: 001caa38 + Token: 0x02000004 + MethodTable: 009038ec + EEClass: 03ee1b84 + Name: Customer + +The module you are "browsing" with Token2EE needs to be loaded in the process. +This function doesn't see much use, especially since a tool like ILDASM can +show the mapping between metadata tokens and types/methods in a friendlier way. +But it could be handy sometimes. + +You can pass "*" for <module name> to find what that token maps to in every +loaded managed module. <module name> can also be the debugger's name for a +module, such as mscorlib or image00400000. +\\ + +COMMAND: dumpmodule. +DumpModule [-mt] <Module address> + +You can get a Module address from DumpDomain, DumpAssembly and other +functions. Here is sample output: + + (lldb) sos DumpModule 1caa50 + Name: /home/user/pub/unittest + Attributes: PEFile + Assembly: 001ca248 + LoaderHeap: 001cab3c + TypeDefToMethodTableMap: 03ec0010 + TypeRefToMethodTableMap: 03ec0024 + MethodDefToDescMap: 03ec0064 + FieldDefToDescMap: 03ec00a4 + MemberRefToDescMap: 03ec00e8 + FileReferencesMap: 03ec0128 + AssemblyReferencesMap: 03ec012c + MetaData start address: 00402230 (1888 bytes) + +The Maps listed map metadata tokens to CLR data structures. Without going into +too much detail, you can examine memory at those addresses to find the +appropriate structures. For example, the TypeDefToMethodTableMap above can be +examined: + + (lldb) dd 3ec0010 + 03ec0010 00000000 00000000 0090320c 0090375c + 03ec0020 009038ec ... + +This means TypeDef token 2 maps to a MethodTable with the value 0090320c. You +can run DumpMT to verify that. The MethodDefToDescMap takes a MethodDef token +and maps it to a MethodDesc, which can be passed to dumpmd. + +There is a new option "-mt", which will display the types defined in a module, +and the types referenced by the module. For example: + + (lldb) sos DumpModule -mt 1aa580 + Name: /home/user/pub/unittest + ...<etc>... + MetaData start address: 0040220c (1696 bytes) + + Types defined in this module + + MT TypeDef Name + -------------------------------------------------------------------------- + 030d115c 0x02000002 Funny + 030d1228 0x02000003 Mainy + + Types referenced in this module + + MT TypeRef Name + -------------------------------------------------------------------------- + 030b6420 0x01000001 System.ValueType + 030b5cb0 0x01000002 System.Object + 030fceb4 0x01000003 System.Exception + 0334e374 0x0100000c System.Console + 03167a50 0x0100000e System.Runtime.InteropServices.GCHandle + 0336a048 0x0100000f System.GC + +\\ + +COMMAND: dumpassembly. +DumpAssembly <Assembly address> + +Example output: + + (lldb) sos DumpAssembly 1ca248 + Parent Domain: 0014f000 + Name: /home/user/pub/unittest + ClassLoader: 001ca060 + Module Name + 001caa50 /home/user/pub/unittest + +An assembly can consist of multiple modules, and those will be listed. You can +get an Assembly address from the output of DumpDomain. +\\ + +COMMAND: dumpruntimetypes. +DumpRuntimeTypes + +DumpRuntimeTypes finds all System.RuntimeType objects in the gc heap and +prints the type name and MethodTable they refer too. Sample output: + + Address Domain MT Type Name + ------------------------------------------------------------------------------ + a515f4 14a740 5baf8d28 System.TypedReference + a51608 14a740 5bb05764 System.Globalization.BaseInfoTable + a51958 14a740 5bb05b24 System.Globalization.CultureInfo + a51a44 14a740 5bb06298 System.Globalization.GlobalizationAssembly + a51de0 14a740 5bb069c8 System.Globalization.TextInfo + a56b98 14a740 5bb12d28 System.Security.Permissions.HostProtectionResource + a56bbc 14a740 5baf7248 System.Int32 + a56bd0 14a740 5baf3fdc System.String + a56cfc 14a740 5baf36a4 System.ValueType + ... + +This command will print a "?" in the domain column if the type is loaded into multiple +AppDomains. For example: + + (lldb) sos DumpRuntimeTypes + Address Domain MT Type Name + ------------------------------------------------------------------------------ + 28435a0 ? 3f6a8c System.TypedReference + 28435b4 ? 214d6c System.ValueType + 28435c8 ? 216314 System.Enum + 28435dc ? 2147cc System.Object + 284365c ? 3cd57c System.IntPtr + 2843670 ? 3feaac System.Byte + 2843684 ? 23a544c System.IEquatable`1[[System.IntPtr, mscorlib]] + 2843784 ? 3c999c System.Int32 + 2843798 ? 3caa04 System.IEquatable`1[[System.Int32, mscorlib]] + +\\ + +COMMAND: verifyheap. +VerifyHeap + +VerifyHeap is a diagnostic tool that checks the garbage collected heap for +signs of corruption. It walks objects one by one in a pattern like this: + + o = firstobject; + while(o != endobject) + { + o.ValidateAllFields(); + o = (Object *) o + o.Size(); + } + +If an error is found, VerifyHeap will report it. I'll take a perfectly good +object and corrupt it: + + (lldb) dumpobj a79d40 + Name: Customer + MethodTable: 009038ec + EEClass: 03ee1b84 + Size: 20(0x14) bytes + (/home/user/pub/unittest) + Fields: + MT Field Offset Type Attr Value Name + 009038ec 4000008 4 CLASS instance 00a79ce4 name + 009038ec 4000009 8 CLASS instance 00a79d2c bank + 009038ec 400000a c System.Boolean instance 1 valid + + (lldb) ed a79d40+4 01 (change the name field to the bogus pointer value 1) + (lldb) sos VerifyHeap + object 01ee60dc: bad member 00000003 at 01EE6168 + Last good object: 01EE60C4. + +If this gc heap corruption exists, there is a serious bug in your own code or +in the CLR. In user code, an error in constructing PInvoke calls can cause +this problem, and running with Managed Debugging Assistants is advised. If that +possibility is eliminated, consider contacting Microsoft Product Support for +help. + +\\ + +COMMAND: dumplog. +DumpLog [-addr <addressOfStressLog>] [<Filename>] + +To aid in diagnosing hard-to-reproduce stress failures, the CLR team added an +in-memory log capability. The idea was to avoid using locks or I/O which could +disturb a fragile repro environment. The DumpLog function allows you to write +that log out to a file. If no Filename is specified, the file "Stresslog.txt" +in the current directory is created. + +The optional argument addr allows one to specify a stress log other then the +default one. + + (lldb) dumplog + Attempting to dump Stress log to file 'StressLog.txt' + ................. + SUCCESS: Stress log dumped + +To turn on the stress log, set the following registry keys under +HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework: + + +(DWORD) StressLog = 1 +(DWORD) LogFacility = 0xffffffbf (this is a bit mask, almost all logging is on. + This is also the default value if the key + isn't specified) +(DWORD) StressLogSize = 65536 (this is the default value if the key isn't + specified) +(DWORD) LogLevel = 6 (this is the default value if the key isn't + specified. The higher the number the more + detailed logs are generated. The maximum + value is decimal 10) + +StressLogSize is the size in bytes of the in-memory log allocated for each +thread in the process. In the case above, each thread gets a 64K log. You +could increase this to get more logging, but more memory will be required for +this log in the process. For example, 20 threads with 524288 bytes per thread +has a memory demand of 10 Megabytes. The stress log is circular so new entries +will replace older ones on threads which have reached their buffer limit. + +The log facilities are defined as follows: + GC 0x00000001 + GCINFO 0x00000002 + STUBS 0x00000004 + JIT 0x00000008 + LOADER 0x00000010 + METADATA 0x00000020 + SYNC 0x00000040 + EEMEM 0x00000080 + GCALLOC 0x00000100 + CORDB 0x00000200 + CLASSLOADER 0x00000400 + CORPROF 0x00000800 + REMOTING 0x00001000 + DBGALLOC 0x00002000 + EH 0x00004000 + ENC 0x00008000 + ASSERT 0x00010000 + VERIFIER 0x00020000 + THREADPOOL 0x00040000 + GCROOTS 0x00080000 + INTEROP 0x00100000 + MARSHALER 0x00200000 + IJW 0x00400000 + ZAP 0x00800000 + STARTUP 0x01000000 + APPDOMAIN 0x02000000 + CODESHARING 0x04000000 + STORE 0x08000000 + SECURITY 0x10000000 + LOCKS 0x20000000 + BCL 0x40000000 + +Here is some sample output: + + 3560 9.981137099 : `SYNC` RareEnablePremptiveGC: entering. + Thread state = a030 + + 3560 9.981135033 : `GC`GCALLOC`GCROOTS` ========== ENDGC 4194 (gen = 2, + collect_classes = 0) ==========={ + + 3560 9.981125826 : `GC` Segment mem 00C61000 alloc + = 00D071F0 used 00D09254 committed 00D17000 + + 3560 9.981125726 : `GC` Generation 0 [00CED07C, 00000000 + ] cur = 00000000 + + 3560 9.981125529 : `GC` Generation 1 [00CED070, 00000000 + ] cur = 00000000 + + 3560 9.981125103 : `GC` Generation 2 [00C61000, 00000000 + ] cur = 00000000 + + 3560 9.981124963 : `GC` GC Heap 00000000 + + 3560 9.980618994 : `GC`GCROOTS` GcScanHandles (Promotion Phase = 0) + +The first column is the OS thread ID for the thread appending to the log, +the second column is the timestamp, the third is the facility category for the +log entry, and the fourth contains the log message. The facility field is +expressed as `facility1`facility2`facility3`. This facilitates the creation of +filters for displaying only specific message categories. To make sense of this +log, you would probably want the Shared Source CLI to find out exactly where +the log comes from. +\\ + +COMMAND: findappdomain. +FindAppDomain <Object address> + +FindAppDomain will attempt to resolve the AppDomain of an object. For example, +using an Object Pointer from the output of DumpStackObjects: + + (lldb) sos FindAppDomain 00a79d98 + AppDomain: 0014f000 + Name: unittest.exe + ID: 1 + +You can find out more about the AppDomain with the DumpDomain command. Not +every object has enough clues about it's origin to determine the AppDomain. +Objects with Finalizers are the easiest case, as the CLR needs to be able to +call those when an AppDomain shuts down. +\\ + + diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index eb71163d1a..1a4232908e 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -2294,6 +2294,7 @@ size_t FormatGeneratedException (DWORD_PTR dataPtr, // or did not update so (when ste is an explicit frames), do not update wszBuffer if (Status == S_OK) { +#ifndef FEATURE_PAL char filename[MAX_LONGPATH+1] = ""; ULONG linenum = 0; if (bLineNumbers @@ -2307,12 +2308,15 @@ size_t FormatGeneratedException (DWORD_PTR dataPtr, if (!bLineNumbers) { +#endif // FEATURE_PAL swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %s\n"), so.String()); +#ifndef FEATURE_PAL } else { swprintf_s(wszLineBuffer, _countof(wszLineBuffer), W(" %s [%S @ %d]\n"), so.String(), filename, linenum); } +#endif // FEATURE_PAL Length += _wcslen(wszLineBuffer); @@ -6102,12 +6106,14 @@ void IssueDebuggerBPCommand ( CLRDATA_ADDRESS addr ) if (!bUnique) { bUnique = TRUE; - for ( int i = 0; i < curLimit; ++i ) + for (int i = 0; i < curLimit; ++i) + { if (alreadyPlacedBPs[i] == addr) { bUnique = FALSE; break; } + } } if (bUnique) { @@ -6115,7 +6121,7 @@ void IssueDebuggerBPCommand ( CLRDATA_ADDRESS addr ) static WCHAR wszNameBuffer[1024]; // should be large enough // get the MethodDesc name - CLRDATA_ADDRESS pMD; + CLRDATA_ADDRESS pMD; if (g_sos->GetMethodDescPtrFromIP(addr, &pMD) != S_OK || g_sos->GetMethodDescName(pMD, 1024, wszNameBuffer, NULL) != S_OK) { @@ -6130,8 +6136,10 @@ void IssueDebuggerBPCommand ( CLRDATA_ADDRESS addr ) ExtOut("Setting breakpoint: %s [%S]\n", buffer, wszNameBuffer); g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); - if ( curLimit < MaxBPsCached ) + if (curLimit < MaxBPsCached) + { alreadyPlacedBPs[curLimit++] = addr; + } } } @@ -6299,6 +6307,21 @@ public: } #endif + void CleanupNotifications() + { +#ifdef FEATURE_PAL + if (m_breakpoints == NULL) + { + ULONG32 flags = 0; + g_clrData->GetOtherNotificationFlags(&flags); + flags &= ~(CLRDATA_NOTIFY_ON_MODULE_LOAD | CLRDATA_NOTIFY_ON_MODULE_UNLOAD); + g_clrData->SetOtherNotificationFlags(flags); + + g_ExtClient->ClearExceptionCallback(); + } +#endif + } + void ClearBreakpoint(size_t breakPointToClear) { PendingBreakpoint *pCur = m_breakpoints; @@ -6320,6 +6343,7 @@ public: { ExtOut("Invalid pending breakpoint index.\n"); } + CleanupNotifications(); } void ClearAllBreakpoints() @@ -6332,11 +6356,14 @@ public: iBreakpointIndex++; pCur = pNext; } + CleanupNotifications(); + ExtOut("All pending breakpoints cleared.\n"); } HRESULT LoadSymbolsForModule(TADDR mod, SymbolReader* pSymbolReader) { +#ifndef FEATURE_PAL HRESULT Status = S_OK; ToRelease<IXCLRDataModule> module; IfFailRet(g_sos->GetModule(mod, &module)); @@ -6374,7 +6401,7 @@ public: ExtOut("SOS warning: No symbols for module %S, source line breakpoints in this module will not bind hr=0x%x\n", wszNameBuffer, Status); return S_FALSE; // not finding symbols is a typical case } - +#endif // FEATURE_PAL return S_OK; } @@ -6906,7 +6933,7 @@ HRESULT HandleCLRNotificationEvent() HRESULT Status = g_clrData->TranslateExceptionRecordToNotification(&dle.ExceptionRecord, &Notification); if (Status != S_OK) { - ExtOut("Error processing exception notification\n"); + ExtErr("Error processing exception notification\n"); return Status; } else @@ -6930,6 +6957,14 @@ HRESULT HandleCLRNotificationEvent() return S_OK; } +#ifdef FEATURE_PAL +HRESULT HandleExceptionNotification(PDEBUG_CLIENT Client) +{ + INIT_API(); + return HandleCLRNotificationEvent(); +} +#endif + DECLARE_API(HandleCLRN) { INIT_API(); @@ -6940,7 +6975,7 @@ DECLARE_API(HandleCLRN) DECLARE_API(bpmd) { - INIT_API_NOEE(); + INIT_API(); MINIDUMP_NOT_SUPPORTED(); int i; char buffer[1024]; @@ -6957,7 +6992,7 @@ DECLARE_API(bpmd) // StringHolder DllName,TypeName; - int lineNumber; + int lineNumber = 0; size_t Offset = 0; DWORD_PTR pMD = NULL; @@ -7029,6 +7064,7 @@ DECLARE_API(bpmd) CHAR* pColon = strchr(DllName.data, ':'); if(NULL != pColon) { +#ifndef FEATURE_PAL fIsFilename = true; *pColon = '\0'; pColon++; @@ -7043,6 +7079,10 @@ DECLARE_API(bpmd) fBadParam = true; } if(nArg != 1) fBadParam = 1; +#else + ExtOut("File name:Line number not supported\n"); + fBadParam = true; +#endif // FEATURE_PAL } } @@ -7083,7 +7123,10 @@ DECLARE_API(bpmd) LPWSTR FunctionName = (LPWSTR)alloca(mdNameLen * sizeof(WCHAR)); LPWSTR Filename = (LPWSTR)alloca(MAX_LONGPATH * sizeof(WCHAR)); - BOOL bNeedNotificationExceptions=FALSE; + BOOL bNeedNotificationExceptions = FALSE; +#ifdef FEATURE_PAL + BOOL bNeedModuleNotificationExceptions = FALSE; +#endif if (pMD == NULL) { @@ -7114,7 +7157,7 @@ DECLARE_API(bpmd) else { // get the module list - moduleList =ModuleFromName(fIsFilename ? NULL : DllName.data, &numModule); + moduleList = ModuleFromName(fIsFilename ? NULL : DllName.data, &numModule); // Its OK if moduleList is NULL // There is a very normal case when checking for modules after clr is loaded @@ -7136,7 +7179,9 @@ DECLARE_API(bpmd) { ToRelease<IXCLRDataModule> ModDef; if (g_sos->GetModule(moduleList[iModule], &ModDef) != S_OK) + { continue; + } HRESULT symbolsLoaded = S_FALSE; if(!fIsFilename) @@ -7199,15 +7244,17 @@ DECLARE_API(bpmd) } } - if(g_bpoints.Update(moduleList[iModule], FALSE)) + if (g_bpoints.Update(moduleList[iModule], FALSE)) + { bNeedNotificationExceptions = TRUE; + } } if (!fNoFutureModule) { // add a pending breakpoint that will find future loaded modules, and // wait for the module load notification. - if(!fIsFilename) + if (!fIsFilename) { g_bpoints.Add(ModuleName, FunctionName, NULL, (DWORD)Offset); } @@ -7216,6 +7263,9 @@ DECLARE_API(bpmd) g_bpoints.Add(Filename, lineNumber, NULL); } bNeedNotificationExceptions = TRUE; +#ifdef FEATURE_PAL + bNeedModuleNotificationExceptions = TRUE; +#endif } } else /* We were given a MethodDesc already */ @@ -7299,13 +7349,14 @@ DECLARE_API(bpmd) sprintf_s(buffer, _countof(buffer), "sxe -c \"!HandleCLRN\" clrn"); Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); #else - sprintf_s(buffer, _countof(buffer), "breakpoint set -E c++ -h false -w true"); - Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); - if (Status == S_OK) + if (bNeedModuleNotificationExceptions) { - sprintf_s(buffer, _countof(buffer), "breakpoint command add -o \"sos HandleCLRN\""); - Status = g_ExtControl->Execute(DEBUG_EXECUTE_NOT_LOGGED, buffer, 0); + ULONG32 flags = 0; + g_clrData->GetOtherNotificationFlags(&flags); + flags |= (CLRDATA_NOTIFY_ON_MODULE_LOAD | CLRDATA_NOTIFY_ON_MODULE_UNLOAD); + g_clrData->SetOtherNotificationFlags(flags); } + Status = g_ExtClient->SetExceptionCallback(HandleExceptionNotification); #endif // FEATURE_PAL } @@ -14141,6 +14192,8 @@ _EFN_GetManagedObjectFieldInfo( return S_OK; } +#endif // FEATURE_PAL + void PrintHelp (__in_z LPCSTR pszCmdName) { static LPSTR pText = NULL; @@ -14157,39 +14210,30 @@ void PrintHelp (__in_z LPCSTR pszCmdName) return; } #else -#define SOS_DOCUMENT_FILENAME "sosdocs.txt" - - char lpFilename[MAX_LONGPATH+12]; // + 12 to make enough room for strcat function. - DWORD nReturnedSize; - nReturnedSize = GetModuleFileName(g_hInstance, lpFilename, MAX_LONGPATH); - if ( nReturnedSize == 0 || nReturnedSize == MAX_LONGPATH ) { - // We consider both of these cases as failed. - ExtOut("Error getting the name for the current module\n"); + int err = PAL_InitializeDLL(); + if(err != 0) + { + ExtOut("Error initializing PAL\n"); return; } - - // Find the last "\" or "/" in the path. - char * pChar = lpFilename + strlen(lpFilename) - 1; - while ( pChar != lpFilename-1 && * pChar != '\\' && * pChar != '/' ) { * pChar-- = 0; } - strcat(lpFilename, SOS_DOCUMENT_FILENAME); + char lpFilename[MAX_LONGPATH + 12]; // + 12 to make enough room for strcat function. + strcpy_s(lpFilename, _countof(lpFilename), g_ExtClient->GetCoreClrDirectory()); + strcat_s(lpFilename, _countof(lpFilename), "sosdocsunix.txt"); - HANDLE hSosDocFile = CreateFileA(lpFilename, - GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + HANDLE hSosDocFile = CreateFileA(lpFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hSosDocFile == INVALID_HANDLE_VALUE) { ExtOut("Error finding documentation file\n"); return; } - HANDLE hMappedSosDocFile = CreateFileMappingA(hSosDocFile, - NULL, PAGE_READONLY, 0, 0, NULL); + HANDLE hMappedSosDocFile = CreateFileMappingA(hSosDocFile, NULL, PAGE_READONLY, 0, 0, NULL); CloseHandle(hSosDocFile); if (hMappedSosDocFile == NULL) { ExtOut("Error mapping documentation file\n"); return; } - pText = (LPSTR)MapViewOfFile(hMappedSosDocFile, - FILE_MAP_READ, 0, 0, 0); + pText = (LPSTR)MapViewOfFile(hMappedSosDocFile, FILE_MAP_READ, 0, 0, 0); CloseHandle(hMappedSosDocFile); if (pText == NULL) { @@ -14300,5 +14344,3 @@ Help(PDEBUG_CLIENT Client, PCSTR Args) return S_OK; } - -#endif // FEATURE_PAL diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp index 9e7de5412b..8066a22ce9 100644 --- a/src/ToolBox/SOS/Strike/util.cpp +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -5247,7 +5247,7 @@ OutputVaList( va_list args) { #ifdef FEATURE_PAL - char str[1024]; + char str[4096]; // Try and format our string into a fixed buffer first and see if it fits int length = _vsnprintf(str, sizeof(str), format, args); @@ -5548,9 +5548,15 @@ NoOutputHolder::~NoOutputHolder() // Code to support mapping RVAs to managed code line numbers. // +#ifndef FEATURE_PAL + // // This function retrieves ImageInfo related to the module // containing the addressed passed in "Base". +// +// NOTE: This doesn't work on xplat/PAL because the managed +// assembly PE isn't in the native debugger's module list. +// HRESULT GetImageFromBase( ___in ULONG64 Base, @@ -5815,7 +5821,6 @@ ConvertNativeToIlOffset( return Status; } - // Based on a native offset, passed in the first argument this function // identifies the corresponding source file name and line number. HRESULT @@ -5825,11 +5830,6 @@ GetLineByOffset( __out_ecount(cbFileName) LPSTR lpszFileName, ___in ULONG cbFileName) { - -#ifdef FEATURE_PAL - return E_FAIL; -#else - HRESULT hr = S_OK; ULONG64 displacement = 0; @@ -5910,10 +5910,9 @@ fallback: cbFileName, NULL, &displacement); - -#endif // FEATURE_PAL } +#endif // FEATURE_PAL void TableOutput::ReInit(int numColumns, int defaultColumnWidth, Alignment alignmentDefault, int indent, int padding) { @@ -6521,7 +6520,8 @@ WString MethodNameFromIP(CLRDATA_ADDRESS ip, BOOL bSuppressLines) { methodOutput = W("<unknown>"); } - + +#ifndef FEATURE_PAL if (!bSuppressLines && SUCCEEDED(GetLineByOffset(TO_CDADDR(ip), &linenum, filename, MAX_LONGPATH+1))) { @@ -6531,6 +6531,7 @@ WString MethodNameFromIP(CLRDATA_ADDRESS ip, BOOL bSuppressLines) methodOutput += WString(W(" [")) + wfilename + W(" @ ") + Decimal(linenum) + W("]"); } +#endif // FEATURE_PAL } return methodOutput; diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h index aa455cb18e..aadf353d64 100644 --- a/src/ToolBox/SOS/Strike/util.h +++ b/src/ToolBox/SOS/Strike/util.h @@ -2400,12 +2400,14 @@ public: HRESULT SymbolReader::ResolveSequencePoint(__in_z WCHAR* pFilename, ULONG32 lineNumber, mdMethodDef* pToken, ULONG32* pIlOffset); }; +#ifndef FEATURE_PAL HRESULT GetLineByOffset( ___in ULONG64 IP, ___out ULONG *pLinenum, __out_ecount(cbFileName) LPSTR lpszFileName, ___in ULONG cbFileName); +#endif // FEATURE_PAL /// X86 Context #define X86_SIZE_OF_80387_REGISTERS 80 diff --git a/src/ToolBox/SOS/lldbplugin/debugclient.cpp b/src/ToolBox/SOS/lldbplugin/debugclient.cpp index fad404a820..bb34da7f3b 100644 --- a/src/ToolBox/SOS/lldbplugin/debugclient.cpp +++ b/src/ToolBox/SOS/lldbplugin/debugclient.cpp @@ -13,9 +13,11 @@ ULONG g_currentThreadIndex = -1; ULONG g_currentThreadSystemId = -1; char *g_coreclrDirectory; -DebugClient::DebugClient(lldb::SBDebugger &debugger, lldb::SBCommandReturnObject &returnObject) : +DebugClient::DebugClient(lldb::SBDebugger &debugger, lldb::SBCommandReturnObject &returnObject, lldb::SBProcess *process, lldb::SBThread *thread) : m_debugger(debugger), - m_returnObject(returnObject) + m_returnObject(returnObject), + m_currentProcess(process), + m_currentThread(thread) { returnObject.SetStatus(lldb::eReturnStatusSuccessFinishResult); } @@ -171,7 +173,8 @@ DebugClient::Execute( } // PAL raise exception function and exception record pointer variable name -// See coreclr\src\pal\src\exception\seh-unwind.cpp for the details. +// See coreclr\src\pal\src\exception\seh-unwind.cpp for the details. This +// function depends on RtlpRaisException not being inlined or optimized. #define FUNCTION_NAME "RtlpRaiseException" #define VARIABLE_NAME "ExceptionRecord" @@ -206,12 +209,15 @@ DebugClient::GetLastEventInformation( { return E_FAIL; } - lldb::SBThread thread = process.GetSelectedThread(); + lldb::SBThread thread = GetCurrentThread(); if (!thread.IsValid()) { return E_FAIL; } + *processId = process.GetProcessID(); + *threadId = thread.GetThreadID(); + // Enumerate each stack frame at the special "throw" // breakpoint and find the raise exception function // with the exception record parameter. @@ -1196,6 +1202,70 @@ DebugClient::VirtualUnwind( return S_OK; } +bool +ExceptionBreakpointCallback( + void *baton, + lldb::SBProcess &process, + lldb::SBThread &thread, + lldb::SBBreakpointLocation &location) +{ + lldb::SBDebugger debugger = process.GetTarget().GetDebugger(); + + // Send the normal and error output to stdout/stderr since we + // don't have a return object from the command interpreter. + lldb::SBCommandReturnObject result; + result.SetImmediateOutputFile(stdout); + result.SetImmediateErrorFile(stderr); + + // Save the process and thread to be used by the current process/thread + // helper functions. + DebugClient* client = new DebugClient(debugger, result, &process, &thread); + return ((PFN_EXCEPTION_CALLBACK)baton)(client) == S_OK; +} + +lldb::SBBreakpoint g_exceptionbp; + +HRESULT +DebugClient::SetExceptionCallback( + PFN_EXCEPTION_CALLBACK callback) +{ + if (!g_exceptionbp.IsValid()) + { + lldb::SBTarget target = m_debugger.GetSelectedTarget(); + if (!target.IsValid()) + { + return E_FAIL; + } + lldb::SBBreakpoint exceptionbp = target.BreakpointCreateForException(lldb::LanguageType::eLanguageTypeC_plus_plus, false, true); + if (!exceptionbp.IsValid()) + { + return E_FAIL; + } +#ifdef FLAGS_ANONYMOUS_ENUM + exceptionbp.AddName("DoNotDeleteOrDisable"); +#endif + exceptionbp.SetCallback(ExceptionBreakpointCallback, (void *)callback); + g_exceptionbp = exceptionbp; + } + return S_OK; +} + +HRESULT +DebugClient::ClearExceptionCallback() +{ + if (g_exceptionbp.IsValid()) + { + lldb::SBTarget target = m_debugger.GetSelectedTarget(); + if (!target.IsValid()) + { + return E_FAIL; + } + target.BreakpointDelete(g_exceptionbp.GetID()); + g_exceptionbp = lldb::SBBreakpoint(); + } + return S_OK; +} + //---------------------------------------------------------------------------- // Helper functions //---------------------------------------------------------------------------- @@ -1205,10 +1275,17 @@ DebugClient::GetCurrentProcess() { lldb::SBProcess process; - lldb::SBTarget target = m_debugger.GetSelectedTarget(); - if (target.IsValid()) + if (m_currentProcess == nullptr) { - process = target.GetProcess(); + lldb::SBTarget target = m_debugger.GetSelectedTarget(); + if (target.IsValid()) + { + process = target.GetProcess(); + } + } + else + { + process = *m_currentProcess; } return process; @@ -1219,10 +1296,17 @@ DebugClient::GetCurrentThread() { lldb::SBThread thread; - lldb::SBProcess process = GetCurrentProcess(); - if (process.IsValid()) + if (m_currentThread == nullptr) + { + lldb::SBProcess process = GetCurrentProcess(); + if (process.IsValid()) + { + thread = process.GetSelectedThread(); + } + } + else { - thread = process.GetSelectedThread(); + thread = *m_currentThread; } return thread; diff --git a/src/ToolBox/SOS/lldbplugin/debugclient.h b/src/ToolBox/SOS/lldbplugin/debugclient.h index 036c17360f..e0ceda28fc 100644 --- a/src/ToolBox/SOS/lldbplugin/debugclient.h +++ b/src/ToolBox/SOS/lldbplugin/debugclient.h @@ -9,17 +9,21 @@ private: lldb::SBDebugger &m_debugger; lldb::SBCommandReturnObject &m_returnObject; + lldb::SBProcess *m_currentProcess; + lldb::SBThread *m_currentThread; + void OutputString(ULONG mask, PCSTR str); - lldb::SBProcess GetCurrentProcess(); - lldb::SBThread GetCurrentThread(); - lldb::SBFrame GetCurrentFrame(); ULONG64 GetModuleBase(lldb::SBTarget& target, lldb::SBModule& module); DWORD_PTR GetExpression(lldb::SBFrame& frame, lldb::SBError& error, PCSTR exp); void GetContextFromFrame(lldb::SBFrame& frame, DT_CONTEXT *dtcontext); DWORD_PTR GetRegister(lldb::SBFrame& frame, const char *name); + lldb::SBProcess GetCurrentProcess(); + lldb::SBThread GetCurrentThread(); + lldb::SBFrame GetCurrentFrame(); + public: - DebugClient(lldb::SBDebugger &debugger, lldb::SBCommandReturnObject &returnObject); + DebugClient(lldb::SBDebugger &debugger, lldb::SBCommandReturnObject &returnObject, lldb::SBProcess *process = nullptr, lldb::SBThread *thread = nullptr); ~DebugClient(); //---------------------------------------------------------------------------- @@ -198,4 +202,9 @@ public: DWORD threadID, ULONG32 contextSize, PBYTE context); + + HRESULT SetExceptionCallback( + PFN_EXCEPTION_CALLBACK callback); + + HRESULT ClearExceptionCallback(); }; diff --git a/src/ToolBox/SOS/lldbplugin/inc/dbgeng.h b/src/ToolBox/SOS/lldbplugin/inc/dbgeng.h index 04b27fbf8e..83803fb150 100644 --- a/src/ToolBox/SOS/lldbplugin/inc/dbgeng.h +++ b/src/ToolBox/SOS/lldbplugin/inc/dbgeng.h @@ -447,6 +447,9 @@ typedef class IDebugRegister* PDEBUG_REGISTERS; // IDebugClient //---------------------------------------------------------------------------- +class IDebugClient; +typedef HRESULT (*PFN_EXCEPTION_CALLBACK)(IDebugClient* client); + class IDebugClient : IDebugControl2, IDebugDataSpaces, IDebugSymbols, IDebugSystemObjects, IDebugRegister { public: @@ -458,10 +461,18 @@ public: virtual DWORD_PTR GetExpression( /* [in] */ PCSTR exp) = 0; + // Unwind one native stack frame given a thread and register context virtual HRESULT VirtualUnwind( /* [in] */ DWORD threadID, /* [in] */ ULONG32 contextSize, /* [in, out, size_is(contextSize)] */ PBYTE context) = 0; + + // Set an exception throw callback + virtual HRESULT SetExceptionCallback( + /* [in] */ PFN_EXCEPTION_CALLBACK callback) = 0; + + // Clear the exception throw callback + virtual HRESULT ClearExceptionCallback() = 0; }; typedef class IDebugClient* PDEBUG_CLIENT; diff --git a/src/ToolBox/SOS/lldbplugin/setclrpathcommand.cpp b/src/ToolBox/SOS/lldbplugin/setclrpathcommand.cpp index e5fda7b727..ab91f9e734 100644 --- a/src/ToolBox/SOS/lldbplugin/setclrpathcommand.cpp +++ b/src/ToolBox/SOS/lldbplugin/setclrpathcommand.cpp @@ -24,23 +24,23 @@ public: { if (arguments[0] == NULL) { - result.Printf("setclrpath error - no path\n"); - return false; + result.Printf("Load path for sos/dac/dbi: '%s'\n", g_coreclrDirectory == NULL ? "<none>" : g_coreclrDirectory); } + else { + if (g_coreclrDirectory != NULL) + { + free(g_coreclrDirectory); + } - if (g_coreclrDirectory != NULL) - { - free(g_coreclrDirectory); - } + std::string path(arguments[0]); + if (path[path.length() - 1] != '/') + { + path.append("/"); + } - std::string path(arguments[0]); - if (path[path.length() - 1] != '/') - { - path.append("/"); + g_coreclrDirectory = strdup(path.c_str()); + result.Printf("Set load path for sos/dac/dbi to '%s'\n", g_coreclrDirectory); } - - g_coreclrDirectory = strdup(path.c_str()); - result.Printf("Set load path for sos/dac/dbi to %s\n", g_coreclrDirectory); return result.Succeeded(); } }; diff --git a/src/ToolBox/SOS/lldbplugin/setsostidcommand.cpp b/src/ToolBox/SOS/lldbplugin/setsostidcommand.cpp index 76d477baac..ab7e5298bb 100644 --- a/src/ToolBox/SOS/lldbplugin/setsostidcommand.cpp +++ b/src/ToolBox/SOS/lldbplugin/setsostidcommand.cpp @@ -24,13 +24,23 @@ public: { if (arguments[0] == NULL) { - result.Printf("Clearing sos thread os id/index\n"); + if (g_currentThreadSystemId == -1 || g_currentThreadIndex == -1) + { + result.Printf("sos OS tid not mapped\n"); + } + else { + result.Printf("sos OS tid 0x%x mapped to lldb thread index %d\n", + g_currentThreadSystemId, g_currentThreadIndex); + } + } + else if (strcmp(arguments[0], "-clear") == 0) { g_currentThreadIndex = -1; g_currentThreadSystemId = -1; + result.Printf("Cleared sos OS tid/index\n"); } else if (arguments[1] == NULL) { - result.Printf("Need thread index parameter that maps to the os id\n"); + result.Printf("Need thread index parameter that maps to the OS tid\n"); } else { @@ -40,7 +50,7 @@ public: ULONG index = strtoul(arguments[1], NULL, 16); g_currentThreadIndex = index; - result.Printf("Set sos thread os id to 0x%x which maps to lldb thread index %d\n", tid, index); + result.Printf("Mapped sos OS tid 0x%x to lldb thread index %d\n", tid, index); } return result.Succeeded(); } diff --git a/src/ToolBox/SOS/lldbplugin/soscommand.cpp b/src/ToolBox/SOS/lldbplugin/soscommand.cpp index e9a20565e6..71065b866b 100644 --- a/src/ToolBox/SOS/lldbplugin/soscommand.cpp +++ b/src/ToolBox/SOS/lldbplugin/soscommand.cpp @@ -120,16 +120,21 @@ bool sosCommandInitialize(lldb::SBDebugger debugger) { lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter(); - interpreter.AddCommand("sos", new sosCommand(NULL), "Various coreclr debugging commands. sos <command-name> <args>"); + interpreter.AddCommand("sos", new sosCommand(NULL), "Various coreclr debugging commands. See 'soshelp' for more details. sos <command-name> <args>"); interpreter.AddCommand("bpmd", new sosCommand("bpmd"), "Creates a breakpoint at the specified managed method in the specified module."); interpreter.AddCommand("clrstack", new sosCommand("ClrStack"), "Provides a stack trace of managed code only."); interpreter.AddCommand("clrthreads", new sosCommand("Threads"), "List the managed threads running."); interpreter.AddCommand("dumpheap", new sosCommand("DumpHeap"), "Displays info about the garbage-collected heap and collection statistics about objects."); + interpreter.AddCommand("dumplog", new sosCommand("DumpLog"), "Writes the contents of an in-memory stress log to the specified file."); + interpreter.AddCommand("dumpmd", new sosCommand("DumpMD"), "Displays information about a MethodDesc structure at the specified address."); + interpreter.AddCommand("dumpmt", new sosCommand("DumpMT"), "Displays information about a method table at the specified address."); interpreter.AddCommand("dumpobj", new sosCommand("DumpObj"), "Displays info about an object at the specified address."); interpreter.AddCommand("dso", new sosCommand("DumpStackObjects"), "Displays all managed objects found within the bounds of the current stack."); interpreter.AddCommand("eeheap", new sosCommand("EEHeap"), "Displays info about process memory consumed by internal runtime data structures."); interpreter.AddCommand("gcroot", new sosCommand("GCRoot"), "Displays info about references (or roots) to an object at the specified address."); interpreter.AddCommand("ip2md", new sosCommand("IP2MD"), "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled."); - interpreter.AddCommand("printexception", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); + interpreter.AddCommand("name2ee", new sosCommand("Name2EE"), "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module."); + interpreter.AddCommand("pe", new sosCommand("PrintException"), "Displays and formats fields of any object derived from the Exception class at the specified address."); + interpreter.AddCommand("soshelp", new sosCommand("Help"), "Displays all available commands when no parameter is specified, or displays detailed help information about the specified command. soshelp <command>"); return true; } diff --git a/src/pal/src/exception/seh-unwind.cpp b/src/pal/src/exception/seh-unwind.cpp index f0749e7b29..dde7ba87fc 100644 --- a/src/pal/src/exception/seh-unwind.cpp +++ b/src/pal/src/exception/seh-unwind.cpp @@ -556,9 +556,16 @@ Note: The name of this function and the name of the ExceptionRecord parameter is used in the sos lldb plugin code to read the exception record. See coreclr\src\ToolBox\SOS\lldbplugin\debugclient.cpp. + + This function must not be inlined or optimized so the below PAL_VirtualUnwind + calls end up with RaiseException caller's context and so the above debugger + code finds the function and ExceptionRecord parameter. --*/ PAL_NORETURN -static void RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord) +__attribute__((noinline)) +__attribute__((optnone)) +static void +RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord) { // Capture the context of RtlpRaiseException. CONTEXT ContextRecord; @@ -572,6 +579,7 @@ static void RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord) // The frame we're looking at now is RaiseException. We have to unwind one // level further to get the actual context user code could be resumed at. PAL_VirtualUnwind(&ContextRecord, NULL); + #if defined(_X86_) ExceptionRecord->ExceptionAddress = (void *) ContextRecord.Eip; #elif defined(_AMD64_) @@ -582,19 +590,7 @@ static void RtlpRaiseException(EXCEPTION_RECORD *ExceptionRecord) #error unsupported architecture #endif - EXCEPTION_POINTERS pointers; - pointers.ExceptionRecord = ExceptionRecord; - pointers.ContextRecord = &ContextRecord; - - SEHRaiseException(InternalGetCurrentThread(), &pointers, 0); -} - -PAL_NORETURN -void SEHRaiseException(CPalThread *pthrCurrent, - PEXCEPTION_POINTERS lpExceptionPointers, - int signal_code) -{ - throw PAL_SEHException(lpExceptionPointers->ExceptionRecord, lpExceptionPointers->ContextRecord); + throw PAL_SEHException(ExceptionRecord, &ContextRecord); } /*++ diff --git a/src/pal/src/include/pal/seh.hpp b/src/pal/src/include/pal/seh.hpp index 7abddec882..4946d944e9 100644 --- a/src/pal/src/include/pal/seh.hpp +++ b/src/pal/src/include/pal/seh.hpp @@ -75,32 +75,6 @@ Return value: VOID SEHProcessException(PEXCEPTION_POINTERS pointers); -/*++ -Function : - SEHRaiseException - - Raise an exception given a specified exception information. - -Parameters : - CPalThread * pthrCurrent : reference to the current thread. - PEXCEPTION_POINTERS lpExceptionPointers : specification of exception - to raise. - int signal_code : signal that caused the exception, if applicable; - 0 otherwise - - (no return value; function should never return) - -Notes : - The PAL does not support continuing execution after an exception was raised - (using EXCEPTION_CONTINUE_EXECUTION). For this reason, this function should - never return. ---*/ -PAL_NORETURN -void SEHRaiseException( - CorUnix::CPalThread *pthrCurrent, - PEXCEPTION_POINTERS lpExceptionPointers, - int signal_code ); - #if !HAVE_MACH_EXCEPTIONS // TODO: Implement for Mach exceptions. Not in CoreCLR surface area. /*++ |