<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>A Taste of Logic : Debugging</title><link>http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx</link><description>Tags: Debugging</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>Creating Strings - A Case Study</title><link>http://blogs.msdn.com/jamesche/archive/2007/12/11/creating-string-a-case-study.aspx</link><pubDate>Tue, 11 Dec 2007 22:45:27 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:6737859</guid><dc:creator>jamesche</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/6737859.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=6737859</wfw:commentRss><description>&lt;p&gt;I recently worked an issue with a customer who was experiencing high CPU. Performance Monitor data showed that the % of Time in GC was 46%. We generally recommend that you not be in GC more than 5% of the time, so this was a point of interest.&lt;/p&gt;  &lt;p&gt;One of the common causes of very frequent GC is GC.Collect() being called. I checked the # of Induced GC Perfmon counter and it was greater than 8,000 with a process uptime of only a few hours. This was obviously a clear sign that someone was calling GC.Collect() a lot! ASP.NET will call GC.Collect() at times, but not nearly this often. &lt;/p&gt;  &lt;p&gt;We found a DLL that was causing that problem and fixed it. Good news. Induced GC was now 0. Bad news. CPU and Time in GC were both still high. &lt;/p&gt;  &lt;p&gt;Many issues that we work are like this; like peeling an onion. We resolve one part of the issue only to discover other problems coming to the surface. This was just such an issue. Once we resolved induced GCs, a pattern became clear.&lt;/p&gt;  &lt;p&gt;Have a look at the Perfmon data shown below. The color-key appears below the image:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://www.jimcobooks.com/blog/CreatingStringACaseStudy_C166/Perf.jpg"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="461" alt="Perf" src="http://www.jimcobooks.com/blog/CreatingStringACaseStudy_C166/Perf_thumb.jpg" width="569" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Red: % of Time in GC    &lt;br /&gt;Blue: % Processor Time     &lt;br /&gt;Tan: # of Bytes in All Heaps     &lt;br /&gt;Black: Large Object Heap Size&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;The first thing to notice is that things started out okay and then went bad very quickly. One of the things that can cause that pattern is a problem call stack. (In fact, that's the issue here. More soon.) We can also see that something is creating a lot of objects and that's causing considerable memory pressure. That's causing us to go into GC often, but even when we're not in GC, you can see that CPU remains high.&lt;/p&gt;  &lt;p&gt;The first thing I decided to do was to check the Large Object Heap to see what we had there.&lt;/p&gt;  &lt;div class="debugCmd"&gt;   &lt;p&gt;0:046&amp;gt; !dumpheap -min 85000 -stat&lt;/p&gt;    &lt;p&gt;Statistics:&lt;/p&gt;    &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; MT&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Count&amp;#160;&amp;#160;&amp;#160;&amp;#160; TotalSize Class Name&lt;/p&gt;    &lt;p&gt;0x000f3198&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 69&amp;#160;&amp;#160;&amp;#160; 87,714,456&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Free&lt;/p&gt;    &lt;p&gt;0x03084300&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; 123&amp;#160;&amp;#160; 158,192,160 System.String&lt;/p&gt;    &lt;p&gt;Total 192 objects, Total size: 245,906,616&lt;/p&gt;    &lt;p&gt;&amp;#160;&lt;/p&gt; &lt;/div&gt;  &lt;p&gt;This tells me that the memory we see in the LOH is taken up by strings. In fact, when I looked at some of these strings in the debugger, they turned out to be XML data generated by the customer. &lt;/p&gt;  &lt;p&gt;I then checked to see where one of these big strings was rooted. It was rooted in a thread, so I checked that stack and saw this:&lt;/p&gt;  &lt;div class="debugCmd"&gt;   &lt;p&gt;0:053&amp;gt; kL&lt;/p&gt;    &lt;p&gt;ChildEBP RetAddr&amp;#160; &lt;/p&gt;    &lt;p&gt;0635b890 791c134c mscorsvr!wstrcopy+0x26&lt;/p&gt;    &lt;p&gt;0635b8a4 035ac67c mscorsvr!COMString::FillStringChecked+0x37&lt;/p&gt;    &lt;p&gt;&amp;#160;&lt;/p&gt; &lt;/div&gt;  &lt;p&gt;This tells me that we're making a copy of a string, so I checked the managed stack. When I did, I found a couple of interesting problems. First of all, the customer had a function call that was recursive and had hundreds of iterations already. Secondly, I could tell that each iteration of that function created more strings via wstrcopy. This is a clear sign of string concatenation, and indeed, in another dump I could see concat being called.&lt;/p&gt;  &lt;p&gt;In the end, the customer's allocation pattern caused memory pressure and caused us to go into GC much more often than we should have. (In particular, the customer was creating a large number of objects on almost a per-request basis.) Because of this, the customer experienced high CPU and poor application performance.&lt;/p&gt;  &lt;p&gt;I hope this helps others who might see the same kind of thing in production applications.&lt;/p&gt;  &lt;p&gt;Jim&lt;/p&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=6737859" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item><item><title>Getting to the Root Cause of SqlExceptions</title><link>http://blogs.msdn.com/jamesche/archive/2007/02/28/getting-to-the-root-cause-of-sqlexceptions.aspx</link><pubDate>Thu, 01 Mar 2007 02:18:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:1776192</guid><dc:creator>jamesche</dc:creator><slash:comments>1</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/1776192.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=1776192</wfw:commentRss><description>&lt;P&gt;It's pretty common for me to work cases where something bad happens when an ASP.NET application is accessing a database. The nature of my job dictates that troubleshooting on these applications is most often performed in a production environment via post mortem debugging. Fortunately, our toolset for doing that kind of work is really great.&lt;/P&gt;
&lt;P class=Note&gt;If you aren't familiar with how to load up a dump in Windbg, set up symbols, etc., check out &lt;A href="http://blogs.msdn.com/jamesche/archive/2005/12/20/clarifications-on-debugging.aspx" target=_blank mce_href="http://blogs.msdn.com/jamesche/archive/2005/12/20/clarifications-on-debugging.aspx"&gt;my post on that topic&lt;/A&gt;.&lt;/P&gt;
&lt;P&gt;Finding out exactly what errors occurred when hitting a database is easy to do, but it's time consuming. Let's have a look at a particular SqlException that occurred when accessing a database.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do 106fd670 &lt;BR&gt;Name: System.Data.SqlClient.SqlException&lt;BR&gt;MethodTable 0x0343232c&lt;BR&gt;EEClass 0x034ffef4&lt;BR&gt;Size 68(0x44) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;mdToken: 0x020001c4 (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)&lt;BR&gt;FieldDesc*: 0x03432228&lt;BR&gt;MT Field Offset Type Attr Value Name&lt;BR&gt;0x79b96824 0x400001d 0x4 CLASS instance 0x00000000 _className&lt;BR&gt;0x79b96824 0x400001e 0x8 CLASS instance 0x00000000 _exceptionMethod&lt;BR&gt;0x79b96824 0x400001f 0xc CLASS instance 0x00000000 _exceptionMethodString&lt;BR&gt;0x79b96824 0x4000020 0x10 CLASS instance 0x1c2ef88c _message&lt;BR&gt;0x79b96824 0x4000021 0x14 CLASS instance 0x00000000 _innerException&lt;BR&gt;0x79b96824 0x4000022 0x18 CLASS instance 0x00000000 _helpURL&lt;BR&gt;0x79b96824 0x4000023 0x1c CLASS instance 0x106fd8c8 _stackTrace&lt;BR&gt;0x79b96824 0x4000024 0x20 CLASS instance 0x00000000 _stackTraceString&lt;BR&gt;0x79b96824 0x4000025 0x24 CLASS instance 0x00000000 _remoteStackTraceString&lt;BR&gt;0x79b96824 0x4000026 0x2c System.Int32 instance 0 _remoteStackIndex&lt;BR&gt;0x79b96824 0x4000027 0x30 System.Int32 instance -2146232060 _HResult&lt;BR&gt;0x79b96824 0x4000028 0x28 CLASS instance 0x00000000 _source&lt;BR&gt;0x79b96824 0x4000029 0x34 System.Int32 instance 0 _xptrs&lt;BR&gt;0x79b96824 0x400002a 0x38 System.Int32 instance -532459699 _xcode&lt;BR&gt;0x0343232c 0x4000fb6 0x3c CLASS instance 0x106fd6b4 _errors&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;If you look at the _message field, you'll see this:&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do 0x1c2ef88c &lt;BR&gt;Name: System.String&lt;BR&gt;MethodTable 0x79b94638&lt;BR&gt;EEClass 0x79b94984&lt;BR&gt;Size 44(0x2c) bytes&lt;BR&gt;GC Generation: 2&lt;BR&gt;mdToken: 0x0200000f (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)&lt;BR&gt;&lt;STRONG&gt;String: System error.&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;This is a generic error that tells you absolutely nothing. Essentially, it's telling you that there was an error. The key to finding out what that error actually was is the _errors field in the exception. (It's the last field listed in the exception above.) The _errors field is a SqlErrorsCollection as shown here:&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do 0x106fd6b4 &lt;BR&gt;Name: System.Data.SqlClient.SqlErrorCollection&lt;BR&gt;MethodTable 0x0343249c&lt;BR&gt;EEClass 0x034fff58&lt;BR&gt;Size 12(0xc) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;mdToken: 0x020001c3 (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)&lt;BR&gt;FieldDesc*: 0x034323bc&lt;BR&gt;MT Field Offset Type Attr Value Name&lt;BR&gt;0x0343249c 0x4000fb5 0x4 CLASS instance 0x106fd6c0 errors&lt;/P&gt;
&lt;P&gt;The &lt;STRONG&gt;errors&lt;/STRONG&gt; field here is an ArrayList of SqlError objects. Let's have a look at that.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do 0x106fd6c0 &lt;BR&gt;Name: System.Collections.ArrayList&lt;BR&gt;MethodTable 0x79ba2ee4&lt;BR&gt;EEClass 0x79ba3020&lt;BR&gt;Size 24(0x18) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;mdToken: 0x02000100 (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)&lt;BR&gt;FieldDesc*: 0x79ba3084&lt;BR&gt;MT Field Offset Type Attr Value Name&lt;BR&gt;0x79ba2ee4 0x4000362 0x4 CLASS instance 0x106fd6d8 _items&lt;BR&gt;0x79ba2ee4 0x4000363 0xc System.Int32 instance 1 _size&lt;BR&gt;0x79ba2ee4 0x4000364 0x10 System.Int32 instance 1 _version&lt;BR&gt;0x79ba2ee4 0x4000365 0x8 CLASS instance 0x00000000 _syncRoot&lt;/P&gt;
&lt;P&gt;Now, to get to the actual errors in that ArrayList, you need to dump the _items field, and to do that, you need to use the &lt;STRONG&gt;-v&lt;/STRONG&gt; switch as follows:&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do -v 0x106fd6d8 &lt;BR&gt;Name: System.Object[]&lt;BR&gt;MethodTable 0x020a209c&lt;BR&gt;EEClass 0x020a2018&lt;BR&gt;Size 80(0x50) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;Array: Rank 1, Type CLASS&lt;BR&gt;Element Type: System.Object&lt;BR&gt;Content: 16 items&lt;BR&gt;------ Will only dump out valid managed objects ----&lt;BR&gt;Address MT Class Name&lt;BR&gt;0x106fd780 0x0343263c System.Data.SqlClient.SqlError&lt;BR&gt;----------&lt;/P&gt;
&lt;P&gt;Alright, now we're getting somewhere. We have the specific SqlError instance that is associated with the SqlException that occurred. Let's dump that.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do 0x106fd780&lt;BR&gt;Name: System.Data.SqlClient.SqlError&lt;BR&gt;MethodTable 0x0343263c&lt;BR&gt;EEClass 0x03710010&lt;BR&gt;Size 36(0x24) bytes&lt;BR&gt;GC Generation: 0&lt;BR&gt;mdToken: 0x020001c2 (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)&lt;BR&gt;FieldDesc*: 0x03432520&lt;BR&gt;MT Field Offset Type Attr Value Name&lt;BR&gt;0x0343263c 0x4000fad 0x4 CLASS instance 0x182f7d34 source&lt;BR&gt;0x0343263c 0x4000fae 0x14 System.Int32 instance -2 number&lt;BR&gt;0x0343263c 0x4000faf 0x1c System.Byte instance 0 state&lt;BR&gt;0x0343263c 0x4000fb0 0x1d System.Byte instance 10 errorClass&lt;BR&gt;0x0343263c 0x4000fb1 0x8 CLASS instance 0x1c2eb298 server&lt;BR&gt;0x0343263c 0x4000fb2 0xc CLASS instance 0x14384218 message&lt;BR&gt;0x0343263c 0x4000fb3 0x10 CLASS instance 0x106fd73c procedure&lt;BR&gt;0x0343263c 0x4000fb4 0x18 System.Int32 instance 0 lineNumber&lt;/P&gt;
&lt;P&gt;Now we have something we can use. We have the server, the error message, and the procedure. If we look at the error message, here's what we see:&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !do 0x14384218 &lt;BR&gt;Name: System.String&lt;BR&gt;MethodTable 0x79b94638&lt;BR&gt;EEClass 0x79b94984&lt;BR&gt;Size 248(0xf8) bytes&lt;BR&gt;GC Generation: 2&lt;BR&gt;mdToken: 0x0200000f (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)&lt;BR&gt;String: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.&lt;/P&gt;
&lt;P&gt;That's a heck of a lot more useful than "System error," but it was a lot of work! You can save yourself some effort by scripting this. Each dump of an object contains an offset for each field. The field exists at that offset. In the SqlError above, the &lt;STRONG&gt;message&lt;/STRONG&gt; field is at an offset of 0xc. &lt;/P&gt;
&lt;P class=debugCmd&gt;0x0343263c 0x4000fb2 &lt;STRONG&gt;0xc&lt;/STRONG&gt; CLASS instance 0x14384218 message&lt;/P&gt;
&lt;P&gt;Therefore, you can get the message of this exception by dumping the address located at an offset of 0xc from the address of the SqlError itself. You can do that like this:&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; &lt;STRONG&gt;!do poi(0x106fd780+c)&lt;/STRONG&gt;&lt;BR&gt;Name: System.String&lt;BR&gt;MethodTable 0x79b94638&lt;BR&gt;EEClass 0x79b94984&lt;BR&gt;Size 248(0xf8) bytes&lt;BR&gt;GC Generation: 2&lt;BR&gt;mdToken: 0x0200000f (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)&lt;BR&gt;String: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.&lt;/P&gt;
&lt;P&gt;Look familiar? This is an important concept because it means that you can take the original SqlException object and follow the trail all the way to the actual error message. &lt;/P&gt;
&lt;P&gt;What if you want to just dump out details of all SQL errors that are on the heap? That's easy to, and the easiest way to do it is to write a script that you can then run inside of Windbg. What appears below is just such a script:&lt;/P&gt;
&lt;P class=debugCmd&gt;.logopen ${$CurrentDumpPath}\SqlErrors.txt&lt;BR&gt;.printf "Dumping all SqlError objects on the heap..."&lt;BR&gt;.printf "\n\n"&lt;BR&gt;&lt;BR&gt;.foreach (se {!dumpheap -short -mt 0x0343263c}) &lt;BR&gt;{&lt;BR&gt;&lt;BR&gt;.echo "=============================================================="&lt;BR&gt;.echo "SqlError : ${se}"&lt;BR&gt;.printf "Server Instance: "&lt;BR&gt;du poi(${se}+8)+c&lt;BR&gt;.printf "Procedure: "&lt;BR&gt;du poi(${se}+10)+c&lt;BR&gt;.printf "Error Message: "&lt;BR&gt;du poi(${se}+c)+c&lt;BR&gt;.printf "\n\n"&lt;BR&gt;&lt;BR&gt;}&lt;BR&gt;.logclose&lt;/P&gt;
&lt;P&gt;The first thing this script does is open a log file in the location where the dump file exists. That way, you'll automatically have a text file containing all of the errors. It then uses a &lt;STRONG&gt;.foreach&lt;/STRONG&gt; token to loop through all of the System.Data.SqlClient.SqlError objects. (In other words, all objects in MethodTable 0x0343263c.) For each one of those suckers, it dumps out the different fields using the offsets we looked at before.&lt;/P&gt;
&lt;P&gt;All of these fields are text values, and text in the Framework is represented using Unicode. That's why I use the &lt;STRONG&gt;du&lt;/STRONG&gt; command (&lt;STRONG&gt;d&lt;/STRONG&gt;ump &lt;STRONG&gt;U&lt;/STRONG&gt;nicode) to dump that out. You'll also notice that each one is dumping out the address+c. Unicode strings are at an offset of 12 from the address of the string, so you have to dump out the string starting at address+c.&lt;/P&gt;
&lt;P&gt;If you save the above into a text file and then copy it into the same folder where the debuggers are installed, you can then run it from within Windbg like this:&lt;/P&gt;
&lt;P class=debugCmd&gt;$&amp;gt;&amp;lt;dumpsqlerrors.txt&lt;/P&gt;
&lt;P&gt;That will give you output similar to this:&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; $&amp;gt;&amp;lt;dumpsqlerrors.txt&lt;BR&gt;Opened log file 'C:\Users\jamesche\Desktop\SqlErrors.txt'&lt;BR&gt;Dumping all SqlError objects on the heap...&lt;BR&gt;&lt;BR&gt;==============================================================&lt;BR&gt;SqlError : 0x106fd780&lt;BR&gt;Server Instance: 1c2eb2a4 "DBASESVR"&lt;BR&gt;Procedure: 106fd748 "ConnectionRead (recv())."&lt;BR&gt;Error Message: 14384224 "Timeout expired. The timeout pe"&lt;BR&gt;14384264 "riod elapsed prior to completion"&lt;BR&gt;143842a4 " of the operation or the server "&lt;BR&gt;143842e4 "is not responding."&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;==============================================================&lt;BR&gt;SqlError : 0x10721d38&lt;BR&gt;Server Instance: 1c2eb2a4 "DBASESVR"&lt;BR&gt;Procedure: 10721d00 "ConnectionRead (recv())."&lt;BR&gt;Error Message: 14384224 "Timeout expired. The timeout pe"&lt;BR&gt;14384264 "riod elapsed prior to completion"&lt;BR&gt;143842a4 " of the operation or the server "&lt;BR&gt;143842e4 "is not responding."&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;==============================================================&lt;BR&gt;SqlError : 0x185266a8&lt;BR&gt;Server Instance: 1c2eb2a4 "DBASESVR"&lt;BR&gt;Procedure: 18526670 "ConnectionRead (recv())."&lt;BR&gt;Error Message: 14384224 "Timeout expired. The timeout pe"&lt;BR&gt;14384264 "riod elapsed prior to completion"&lt;BR&gt;143842a4 " of the operation or the server "&lt;BR&gt;143842e4 "is not responding."&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;==============================================================&lt;BR&gt;SqlError : 0x18549968&lt;BR&gt;Server Instance: 18549948 "SQL-01"&lt;BR&gt;Procedure: 1c2d0230 ""&lt;BR&gt;Error Message: 185498d0 "Changed language setting to us_e"&lt;BR&gt;18549910 "nglish."&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;==============================================================&lt;BR&gt;SqlError : 0x1c42e894&lt;BR&gt;Server Instance: 1c2eb2a4 "DBASESVR"&lt;BR&gt;Procedure: 1c42e85c "ConnectionRead (recv())."&lt;BR&gt;Error Message: 14384224 "Timeout expired. The timeout pe"&lt;BR&gt;14384264 "riod elapsed prior to completion"&lt;BR&gt;143842a4 " of the operation or the server "&lt;BR&gt;143842e4 "is not responding."&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;==============================================================&lt;BR&gt;SqlError : 0x1c47faa8&lt;BR&gt;Server Instance: 1c2eb2a4 "DBASESVR"&lt;BR&gt;Procedure: 1c47fa70 "ConnectionRead (recv())."&lt;BR&gt;Error Message: 14384224 "Timeout expired. The timeout pe"&lt;BR&gt;14384264 "riod elapsed prior to completion"&lt;BR&gt;143842a4 " of the operation or the server "&lt;BR&gt;143842e4 "is not responding."&lt;BR&gt;&lt;BR&gt;&lt;BR&gt;Closing open log file C:\Users\jamesche\Desktop\DumpSqlErrors.txt&lt;/P&gt;
&lt;P class=Note&gt;The layout of the SqlError object isn't static. I wrote this script for ASP.NET 1.1 SP1. &lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=1776192" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category><category domain="http://blogs.msdn.com/jamesche/archive/tags/ASP.NET/default.aspx">ASP.NET</category></item><item><title>Generating a Dump In Your Code Using DebugBreak</title><link>http://blogs.msdn.com/jamesche/archive/2006/04/12/generating-a-dump-in-your-code-using-debugbreak.aspx</link><pubDate>Wed, 12 Apr 2006 20:29:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:575100</guid><dc:creator>jamesche</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/575100.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=575100</wfw:commentRss><description>&lt;P&gt;There are many issues that pop up that really require a user mode dump in order to successfully diagnose root cause. I will occasionally visit the ASP.NET newsgroups, and when I do, I read only those posts with a subject that interests me. I find that I gravitate towards crashes, high memory issues, hangs . . . in other words, those issues that require a user-mode debug in order to reach resolution. &lt;/P&gt;
&lt;P&gt;Hangs, high memory, crashes, and other such issues lend themselves well to getting a dump easily. Adplus is a great tool for such things, and if you're dealing with any of the aforementioned issues, getting a dump is a no-brainer. However, there are many issues that require dump analysis that aren't so straight-forward. I'm working just such an issue right now.&lt;/P&gt;
&lt;P&gt;I have a high-visibility customer experiencing a pretty serious problem. Their Web application is occasionally allowing one person to see another person's data. When this happens, the nature of the issue causes a System.Net.WebException to occur. In order to really dig into what's going on, we need to look into the down and dirty details of the HttpRequest and HttpResponse object at &lt;EM&gt;precisely&lt;/EM&gt; the moment that the issue occurs. (When I say "precisely", I mean just that. A tenth of a second too late and we're toast.) We need a dump at just that moment. &lt;/P&gt;
&lt;P&gt;We can use an adplus configuration file to dump when a WebException occurs, but that causes us to dump like crazy for every single occurrence of this exception type. That won't do. The application is throwing that exception for other reasons and we fill up disk space pretty quickly with dump files that are useless to us. We need a way to focus on just the specific WebException that's occurring when the problem occurs. Fortunately, we know the page that is executing when the error occurs. We can use that to get a dump by making an explicit call to kernel32!DebugBreak via P/Invoke. I have never tried this before, but the concept is a proven technique. No doubt about it; this is the way to go in this case.&lt;/P&gt;
&lt;P&gt;In this particular case, the customer is making an HttpWebRequest call that sometimes results in the WebException we're interested in. The first step is to wrap the WebRequest call in a try...catch block and catch the WebException. &lt;/P&gt;
&lt;P class=debugCmd&gt;try&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// make the HttpWebRequest call&lt;BR&gt;&lt;BR&gt;}&lt;BR&gt;catch(System.Net.WebException e)&lt;BR&gt;{&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; // call DebugBreak&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DebugBreak();&lt;BR&gt;}&lt;/P&gt;
&lt;P&gt;Now we need to add the P/Invoke call to DebugBreak.&lt;/P&gt;
&lt;P class=debugCmd&gt;[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError=true)]&lt;BR&gt;internal static extern void DebugBreak(); &lt;/P&gt;
&lt;P&gt;In order for all of this to work, we need to use Adplus to hook up the debugger (CDB in this case) and monitor for the BreakpointException breakpoint. I'll use an Adplus configuration file to do that. It looks like this.&lt;/P&gt;
&lt;P class=debugCmd&gt;&amp;lt;ADPlus&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;lt;Exceptions&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Option&amp;gt; NoDumpOnFirstChance &amp;lt;/Option&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Config&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Code&amp;gt; bpec &amp;lt;/Code&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Actions1&amp;gt; FullDump &amp;lt;/Actions1&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ReturnAction1&amp;gt; gh &amp;lt;/ReturnAction1&amp;gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/Config&amp;gt;&lt;BR&gt;&amp;nbsp; &amp;lt;/Exceptions&amp;gt;&lt;BR&gt;&amp;lt;/ADPlus&amp;gt;&lt;/P&gt;
&lt;P&gt;This is a pretty simple config file. The &amp;lt;Code&amp;gt; section defines a BreakpointException via the use of bpec. &amp;lt;Actions1&amp;gt; indicates the action we will take on first-chance exceptions that match the code. &amp;lt;ReturnAction1&amp;gt; specifies what we'll do after we dump. In this case, after dumping, we're executing a "gh" command which means "go / consider the exception handled." I've saved this config file into the same directory where the Debugging Tools for Windows are installed and named it &lt;STRONG&gt;debugbreak.cfg&lt;/STRONG&gt;. &lt;/P&gt;
&lt;P&gt;Now we need to run Adplus with the following command line.&lt;/P&gt;
&lt;P class=debugCmd&gt;cscript adplus.vbs -crash -pn w3wp.exe -c debugbreak.cfg -o c:\dbrkdump&lt;/P&gt;
&lt;P&gt;Everything's set up now and we're ready to capture a dump when the problem occurs. &lt;/P&gt;
&lt;P&gt;You can use this same technique anytime that you need to make your code cause a dump to be created. In fact, if you are writing code that you think might produce issues where a dump is required, you can put such code in your application in various places so that you can create a dump in various situations. If you decide to do that, it's advisable to check a Registry key or use some other method to determine if you should be calling DebugBreak.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=575100" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item><item><title>Out of memory! Are you nuts!?</title><link>http://blogs.msdn.com/jamesche/archive/2006/01/20/out-of-memory-are-you-nuts.aspx</link><pubDate>Sat, 21 Jan 2006 03:00:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:515583</guid><dc:creator>jamesche</dc:creator><slash:comments>8</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/515583.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=515583</wfw:commentRss><description>&lt;P&gt;I may have said this before (my memory's not what it was prior to decade number 4), but the most common issues I debug are memory issues. These range from high memory issues (categorized as 800MB of more memory in use by the worker process) to OutOfMemory exceptions, or OOM. High memory cases don't usually accompany any questions from customers other than "what the heck is taking so much memory?", but OOM conditions are often accompanied by "Why am I seeing OOM when the process is using 1GB of memory on a box with 4GB of memory available?" That's a good question, and fortunately, that question has a good answer as well.&lt;/P&gt;
&lt;P&gt;Before we go into why you might see an OOM exception, you first need to understand the basics of how we allocate memory in the .NET world. We allocate memory in&amp;nbsp;large contiguous blocks. Any objects you create are created in our heaps that are located within the memory segments we've allocated. As long as there is sufficient free space in the managed heap for your object, we won't allocate more memory from the OS. As objects are freed from gen 0, 1, and 2, we compact the heap so that you don't run into any problems with memory fragmentation in those heaps. The large object heap (LOH), which is where any object that's 85K or larger lives, is never compacted. However, we do maintain a list of free blocks in the LOH segments, and we'll reuse free blocks if we can. Therefore, fragmentation in the LOH is rarely an issue.&lt;/P&gt;
&lt;P&gt;With that out of the way, suppose you need to allocate an object that is 40K in size and we don't have enough memory on the gen 0 heap to create that object. We'll then go to the OS and attempt to allocate another contiguous segment. If we're unable to get a segment of contiguous memory from the OS, we'll throw an OOM exception. &lt;/P&gt;
&lt;P&gt;Another scenario: suppose that GC runs and we free all of the objects within a particular segment. We will then release that segment back to the OS. Now let's say that 5 minutes later, we need to grow the heap again. We go back to the OS to get a new segment, but some other process has placed a 1K allocation right at the beginning of that segment that we released. What happens? Well, if that 1k allocation fragments&amp;nbsp;your memory and we're unable to find contiguous&amp;nbsp;space&amp;nbsp;. . . you guessed it. An OOM occurs.&lt;/P&gt;
&lt;P&gt;So how do you avoid seeing this kind of thing? The first thing you can do is try and avoid a high memory situation. Almost all of the OOM cases we see involve high memory as well. That's because as you approach 80% of available memory for the process (and that's 2GB on a 32-bit machine without the 3GB switch enabled), the likelihood of seeing an OOM begins to increase exponentially. The reason for this (based on the information above) is pretty obvious. If you have reached the 80% mark, the chances of us finding a&amp;nbsp;large, contiguous&amp;nbsp;chunk of memory go down pretty substantially due to fragmentation. How do you avoid a high memory situation? There are a lot of answers to that question, but a few are:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Don't create a large number of objects on a per-request basis 
&lt;LI&gt;Don't store large objects in Session 
&lt;LI&gt;Don't cache user-specific data 
&lt;LI&gt;Call Dispose() on objects that provide a Dispose() method 
&lt;LI&gt;Call Marshal.ReleaseComObject in a loop on your COM objects (FinalReleaseComObject without the loop in 2.0) &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;I don't mean for that to be an exhaustive list, but you get the drift.&lt;/P&gt;
&lt;P&gt;Another thing that you want to do to avoid OOM situations is to avoid fragmentation. If system memory is fragmented, it becomes more difficult for the CLR to find contiguous memory to grow the managed heap. There are also a lot of causes of memory fragmentation, but I'd have to say that one of the most common we see is caused by dynamic assemblies. (See my &lt;A href="http://blogs.msdn.com/jamesche/archive/2005/12/20/506195.aspx" target=_blank mce_href="http://blogs.msdn.com/jamesche/archive/2005/12/20/506195.aspx"&gt;blog entry on the Debug attribute&lt;/A&gt;.) Other causes of dynamic assemblies are:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;XML serialization (We have &lt;A href="http://support.microsoft.com/?id=886385" target=_blank mce_href="http://support.microsoft.com/?id=886385"&gt;an article&lt;/A&gt; on this) 
&lt;LI&gt;Script blocks in XSL transforms (We have &lt;A href="http://support.microsoft.com/?id=313997" target=_blank mce_href="http://support.microsoft.com/?id=313997"&gt;an article&lt;/A&gt; on this, too) 
&lt;LI&gt;Heavy use of regular expressions 
&lt;LI&gt;Creating your own dynamic assemblies en masse &lt;/LI&gt;&lt;/UL&gt;
&lt;P&gt;There are a lot of other reasons for fragmentation as well, but these are among the most common.&lt;/P&gt;
&lt;P&gt;I hope this helps to explain why OOM conditions occur and how you can avoid them.&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=515583" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item><item><title>Getting the PID and TID of a COM Call</title><link>http://blogs.msdn.com/jamesche/archive/2005/12/30/getting-the-pid-and-tid-of-a-com-call.aspx</link><pubDate>Fri, 30 Dec 2005 22:40:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:508265</guid><dc:creator>jamesche</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/508265.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=508265</wfw:commentRss><description>&lt;P&gt;There are times when I may be debugging a latency issue that involves a COM component running in a dllhost process. Depending on where we are in that process, I may need to debug the dllhost process in addition to the aspnet_wp or w3wp process. So how do you know which dllhost process to debug, and once you do know the process, how do you know which thread your COM call is executing on? Getting that information isn't hard, but it's not something that you'll be able to figure out on your own.&lt;/P&gt;
&lt;H3&gt;Finding the Thread Making the COM Call &lt;/H3&gt;
&lt;P&gt;The first thing you need to do is locate the thread in the aspnet_wp or w3wp process that is executing the COM call. What you want to do is look for a native call to rpcrt4!I_RpcSendReceive. To do that, use the "~*kp" command in the debugger. This will dump out the stack for all threads. &lt;/P&gt;
&lt;P&gt;You can see the thread that you should target in the output below.&lt;/P&gt;
&lt;P class=debugCmd&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;STRONG&gt;26&lt;/STRONG&gt; Id: 560.a34 Suspend: 0 Teb: 7ff6c000 Unfrozen&lt;BR&gt;ChildEBP RetAddr &lt;BR&gt;01bffb68 77d5f2a1 NTDLL!NtRequestWaitReplyPort+0xb&lt;BR&gt;01bffb94 77d5d675 rpcrt4!LRPC_CCALL::SendReceive+0x11e&lt;BR&gt;&lt;STRONG&gt;01bffba0 7cf05eb8 rpcrt4!I_RpcSendReceive+0x2c &lt;/STRONG&gt;&amp;nbsp;&lt;BR&gt;01bffbc0 7cf05d4a OLE32!any_handle &amp;lt;PERF&amp;gt; &lt;BR&gt;01bffbd8 7cf05c57 OLE32!any_handle &amp;lt;PERF&amp;gt; &lt;BR&gt;01bffc18 7cf05eae OLE32!any_handle &amp;lt;PERF&amp;gt; &lt;BR&gt;01bffc88 7ce35b67 OLE32!any_handle &amp;lt;PERF&amp;gt; &lt;BR&gt;01bffce0 77d9a063 OLE32!CPagedVector::GetTableWithSect+0x1f7&lt;BR&gt;01bffcfc 77d9a011 rpcrt4!NdrProxySendReceive+0x4c&lt;BR&gt;01bfff44 77d99db8 rpcrt4!NdrClientCall2+0x4f5&lt;BR&gt;01bfff60 77d4183f rpcrt4!ObjectStublessClient+0x76&lt;BR&gt;01bfff70 787f5818 rpcrt4!ObjectStubless+0xf&lt;BR&gt;01bfffb4 7c57b388 comsvcs!CEventDispatcher::GetEventServerInfoThread(void * pData = 0xffffffff)+0x118&lt;BR&gt;01bfffec 00000000 KERNEL32!BaseThreadStart+0x52&lt;/P&gt;
&lt;P&gt;I've bolded the call that you want to look for in the stack above. Once you locate that stack, switch to that thread. In this case, it's thread 26, so I'll issue the "~26s" command to the debugger to switch to that thread.&lt;/P&gt;
&lt;H3&gt;Digging Into OLE32 Calls &lt;/H3&gt;
&lt;P&gt;The next step is not so easy to do because you won't have access to private symbols for OLE32.dll. That means that you don't have type information. (It also explains why the above stack shows several calls to OLE32!any_handle.) Before we go any further, let's first dump out the stack above with arguments using the kv command. I'll actually use the kvL command so that source paths are not included so that the output will be a bit cleaner. &lt;/P&gt;
&lt;P class=debugCmd&gt;0:026&amp;gt; kvL&lt;BR&gt;ChildEBP RetAddr Args to Child &lt;BR&gt;01bffb68 77d5f2a1 00000858 001297a8 001297a8 NTDLL!NtRequestWaitReplyPort+0xb (FPO: [3,0,0])&lt;BR&gt;01bffb94 77d5d675 0012b098 01bffbc0 7cf05eb8 rpcrt4!LRPC_CCALL::SendReceive+0x11e (FPO: [Non-Fpo])&lt;BR&gt;01bffba0 7cf05eb8 0012b098 0012923c 0012923c rpcrt4!I_RpcSendReceive+0x2c (FPO: [Non-Fpo])&lt;BR&gt;01bffbc0 7cf05d4a 00000000 0012923c 00000000 OLE32!any_handle &amp;lt;PERF&amp;gt; (OLE32+0xe5eb8)&lt;BR&gt;01bffbd8 7cf05c57 01bffc14 0012923c 0007eee8 OLE32!any_handle &amp;lt;PERF&amp;gt; (OLE32+0xe5d4a)&lt;BR&gt;01bffc18 7cf05eae &lt;STRONG&gt;0012923c&lt;/STRONG&gt; 01bffd4c 01bffd08 &lt;STRONG&gt;OLE32!any_handle &amp;lt;PERF&amp;gt; (OLE32+0xe5c57)&lt;/STRONG&gt;&lt;BR&gt;01bffc88 7ce35b67 0012923c 01bffd4c 01bffd08 OLE32!any_handle &amp;lt;PERF&amp;gt; (OLE32+0xe5eae)&lt;BR&gt;01bffce0 77d9a063 0012923c 01bffd4c 01bffd08 OLE32!CPagedVector::GetTableWithSect+0x1f7 (FPO: [Non-Fpo])&lt;BR&gt;01bffcfc 77d9a011 00120d14 01bffd98 05030117 rpcrt4!NdrProxySendReceive+0x4c (FPO: [Non-Fpo])&lt;BR&gt;01bfff44 77d99db8 7874c538 7874c5aa 01bfff78 rpcrt4!NdrClientCall2+0x4f5 (FPO: [Non-Fpo])&lt;BR&gt;01bfff60 77d4183f 00000018 00000003 7886cf0c rpcrt4!ObjectStublessClient+0x76 (FPO: [Non-Fpo])&lt;BR&gt;01bfff70 787f5818 00120d14 00000000 01bfffa8 rpcrt4!ObjectStubless+0xf&lt;BR&gt;01bfffb4 7c57b388 7886cec8 0197f1fc 00000000 comsvcs!CEventDispatcher::GetEventServerInfoThread+0x118 (FPO: [Uses EBP] [1,7,4]) (CONV: stdcall)&lt;BR&gt;01bfffec 00000000 787f5700 7886cec8 00000000 KERNEL32!BaseThreadStart+0x52 (FPO: [Non-Fpo])&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;Now let's focus on the calls into OLE32. All of the function names you see into OLE32 are wrong because your symbols don't line up. However, don't worry. You still have what you need. The call that you're interested in is the function at offset 0xe5c57. You want to dump out the memory at the address of that function call's first argument, in this case 0012923c. Both this function and the memory address of the first argument are bolded in the output above. To dump that memory, dd the address of the argument as follows: &lt;/P&gt;
&lt;P class=debugCmd&gt;0:026&amp;gt; dd &lt;STRONG&gt;0012923c&lt;/STRONG&gt; &lt;BR&gt;0012923c 7ce681b0 7ce55750 00000003 0000000a&lt;BR&gt;0012924c 00000000 00000000 &lt;STRONG&gt;0007eee8&lt;/STRONG&gt; 0007f7c0&lt;BR&gt;0012925c 0012dd90 0012bda0 7ce6a938 00060005&lt;BR&gt;0012926c 00000000 ffffffff 00000000 7ce681b0&lt;BR&gt;0012927c 7ce55750 00000001 00000124 00000000&lt;BR&gt;0012928c 00000000 0007ee08 00000000 00000000&lt;BR&gt;0012929c 00133100 7ce6a938 00060005 ffffffff&lt;BR&gt;001292ac ffffffff ffffffff 0013a748 f1eef1ee&lt;/P&gt;
&lt;P&gt;Now you want to focus on the 7th dword. (I've bolded it in the output above.) That is the address of an internal class in OLE32 that holds the information that you need. Do a dd on that address as follows.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:026&amp;gt; dd &lt;STRONG&gt;0007eee8&lt;/STRONG&gt; &lt;BR&gt;0007eee8 7cf117a0 0007ee78 &lt;STRONG&gt;00000a9c&lt;/STRONG&gt; &lt;STRONG&gt;000006e8&lt;/STRONG&gt;&lt;BR&gt;0007eef8 3a76f9c2 6ac6c553 7ecc4b7f 6e0ad0df&lt;BR&gt;0007ef08 0000c009 ffff0a9c b72cdaa4 0647c9ba&lt;BR&gt;0007ef18 00000082 00000000 00000000 000e2e00&lt;BR&gt;0007ef28 00000000 001273f0 00000006 ffffffff&lt;BR&gt;0007ef38 0007ecf0 001266b4 00000003 00000000&lt;BR&gt;0007ef48 00000000 00000001 00000000 00060005&lt;BR&gt;0007ef58 00000000 f1eef1ee 00000000 00000000&lt;/P&gt;
&lt;P&gt;The values you are interested in here are the 3rd and 4th dwords. (I've bolded them.) The 3rd dword is the PID of the dllhost process that this COM call is executing in. In this case, it's hex a9c. You can easily convert that to decimal in the debugger using the ? command as follows.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:026&amp;gt; ? &lt;STRONG&gt;a9c&lt;/STRONG&gt;&lt;BR&gt;Evaluate expression: &lt;STRONG&gt;2716&lt;/STRONG&gt; = 00000a9c&lt;/P&gt;
&lt;P&gt;So now you know that you're looking for the dllhost process with a PID of 2716. The thread that your COM call is executing on is the 4th dword of the output above, in this case 6e8. To find that thread, open the dump of the dllhost process ID 2716 and issue a "~" command. You'll see the thread ID as follows.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; ~&lt;BR&gt;. 0 Id: a9c.464 Suspend: 0 Teb: 7ffde000 Unfrozen&lt;BR&gt;1 Id: a9c.c94 Suspend: 0 Teb: 7ffdd000 Unfrozen&lt;BR&gt;2 Id: a9c.4bc Suspend: 0 Teb: 7ffdc000 Unfrozen&lt;BR&gt;3 Id: a9c.b10 Suspend: 0 Teb: 7ffd9000 Unfrozen&lt;BR&gt;4 Id: a9c.c30 Suspend: 0 Teb: 7ffd8000 Unfrozen&lt;BR&gt;5 Id: a9c.c24 Suspend: 0 Teb: 7ffd7000 Unfrozen&lt;BR&gt;6 Id: a9c.65c Suspend: 0 Teb: 7ffd6000 Unfrozen&lt;BR&gt;7 Id: a9c.398 Suspend: 0 Teb: 7ffd4000 Unfrozen&lt;BR&gt;8 Id: a9c.3dc Suspend: 0 Teb: 7ffaf000 Unfrozen&lt;BR&gt;9 Id: a9c.c2c Suspend: 0 Teb: 7ffd5000 Unfrozen&lt;BR&gt;10 Id: a9c.b68 Suspend: 0 Teb: 7ffae000 Unfrozen&lt;BR&gt;11 Id: a9c.2c0 Suspend: 0 Teb: 7ffad000 Unfrozen&lt;BR&gt;12 Id: a9c.b74 Suspend: 0 Teb: 7ffac000 Unfrozen&lt;BR&gt;&lt;STRONG&gt;13&lt;/STRONG&gt; Id: a9c.&lt;STRONG&gt;6e8&lt;/STRONG&gt; Suspend: 0 Teb: 7ffab000 Unfrozen&lt;/P&gt;
&lt;P&gt;Your COM call is executing on thread 13. You now have the information that you need to dig into that thread and see what's going on.&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=508265" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item><item><title>Digging Into Objects</title><link>http://blogs.msdn.com/jamesche/archive/2005/12/22/digging-into-objects.aspx</link><pubDate>Fri, 23 Dec 2005 00:30:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:506940</guid><dc:creator>jamesche</dc:creator><slash:comments>6</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/506940.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=506940</wfw:commentRss><description>&lt;P&gt;One of our engineers asked me yesterday about getting the total size for objects in memory. The debugger is adept at doing such things, but you have to understand how object sizes are interpreted.&lt;/P&gt;
&lt;P&gt;Let me start by giving you some background on how I'll typically deal with troubleshooting a high memory situation. The first thing of interest to me is whether we're dealing with a managed leak (.NET code) or a native leak. To discover that, I'll use the !eeheap debugger command.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:000&amp;gt; !eeheap -gc&lt;BR&gt;Loaded Son of Strike data table version 5 from "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorsvr.dll"&lt;BR&gt;Number of GC Heaps: 4&lt;BR&gt;Large object heap starts at 0x232d0030&lt;BR&gt;segment begin allocated size&lt;BR&gt;0x232d0000 0x232d0030 0x23bdac68 0x0090ac38(9,481,272)&lt;BR&gt;Heap Size 0x16980074(379,060,340)&lt;BR&gt;------------------------------&lt;BR&gt;GC Heap Size 0x575ef130(1,465,839,920)&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;I've edited out much of the output here for the sake of brevity, but you can clearly see that this output illustrates an enormous amount of memory being used by the managed heaps. (The -gc switch shows us what's in the GC heaps or managed heaps.) In this case, we're looking at over 1GB of memory in use. Based on that, I know that I need to dig into managed objects in order to locate the source of the problem.&lt;/P&gt;
&lt;P&gt;In this post, I'm not going to dig into the particular dump I've shown above because it's an actual customer dump that I'm working on. Instead, I will show a simplified example based off of a simple example application. Don't worry. The concepts are actually more clearly illustrated from the example dump I will use.&lt;/P&gt;
&lt;P class=SubTitle mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P class=SubTitle&gt;What's using my memory?&lt;/P&gt;
&lt;P&gt;Once you know that your memory is being sucked up by managed objects, you'll need to dig into the managed heaps to find out where your memory is being used. The first step in doing that is to dump out the heap statistics using !dumpheap -stat.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !dumpheap -stat&lt;BR&gt;Loaded Son of Strike data table version 5 from "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorwks.dll"&lt;BR&gt;total 108,014 objects&lt;BR&gt;Statistics:&lt;BR&gt;MT &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Count &amp;nbsp;TotalSize &amp;nbsp;Class Name&lt;BR&gt;0x79c025fc 1 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;12 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.UnloadWorker&lt;BR&gt;0x79bfc98c 1 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;12 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Runtime.Remoting.Activation.ActivationListener&lt;BR&gt;0x79bfc7c4 1 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;12 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Runtime.Remoting.Activation.LocalActivator&lt;BR&gt;0x79bfc390 1 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;12 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Reflection.DefaultMemberAttribute&lt;BR&gt;0x79bf48ac 1 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;12 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Runtime.Remoting.Messaging.IllogicalCallContext&lt;BR&gt;0x033c7c24 10 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;200 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Staticmethod.Sclass&lt;BR&gt;0x00be209c 13,563 714,108 &amp;nbsp;&amp;nbsp;&amp;nbsp;System.Object[]&lt;BR&gt;0x79b925c8 21,893 2,659,728 &amp;nbsp;System.String&lt;BR&gt;0x00be2c3c 78 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;10,705,064 System.Byte[]&lt;BR&gt;Total 108,014 objects, Total size: 16,259,364&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;This output shows you all of the objects in the managed heap ordered by the amount of memory used by that type. The first column you see is the address of the method table (MT) followed by the count of objects, the total size of all of the objects (which is what we'll dig into soon), and the class name. Naturally I have cut down the size of the output dramatically for this example, but what we need is displayed above.&lt;/P&gt;
&lt;P&gt;In this example, it's clear that the majority of memory is taken up by System.Byte[], about 10MB. I'm interested in seeing where those byte arrays are coming from and the debugger will let you do that pretty easily using the !gcroot command. However, !gcroot works on the address of an object, and right now we only have the address of a method table. To find out the address of the objects within the method table, use !dumpheap with the -mt switch and pass it the method table address that you got from the !dumpheap -stat command.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !dumpheap -mt 0x00be2c3c&lt;BR&gt;Address &amp;nbsp;&amp;nbsp;&amp;nbsp;MT &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Size &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Gen&lt;BR&gt;0x00f02c54 0x00be2c3c 172 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;0x00f02efc 0x00be2c3c 12 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;0x00f062e8 0x00be2c3c 28 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;0x00f06438 0x00be2c3c 76 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;0x00f094a8 0x00be2c3c 172 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;0x00f09628 0x00be2c3c 172 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;&lt;BR&gt;. . . more output here . . . &lt;BR&gt;&lt;BR&gt;0x01f0c020 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0200c030 0x00be2c3c 1,048,592 &amp;nbsp;3 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;System.Byte[] &lt;BR&gt;0x0210c050 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0220c070 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0230c090 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0240c0a0 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0250c0c0 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0260c0e0 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0270c100 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;0x0280c120 0x00be2c3c 1,048,592 &amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; System.Byte[] &lt;BR&gt;total 78 objects&lt;/P&gt;
&lt;P&gt;You can clearly see a pattern here. Most of the memory from byte arrays is taken up by byte arrays that are almost exactly 1MB in size. (Notice also that this ouput shows you what generation an object is in. The large byte arrays are in the large object heap which is denoted here by gen 3.) In fact, many times when you have a memory issue, you will see patterns like this, but they are nowhere near as obvious as this. :)&lt;/P&gt;
&lt;P&gt;Now that we have a better idea as to which byte arrays are the culprits, it's time to see what created them by using !gcroot on the address as I discussed above. We'll pick the first large one.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !gcroot 0x01f0c020 &lt;BR&gt;Scan Thread 1 (0x1720)&lt;BR&gt;Scan Thread 5 (0x13f0)&lt;BR&gt;Scan Thread 8 (0xff8)&lt;BR&gt;Scan HandleTable 0x156aa0&lt;BR&gt;Scan HandleTable 0x15a4b8&lt;BR&gt;Scan HandleTable 0x235c68&lt;BR&gt;HANDLE(Strong):44f1148:Root:0xf56500(System.Web.NativeFileChangeNotification)-&amp;gt;&lt;BR&gt;0xf564e4(System.Web.DirMonCompletion)-&amp;gt;0xf56214(System.Web.DirectoryMonitor)-&amp;gt;&lt;BR&gt;0xf56318(System.Web.FileMonitor)-&amp;gt;0xf56348(System.Collections.Hashtable)-&amp;gt;&lt;BR&gt;0xf5637c(System.Collections.Hashtable/bucket[])-&amp;gt;0xf5467c(System.Web.FileChangesMonitor)-&amp;gt;&lt;BR&gt;0xf55da4(System.Web.FileChangeEventHandler)-&amp;gt;0xf532d0(System.Web.HttpRuntime)-&amp;gt;&lt;BR&gt;0xf5496c(System.Web.Caching.CacheSingle)-&amp;gt;0xf54a1c(System.Web.Caching.CacheExpires)-&amp;gt;&lt;BR&gt;0xf54c68(System.Object[])-&amp;gt;0xf55728(System.Web.Caching.ExpiresBucket)-&amp;gt;0x13700e8(System.Web.Caching.ExpiresEntry[])-&amp;gt;&lt;BR&gt;0x1370098(System.Web.Caching.CacheEntry)-&amp;gt;0x1370068(System.Web.SessionState.InProcSessionState)-&amp;gt;&lt;BR&gt;0xfbfd38(System.Web.SessionState.SessionDictionary)-&amp;gt;0xfbff80(System.Collections.Hashtable)-&amp;gt;&lt;BR&gt;0xfc924c(System.Collections.Hashtable/bucket[])-&amp;gt;&lt;BR&gt;0xfc0bdc(System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)-&amp;gt;&lt;BR&gt;0xfc0b9c(Staticmethod.Sclass)-&amp;gt;0x1f0c020(System.Byte[])&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;The !gcroot command walks the object tree and tells you where an object is rooted. (A discussion of this is outside of the scope of what I'm discussing here.) You can see that we scanned the threads as well as handletables. Each app domain had a handletable where objects can also be rooted. In this case, our byte array is rooted in a handletable and the output shows some interesting things worth discussing. &lt;/P&gt;
&lt;P&gt;Let's look at this output starting at the CacheEntry object. You can see this byte array rooted in a CacheEntry object, then in InProcSessionState, and eventually, in a Staticmethod.Sclass object. What this tells us is that the Staticmethod.Sclass object is being stored in InProc Session state and that it is rooting the byte array. (In ASP.NET InProc Session state, objects are stored in cache which is why you see this rooted in a CacheEntry.)&lt;/P&gt;
&lt;P&gt;Because of this ouput, I'm interested in having a look into the Staticmethod.Sclass object. If you refer back to the output of !dumpheap -stat, you'll see that there are 10 of these objects for a total of 200 bytes:&lt;/P&gt;
&lt;P class=debugCmd&gt;0x033c7c24 10 200 Staticmethod.Sclass&lt;/P&gt;
&lt;P&gt;Let's use !dumpheap -mt to dump out these individual objects just as we did before.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !dumpheap -mt 0x033c7c24 &lt;BR&gt;Address &amp;nbsp;&amp;nbsp;&amp;nbsp;MT &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Size Gen&lt;BR&gt;0x00f0f164 0x033c7c24 20 &amp;nbsp;&amp;nbsp;2 Staticmethod.Sclass &lt;BR&gt;0x00fc0b9c 0x033c7c24 20&amp;nbsp;&amp;nbsp; 2 Staticmethod.Sclass &lt;BR&gt;0x00fc0c08 0x033c7c24 20 &amp;nbsp;&amp;nbsp;2 Staticmethod.Sclass &lt;BR&gt;0x00fc0c48 0x033c7c24 20 &amp;nbsp;&amp;nbsp;2 Staticmethod.Sclass &lt;BR&gt;0x00fc4230 0x033c7c24 20 &amp;nbsp;&amp;nbsp;2 Staticmethod.Sclass &lt;BR&gt;0x00fc427c 0x033c7c24 20 &amp;nbsp;&amp;nbsp;1 Staticmethod.Sclass &lt;BR&gt;0x00fc42bc 0x033c7c24 20 &amp;nbsp;&amp;nbsp;1 Staticmethod.Sclass &lt;BR&gt;0x00fc42fc 0x033c7c24 20 &amp;nbsp;&amp;nbsp;1 Staticmethod.Sclass &lt;BR&gt;0x00fc433c 0x033c7c24 20 &amp;nbsp;&amp;nbsp;1 Staticmethod.Sclass &lt;BR&gt;0x00fc93a8 0x033c7c24 20 &amp;nbsp;&amp;nbsp;1 Staticmethod.Sclass &lt;BR&gt;total 10 objects&lt;/P&gt;
&lt;P&gt;Sure enough, there are 10 of these and each is 20 bytes for a total of 200 bytes. However, if you were to use the !objsize command to get the size of one of these objects, you might be surprised to see how large it actually is.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !objsize 0x00f0f164 &lt;BR&gt;sizeof(0xf0f164) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;What the heck? Which one of these commands is giving me wrong information? Actually, neither. The output that you see from !dumpheap -mt shows you the size of ONLY the Staticmethod.Sclass object. It doesn't include the size of members of that class because those members are listed elsewhere in the output.&amp;nbsp;Let's dig into this object size output a bit more.&lt;/P&gt;
&lt;P&gt;You can get a good idea of what makes up the 1MB or so used by this object by simply using the -v switch for !objsize like so.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !objsize 0x00f0f164 -v&lt;BR&gt;sizeof(0xf0f164) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc0bb0) = 32 ( 0x20) bytes (System.String)&lt;BR&gt;sizeof(0x230c090) = 1,048,588 (0x10000c) bytes (System.Byte[])&lt;/P&gt;
&lt;P&gt;Now things make a bit more sense. We&amp;nbsp;can see from this ouput that the Staticmethod.Sclass object has a couple of member variables; a&amp;nbsp;byte array that is 1,048,588 bytes and a string that's 32 bytes. Add the original 20 bytes to those two and you end up with 1,048,640, the value that !objsize returned. Pretty cool!&lt;/P&gt;&lt;BR&gt;
&lt;DIV class=Note&gt;&lt;B&gt;Note:&lt;/B&gt; It's worth noting that in normal circumstances, you would want to use a command like db to dump out these byte arrays and see what's in them. In this case, we're only interested in discussing the details of getting the size of objects, so I won't go into that in this post. Besides, I happen to know that there's nothing in these byte arrays. :)&lt;/DIV&gt;&lt;BR&gt;
&lt;P&gt;Now for another cool trick. Suppose that you wanted to dump out the !objsize output for all of these. In this example, that's not too hard. After all, there are only 10 of them. However, suppose there were 3,000 of them. That would be quite an undertaking if you had to do it manually, but you don't have to. You can use the .foreach token in the debugger to do it for you! Check this out.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; .foreach (obj {!dumpheap -mt 0x033c7c24 -short}) {!objsize obj}&lt;BR&gt;sizeof(0xf0f164) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc0b9c) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc0c08) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc0c48) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc4230) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc427c) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc42bc) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc42fc) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc433c) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;BR&gt;sizeof(0xfc93a8) = 1,048,640 (0x100040) bytes (Staticmethod.Sclass)&lt;/P&gt;
&lt;P&gt;The format of this command is pretty simple. The "obj" is simply a variable that is populated by the output from the command in curly brackets immediately after it. In this case, I'm running !dumpheap -mt on the method table of the Staticmethod.Sclass objects. The -short switch causes the command to ONLY output the address of the object and no other details. Here's what that command outputs.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; !dumpheap -mt 0x033c7c24 -short&lt;BR&gt;0xf0f164&lt;BR&gt;0xfc0b9c&lt;BR&gt;0xfc0c08&lt;BR&gt;0xfc0c48&lt;BR&gt;0xfc4230&lt;BR&gt;0xfc427c&lt;BR&gt;0xfc42bc&lt;BR&gt;0xfc42fc&lt;BR&gt;0xfc433c&lt;BR&gt;0xfc93a8&lt;/P&gt;
&lt;P&gt;As you can see, this is the perfect kind of output if what we really want to do is loop through each one of these addresses and run a command on each. That's the purpose of the -short switch. I use it often, and now you see why. &lt;/P&gt;
&lt;P&gt;The second set of curly brackets contains a second command to run on each "obj" variable, in this case, the !objsize command. The result is the size output of each object as you can see in the listing above. &lt;/P&gt;
&lt;P&gt;Back to our theoretical example of 3,000 objects. You now know how to use the .foreach token to get the size of each, but you are still left with using a calculator to add them all up. Once again, the debugger comes to your rescue. You can use a feature of the debugger called "pseudo-registers" to store the cumulative size of objects and then output the total value once they've all been calculated. This allows you to actually add up the total values as you dump them. The command to do that is a bit trickier. Here it is.&lt;/P&gt;
&lt;P class=debugCmd&gt;0:013&amp;gt; r $t2 = 0; .foreach ( obj {!dumpheap -short -mt 0x033c7c24} ) { r $t2 = $t2 + 1; .foreach( theSize { !objsize -short obj } ) { r $t1 = $t1 + theSize; ? theSize } }; .echo Total Size:; ?$t1&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Evaluate expression: 1048640 = 00100040&lt;BR&gt;Total Size:&lt;BR&gt;Evaluate expression: 10486400 = 00a00280&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;This command uses the pseudo-registers I mentioned above to keep track of the objects. There are 20 pseudo-registers, $t0 - $t19. This command begins by assigning the value of 0 to the $t2 pseudo-register. (r $t2 = 0) After we assign that value, we loop through the objects as we did before. However, in this case, we keep track of the total size using a new variable (theSize). We increment the $t1 pseudo-register with each iteration. Finally, we print out the total size using the .echo command to print the value of the $t1 pseudo-register.&lt;/P&gt;
&lt;P&gt;I realize that this may be getting a little confusing. Fortunately, the debugger comes with excellent documentation. I urge you to have a look. Pseudo-registers are a powerful feature of the debugger. The only drawback is that it may become tedious to remember these commands. In a future post, I'll show you how to set up aliases so that you can type a simple command and execute complex instructions that you've written. It's cool, I promise. &lt;/P&gt;
&lt;P&gt;One more important thing to keep in mind. Getting the size of a complex object can take a long time, so you'll want to carefully decide whether or not you have the time for a command of this level of complexity to complete. I worked a case once where a customer had millions of a particular object. After running the debugger on it for over a day, I did some math and determined that it was going to take 36 years for the command to complete! The moral? Do your math first!&lt;/P&gt;
&lt;P&gt;Happy debugging, and Merry Christmas! &lt;/P&gt;
&lt;P&gt;Jim &lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=506940" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item><item><title>Are you in GC?</title><link>http://blogs.msdn.com/jamesche/archive/2005/12/20/are-you-in-gc.aspx</link><pubDate>Tue, 20 Dec 2005 20:49:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:506005</guid><dc:creator>jamesche</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/506005.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=506005</wfw:commentRss><description>&lt;P&gt;When you're debugging, you're really just looking into internal memory structures in a process. When it comes to debugging an ASP.NET application, you are oftentimes dumping out method tables, objects, etc. The ability to do so requires a known address. That's no problem most of the time. However, if you're in a GC when a dump is taken, we may be in the process of moving some of those structures around for compaction. In that case, you may see what looks like managed heap corruption when in fact you're seeing the result of the compaction phase of GC. For that reason, it's often convenient to know if you're in the process of GC when you crack open a dump. To do so, you can check GcHeap::GcInProgress.&lt;/P&gt;
&lt;P&gt;First thing you'll want to do is make sure that you have a good symbol path. You can check your symbol path by running the .sympath command in Windbg as so:&lt;/P&gt;&lt;FONT size=1&gt;
&lt;P&gt;&lt;FONT color=#000000&gt;
&lt;DIV class=debugCmd&gt;&lt;BR&gt;&lt;FONT face="Courier New" size=2&gt;0:000&amp;gt; .sympath&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;Symbol search path is: srv*c:\\symbols*http://msdl.microsoft.com/download/symbols&lt;/FONT&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;BR&gt;&lt;BR&gt;&lt;/DIV&gt;&lt;BR&gt;
&lt;P&gt;You can set your symbol path inWindbg using the same command like so:&lt;/P&gt;&lt;FONT face="Courier New" size=2&gt;
&lt;DIV class=debugCmd&gt;&lt;BR&gt;&lt;FONT face="Courier New" size=2&gt;.sympath srv*c:\\symbols*http://msdl.microsoft.com/download/symbols&lt;BR&gt;&lt;BR&gt;&lt;/FONT&gt;&lt;/DIV&gt;&lt;/FONT&gt;&lt;BR&gt;
&lt;P&gt;What this command does is set up a local symbol server in the c:\symbols directory. When Windbg needs symbols for a particular module, it checks the c:\symbols directory for those symbols. If they're there, great. If not, it downloads the symbols from the Microsoft public symbol server (&lt;A href="http://msdl.microsoft.com/download/symbols" mce_href="http://msdl.microsoft.com/download/symbols"&gt;http://msdl.microsoft.com/download/symbols&lt;/A&gt;) and stores them in the c:\symbols folder. Note that it can take a bit of time for symbols to download.&lt;/P&gt;
&lt;P&gt;Now you'll need to know whether you are running workstation or server GC. In most cases, this is determined by whether you are running a multi-proc box. (Hyperthreaded boxes do count as multi-proc.) A multi-proc box will usually be running server GC (mscorsvr.dll) and a single-proc box will usually be running workstation GC (mscorwks.dll). You can double-check to see which you have loaded using the &lt;FONT face="Courier New"&gt;lmv&lt;/FONT&gt; command in Windbg.&lt;/P&gt;&lt;FONT size=1&gt;
&lt;DIV class=debugCmd&gt;&lt;FONT face="Courier New" size=2&gt;0:000&amp;gt; lmv mmscorsvr&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;start end module name&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;791b0000 79419000 mscorsvr (deferred) &lt;/FONT&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;FONT face="Courier New" size=2&gt;Image path: C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorsvr.dll&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;Image name: mscorsvr.dll&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;Timestamp: Fri Feb 18 14:58:55 2005 (4216570F)&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;CheckSum: 0026A38C&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;ImageSize: 00269000&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;File version: 1.1.4322.2300&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;Product version: 1.1.4322.2300&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;File flags: 20 (Mask 3F) Special&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;File OS: 4 Unknown Win32&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;File type: 2.0 Dll&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;File date: 00000000.00000000&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;Translations: 0409.04e4&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;CompanyName: Microsoft Corporation&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;ProductName: Microsoft .NET Framework&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;InternalName: MSCORSVR.DLL&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;OriginalFilename: mscorsvr.dll&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;ProductVersion: 1.1.4322.2300&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;FileVersion: 1.1.4322.2300&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;FileDescription: Microsoft .NET Runtime Common Language Runtime - Server&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;LegalCopyright: Copyright © Microsoft Corporation 1998-2002. All rights reserved.&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;LegalTrademarks: Microsoft® is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation&lt;BR&gt;&lt;/FONT&gt;&lt;FONT face="Courier New" size=2&gt;Comments: Microsoft .NET Runtime Common Language Runtime - Server&lt;/FONT&gt;&lt;/P&gt;&lt;/DIV&gt;&lt;/FONT&gt;
&lt;P&gt;Notice that the command is &lt;FONT face="Courier New"&gt;lmv m&lt;EM&gt;&amp;lt;module_name&amp;gt;&lt;/EM&gt;&lt;/FONT&gt;. In other words, don't forget the &lt;FONT face="Courier New"&gt;m&lt;/FONT&gt; in front of the module name. (You can also put the 'm' immediately after the 'v' followed by a space and the module name. My muscle memory simply types it the other way.) In the case above, it's clear that I'm running server GC (mscorsvr.dll). Now you're ready to check for GC.&lt;/P&gt;
&lt;P&gt;To check for the status of GC, you will use the &lt;FONT face="Courier New"&gt;dd&lt;/FONT&gt; command in the debugger. The &lt;FONT face="Courier New"&gt;dd&lt;/FONT&gt; command allows you to display double-word values. The format for the &lt;FONT face="Courier New"&gt;dd&lt;/FONT&gt; command is &lt;FONT face="Courier New"&gt;dd &lt;EM&gt;&amp;lt;address&amp;gt;&lt;/EM&gt;&lt;/FONT&gt; and in this case, you want to dump out the address of &lt;FONT face="Courier New"&gt;mscorsvr!GCHeap::GcInProgress&lt;/FONT&gt;.&lt;/P&gt;&lt;FONT size=1&gt;
&lt;DIV class=debugCmd&gt;&lt;FONT face="Courier New" size=2&gt;0:000&amp;gt; dd mscorsvr!GCHeap::GcInProgress&lt;BR&gt;793e3218 00000000 00000004 00000028 00000002&lt;BR&gt;793e3228 00000003 00000002 0000000f 00000001&lt;BR&gt;793e3238 00000000 00000001 00000001 00000002&lt;BR&gt;793e3248 7c821ad8 01a31e90 ffffffff 00f0ba0f&lt;BR&gt;793e3258 792f2e08 00000000 00000000 792543ac&lt;BR&gt;793e3268 00000000 00000000 7921d1fd 00000000&lt;BR&gt;793e3278 00000000 792543c4 00000000 00000000&lt;BR&gt;793e3288 791b4151 00000000 00000000 792f3263&lt;/FONT&gt;&lt;/DIV&gt;&lt;/FONT&gt;
&lt;P&gt;Note that &lt;FONT face="Courier New"&gt;dd&lt;/FONT&gt; dumps out 32 DWORDs by default. You are actually interested in the first DWORD here, so you can clean up the output a bit by using a different command:&lt;/P&gt;
&lt;DIV class=debugCmd&gt;&lt;FONT face="Courier New" size=2&gt;0:000&amp;gt; dd mscorsvr!GCHeap::GcInProgress L1&lt;BR&gt;793e3218 00000000&lt;/FONT&gt;&lt;/DIV&gt;
&lt;P&gt;Now we have the one DWORD we're interested in and we can clearly see that the value is 0. Therefore, we know that we are not currently in GC.&lt;/P&gt;
&lt;P&gt;One note: If you were using workstation GC, you will need to replace &lt;FONT face="Courier New"&gt;mscorsvr&lt;/FONT&gt; with &lt;FONT face="Courier New"&gt;mscorwks&lt;/FONT&gt; in these commands.&lt;/P&gt;
&lt;P&gt;Have fun!&lt;/P&gt;
&lt;P&gt;Jim&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=506005" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item><item><title>Clarifications on Debugging</title><link>http://blogs.msdn.com/jamesche/archive/2005/12/20/clarifications-on-debugging.aspx</link><pubDate>Tue, 20 Dec 2005 18:46:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:506848</guid><dc:creator>jamesche</dc:creator><slash:comments>3</slash:comments><comments>http://blogs.msdn.com/jamesche/comments/506848.aspx</comments><wfw:commentRss>http://blogs.msdn.com/jamesche/commentrss.aspx?PostID=506848</wfw:commentRss><description>&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;When I'm talking to ASP.NET developers who are experiencing memory issues, crashes, hangs, etc., I will often say, "Hook up the debugger and see what's going on." More often than not, the response that I get from that recommendation is one of confusion. Most developers think of "the debugger" as Visual Studio or some other source code debugger. In fact, when I say "the debugger", I'm talking about Windbg, a debugger included in the &lt;/FONT&gt;&lt;A href="http://www.microsoft.com/whdc/devtools/debugging/default.mspx" mce_href="http://www.microsoft.com/whdc/devtools/debugging/default.mspx"&gt;&lt;FONT face=Tahoma size=2&gt;Debugging Tools for Windows&lt;/FONT&gt;&lt;/A&gt;&lt;FONT face=Tahoma size=2&gt;.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;Windbg allows you to do most of the same debugging tasks that you can perform in Visual Studio, but it has additional capabilities. Those additional capabilities come at the price of being a bit harder to learn how to use. But hey, if you are an ASP.NET developer, you're obviously smart enough to learn new tricks. Learning to use Windbg is definitely a trick worth learning.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;Suppose you have an ASP.NET application that consumes large amounts of memory over time. If you want to find out what's taking up so much memory, you can profile the application. However, there are some serious drawbacks to taking that approach. Profiling is expensive from a performance standpoint. In fact, in a production environment, running a profiler is most often not an option. What's a developer to do? I have an answer! You can use the Debugging Tools for Windows to create a user-mode dump file which can then be analyzed with Windbg to determine where you are consuming memory. &lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;First thing you'll need to do is create a dump file. The easiest way to do that is to use ADPlus. Here's an article that will walk you through that process:&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;286350&amp;nbsp;How to use ADPlus to troubleshoot "hangs" and "crashes"&lt;BR&gt;&lt;/FONT&gt;&lt;A href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;286350" mce_href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;286350"&gt;&lt;FONT face=Tahoma size=2&gt;http://support.microsoft.com/default.aspx?scid=kb;EN-US;286350&lt;/FONT&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;After you've got a dump file, you'll need to set up Windbg to debug it. I wrote an article on this about a year ago. While I certainly wouldn't claim that this article is the best resource available on the subject, it's one that I have handy so it's what you're going to get. :)&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;892277&amp;nbsp;Troubleshooting ASP.NET using WinDbg and the SOS extension&lt;BR&gt;&lt;/FONT&gt;&lt;A href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;892277" mce_href="http://support.microsoft.com/default.aspx?scid=kb;EN-US;892277"&gt;&lt;FONT face=Tahoma size=2&gt;http://support.microsoft.com/default.aspx?scid=kb;EN-US;892277&lt;/FONT&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;Note that this is all 1.x specific.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;The article above is a crash dump analysis, and a &lt;EM&gt;very&lt;/EM&gt; simple one at that. If you want details on how to debug memory issues, your best bet is to review our PAG documentation. It's excellent information for debuggers.&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;A href="http://msdn2.microsoft.com/en-us/library/ms954594.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms954594.aspx"&gt;&lt;FONT face=Tahoma size=2&gt;http://msdn2.microsoft.com/en-us/library/ms954594.aspx&lt;/FONT&gt;&lt;/A&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;From here on out, I'll try to provide you with some cool debugging tricks that come straight from the work that I do everyday. Hopefully it will be useful to you in your debugging endeavors!&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;Jim&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT face=Tahoma size=2&gt;&lt;/FONT&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=506848" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/jamesche/archive/tags/Debugging/default.aspx">Debugging</category></item></channel></rss>