Skip to content
Sasha Goldshtein edited this page Jul 16, 2015 · 27 revisions

About msos

msos was born out of a need to debug .NET process dumps without the SOS.dll extension available. It quickly turned into more than that with a variety of commands and options that, in many cases, exceed what SOS.dll has to offer.

You should use msos when you don't have access to SOS.dll (e.g., when debugging Windows Phone dumps), when you need some of msos's advanced functionality, or simply when you don't want to deal with the overhead of the full-blown WinDbg debugger. At this time, msos offers commands (such as !hq) that are extremely difficult to implement using the SOS.dll extension.

This page serves as a reference; instead, you might prefer a gentle introduction in the form of a tutorial.

Getting Started

msos is a standalone command-line debugger. It can attach to a live process (in non-invasive mode) or open a dump file. You will need the x86 version of msos to debug x86 targets, and the x64 version to debug x64 targets. This is a limitation of the .NET debugging API.

msos is designed to be self-documenting. For example, try running msos --help from the command line to see the first-level command-line options. When inside msos, type help to get a list of commands or help cmdname to get information about a specific command's switches.

The command-line switches are case-sensitive, but the command verbs are not. For example, you can use !clrstack, !CLRStack, or !CLRSTACK with the same effect.

To open a dump file, use the -z switch:

msos -z myapp.dmp

To attach to a running process, use either the --pn or the --pid switches. If there is more than one process with the given name, you will be asked to specify the process id.

msos --pid 1008
msos --pn devenv

Note: if there are multiple versions of the CLR loaded into the target process, you can select the desired one using the --clrVersion switch.

Automation

You can run msos and ask it to execute a set of initial commands, separated by semicolons. This is useful for automatic analysis, or when you always begin a debugging session by displaying basic information. For example, if you always begin a session by looking at the list of modules followed by the current thread's stack, use this command line:

msos -z myapp.dmp -c "!lm; !clrstack"

You can include the q command so that the debugger exits without waiting for further input. This is especially useful when analyzing multiple dump files in a row, automatically, as part of a script.

You can also load initial commands from a file. Create a file with each command on a separate line, and pass that file to the -i switch:

msos -z myapp.dmp -i commands.txt

Finally, you can pipe msos's output to a file (instead of the console) by using the -o switch:

msos -z myapp.dmp -i commands.txt -o output.txt

Basic Crash Analysis

When you give msos a crash dump, it will automatically try to switch to the crashing thread and display the exception information along with its stack trace. The !pe (or !PrintException) command can be used to display the current exception again. If you have the address of a .NET exception object, pass it to !pe as a parameter.

Inspecting Threads and Stacks

To display a list of all managed threads in the target, run !Threads. When available, additional information is displayed in the Special and Exception columns. The Lock# column indicates the number of locks the thread currently owns.

1> !Threads
2 CLR threads, 0 CLR thread pool threads, 1 background threads
MgdId  OSId   Lock#  Apt    Special              Exception
1      5980   1      MTA
2      8952   0      MTA    Finalizer

msos attempts to set the current thread to either the thread that encountered an exception (useful with crash dumps), or the first managed thread it can find. You can switch threads by using the ~ command and the managed thread id:

~ 7

To display the current thread's managed stack, run !CLRStack. If the -a switch is specified, this command displays some of the local variables for each frame. Only local variables pointing to reference types (on the heap) are currently supported, and only variable addresses (not names) are available.

1> !clrstack
SP                   IP                   Function
000000000020F180     0000000000000000     InlinedCallFrame
000000000020F17C     0000000073C8B7BF     DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
000000000020F180     0000000000000000     InlinedCallFrame
000000000020F1E4     00000000743DCD64     System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)
000000000020F218     00000000743DCC6B     System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)
000000000020F238     0000000073C74EC8     System.IO.StreamReader.ReadBuffer()
000000000020F24C     0000000073BDE90F     System.IO.StreamReader.ReadLine()
000000000020F268     00000000743E2E5D     System.IO.TextReader+SyncTextReader.ReadLine()
000000000020F278     00000000742EA842     System.Console.ReadLine()
000000000020F280     00000000003D00C9     VSDebugging.Program.Main(System.String[])
000000000020F410     0000000000000000     GCFrame

The !ThreadPool command displays general information about the CLR thread pool.

The !SyncBlk command displays all synchronization objects. These include Monitors (the C# lock statement acquires and releases a Monitor), Reader-Writer Locks, and other synchronization mechanisms. When available, ownership information is also displayed along with a list of threads waiting for the synchronization object. For example, in the following example, thread #1 owns the first monitor:

1> !syncblk
Address              Type       Locked   Owner(s)             Waiter(s)
0000000001fc3f34     Monitor    1        1
0000000001fc21f0     None       0

Memory and Objects

Use the !dumpheap --stat command to get an initial reading of what's going on with your managed heap. It displays statistics for each type indicating the number of objects from that type and their total size. Generally, object sizes do not include the sizes of any children. For example, the size of an array of strings doesn't include the size of the strings themselves. The !ObjSize command displays the size of the entire object graph rooted at a specific object.

The !dumpheap command accepts multiple advanced options to filter the output and to display individual objects. The --type switch is most flexible, and takes a regular expression that filters the types of objects to be displayed. For example (omit the --stat switch to get a list of objects, and not just statistics):

1> !dumpheap --type String --stat
Statistics:
MT                   Count      TotalSize  Class Name
0000000073cf4cf8     1          12         System.Collections.Generic.GenericEqualityComparer<System.String>
00000000738ec734     1          48         System.Collections.Generic.Dictionary<System.String,System.Globalization.CultureData>
0000000073cf4b04     2          56         System.Text.StringBuilder
0000000073cf4e04     1          60         System.Collections.Generic.Dictionary+Entry<System.String,System.Globalization.CultureData>[]
0000000073cb5738     18         680        System.String[]
0000000073cf21b4     165        5236       System.String
Total 188 objects

Other heap filtering options include --min, --max and --mt.

To display an individual object, use the !do (or !DumpObj) command. It displays the object's fields (unless you pass the --nofields switch), including any static and thread-static fields. Nested value types are displayed unless you pass the --norecurse switch. Nested reference types are never displayed.

1> !do 0000000001fc3984
Name:     System.Globalization.NumberFormatInfo
MT:       0000000073cf4e7c
Size:     132(0x84) bytes
Assembly: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.
dll
Value:    1942965884
Fields:
Offset   Type                 VT  Attr       Value                Name
0        System.Int32[]       0   instance   33303612             numberGroupSizes
8        System.Int32[]       0   instance   33303628             currencyGroupSizes
10       System.Int32[]       0   instance   33303612             percentGroupSizes
18       System.String        0   instance   +                    positiveSign
20       System.String        0   instance   -                    negativeSign
28       System.String        0   instance   .                    numberDecimalSeparator
30       System.String        0   instance   ,                    numberGroupSeparator
38       System.String        0   instance   ,                    currencyGroupSeparator
40       System.String        0   instance   .                    currencyDecimalSeparator
48       System.String        0   instance   ☼                    currencySymbol
50       System.String        0   instance   <null>               ansiCurrencySymbol
58       System.String        0   instance   NaN                  nanSymbol
60       System.String        0   instance   Infinity             positiveInfinitySymbol
68       System.String        0   instance   -Infinity            negativeInfinitySymbol
70       System.String        0   instance   .                    percentDecimalSeparator
78       System.String        0   instance   ,                    percentGroupSeparator
80       System.String        0   instance   %                    percentSymbol
88       System.String        0   instance   %                    perMilleSymbol
90       System.Object[]      0   instance   33303556             nativeDigits
98       System.Int32         1   instance   0                    m_dataItem
a0       System.Int32         1   instance   2                    numberDecimalDigits
a8       System.Int32         1   instance   2                    currencyDecimalDigits
b0       System.Int32         1   instance   0                    currencyPositivePattern
b8       System.Int32         1   instance   0                    currencyNegativePattern
c0       System.Int32         1   instance   1                    numberNegativePattern
c8       System.Int32         1   instance   0                    percentPositivePattern
d0       System.Int32         1   instance   0                    percentNegativePattern
d8       System.Int32         1   instance   2                    percentDecimalDigits
e0       System.Int32         1   instance   1                    digitSubstitution
e8       System.Boolean       1   instance   True                 isReadOnly
ea       System.Boolean       1   instance   False                m_useUserOverride
ec       System.Boolean       1   instance   True                 m_isInvariant
ee       System.Boolean       1   instance   True                 validForParseAsNumber
f0       System.Boolean       1   instance   True                 validForParseAsCurrency
528      ...NumberFormatInfo  0   shared     static               invariantInfo
   >> Domain:Value  0000000000604ac8:<null> <<

To display collections, use the !DumpCollection command. It supports arrays, lists, and dictionaries. Other collections can be displayed using the !hq command (discussed below).

1> !dumpcollection 0000000001fc1bb0
Type:   System.Collections.Generic.Dictionary<System.Type,System.Security.Policy.EvidenceTypeDescriptor>
Size:   10
Key                                      Value
[0000000001fc1d3c System.RuntimeType]    <null>
[0000000001fc1d58 System.RuntimeType]    <null>
[0000000001fc1d74 System.RuntimeType]    <null>
[0000000001fc1d90 System.RuntimeType]    <null>
[0000000001fc1dac System.RuntimeType]    <null>
[0000000001fc1dc8 System.RuntimeType]    <null>
[0000000001fc1de4 System.RuntimeType]    <null>
[0000000001fc1e00 System.RuntimeType]    <null>
[0000000001fc1e1c System.RuntimeType]    <null>
[0000000001fc1e38 System.RuntimeType]    <null>
Time: 3 ms, Memory start: 568.008kb, Memory end: 568.008kb, Memory delta: -0b

Use the !ObjSize command to display the size of the object graph rooted at a specific object. This includes all referenced objects.

1> !objsize 0000000001fc3984
0000000001fc3984 graph size is 24 objects, 574 bytes

Memory Leaks

The !EEHeap command provides basic information about the CLR's memory usage in the target. The sizes of each heap and each generation (including the Large Object Heap) are displayed. Non-GC regions (such as the loader heap) are also shown.

1> !EEHeap
GC regions:
Address              Size         Type         Heap#   Commit/Reserve
0000000001fc1000     68.000kb     Ephemeral    0       Committed
0000000001fd2000     15.930mb     Ephemeral    0       Reserved
0000000002fc1000     68.000kb     LargeObject  0       Committed
0000000002fd2000     15.930mb     LargeObject  0       Reserved
Gen    Size
0      11.965kb
1      12b
2      12b
LOH    16.898kb
Total  28.887kb
Other regions:
...snipped...

When chasing memory leaks, the hardest part is understanding why objects are being retained and not collected by the GC. If you need to inspect individual object references and figure out why they are not being collected, it is recommended that you build a heap index. A heap index is an internal msos data structure that collects information on object references, and makes certain operations much faster. The downside is that constructing the index takes time. However, the index can be stored to disk (in compressed form) and used repeatedly in future debugging sessions, or for multiple queries in a single session.

To construct a heap index, use the !bhi command. You can store it to a file or in-memory (if you do not intend to reuse it in a future debugging session). The --fast switch makes index construction faster at the expense of more accurate information pertaining to static variable roots.

Note: you can control the chunk size used by the heap index with the --chunkSize switch. This is an advanced option; use at your own risk. A larger chunk size means the index is smaller and faster to build, but subsequent operations using the index will be slower.

> !bhi -f C:\temp\heapindex --fast
Enumerating roots took 767 ms
Building chunks took 2229 ms
Average referencing chunks per chunk: 8.86
Max referencing chunks per chunk:     5339
Min referencing chunks per chunk:     1
Objects with missing chunk ids:       393
Building chunk index took 17480 ms
Total chunks: 212538, Chunk size: 1024
Memory covered by all segments: 908900496 bytes
Memory covered by all chunks:   217638912 bytes
Wrote index file of size 3.198mb
Saving index to disk took 5584 ms
Time: 26089 ms, Memory start: 569.141kb, Memory end: 37.921mb, Memory delta: +37.365mb

If you have an existing heap index that you would like to load, use the !lhi command. Note that if the index was created by a different version of msos, it will fail to load.

> !lhi -f c:\temp\heapindex
This heap index does not have detailed static root information. As a result, you will not see the
names of static variables referencing your objects, only their addresses. Recreate the index without
the --fast switch to get full information. This may be slower.
Time: 2303 ms, Memory start: 567.227kb, Memory end: 28.221mb, Memory delta: +27.667mb

There are two commands that use the heap index. First, the !refs command displays any object referencing the specified object or referenced by it. Note that if the object is not reachable from roots, i.e. dead from the GC's perspective, you will not see any references to it (even though there could be such references which are also dead).

>  !refs 00000001803f02a8
Note: unrooted (dead) objects will not have any referencing objects displayed.
Object 00000001803f02a8 (System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>) is referenced by the following objects:
  00000001803f0210 (System.ServiceModel.Channels.TransportReplyChannelAcceptor+TransportReplyChannel)
  00000001c0e35b98 (System.Runtime.InputQueue+AsyncQueueReader<System.ServiceModel.Channels.RequestContext>)
Object 00000001803f02a8 (System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>) references the following objects:
  00000001803f02e8 (System.Runtime.InputQueue+ItemQueue<System.ServiceModel.Channels.RequestContext>)
  00000001803f0310 (System.Collections.Generic.Queue<System.Runtime.InputQueue+IQueueReader<System.ServiceModel.Channels.RequestContext>>)
  00000001803f0360 (System.Collections.Generic.List<System.Runtime.InputQueue+IQueueWaiter<System.ServiceModel.Channels.RequestContext>>)
  00000001803f03a8 (System.Action<System.ServiceModel.Channels.RequestContext>)
  00000001803a7438 (System.Func<System.Action<System.AsyncCallback,System.IAsyncResult>>)
Time: 142 ms, Memory start: 28.228mb, Memory end: 29.475mb, Memory delta: +1.247mb

Even more useful is !paths, which relies on the heap index to determine which paths from roots lead to the specified object. It is usually much faster than the !GCRoot command, which performs full heap traversal and does not rely on a reverse index. You can control some aspects of the output, such as the maximum number of paths that will be displayed.

>  !paths 00000001803f0310 --max 1
00000000086fdcb8 -> 00000001802ea9c0 local var thread 76
        -> 00000001802ea9c0 System.ServiceModel.ServiceHost
        -> 00000001802eabc0 System.ServiceModel.Dispatcher.ChannelDispatcherCollection
        -> 00000001802eabe8 System.Collections.Generic.List<System.ServiceModel.Dispatcher.ChannelDispatcherBase>
        -> 00000001803aaeb8 System.ServiceModel.Dispatcher.ChannelDispatcherBase[]
        -> 00000001803aee28 System.ServiceModel.Dispatcher.ChannelDispatcher
        -> 00000001803ae878 System.ServiceModel.Channels.HttpsChannelListener
        -> 00000001803aebe0 System.ServiceModel.Channels.TransportReplyChannelAcceptor
        -> 00000001803f0210 System.ServiceModel.Channels.TransportReplyChannelAcceptor+TransportReplyChannel
        -> 00000001803f02a8 System.Runtime.InputQueue<System.ServiceModel.Channels.RequestContext>
        -> 00000001803f0310 System.Collections.Generic.Queue<System.Runtime.InputQueue+IQueueReader<System.ServiceModel.Channels.RequestContext>>

Total paths displayed: 1
Time: 373 ms, Memory start: 28.230mb, Memory end: 29.602mb, Memory delta: +1.372mb

Use the !GCRoot command to understand why an object is being retained (as explained above, if this command is too slow, use a heap index and the !paths command). Some duplicates might be displayed. In the following output, there is a static variable s_InvariantCultureInfo that references a CultureInfo object, which in turn references the NumberFormatInfo object (at address 0000000001fc3984) that was the target of our query.

1> !gcroot 0000000001fc3984
0000000002FC1430 -> 0000000001FC2594 static var System.Globalization.CultureInfo.s_InvariantCultureInfo
        -> 0000000001FC2594 System.Globalization.CultureInfo
        -> 0000000001FC3984 System.Globalization.NumberFormatInfo

If you are having trouble with finalization, e.g. objects are not being finalized quickly enough, you can review the contents of the f-reachable queue (objects ready for finalization) by using the !frq command:

1> !frq
Address              Size       Class Name
0000000001fc1b54     20         Microsoft.Win32.SafeHandles.SafePEFileHandle
0000000001fc21f0     44         System.Threading.ReaderWriterLock
0000000001fc235c     20         Microsoft.Win32.SafeHandles.SafeFileHandle
0000000001fc3b14     20         Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
0000000001fc3b28     20         Microsoft.Win32.SafeHandles.SafeFileMappingHandle
Total 5 objects ready for finalization

Heap Queries

Heap queries are probably the most powerful feature msos currently has to offer. They have no parallel in SOS.dll, and offer a lot of room for customization and creativity. The !hq command takes an arbitrary C# expression and executes it in a context that gives you access to the current target's heap. You can locate specific objects, access their properties, aggregate statistics, filter, group, and sort the results, and so on.

The following utility methods are available:

  • AllObjects() returns a collection of all heap objects. Each object has special __Type and __Size properties. You can cast the returned objects to ulong to obtain their address. In addition, any fields of the original object are accessible directly. For example, !hq (from o in AllObjects() where o.__Type == "System.String" select (int)o.m_stringLength).Sum() displays the total length of all the strings found on the heap. Auto-generated properties can be accessed directly using the property name -- you don't (and can't) use the backing field (<PropName>k__BackingField) for this.

  • ObjectsOfType("...") is a convenience method that returns a collection of heap objects that have the specified type. The type is matched exactly, e.g. ObjectsOfType("System.String").

  • AllClasses() returns a collection of objects representing each type on the heap. These objects have special __Name, __Fields, __StaticFields, and __ThreadStaticFields properties.

  • Class("...") returns an object representing a specific type. You can access static fields by using their name, but you currently need to provide an app domain object to get the actual value. This will be addressed in the future.

  • Object(...) returns a dynamic object based on the specified address. This is useful when you discovered the object's address using some other command (e.g. !gcroot) but want to write code against it dynamically using C# expressions. Note that most commands display object addresses in hex, so prefix the object address with 0x when passing it to the Object method.

  • ObjectEn(...) returns an enumerable with a single dynamic object based on the specified address. Use this if your query is more conveniently expressed as a LINQ query over an enumerable.

There are two output formats currently supported: --tabular and --json. The tabular format attempts to render the results as a table, which works well when the number of columns is small and the values are not very wide. The JSON format works better for complex objects.

Example queries:

1> !hq --tabular (from obj in AllObjects()
        group obj by obj.__Type into g
        let size = g.Sum(o => (long)o.__Size)
        let count = g.Count()
        orderby size descending
        select new { Type = g.Key, Count = count, Size = size }
       ).Take(10)
Type                               Count                              Size
System.Object[]                    6                                  17308
System.String                      165                                5236
System.RuntimeType                 26                                 728
System.Char[]                      4                                  718
System.String[]                    18                                 680
System.Globalization.CultureDa...  2                                  616
System.Int32[]                     11                                 564
System.Collections.Generic.Dic...  3                                  468
System.Byte[]                      2                                  280
System.Threading.ThreadAbortEx...  2                                  168
Rows: 10

1> !hq --json (from c in ObjectsOfType("System.String") _
               where (int)c.m_stringLength > 100 _
               select new { Size = c.__Size, Value = c.ToString() }
              ).Take(2)
{
  Size = 256
  Value = System.Data.SqlClient.SqlConnection, System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
}
{
  Size = 320
  Value = System.Configuration.Internal.ConfigurationManagerInternal, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
}
Rows: 2
Time: 2697 ms, Memory start: 647.516kb, Memory end: 689.172kb, Memory delta: +41.656kb

To work your way through collections as part of a query, you can use the GetDictionaryItems() method on dictionaries and the GetItems() method on arrays and lists. They both return a collection of dynamic objects that you can work with. Occasionally, if you try to use LINQ to process the returned collections, you might hit a compiler error (e.g., CS1943) because the collection object is dynamic. You can work around this by inserting a cast to a concrete type (not dynamic). For example:

1> !hq --tabular from o in AllObjects()
       where o.__Type.Contains("List<VSDebugging.Person")
       from item in o._items.GetItems()
       select item.Name
Query compilation failed with 1 errors:
c:\Users\Sasha\AppData\Local\Temp\n3wsitxl.0.cs(48,104) : error CS1943: An expression of type 'dynamic' is not allowed in a subsequent from clause in a query expression with source type 'System.Collections.Generic.IEnumerable<dynamic>'.  Type inference failed in the call to 'SelectMany'.

1> !hq --tabular from o in AllObjects()
       where o.__Type.Contains("List<VSDebugging.Person")
       from item in (IEnumerable<dynamic>)o._items.GetItems()
       select item.Name
Mike
Rows: 1

Tip: The preceding examples have long commands that are split across lines for readability. You can actually type multi-line commands into the msos prompt. To do so, end the line with a space followed by an underscore (_). The debugger will then prompt you for the next line. For example:

1> !dumpheap --stat --type _
>    System.Collections.Generic.Dictionary<.*
Statistics:
MT                   Count      TotalSize  Class Name
00000000738ec654     1          48         System.Collections.Generic.Dictionary<System.Type,System.Security.Policy.EvidenceTypeDescriptor>
00000000738ec734     1          48         System.Collections.Generic.Dictionary<System.String,System.Globalization.CultureData>
Total 2 objects

Command Aliases

You can define arbitrary command aliases to help your debugging experience. The .newalias, .listalias, .rmalias, and % commands perform alias-related management tasks. For example:

1> .newalias dh !dumpheap --stat
Time: 1 ms, Memory start: 594.141kb, Memory end: 594.234kb, Memory delta: +96b
1> .listalias
Name                 Command
dh                   !dumpheap --stat
Time: 6 ms, Memory start: 594.164kb, Memory end: 594.281kb, Memory delta: +120b
1> % dh
Alias 'dh' expanded to '!dumpheap --stat'
... command output omitted for brevity ...

Aliases can accept an arbitrary number of parameters by embedding $1, $2 etc. in the command text. For example:

1> .listalias
Name                 Command
dh                   !dumpheap --stat
Time: 1 ms, Memory start: 591.586kb, Memory end: 591.586kb, Memory delta: -0b
1> .rmalias dh
Time: 1 ms, Memory start: 591.672kb, Memory end: 591.578kb, Memory delta: -96b
1> .newalias dh !dumpheap --type $1 --stat
Time: 1 ms, Memory start: 591.852kb, Memory end: 591.961kb, Memory delta: +112b
1> % dh System.String$
Alias 'dh' expanded to '!dumpheap --type System.String$ --stat'
Statistics:
MT                   Count      TotalSize  Class Name
000007fef8a668f0     1286275    63714760   System.String
Total 1286275 objects
Time: 15211 ms, Memory start: 592.430kb, Memory end: 3.710mb, Memory delta: +3.132mb

Like any other command, aliases can be loaded from a file using the -i switch. For example, create a text file called alias.txt with the following contents (or use the sample alias file):

.newalias httprequests !hq --tabular from hc in ObjectsOfType("System.Web.HttpContext").Take($1) _
let elapsed = new TimeSpan(DateTime.Now.Ticks - (long)hc._utcTimestamp.dateData) _
let timeout = new TimeSpan((long)hc._timeout._ticks) _
select new { _
  hc.__Address, _
  Method = hc._request._httpMethod, _
  Code = hc._response._statusCode, _
  Elapsed = ((bool)hc._response._completed || (bool)hc._finishPipelineRequestCalled) ? "Finished" : elapsed.ToString() , _
  Timeout = (bool)hc._timeoutSet ? timeout.ToString() : "No timeout", _
  VirtualPath = hc._request._filePath._virtualPath _
}

Now, run msos on a dump file from an ASP.NET application and use the httprequests alias:

C:\> msos -z myapp.dmp --diag -i alias.txt
... initial output omitted for brevity ...
1> .listalias
Name                 Command
httprequests         !hq --tabular from hc in ObjectsOfType("System.Web.HttpContext").Take($1) let elapsed = new TimeSpan(DateTime.Now.Ticks - (long)hc._utcTimestamp.dateData) let timeout = new TimeSpan((long)hc._timeout._ticks) select new { hc.__Address, Method = hc._request._httpMethod, Code = hc._response._statusCode, Elapsed = ((bool)hc._response._completed || (bool)hc._finishPipelineRequestCalled) ? "Finished" : elapsed.ToString() , Timeout = (bool)hc._timeoutSet ? timeout.ToString() : "No timeout", VirtualPath = hc._request._filePath._virtualPath }
Time: 5 ms, Memory start: 593.891kb, Memory end: 593.695kb, Memory delta: -200b
1> % httprequests 2
Alias 'httprequests' expanded to '!hq --tabular from hc in ObjectsOfType("System.Web.HttpContext").Take(2) let elapsed = new TimeSpan(DateTime.Now.Ticks - (long)hc._utcTimestamp.dateData) let timeout = new TimeSpan((long)hc._timeout._ticks) select new { hc.__Address, Method = hc._request._httpMethod, Code = hc._response._statusCode, Elapsed = ((bool)hc._response._completed || (bool)hc._finishPipelineRequestCalled) ? "Finished" : elapsed.ToString() , Timeout
= (bool)hc._timeoutSet ? timeout.ToString() : "No timeout", VirtualPath = hc._request._filePath._virtualPath }'
__Address         Method            Code              Elapsed           Timeout           VirtualPath
0000000182093...  POST              400               Finished          24855.03:14:0...  /api/OrderService.svc
00000001820be...  POST              200               Finished          24855.03:14:0...  /API/LoginService.svc
Rows: 2
Time: 3012 ms, Memory start: 595.227kb, Memory end: 691.984kb, Memory delta: +96.758kb

By defining useful heap queries as aliases, you can save a lot of typing.

What's more, you can define full-blown functions (or classes) for use in your heap queries. Again, the sample alias file contains some examples. Suppose you often need to reference all the path-like strings that start with C:\ in your queries. You can define a helper with .define, list all available helpers with .listdefines, and remove helpers with .undefine:

1> .define IEnumerable<string> PathLikeStrings() { _
>    return from s in ObjectsOfType("System.String") _
>           where ((string)s).StartsWith(@"C:\") _
>           select (string)s; _
>    }
1> .listdefines
#      Body
0      IEnumerable<string> PathLikeStrings() { return from s in ObjectsOfType("System.String") where ((string)s).StartsWith(@"C:\") select (string)s; }
1> !hq --tabular PathLikeStrings().Take(10)
C:\Temp\API
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config
C:\Temp\API\OrderService.svc63477491502921577495
C:\Temp\
C:\Temp\
C:\Windows\TEMP\tmpDDBA.tmp
C:\Windows\TEMP\tmpDDBB.tmp
C:\Temp\Validator\
C:\Temp\Validator\
C:\Temp
Rows: 10
1> .undefine 0
1> .listdefines
You do not have any helper methods defined. Use .define to define some.

Miscellaneous Commands

Include the --diag switch when launching msos to get diagnostic information after each command is executed. Currently this information includes the time it took to execute each command, and the memory usage before and after the command's execution. This helps detect memory leaks and slow commands.

1> !hq --tabular (from i in Enumerable.Range(0, 100000000) select i).Count()
100000000
Rows: 1
Time: 6,258.01 ms, CPU time: 5,875.00 ms (93.88%)
Memory start: 595.680kb, Memory end: 635.359kb, Memory delta: +39.680kb

The !DumpDomain command lists all the application domains in the current target. If a specific domain id is passed, the modules loaded into that application domain are also displayed.

1> !dumpdomain
Id   Name                                     # Modules Application Base
1    DefaultDomain                            14       c:\windows\system32\inetsrv\
2    /LM/W3SVC/2/ROOT/12987421178073111222... 153      C:\MyService\API\
3    /LM/W3SVC/2/ROOT/API/V/12889123711201... 48       C:\MyService\Validator\
4    /LM/W3SVC/2/ROOT-3-129874220678904342    25       C:\MyService\

The !lm command lists the managed assemblies loaded into the target. Symbol load status is also displayed. If symbols aren't loaded, it doesn't necessarily mean they cannot be found -- the debugger loads symbols on demand.

1> !lm
start                size       symloaded  filename
00000000738e0000     01095000   False      C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
0000000000080000     00008000   False      C:\Temp\VSDebugging\bin\Debug\VSDebugging.exe

Use the db command to display a raw region of memory as hex bytes and ASCII characters. The -l and -c switches control how many bytes to display and how many columns to put in each row, respectively.

1> db 0000000001fc3984 -l 20 -c 10
0000000001fc3984  7c 4e cf 73 3c 2c fc 01 4c 2c   |N.s<,..L,
0000000001fc398e  fc 01 3c 2c fc 01 bc 26 fc 01   ..<,...&..

Use the q command to quit the debugger.

Symbol Loading

Currently, msos loads symbols from the location specified by the _NT_SYMBOL_PATH environment variable. Point it to the location of your symbols and to the Microsoft symbol server for best results. For example:

set _NT_SYMBOL_PATH=d:\mysymbols;\\myserver\moresymbols;srv*C:\symbols*http://msdl.microsoft.com/download/symbols
msos -z myapp.dmp

msos will complain if it needs but cannot find symbols, and will report when symbols are downloaded from the symbol server.