EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateVersionFile", "eng\CreateVersionFile.csproj", "{365381BB-2A89-4F52-BC66-1BB4A9D514BB}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lldbplugin", "src\SOS\lldbplugin\lldbplugin.vcxproj", "{D52C65C4-2C7D-45E6-9F5C-6F3A96796018}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|ARM.Build.0 = Debug|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|ARM64.Build.0 = Debug|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x64.ActiveCfg = Debug|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x64.Build.0 = Debug|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x86.ActiveCfg = Debug|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|x86.Build.0 = Debug|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|ARM.ActiveCfg = Release|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|ARM.Build.0 = Release|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|ARM64.Build.0 = Release|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x64.ActiveCfg = Release|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x64.Build.0 = Release|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x86.ActiveCfg = Release|Any CPU
{20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|x86.Build.0 = Release|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|ARM.Build.0 = Debug|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|ARM64.Build.0 = Debug|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x64.ActiveCfg = Debug|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x64.Build.0 = Debug|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x86.ActiveCfg = Debug|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Debug|x86.Build.0 = Debug|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|ARM.ActiveCfg = Release|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|ARM.Build.0 = Release|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|ARM64.Build.0 = Release|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x64.ActiveCfg = Release|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x64.Build.0 = Release|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x86.ActiveCfg = Release|Any CPU
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD}.Release|x86.Build.0 = Release|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|ARM.Build.0 = Debug|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|ARM64.Build.0 = Debug|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x64.ActiveCfg = Debug|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x64.Build.0 = Debug|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x86.ActiveCfg = Debug|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Debug|x86.Build.0 = Debug|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|Any CPU.ActiveCfg = Release|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|Any CPU.Build.0 = Release|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|ARM.ActiveCfg = Release|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|ARM.Build.0 = Release|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|ARM64.Build.0 = Release|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x64.ActiveCfg = Release|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x64.Build.0 = Release|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x86.ActiveCfg = Release|Any CPU
{730C1201-1848-4F1E-8C1F-6316FB886C35}.Release|x86.Build.0 = Release|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|ARM.Build.0 = Debug|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|ARM64.Build.0 = Debug|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x64.ActiveCfg = Debug|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x64.Build.0 = Debug|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x86.ActiveCfg = Debug|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Debug|x86.Build.0 = Debug|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|ARM.ActiveCfg = Release|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|ARM.Build.0 = Release|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|ARM64.Build.0 = Release|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x64.ActiveCfg = Release|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x64.Build.0 = Release|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x86.ActiveCfg = Release|Any CPU
{1532DB3C-7DCD-45C6-B697-62B8378A16A2}.Release|x86.Build.0 = Release|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|ARM.Build.0 = Debug|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|ARM64.Build.0 = Debug|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|x64.ActiveCfg = Debug|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|x64.Build.0 = Debug|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Debug|x86.Build.0 = Debug|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|ARM.ActiveCfg = Release|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|ARM.Build.0 = Release|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|ARM64.Build.0 = Release|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|x64.ActiveCfg = Release|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|x64.Build.0 = Release|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|x86.ActiveCfg = Release|Any CPU
{365381BB-2A89-4F52-BC66-1BB4A9D514BB}.Release|x86.Build.0 = Release|Any CPU
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|ARM.ActiveCfg = Debug|ARM
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|ARM.Build.0 = Debug|ARM
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|ARM64.Build.0 = Debug|ARM64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|x64.ActiveCfg = Debug|x64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|x64.Build.0 = Debug|x64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|x86.ActiveCfg = Debug|x86
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Debug|x86.Build.0 = Debug|x86
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|Any CPU.ActiveCfg = Release|x86
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|ARM.ActiveCfg = Release|ARM
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|ARM.Build.0 = Release|ARM
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|ARM64.ActiveCfg = Release|ARM64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|ARM64.Build.0 = Release|ARM64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|x64.ActiveCfg = Release|x64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|x64.Build.0 = Release|x64
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|x86.ActiveCfg = Release|x86
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
{20513BA2-A156-4A17-4C70-5AC2DBD4F833} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{6C43BE85-F8C3-4D76-8050-F25CE953A7FD} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
{1532DB3C-7DCD-45C6-B697-62B8378A16A2} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
+ {D52C65C4-2C7D-45E6-9F5C-6F3A96796018} = {41638A4C-0DAF-47ED-A774-ECBBAC0315D7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
DumpObj (dumpobj) Threads (clrthreads)
DumpArray ThreadState
DumpAsync (dumpasync) IP2MD (ip2md)
-DumpStackObjects (dso) u (clru)
-DumpHeap (dumpheap) DumpStack (dumpstack)
-DumpVC EEStack (eestack)
-GCRoot (gcroot) CLRStack (clrstack)
-PrintException (pe) GCInfo
- EHInfo
+DumpDelegate (dumpdelegate) u (clru)
+DumpStackObjects (dso) DumpStack (dumpstack)
+DumpHeap (dumpheap) EEStack (eestack)
+DumpVC CLRStack (clrstack)
+GCRoot (gcroot) GCInfo
+PrintException (pe) EHInfo
bpmd (bpmd)
-
+
Examining CLR data structures Diagnostic Utilities
----------------------------- -----------------------------
DumpDomain (dumpdomain) VerifyHeap
EEHeap (eeheap) FindAppDomain
Name2EE (name2ee) DumpLog (dumplog)
+SyncBlk (syncblk)
DumpMT (dumpmt)
DumpClass (dumpclass)
DumpMD (dumpmd)
\\
COMMAND: dumpasync.
-DumpAsync [-mt <MethodTable address>]
+DumpAsync [-addr <Object Address>]
+ [-mt <MethodTable address>]
[-type <partial type name>]
- [-waiting]
+ [-tasks]
+ [-completed]
+ [-fields]
+ [-stacks]
[-roots]
DumpAsync traverses the garbage collected heap, looking for objects representing
async state machines as created when an async method's state is transferred to the
heap. This command recognizes async state machines defined as "async void", "async Task",
-"async Task<T>", "async ValueTask", and "async ValueTask<T>".
-
-The output includes a block of details for each async state machine object found.
-These details include:
- - a line for the type of the async state machine object, including its MethodTable address,
- its object address, its size, and its type name.
- - a line for the state machine type name as contained in the object.
- - a listing of each field on the state machine.
- - a line for a continuation from this state machine object, if one or more has been registered.
- - discovered GC roots for this async state machine object.
-
-For example:
-
- (lldb) dumpasync -roots
- #0
- 000001989f413de0 00007ff88c506ba8 112 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
- StateMachine: Program+<MethodD>d__4 (struct)
- MT Field Offset Type VT Attr Value Name
- 00007ff8d3df4b80 400000d 0 System.Int32 1 instance 0 <>1__state
- 00007ff8d3e082c0 400000e 8 ...TaskMethodBuilder 1 instance 000001989f413e38 <>t__builder
- 00007ff8d3dfea90 400000f 10 ...vices.TaskAwaiter 1 instance 000001989f413e40 <>u__1
- Continuation: 000001989f413e50 (System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]])
- GC roots:
- Thread 2936c:
- 000000071a37e050 00007ff8d3ac1657 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [d:\repos\coreclr\src\System.Private.CoreLib\src\System\Threading\Tasks\Task.cs @ 2977]
- rbp+10: 000000071a37e0c0
- -> 000001989f413fa0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<Main>d__0, test]]
- -> 000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodA>d__1, test]]
- -> 000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodB>d__2, test]]
- -> 000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]]
- -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
- HandleTable:
- 000001989d8415f8 (pinned handle)
- -> 00000198af3e1038 System.Object[]
- -> 000001989f413410 System.Threading.TimerQueue[]
- -> 000001989f413468 System.Threading.TimerQueue
- -> 000001989f413330 System.Threading.TimerQueueTimer
- -> 000001989f412e40 System.Threading.Tasks.Task+DelayPromise
- -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
- ...
-
-
-The arguments in detail:
-
--mt List only those state machine objects with the MethodTable given.
--type List only those state machine objects whose type name is a
- substring match of the string provided.
--waiting List only those state machines that are currently at an await point.
--roots Include GC root information for each state machine object.
-
+"async Task<T>", "async ValueTask", and "async ValueTask<T>". It also optionally supports
+any other tasks.
\\
COMMAND: dumpstackobjects.
The abbreviation dso can be used for brevity.
\\
+COMMAND: dumpdelegate.
+DumpDelegate <delegate address>
+
+DumpDelegate finds and outputs the one or more method descriptors associated with a delegate object.
+
+For example:
+
+ 0:000> !dumpdelegate
+ Target Method Name
+ 000001461bacb0d8 00007ffc5c894b80 ConsoleApp16.Program.InstanceMethod()
+ 000001461bacb098 00007ffc5c894b68 ConsoleApp16.Program.StaticMethod()
+\\
+
COMMAND: dumpheap.
DumpHeap [-stat]
[-strings]
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
+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.
-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
+-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.
types in a module with DumpModule -mt <module pointer>.
\\
+COMMAND: syncblk.
+SyncBlk [-all | <syncblk number>]
+
+A SyncBlock is a holder for extra information that doesn't need to be created
+for every object. It can hold COM Interop data, HashCodes, and locking
+information for thread-safe operations.
+
+When called without arguments, syncblk will print the list of SyncBlocks
+corresponding to objects that are owned by a thread. For example, a
+
+ lock(MyObject)
+ {
+ ....
+ }
+
+statement will set MyObject to be owned by the current thread. A SyncBlock will
+be created for MyObject, and the thread ownership information stored there
+(this is an oversimplification, see NOTE below). If another thread tries to
+execute the same code, they won't be able to enter the block until the first
+thread exits.
+
+This makes syncblk useful for detecting managed deadlocks. Consider that the
+following code is executed by Threads A & B:
+
+ Resource r1 = new Resource();
+ Resource r2 = new Resource();
+
+ ...
+
+ lock(r1) lock(r2)
+ { {
+ lock(r2) lock(r1)
+ { {
+ ... ...
+ } }
+ } }
+
+This is a deadlock situation, as Thread A could take r1, and Thread B r2,
+leaving both threads with no option but to wait forever in the second lock
+statement. syncblk will detect this with the following output:
+
+ 0:003> syncblk
+ Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
+ 238 001e40ec 3 1 001e4e60 e04 3 00a7a194 Resource
+ 239 001e4124 3 1 001e5980 ab8 4 00a7a1a4 Resource
+
+It means that Thread e04 owns object 00a7a194, and Thread ab8 owns object
+00a7a1a4. Combine that information with the call stacks of the deadlock:
+
+(threads 3 and 4 have similar output)
+ 0:003> bt
+ ChildEBP RetAddr
+ 0404ea04 77f5c524 SharedUserData!SystemCallStub+0x4
+ 0404ea08 77e75ee0 ntdll!NtWaitForMultipleObjects+0xc
+ 0404eaa4 5d9de9d6 KERNEL32!WaitForMultipleObjectsEx+0x12c
+ 0404eb38 5d9def80 clr!Thread::DoAppropriateAptStateWait+0x156
+ 0404ecc4 5d9dd8bb clr!Thread::DoAppropriateWaitWorker+0x360
+ 0404ed20 5da628dd clr!Thread::DoAppropriateWait+0xbb
+ 0404ede4 5da4e2e2 clr!CLREvent::Wait+0x29d
+ 0404ee70 5da4dd41 clr!AwareLock::EnterEpilog+0x132
+ 0404ef34 5da4efa3 clr!AwareLock::Enter+0x2c1
+ 0404f09c 5d767880 clr!AwareLock::Contention+0x483
+ 0404f1c4 03f00229 clr!JITutil_MonContention+0x2c0
+ 0404f1f4 5b6ef077 image00400000!Worker.Work()+0x79
+ ...
+
+By looking at the code corresponding to Worker.Work()+0x79 (run "clru 03f00229"),
+you can see that thread 3 is attempting to acquire the Resource 00a7a1a4, which
+is owned by thread 4.
+
+NOTE:
+It is not always the case that a SyncBlock will be created for every object
+that is locked by a thread. In version 2.0 of the CLR and above, a mechanism
+called a ThinLock will be used if there is not already a SyncBlock for the
+object in question. ThinLocks will not be reported by the syncblk command.
+You can use "dumpheap -thinlock" to list objects locked in this way.
+\\
+
COMMAND: dumpmt.
DumpMT [-MD] <MethodTable address>
#include "ExpressionNode.h"
#include "WatchCmd.h"
-#include <set>
#include <algorithm>
-#include <vector>
#include "tls.h"
#endif // !FEATURE_PAL
+#include <set>
+#include <vector>
+#include <map>
+
BOOL CallStatus;
BOOL ControlC = FALSE;
#ifndef FEATURE_PAL
HMODULE g_hInstance = NULL;
-#include <vector>
#include <algorithm>
#endif // !FEATURE_PAL
return Status;
}
+/**********************************************************************\
+* Routine Description: *
+* *
+* This function is called to dump the contents of a delegate from a *
+* given address. *
+* *
+\**********************************************************************/
+
+DECLARE_API(DumpDelegate)
+{
+ INIT_API();
+ MINIDUMP_NOT_SUPPORTED();
+
+ try
+ {
+ BOOL dml = FALSE;
+ DWORD_PTR dwAddr = 0;
+
+ CMDOption option[] =
+ { // name, vptr, type, hasValue
+ {"/d", &dml, COBOOL, FALSE}
+ };
+ CMDValue arg[] =
+ { // vptr, type
+ {&dwAddr, COHEX}
+ };
+ size_t nArg;
+ if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+ {
+ return Status;
+ }
+ if (nArg != 1)
+ {
+ ExtOut("Usage: !DumpDelegate <delegate object address>\n");
+ return Status;
+ }
+
+ EnableDMLHolder dmlHolder(dml);
+ CLRDATA_ADDRESS delegateAddr = TO_CDADDR(dwAddr);
+
+ if (!sos::IsObject(delegateAddr))
+ {
+ ExtOut("Invalid object.\n");
+ }
+ else
+ {
+ sos::Object delegateObj = TO_TADDR(delegateAddr);
+ if (!IsDerivedFrom(TO_CDADDR(delegateObj.GetMT()), W("System.Delegate")))
+ {
+ ExtOut("Object of type '%S' is not a delegate.", delegateObj.GetTypeName());
+ }
+ else
+ {
+ ExtOut("Target Method Name\n");
+
+ std::vector<CLRDATA_ADDRESS> delegatesRemaining;
+ delegatesRemaining.push_back(delegateAddr);
+ while (delegatesRemaining.size() > 0)
+ {
+ delegateAddr = delegatesRemaining.back();
+ delegatesRemaining.pop_back();
+ delegateObj = TO_TADDR(delegateAddr);
+
+ int offset;
+ if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_target"))) != 0)
+ {
+ CLRDATA_ADDRESS target;
+ MOVE(target, delegateObj.GetAddress() + offset);
+
+ if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_invocationList"))) != 0)
+ {
+ CLRDATA_ADDRESS invocationList;
+ MOVE(invocationList, delegateObj.GetAddress() + offset);
+
+ if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_invocationCount"))) != 0)
+ {
+ int invocationCount;
+ MOVE(invocationCount, delegateObj.GetAddress() + offset);
+
+ if (invocationList == NULL)
+ {
+ CLRDATA_ADDRESS md;
+ DMLOut("%s ", DMLObject(target));
+ if (TryGetMethodDescriptorForDelegate(delegateAddr, &md))
+ {
+ DMLOut("%s ", DMLMethodDesc(md));
+ NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
+ ExtOut("%S\n", g_mdName);
+ }
+ else
+ {
+ ExtOut("(unknown)\n");
+ }
+ }
+ else if (sos::IsObject(invocationList, false))
+ {
+ DacpObjectData objData;
+ if (objData.Request(g_sos, invocationList) == S_OK &&
+ objData.ObjectType == OBJ_ARRAY &&
+ invocationCount <= objData.dwNumComponents)
+ {
+ for (int i = 0; i < invocationCount; i++)
+ {
+ CLRDATA_ADDRESS elementPtr;
+ MOVE(elementPtr, TO_CDADDR(objData.ArrayDataPtr + (i * objData.dwComponentSize)));
+ if (elementPtr != NULL && sos::IsObject(elementPtr, false))
+ {
+ delegatesRemaining.push_back(elementPtr);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return S_OK;
+ }
+ catch (const sos::Exception &e)
+ {
+ ExtOut("%s\n", e.what());
+ return E_FAIL;
+ }
+}
+
CLRDATA_ADDRESS isExceptionObj(CLRDATA_ADDRESS mtObj)
{
// We want to follow back until we get the mt for System.Exception
/**********************************************************************\
* Routine Description: *
* *
-* This function dumps async state machines on GC heap, *
+* This function dumps async state machines on GC heap, *
* displaying details about each async operation found. *
* (May not work if GC is in progress.) *
* *
\**********************************************************************/
+
+void ResolveContinuation(CLRDATA_ADDRESS* contAddr)
+{
+ // Ideally this continuation is itself an async method box.
+ sos::Object contObj = TO_TADDR(*contAddr);
+ if (GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("StateMachine")) == 0)
+ {
+ // It was something else.
+
+ // If it's a standard task continuation, get its task field.
+ int offset;
+ if ((offset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("m_task"))) != 0)
+ {
+ MOVE(*contAddr, contObj.GetAddress() + offset);
+ if (sos::IsObject(*contAddr, false))
+ {
+ contObj = TO_TADDR(*contAddr);
+ }
+ }
+ else
+ {
+ // If it's storing an action wrapper, try to follow to that action's target.
+ if ((offset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("m_action"))) != 0)
+ {
+ MOVE(*contAddr, contObj.GetAddress() + offset);
+ if (sos::IsObject(*contAddr, false))
+ {
+ contObj = TO_TADDR(*contAddr);
+ }
+ }
+
+ // If it was, or if it's storing an action, try to follow through to the action's target.
+ if ((offset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("_target"))) != 0)
+ {
+ MOVE(*contAddr, contObj.GetAddress() + offset);
+ if (sos::IsObject(*contAddr, false))
+ {
+ contObj = TO_TADDR(*contAddr);
+ }
+ }
+ }
+
+ // Use whatever object we ended with.
+ *contAddr = contObj.GetAddress();
+ }
+}
+
+bool TryGetContinuation(CLRDATA_ADDRESS addr, CLRDATA_ADDRESS mt, CLRDATA_ADDRESS* contAddr)
+{
+ // Get the continuation field from the task.
+ int offset = GetObjFieldOffset(addr, mt, W("m_continuationObject"));
+ if (offset != 0)
+ {
+ DWORD_PTR contObjPtr;
+ MOVE(contObjPtr, addr + offset);
+ if (sos::IsObject(contObjPtr, false))
+ {
+ *contAddr = TO_CDADDR(contObjPtr);
+ ResolveContinuation(contAddr);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+struct AsyncRecord
+{
+ CLRDATA_ADDRESS Address;
+ CLRDATA_ADDRESS MT;
+ DWORD Size;
+ CLRDATA_ADDRESS StateMachineAddr;
+ CLRDATA_ADDRESS StateMachineMT;
+ BOOL FilteredByOptions;
+ BOOL IsStateMachine;
+ BOOL IsValueType;
+ BOOL IsTopLevel;
+ int TaskStateFlags;
+ int StateValue;
+ std::vector<CLRDATA_ADDRESS> Continuations;
+};
+
+bool AsyncRecordIsCompleted(AsyncRecord& ar)
+{
+ const int TASK_STATE_COMPLETED_MASK = 0x1600000;
+ return (ar.TaskStateFlags & TASK_STATE_COMPLETED_MASK) != 0;
+}
+
+const char* GetAsyncRecordStatusDescription(AsyncRecord& ar)
+{
+ const int TASK_STATE_RAN_TO_COMPLETION = 0x1000000;
+ const int TASK_STATE_FAULTED = 0x200000;
+ const int TASK_STATE_CANCELED = 0x400000;
+
+ if ((ar.TaskStateFlags & TASK_STATE_RAN_TO_COMPLETION) != 0) return "Success";
+ if ((ar.TaskStateFlags & TASK_STATE_FAULTED) != 0) return "Failed";
+ if ((ar.TaskStateFlags & TASK_STATE_CANCELED) != 0) return "Canceled";
+ return "Pending";
+}
+
+void ExtOutTaskDelegateMethod(sos::Object& obj)
+{
+ DacpFieldDescData actionField;
+ int offset = GetObjFieldOffset(obj.GetAddress(), obj.GetMT(), W("m_action"), TRUE, &actionField);
+ if (offset != 0)
+ {
+ CLRDATA_ADDRESS actionAddr;
+ MOVE(actionAddr, obj.GetAddress() + offset);
+ CLRDATA_ADDRESS actionMD;
+ if (actionAddr != NULL && TryGetMethodDescriptorForDelegate(actionAddr, &actionMD))
+ {
+ NameForMD_s((DWORD_PTR)actionMD, g_mdName, mdNameLen);
+ ExtOut("(%S) ", g_mdName);
+ }
+ }
+}
+
+void ExtOutTaskStateFlagsDescription(int stateFlags)
+{
+ if (stateFlags == 0) return;
+
+ ExtOut("State Flags: ");
+
+ // TaskCreationOptions.*
+ if ((stateFlags & 0x01) != 0) ExtOut("PreferFairness ");
+ if ((stateFlags & 0x02) != 0) ExtOut("LongRunning ");
+ if ((stateFlags & 0x04) != 0) ExtOut("AttachedToParent ");
+ if ((stateFlags & 0x08) != 0) ExtOut("DenyChildAttach ");
+ if ((stateFlags & 0x10) != 0) ExtOut("HideScheduler ");
+ if ((stateFlags & 0x40) != 0) ExtOut("RunContinuationsAsynchronously ");
+
+ // InternalTaskOptions.*
+ if ((stateFlags & 0x0200) != 0) ExtOut("ContinuationTask ");
+ if ((stateFlags & 0x0400) != 0) ExtOut("PromiseTask ");
+ if ((stateFlags & 0x1000) != 0) ExtOut("LazyCancellation ");
+ if ((stateFlags & 0x2000) != 0) ExtOut("QueuedByRuntime ");
+ if ((stateFlags & 0x4000) != 0) ExtOut("DoNotDispose ");
+
+ // TASK_STATE_*
+ if ((stateFlags & 0x10000) != 0) ExtOut("STARTED ");
+ if ((stateFlags & 0x20000) != 0) ExtOut("DELEGATE_INVOKED ");
+ if ((stateFlags & 0x40000) != 0) ExtOut("DISPOSED ");
+ if ((stateFlags & 0x80000) != 0) ExtOut("EXCEPTIONOBSERVEDBYPARENT ");
+ if ((stateFlags & 0x100000) != 0) ExtOut("CANCELLATIONACKNOWLEDGED ");
+ if ((stateFlags & 0x200000) != 0) ExtOut("FAULTED ");
+ if ((stateFlags & 0x400000) != 0) ExtOut("CANCELED ");
+ if ((stateFlags & 0x800000) != 0) ExtOut("WAITING_ON_CHILDREN ");
+ if ((stateFlags & 0x1000000) != 0) ExtOut("RAN_TO_COMPLETION ");
+ if ((stateFlags & 0x2000000) != 0) ExtOut("WAITINGFORACTIVATION ");
+ if ((stateFlags & 0x4000000) != 0) ExtOut("COMPLETION_RESERVED ");
+ if ((stateFlags & 0x8000000) != 0) ExtOut("THREAD_WAS_ABORTED ");
+ if ((stateFlags & 0x10000000) != 0) ExtOut("WAIT_COMPLETION_NOTIFICATION ");
+ if ((stateFlags & 0x20000000) != 0) ExtOut("EXECUTIONCONTEXT_IS_NULL ");
+ if ((stateFlags & 0x40000000) != 0) ExtOut("TASKSCHEDULED_WAS_FIRED ");
+
+ ExtOut("\n");
+}
+
DECLARE_API(DumpAsync)
{
INIT_API();
{
// Process command-line arguments.
size_t nArg = 0;
- TADDR mt = NULL;
+ TADDR mt = NULL, addr = NULL;
ArrayHolder<char> ansiType = NULL;
ArrayHolder<WCHAR> type = NULL;
- BOOL dml = FALSE, waiting = FALSE, roots = FALSE;
+ BOOL dml = FALSE, includeCompleted = FALSE, includeStacks = FALSE, includeRoots = FALSE, includeAllTasks = FALSE, dumpFields = FALSE;
CMDOption option[] =
{ // name, vptr, type, hasValue
- { "-mt", &mt, COHEX, TRUE }, // dump state machines only with a given MethodTable
- { "-type", &ansiType, COSTRING, TRUE }, // dump state machines only that contain the specified type substring
- { "-waiting", &waiting, COBOOL, FALSE }, // dump state machines only when they're in a waiting state
- { "-roots", &roots, COBOOL, FALSE }, // gather GC root information
+ { "-addr", &addr, COHEX, TRUE }, // dump only the async object at the specified address
+ { "-mt", &mt, COHEX, TRUE }, // dump only async objects with a given MethodTable
+ { "-type", &ansiType, COSTRING, TRUE }, // dump only async objects that contain the specified type substring
+ { "-tasks", &includeAllTasks, COBOOL, FALSE }, // include all tasks that can be found on the heap, not just async methods
+ { "-completed", &includeCompleted, COBOOL, FALSE }, // include async objects that are in a completed state
+ { "-fields", &dumpFields, COBOOL, FALSE }, // show relevant fields of found async objects
+ { "-stacks", &includeStacks, COBOOL, FALSE }, // gather and output continuation/stack information
+ { "-roots", &includeRoots, COBOOL, FALSE }, // gather and output GC root information
#ifndef FEATURE_PAL
- { "/d", &dml, COBOOL, FALSE }, // Debugger Markup Language
+ { "/d", &dml, COBOOL, FALSE }, // Debugger Markup Language
#endif
};
- if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg))
- {
- sos::Throw<sos::Exception>("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting] [-roots]");
- }
- if (nArg != 0)
- {
- sos::Throw<sos::Exception>("Unexpected command-line arguments.");
+ if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg) || nArg != 0)
+ {
+ sos::Throw<sos::Exception>(
+ "Usage: DumpAsync [-addr ObjectAddr] [-mt MethodTableAddr] [-type TypeName] [-tasks] [-completed] [-fields] [-stacks] [-roots]\n"
+ "[-addr ObjectAddr] => Only display the async object at the specified address.\n"
+ "[-mt MethodTableAddr] => Only display top-level async objects with the specified method table address.\n"
+ "[-type TypeName] => Only display top-level async objects whose type name includes the specified substring.\n"
+ "[-tasks] => Include Task and Task-derived objects, in addition to any state machine objects found.\n"
+ "[-completed] => Include async objects that represent completed operations but that are still on the heap.\n"
+ "[-fields] => Show the fields of state machines.\n"
+ "[-stacks] => Gather, output, and consolidate based on continuation chains / async stacks for discovered async objects.\n"
+ "[-roots] => Perform a gcroot on each rendered async object.\n"
+ );
}
if (ansiType != NULL)
{
- if (mt != NULL)
- {
- sos::Throw<sos::Exception>("Cannot specify both -mt and -type");
- }
-
size_t ansiTypeLen = strlen(ansiType) + 1;
type = new WCHAR[ansiTypeLen];
MultiByteToWideChar(CP_ACP, 0, ansiType, -1, type, (int)ansiTypeLen);
}
+
EnableDMLHolder dmlHolder(dml);
+ BOOL hasTypeFilter = mt != NULL || ansiType != NULL || addr != NULL;
// Display a message if the heap isn't verified.
sos::GCHeap gcheap;
DisplayInvalidStructuresMessage();
}
- // Print out header for the main line of each async state machine object.
- ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s %s\n", "Address", "MT", "Size", "Name");
-
- // Walk each heap object looking for async state machine objects.
- BOOL missingStateFieldWarning = FALSE;
- int numStateMachines = 0;
+ // Walk each heap object looking for async state machine objects. As we're targeting .NET Core 2.1+, all such objects
+ // will be Task or Task-derived types.
+ std::map<CLRDATA_ADDRESS, AsyncRecord> asyncRecords;
for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr)
{
- // Skip objects we know to be too small to possibly be a state machine.
- // This helps filter out some caching data structures generated by the compiler.
- if (itr->GetSize() <= 24)
+ // Skip objects too small to be state machines or tasks, avoiding some compiler-generated caching data structures.
+ if (itr->GetSize() <= 24)
{
continue;
}
- // Match only MTs the user requested.
- if (mt != NULL && mt != itr->GetMT())
+ // Match only async objects.
+ if (includeAllTasks)
{
- continue;
- }
-
- // Match only type name substrings the user requested.
- if (type != NULL && _wcsstr(itr->GetTypeName(), type) == NULL)
- {
- continue;
+ // If the user has selected to include all tasks and not just async state machine boxes, we simply need to validate
+ // that this is Task or Task-derived, and if it's not, skip it.
+ if (!IsDerivedFrom(itr->GetMT(), W("System.Threading.Tasks.Task")))
+ {
+ continue;
+ }
}
-
- // Match only the two known state machine class name prefixes.
- if (_wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1"), 79) != 0 && // Normal box.
- _wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+DebugFinalizableAsyncStateMachineBox`1"), 95) != 0) // Used when certain ETW events enabled.
+ else
{
- continue;
+ // Otherwise, we only care about AsyncStateMachineBox`1 as well as the DebugFinalizableAsyncStateMachineBox`1
+ // that's used when certain ETW events are set.
+ if (_wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1"), 79) != 0 &&
+ _wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+DebugFinalizableAsyncStateMachineBox`1"), 95) != 0)
+ {
+ continue;
+ }
}
- // Get the async state machine object's StateMachine field. If we can't, it's not
- // an async state machine we can handle.
+ // Create an AsyncRecord to store the state for this instance. We're likely going to keep the object at this point,
+ // though we may still discard/skip it with a few checks later; to do that, though, we'll need some of the info
+ // gathered here, so we construct the record to store the data.
+ AsyncRecord ar;
+ ar.Address = itr->GetAddress();
+ ar.MT = itr->GetMT();
+ ar.Size = (DWORD)itr->GetSize();
+ ar.StateMachineAddr = itr->GetAddress();
+ ar.StateMachineMT = itr->GetMT();
+ ar.IsValueType = false;
+ ar.IsTopLevel = true;
+ ar.IsStateMachine = false;
+ ar.TaskStateFlags = 0;
+ ar.StateValue = 0;
+ ar.FilteredByOptions = // we process all objects to support forming proper chains, but then only display ones that match the user's request
+ (mt == NULL || mt == itr->GetMT()) && // Match only MTs the user requested.
+ (type == NULL || _wcsstr(itr->GetTypeName(), type) != NULL) && // Match only type name substrings the user requested.
+ (addr == NULL || addr == itr->GetAddress()); // Match only the object at the specified address.
+
+ // Get the state flags for the task. This is used to determine whether async objects are completed (and thus should
+ // be culled by default). It avoids our needing to depend on interpreting the compiler's "<>1__state" field, and also lets
+ // us display state information for non-async state machine objects.
+ DacpFieldDescData stateFlagsField;
+ int offset = GetObjFieldOffset(ar.Address, ar.MT, W("m_stateFlags"), TRUE, &stateFlagsField);
+ if (offset != 0)
+ {
+ MOVE(ar.TaskStateFlags, ar.Address + offset);
+ }
+
+ // Get the async state machine object's StateMachine field.
DacpFieldDescData stateMachineField;
int stateMachineFieldOffset = GetObjFieldOffset(TO_CDADDR(itr->GetAddress()), itr->GetMT(), W("StateMachine"), TRUE, &stateMachineField);
- if (stateMachineFieldOffset <= 0)
+ if (stateMachineFieldOffset != 0)
{
- continue;
+ ar.IsStateMachine = true;
+ ar.IsValueType = stateMachineField.Type == ELEMENT_TYPE_VALUETYPE;
+
+ // Get the address and method table of the state machine. While it'll generally be a struct, it is valid for it to be a
+ // class (the C# compiler generates a class in debug builds to better support Edit-And-Continue), so we accommodate both.
+ DacpFieldDescData stateField;
+ int stateFieldOffset = -1;
+ if (ar.IsValueType)
+ {
+ ar.StateMachineAddr = itr->GetAddress() + stateMachineFieldOffset;
+ ar.StateMachineMT = stateMachineField.MTOfType;
+ stateFieldOffset = GetValueFieldOffset(ar.StateMachineMT, W("<>1__state"), &stateField);
+ }
+ else
+ {
+ MOVE(ar.StateMachineAddr, itr->GetAddress() + stateMachineFieldOffset);
+ DacpObjectData objData;
+ if (objData.Request(g_sos, ar.StateMachineAddr) == S_OK)
+ {
+ ar.StateMachineMT = objData.MethodTable; // update from Canon to actual type
+ stateFieldOffset = GetObjFieldOffset(ar.StateMachineAddr, ar.StateMachineMT, W("<>1__state"), TRUE, &stateField);
+ }
+ }
+
+ if (stateFieldOffset >= 0 && (ar.IsValueType || stateFieldOffset != 0))
+ {
+ MOVE(ar.StateValue, ar.StateMachineAddr + stateFieldOffset);
+ }
}
- // Get the address and method table of the state machine. While it'll generally be a struct,
- // it is valid for it to be a class, so we accommodate both.
- BOOL bStateMachineIsValueType = stateMachineField.Type == ELEMENT_TYPE_VALUETYPE;
- CLRDATA_ADDRESS stateMachineAddr;
- CLRDATA_ADDRESS stateMachineMT;
- if (bStateMachineIsValueType)
+ // If we only want to include incomplete async objects, skip this one if it's completed.
+ if (!includeCompleted && AsyncRecordIsCompleted(ar))
{
- stateMachineAddr = itr->GetAddress() + stateMachineFieldOffset;
- stateMachineMT = stateMachineField.MTOfType;
+ continue;
}
- else
+
+ // If the user has asked to include "async stacks" information, resolve any continuation
+ // that might be registered with it. This could be a single continuation, or it could
+ // be a list of continuations in the case of the same task being awaited multiple times.
+ CLRDATA_ADDRESS nextAddr;
+ if (includeStacks && TryGetContinuation(itr->GetAddress(), itr->GetMT(), &nextAddr))
{
- MOVE(stateMachineAddr, itr->GetAddress() + stateMachineFieldOffset);
- DacpObjectData objData;
- if (objData.Request(g_sos, stateMachineAddr) != S_OK)
+ sos::Object contObj = TO_TADDR(nextAddr);
+ if (_wcsncmp(contObj.GetTypeName(), W("System.Collections.Generic.List`1"), 33) == 0)
{
- // Couldn't get the class-based object; just skip this state machine.
- continue;
+ // The continuation is a List<object>. Iterate through its internal object[]
+ // looking for non-null objects, and adding each one as a continuation.
+ int itemsOffset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("_items"));
+ if (itemsOffset != 0)
+ {
+ DWORD_PTR listItemsPtr;
+ MOVE(listItemsPtr, contObj.GetAddress() + itemsOffset);
+ if (sos::IsObject(listItemsPtr, false))
+ {
+ DacpObjectData objData;
+ if (objData.Request(g_sos, TO_CDADDR(listItemsPtr)) == S_OK && objData.ObjectType == OBJ_ARRAY)
+ {
+ for (int i = 0; i < objData.dwNumComponents; i++)
+ {
+ CLRDATA_ADDRESS elementPtr;
+ MOVE(elementPtr, TO_CDADDR(objData.ArrayDataPtr + (i * objData.dwComponentSize)));
+ if (elementPtr != NULL && sos::IsObject(elementPtr, false))
+ {
+ ResolveContinuation(&elementPtr);
+ ar.Continuations.push_back(elementPtr);
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ ar.Continuations.push_back(contObj.GetAddress());
}
- stateMachineMT = objData.MethodTable; // update from Canon to actual type
}
- // Get the current state value of the state machine. If the user has requested to filter down
- // to only those state machines that are currently at an await, compare it against the expected
- // waiting values. This value can also be used in later analysis.
- int stateValue = -2;
- DacpFieldDescData stateField;
- int stateFieldOffset = bStateMachineIsValueType ?
- GetValueFieldOffset(stateMachineMT, W("<>1__state"), &stateField) :
- GetObjFieldOffset(stateMachineAddr, stateMachineMT, W("<>1__state"), TRUE, &stateField);
- if (stateFieldOffset < 0 || (!bStateMachineIsValueType && stateFieldOffset == 0))
+ // We've gathered all of the needed information for this heap object. Add it to our list of async records.
+ asyncRecords.insert(std::pair<CLRDATA_ADDRESS, AsyncRecord>(ar.Address, ar));
+ }
+
+ // As with DumpHeap, output a summary table about all of the objects we found. In contrast, though, his is based on the filtered
+ // list of async records we gathered rather than everything on the heap.
+ if (addr == NULL) // no point in stats if we're only targeting a single object
+ {
+ HeapStat stats;
+ for (std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt)
{
- missingStateFieldWarning = TRUE;
- if (waiting)
+ if (!hasTypeFilter || arIt->second.FilteredByOptions)
{
- // waiting was specified and we couldn't find the field to satisfy the query,
- // so skip this object.
- continue;
+ stats.Add((DWORD_PTR)arIt->second.MT, (DWORD)arIt->second.Size);
}
}
- else
+ stats.Sort();
+ stats.Print();
+ }
+
+ // If the user has asked for "async stacks" and if there's not MT/type name filter, look through all of our async records
+ // to find the "top-level" nodes that start rather than that are a part of a continuation chain. When we then iterate through
+ // async records, we only print ones out that are still classified as top-level. We don't do this if there's a type filter
+ // because in that case we consider those and only those objects to be top-level.
+ if (includeStacks && !hasTypeFilter)
+ {
+ size_t uniqueChains = asyncRecords.size();
+ for (std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt)
{
- MOVE(stateValue, stateMachineAddr + stateFieldOffset);
- if (waiting && stateValue < 0)
+ for (std::vector<CLRDATA_ADDRESS>::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt)
{
- // 0+ values correspond to the await in linear sequence in the method, so a non-negative
- // value indicates the state machine is at an await. Since we're filtering for waiting,
- // anything else should be skipped.
- continue;
+ std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator found = asyncRecords.find(*contIt);
+ if (found != asyncRecords.end())
+ {
+ if (found->second.IsTopLevel)
+ {
+ found->second.IsTopLevel = false;
+ uniqueChains--;
+ }
+ }
}
}
- // We now have a state machine that's passed all of our criteria. Print out its details.
+ ExtOut("In %d chains.\n", uniqueChains);
+ }
- // Print out top level description of the state machine object.
- ExtOut("#%d\n", numStateMachines);
- numStateMachines++;
- DMLOut("%s %s %8d", DMLObject(itr->GetAddress()), DMLDumpHeapMT(itr->GetMT()), itr->GetSize());
- ExtOut(" %S\n", itr->GetTypeName());
+ // Print out header for the main line of each result.
+ ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s ", "Address", "MT", "Size");
+ if (includeCompleted) ExtOut("%8s ", "Status");
+ ExtOut("%10s %s\n", "State", "Description");
+
+ // Output each top-level async record.
+ int counter = 0;
+ for (std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt)
+ {
+ if (!arIt->second.IsTopLevel || (hasTypeFilter && !arIt->second.FilteredByOptions))
+ {
+ continue;
+ }
- // Output the state machine's name and fields.
+ // Output the state machine's details as a single line.
+ sos::Object obj = TO_TADDR(arIt->second.Address);
DacpMethodTableData mtabledata;
DacpMethodTableFieldData vMethodTableFields;
- if (mtabledata.Request(g_sos, stateMachineMT) == S_OK &&
- vMethodTableFields.Request(g_sos, stateMachineMT) == S_OK &&
+ if (arIt->second.IsStateMachine &&
+ mtabledata.Request(g_sos, arIt->second.StateMachineMT) == S_OK &&
+ vMethodTableFields.Request(g_sos, arIt->second.StateMachineMT) == S_OK &&
vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0)
{
- sos::MethodTable mt = (TADDR)stateMachineMT;
- ExtOut("StateMachine: %S (%s)\n", mt.GetName(), bStateMachineIsValueType ? "struct" : "class");
- DisplayFields(stateMachineMT, &mtabledata, &vMethodTableFields, (DWORD_PTR)stateMachineAddr, TRUE, bStateMachineIsValueType);
+ // This has a StateMachine. Output its details.
+ sos::MethodTable mt = TO_TADDR(arIt->second.StateMachineMT);
+ DMLOut("%s %s %8d ", DMLAsync(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize());
+ if (includeCompleted) ExtOut("%8s ", GetAsyncRecordStatusDescription(arIt->second));
+ ExtOut("%10d %S\n", arIt->second.StateValue, mt.GetName());
+ if (dumpFields) DisplayFields(arIt->second.StateMachineMT, &mtabledata, &vMethodTableFields, (DWORD_PTR)arIt->second.StateMachineAddr, TRUE, arIt->second.IsValueType);
+ }
+ else
+ {
+ // This does not have a StateMachine. Output the details of the Task itself.
+ DMLOut("%s %s %8d ", DMLAsync(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize());
+ if (includeCompleted) ExtOut("%8s ", GetAsyncRecordStatusDescription(arIt->second));
+ ExtOut("[%08x] %S ", arIt->second.TaskStateFlags, obj.GetTypeName());
+ ExtOutTaskDelegateMethod(obj);
+ ExtOut("\n");
+ if (dumpFields) ExtOutTaskStateFlagsDescription(arIt->second.TaskStateFlags);
}
- // If the object already has a registered continuation, output it.
- int iContOffset = GetObjFieldOffset(TO_CDADDR(itr->GetAddress()), itr->GetMT(), W("m_continuationObject"));
- if (iContOffset > 0)
+ // If we gathered any continuations for this record, output the chains now.
+ if (includeStacks && arIt->second.Continuations.size() > 0)
{
- DWORD_PTR ContObjPtr;
- MOVE(ContObjPtr, itr->GetAddress() + iContOffset);
- DMLOut("Continuation: %s", DMLObject(ContObjPtr));
- if (sos::IsObject(ContObjPtr, false))
+ ExtOut(includeAllTasks ? "Continuation chains:\n" : "Async \"stack\":\n");
+ std::vector<std::pair<int, CLRDATA_ADDRESS>> continuationChainToExplore;
+ continuationChainToExplore.push_back(std::pair<int, CLRDATA_ADDRESS>(1, obj.GetAddress()));
+
+ // Do a depth-first traversal of continuations, outputting each continuation found and then
+ // looking in our gathered objects list for its continuations.
+ std::set<CLRDATA_ADDRESS> seen;
+ while (continuationChainToExplore.size() > 0)
{
- sos::Object contObj = ContObjPtr;
- ExtOut(" (%S)", contObj.GetTypeName());
+ // Pop the next continuation from the stack.
+ std::pair<int, CLRDATA_ADDRESS> cur = continuationChainToExplore.back();
+ continuationChainToExplore.pop_back();
+
+ // Get the async record for this continuation. It should be one we already know about.
+ std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator curAsyncRecord = asyncRecords.find(cur.second);
+ if (curAsyncRecord == asyncRecords.end())
+ {
+ continue;
+ }
+
+ // Make sure to avoid cycles in the rare case where async records may refer to each other.
+ if (seen.find(cur.second) != seen.end())
+ {
+ continue;
+ }
+ seen.insert(cur.second);
+
+ // Iterate through all continuations from this object.
+ for (std::vector<CLRDATA_ADDRESS>::iterator contIt = curAsyncRecord->second.Continuations.begin(); contIt != curAsyncRecord->second.Continuations.end(); ++contIt)
+ {
+ sos::Object cont = TO_TADDR(*contIt);
+
+ // Print out the depth of the continuation with dots, then its address.
+ for (int i = 0; i < cur.first; i++) ExtOut(".");
+ DMLOut("%s ", DMLObject(cont.GetAddress()));
+
+ // Print out the name of the method for this task's delegate if it has one (state machines won't, but others tasks may).
+ ExtOutTaskDelegateMethod(cont);
+
+ // Find the async record for this continuation, and output its name. If it's a state machine,
+ // also output its current state value so that a user can see at a glance its status.
+ std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator contAsyncRecord = asyncRecords.find(cont.GetAddress());
+ if (contAsyncRecord != asyncRecords.end())
+ {
+ sos::MethodTable contMT = TO_TADDR(contAsyncRecord->second.StateMachineMT);
+ if (contAsyncRecord->second.IsStateMachine) ExtOut("(%d) ", contAsyncRecord->second.StateValue);
+ ExtOut("%S\n", contMT.GetName());
+ }
+ else
+ {
+ ExtOut("%S\n", cont.GetTypeName());
+ }
+
+ // Add this continuation to the stack to explore.
+ continuationChainToExplore.push_back(std::pair<int, CLRDATA_ADDRESS>(cur.first + 1, *contIt));
+ }
}
- ExtOut("\n");
}
- // Finally, output gcroots, as they can serve as call stacks, and also help to highlight
- // state machines that aren't being kept alive.
- if (roots)
+ // Finally, output gcroots, as they can serve as alternative/more detailed "async stacks", and also help to highlight
+ // state machines that aren't being kept alive. However, they're more expensive to compute, so they're opt-in.
+ if (includeRoots)
{
ExtOut("GC roots:\n");
IncrementIndent();
GCRootImpl gcroot;
- int numRoots = gcroot.PrintRootsForObject(*itr, FALSE, FALSE);
+ int numRoots = gcroot.PrintRootsForObject(obj.GetAddress(), FALSE, FALSE);
DecrementIndent();
-
- if (stateValue >= 0 && numRoots == 0)
+ if (numRoots == 0 && !AsyncRecordIsCompleted(arIt->second))
{
- ExtOut("Incomplete state machine (<>1__state == %d) with 0 roots.\n", stateValue);
+ ExtOut("Incomplete state machine or task with 0 roots.\n");
}
}
- ExtOut("\n");
+ // If we're rendering more than one line per entry, output a separator to help distinguish the entries.
+ if (dumpFields || includeStacks || includeRoots)
+ {
+ ExtOut("--------------------------------------------------------------------------------\n");
+ }
}
- ExtOut("\nFound %d state machines.\n", numStateMachines);
- if (missingStateFieldWarning)
- {
- ExtOut("Warning: Could not find a state machine's <>1__state field.\n");
- }
return S_OK;
}
catch (const sos::Exception &e)
#endif // FEATURE_PAL
}
+#endif // FEATURE_PAL
+
/**********************************************************************\
* Routine Description: *
* *
ExtOut("-----------------------------\n");
ExtOut("Total %d\n", dwCount);
+#ifdef FEATURE_COMINTEROP
ExtOut("CCW %d\n", CCWCount);
ExtOut("RCW %d\n", RCWCount);
ExtOut("ComClassFactory %d\n", CFCount);
+#endif
ExtOut("Free %d\n", freeCount);
return Status;
}
+#ifndef FEATURE_PAL
+
#ifdef FEATURE_COMINTEROP
struct VisitRcwArgs
{
if ((Status = threadpool.Request(g_sos)) == S_OK)
{
- BOOL doHCDump = FALSE;
+ BOOL doHCDump = FALSE, doWorkItemDump = FALSE, dml = FALSE;
CMDOption option[] =
{ // name, vptr, type, hasValue
- {"-ti", &doHCDump, COBOOL, FALSE}
+ {"-ti", &doHCDump, COBOOL, FALSE},
+ {"-wi", &doWorkItemDump, COBOOL, FALSE},
+#ifndef FEATURE_PAL
+ {"/d", &dml, COBOOL, FALSE},
+#endif
};
if (!GetCMDOption(args, option, _countof(option), NULL, 0, NULL))
return Status;
}
+ EnableDMLHolder dmlHolder(dml);
+
ExtOut ("CPU utilization: %d%%\n", threadpool.cpuUtilization);
ExtOut ("Worker Thread:");
ExtOut (" Total: %d", threadpool.NumWorkingWorkerThreads + threadpool.NumIdleWorkerThreads + threadpool.NumRetiredWorkerThreads);
workRequestPtr = workRequestData.NextWorkRequest;
}
+ if (doWorkItemDump && g_snapshot.Build())
+ {
+ // Display a message if the heap isn't verified.
+ sos::GCHeap gcheap;
+ if (!gcheap.AreGCStructuresValid())
+ {
+ DisplayInvalidStructuresMessage();
+ }
+
+ // Walk every heap item looking for the global queue and local queues.
+ ExtOut("\nQueued work items:\n%" POINTERSIZE "s %" POINTERSIZE "s %s\n", "Queue", "Address", "Work Item");
+ HeapStat stats;
+ for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr)
+ {
+ if (_wcscmp(itr->GetTypeName(), W("System.Threading.ThreadPoolWorkQueue")) == 0)
+ {
+ // We found a global queue (there should be only one, given one AppDomain).
+ // Get its workItems ConcurrentQueue<IThreadPoolWorkItem>.
+ int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("workItems"));
+ if (offset > 0)
+ {
+ DWORD_PTR workItemsConcurrentQueuePtr;
+ MOVE(workItemsConcurrentQueuePtr, itr->GetAddress() + offset);
+ if (sos::IsObject(workItemsConcurrentQueuePtr, false))
+ {
+ // We got the ConcurrentQueue. Get its head segment.
+ sos::Object workItemsConcurrentQueue = TO_TADDR(workItemsConcurrentQueuePtr);
+ offset = GetObjFieldOffset(workItemsConcurrentQueue.GetAddress(), workItemsConcurrentQueue.GetMT(), W("_head"));
+ if (offset > 0)
+ {
+ // Now, walk from segment to segment, each of which contains an array of work items.
+ DWORD_PTR segmentPtr;
+ MOVE(segmentPtr, workItemsConcurrentQueue.GetAddress() + offset);
+ while (sos::IsObject(segmentPtr, false))
+ {
+ sos::Object segment = TO_TADDR(segmentPtr);
+
+ // Get the work items array. It's an array of Slot structs, which starts with the T.
+ offset = GetObjFieldOffset(segment.GetAddress(), segment.GetMT(), W("_slots"));
+ if (offset <= 0)
+ {
+ break;
+ }
+
+ DWORD_PTR slotsPtr;
+ MOVE(slotsPtr, segment.GetAddress() + offset);
+ if (!sos::IsObject(slotsPtr, false))
+ {
+ break;
+ }
+
+ // Walk every element in the array, outputting details on non-null work items.
+ DacpObjectData slotsArray;
+ if (slotsArray.Request(g_sos, TO_CDADDR(slotsPtr)) == S_OK && slotsArray.ObjectType == OBJ_ARRAY)
+ {
+ for (int i = 0; i < slotsArray.dwNumComponents; i++)
+ {
+ CLRDATA_ADDRESS workItemPtr;
+ MOVE(workItemPtr, TO_CDADDR(slotsArray.ArrayDataPtr + (i * slotsArray.dwComponentSize))); // the item object reference is at the beginning of the Slot
+ if (workItemPtr != NULL && sos::IsObject(workItemPtr, false))
+ {
+ sos::Object workItem = TO_TADDR(workItemPtr);
+ stats.Add((DWORD_PTR)workItem.GetMT(), (DWORD)workItem.GetSize());
+ DMLOut("%" POINTERSIZE "s %s %S", "[Global]", DMLObject(workItem.GetAddress()), workItem.GetTypeName());
+ if ((offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("_callback"))) > 0 ||
+ (offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("m_action"))) > 0)
+ {
+ CLRDATA_ADDRESS delegatePtr;
+ MOVE(delegatePtr, workItem.GetAddress() + offset);
+ CLRDATA_ADDRESS md;
+ if (TryGetMethodDescriptorForDelegate(delegatePtr, &md))
+ {
+ NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
+ ExtOut(" => %S", g_mdName);
+ }
+ }
+ ExtOut("\n");
+ }
+ }
+ }
+
+ // Move to the next segment.
+ DacpFieldDescData segmentField;
+ offset = GetObjFieldOffset(segment.GetAddress(), segment.GetMT(), W("_nextSegment"), TRUE, &segmentField);
+ if (offset <= 0)
+ {
+ break;
+ }
+
+ MOVE(segmentPtr, segment.GetAddress() + offset);
+ if (segmentPtr == NULL)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (_wcscmp(itr->GetTypeName(), W("System.Threading.ThreadPoolWorkQueue+WorkStealingQueue")) == 0)
+ {
+ // We found a local queue. Get its work items array.
+ int offset = GetObjFieldOffset(itr->GetAddress(), itr->GetMT(), W("m_array"));
+ if (offset > 0)
+ {
+ // Walk every element in the array, outputting details on non-null work items.
+ DWORD_PTR workItemArrayPtr;
+ MOVE(workItemArrayPtr, itr->GetAddress() + offset);
+ DacpObjectData workItemArray;
+ if (workItemArray.Request(g_sos, TO_CDADDR(workItemArrayPtr)) == S_OK && workItemArray.ObjectType == OBJ_ARRAY)
+ {
+ for (int i = 0; i < workItemArray.dwNumComponents; i++)
+ {
+ CLRDATA_ADDRESS workItemPtr;
+ MOVE(workItemPtr, TO_CDADDR(workItemArray.ArrayDataPtr + (i * workItemArray.dwComponentSize)));
+ if (workItemPtr != NULL && sos::IsObject(workItemPtr, false))
+ {
+ sos::Object workItem = TO_TADDR(workItemPtr);
+ stats.Add((DWORD_PTR)workItem.GetMT(), (DWORD)workItem.GetSize());
+ DMLOut("%s %s %S", DMLObject(itr->GetAddress()), DMLObject(workItem.GetAddress()), workItem.GetTypeName());
+ if ((offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("_callback"))) > 0 ||
+ (offset = GetObjFieldOffset(workItem.GetAddress(), workItem.GetMT(), W("m_action"))) > 0)
+ {
+ CLRDATA_ADDRESS delegatePtr;
+ MOVE(delegatePtr, workItem.GetAddress() + offset);
+ CLRDATA_ADDRESS md;
+ if (TryGetMethodDescriptorForDelegate(delegatePtr, &md))
+ {
+ NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
+ ExtOut(" => %S", g_mdName);
+ }
+ }
+ ExtOut("\n");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Output a summary.
+ stats.Sort();
+ stats.Print();
+ ExtOut("\n");
+ }
+
if (doHCDump)
{
ExtOut ("--------------------------------------\n");
return Status;
}
-BOOL derivedFrom(CLRDATA_ADDRESS mtObj, __in_z LPWSTR baseString)
-{
- // We want to follow back until we get the mt for System.Exception
- DacpMethodTableData dmtd;
- CLRDATA_ADDRESS walkMT = mtObj;
- while(walkMT != NULL)
- {
- if (dmtd.Request(g_sos, walkMT) != S_OK)
- {
- break;
- }
- NameForMT_s (TO_TADDR(walkMT), g_mdName, mdNameLen);
- if (_wcscmp (baseString, g_mdName) == 0)
- {
- return TRUE;
- }
- walkMT = dmtd.ParentMethodTable;
- }
- return FALSE;
-}
-
// This is an experimental and undocumented SOS API that attempts to step through code
// stopping once jitted code is reached. It currently has some issues - it can take arbitrarily long
// to reach jitted code and canceling it is terrible. At best it doesn't cancel, at worst it
{
NameForMT_s (taMT, g_mdName, mdNameLen);
if ((_wcscmp(g_mdName,typeNameWide) == 0) ||
- (fDerived && derivedFrom(taMT, typeNameWide)))
+ (fDerived && IsDerivedFrom(taMT, typeNameWide)))
{
sprintf_s(buffer,_countof (buffer),
"r$t%d=1",