Friday, May 23, 2008

Most of the work I currently do (during the day) is with WinForms applications and most of the time, debugging their problems really isn't all that hard. Application hangs or crashes that only occur on one machine at a remote location are not easy though. We ran into one of those situations a few weeks back. I told our support group that if they saw the hang again to let me know and I would swing by so that we could get a memory dump of the application and start debugging it. Here's everything I did to figure out what went wrong.

First thing, install Debugging Tools for Windows. This will get you things like adplus and windbg. Make sure you pick the right version for your platform (i.e. x86 or x64) because the debugger has to match the platform you're debugging on. I already had it installed on my machine, but I shared the directory so that I could access the tools on the remote machine that was having the hang.

Next, I remoted into the machine with the problem and ran adplus to get a memory dump. To do this, I first copied over the debugging directory to the remote machine from my share. Luckily, the Debugging Tools can be copied and therefore don't need an MSI or setup executable. To get the dump, I pulled up a command prompt, moved to the debugging directory, and ran 'adplus -hang -p 3423' where 3423 was the process ID for the application that was hanging. If you're dealing with an application that is crashing, you would need to pass different arguments in. By the way, note that adplus is just a VBScript - this means that you can open it and see what they're doing if you're so inclined. You can get the PID from either task manager or process explorer.

image

Let this script run to completion and it should create the memory dump (in this case, a minidump) in a directory that looks something like this:

image

I then zipped up this directory and copied it back over to my machine so that the user could get back to work :-) Get back to your desk because you'll be able to do everything else there.

This is where it gets fun, because we get to use WinDbg. Before doing any real work with WinDbg, I would strongly recommend getting your colors customized because it is very hard to spot errors or warnings when all the text is black on white. Check out Tess's post on setting up a custom workspace for WinDbg. I set up WinDbg exactly as she did in the post and it works quite well.

Next, you can open your minidump. Under the File menu, there is an "Open Crash Dump" command that you can use (Ctrl+D for you keyboard guys) so pull that up and open your minidump file. It should look something like this:

image

You can think of WinDbg sort of like a command prompt, even though it has windows and buttons. It is just as user friendly as the command prompt is when you're sitting at the C: prompt. In other words, it isn't friendly at all. In this case, instead of a C: prompt, you have the prompt at the bottom of the screen.

WinDbg starts out as an unmanaged/native debugger, which means you could debug your managed application, but it will be fairly difficult with just native commands. What you need to use is a tool called SOS. John Robbin's, master debugging guru guy, has an excellent MSDN Magazine article on SOS that you should read for more information. My next step was to load up the SOS extension in WinDbg by typing '.loadby sos mscorwks' which tells WinDbg to load the SOS extension from the same directory as the mscorwks assembly was loaded from. (check out this comment for cases when the loadby might not work, such as when mscorwks hasn't been loaded yet)

image

When I typed it in, I unfortunately got an error, though (pasted below for Googlibility, if that's a word).

PDB symbol for mscorwks.dll not loaded
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
            2) the file mscordacwks.dll that matches your version of mscorwks.dll is
                in the version directory
            3) or, if you are debugging a dump file, verify that the file
                mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path.
            4) you are debugging on the same architecture as the dump file.
                For example, an IA64 dump file must be debugged on an IA64
                machine.

You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll.  .cordll -ve -u -l will do a verbose reload.
If that succeeds, the SOS command should work on retry.

If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.

This got me digging for a while to figure out why I couldn't load up SOS. Quite a few other people were curious, too. The gist of it can be summed up in this Microsoft Connect post, which details that you have to have the exact same version of the .NET Framework as the machine where the minidump was taken. In short, I had installed .NET 3.5 on my machine which included patches to .NET 2.0 while the user who was having the problem was a few versions back.

To confirm this, you can use the loaded modules command (lm) to see all of the modules that were loaded at the time of the minidump. I specifically used 'lmv m mscorwks'. The 'v' apparently means details or something and the 'm mscorwks' tells the command to match on modules named 'mscorwks'. Here is the result, with the file version info from my local mscorwks file as well (found in c:\windows\microsoft.net\framework\v2.0.50727).

image

Don't despair if you're in this situation, because I have a solution. Virtual machines! It takes a bit of work, but you can get a VM set up with the same framework version. I started out with blank Windows XP SP2 VM and installed the .NET Framework 2.0 RTM on it. Turns out, I was still off. The below screenshot is from the VM.

image

I was starting to get frustrated at this point, but Doug Stewart saved the day with a couple of posts on .NET 2.0 versions, one on .NET 2.0 revisions and one on the version history of the CLR 2.0. I was able to determine that the problem computer was running a patch from KB928365. I installed the patch on the VM and... SUCCESS! SOS loaded up!

With SOS loaded, you run a lot more interesting and powerful commands from WinDbg, like !threads, !CLRStack, !analyze and more. Here are the basic steps I took to attempt to narrow down what was going on.

I ran !threads, which displayed all of the threads that were running at the time the minidump was taken.

image

You can see that, though it looks like a lot was going on, there really was the main STA thread (the thread that contains the WndProc which pumps the Windows messages) and a lot of system threads like the Finalizer threads and Completion Port threads.

You can switch between threads, by using the ~[THREAD]s command, like ~11s, which switches to the thread with the ID of 11. Once switched to a thread, you can run !clrstack and get the call stack for that thread.

image

It looks like thread 11 was an animation timer. Back on the main thread (STA), it really just looks like normal WndProc activity - nothing too strange.

image

It was looking like I wasn't getting anywhere, so I decided to try something else. I ran the DumpStackObjects (!dso) command, which gives me all of the object instances on the stack.

image

Looks like the application in question was using an Infragistics library, but the thing I was really interested in was the bottom one, the WinFormsAppContext, which was an instance of an ApplicationContext. From that instance, I could run the !do (dump object) command to see details about it.

image

From there, I wanted to find which mainForm it was using. I ran !do on the Value column of the mainForm instance.

image

Nice! Now I know the name of the instance that was loaded! At this point, I've got a much better idea about what is going on and can now start digging through some code to try to determine what it is doing.

The rest of the story is fairly boring because, after opening the offending code, I spotted the problem fairly quickly. The hang, if you even want to call it that, had nothing to do with threading. It just looked like the application was hanging because, in the Form's Closing event, the application in question was cancelling the close and hiding itself. I have no idea why, but I guess that's debugging for you.

To finish off, I'd like to share some excellent debugging resources, particularly with WinDbg.

Hope this helps!

posted on Friday, May 23, 2008 8:57:15 AM (Central Standard Time, UTC-06:00)  #    Comments [3]
 Wednesday, May 21, 2008

I've been meaning to get a local version of my blog running so that I could test changes and tweaks to my theme and I finally found some time yesterday to do that. Can you tell the difference? Basically, I added the social networking links on the right as well as the Tech Ed 2008 badge.

Tech Ed 2008 - Meet Me There

This will be my first time to go to Tech Ed, so I'm excited. I've only been to one other conference and that was devLink last year (which was awesome - I still need to register for this year!). There will be 3 other guys from my company going and I know that Randy Walker and I think Jay Smith will be as well. Anyone else out there going? Let me know in the comments! Also, anyone planning to attend Party with Palermo? I haven't registered yet, but I'm thinking about it.

Speaking of conferences, the Northwest Arkansas area is planning a technology summit. I've missed out on the discussions about it so far but I'm hoping to make the next meeting to talk about it. We'll make sure information gets out regarding the specifics so that everyone can participate.

posted on Wednesday, May 21, 2008 7:18:48 AM (Central Standard Time, UTC-06:00)  #    Comments [0]
 Wednesday, May 07, 2008

A coworker swung by a few days ago to ask some questions about using Reflection. I learn really well by example so I decided to use Powershell to show using Reflection. Below is the session I used and later emailed to him. You can see a few spots at the bottom of the example where I was unsure of the syntax on passing an empty parameter array, but I figured it out.

This is a good example of why I like Powershell :-)

  1 [1] » "haha".gettype()
  2
  3 IsPublic IsSerial Name                                     BaseType
  4 -------- -------- ----                                     --------
  5 True     True     String                                   System.Object
  6
  7
  8 [2] » $temp = "haha".gettype()
  9 [3] » $temp.GetProperties()
10
11
12 MemberType    : Property
13 Name          : Chars
14 DeclaringType : System.String
15 ReflectedType : System.String
16 MetadataToken : 385875994
17 Module        : CommonLanguageRuntimeLibrary
18 PropertyType  : System.Char
19 Attributes    : None
20 CanRead       : True
21 CanWrite      : False
22 IsSpecialName : False
23
24 MemberType    : Property
25 Name          : Length
26 DeclaringType : System.String
27 ReflectedType : System.String
28 MetadataToken : 385875995
29 Module        : CommonLanguageRuntimeLibrary
30 PropertyType  : System.Int32
31 Attributes    : None
32 CanRead       : True
33 CanWrite      : False
34 IsSpecialName : False
35
36
37
38 [4] » $temp.GetProperties()[0]
39
40
41 MemberType    : Property
42 Name          : Chars
43 DeclaringType : System.String
44 ReflectedType : System.String
45 MetadataToken : 385875994
46 Module        : CommonLanguageRuntimeLibrary
47 PropertyType  : System.Char
48 Attributes    : None
49 CanRead       : True
50 CanWrite      : False
51 IsSpecialName : False
52
53
54
55 [5] » $temp.GetProperties()[0].name
56 Chars
57 [6] » $temp.GetProperties()[1].name
58 Length
59 [7] » $temp.GetProperties()[1].GetGetMethod()
60
61
62 Name                       : get_Length
63 DeclaringType              : System.String
64 ReflectedType              : System.String
65 MemberType                 : Method
66 MetadataToken              : 100663629
67 Module                     : CommonLanguageRuntimeLibrary
68 MethodHandle               : System.RuntimeMethodHandle
69 Attributes                 : PrivateScope, Public, HideBySig, SpecialName
70 CallingConvention          : Standard, HasThis
71 ReturnType                 : System.Int32
72 ReturnTypeCustomAttributes : Int32
73 ReturnParameter            : Int32
74 IsGenericMethod            : False
75 IsGenericMethodDefinition  : False
76 ContainsGenericParameters  : False
77 IsPublic                   : True
78 IsPrivate                  : False
79 IsFamily                   : False
80 IsAssembly                 : False
81 IsFamilyAndAssembly        : False
82 IsFamilyOrAssembly         : False
83 IsStatic                   : False
84 IsFinal                    : False
85 IsVirtual                  : False
86 IsHideBySig                : True
87 IsAbstract                 : False
88 IsSpecialName              : True
89 IsConstructor              : False
90
91
92
93 [8] » $temp.GetProperties()[1].GetGetMethod().Invoke
94
95
96 MemberType          : Method
97 OverloadDefinitions : {System.Object Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo cu
98                       lture), System.Object Invoke(Object obj, Object[] parameters)}
99 TypeNameOfValue     : System.Management.Automation.PSMethod
100 Value               : System.Object Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo cul
101                       ture), System.Object Invoke(Object obj, Object[] parameters)
102 Name                : Invoke
103 IsInstance          : True
104
105
106
107 [9] » $temp.GetProperties()[1].GetGetMethod().Invoke("hello world", [])
108 Unable to find type []: make sure that the assembly containing this type is loaded.
109 At line:1 char:65
110 + $temp.GetProperties()[1].GetGetMethod().Invoke("hello world", []) <<<<
111 [10] » $temp.GetProperties()[1].GetGetMethod().Invoke("hello world")
112 Cannot find an overload for "Invoke" and the argument count: "1".
113 At line:1 char:47
114 + $temp.GetProperties()[1].GetGetMethod().Invoke( <<<< "hello world")
115 [11] » $temp.GetProperties()[1].GetGetMethod().Invoke("hello world", {})
116 Exception calling "Invoke" with "2" argument(s): "Parameter count mismatch."
117 At line:1 char:47
118 + $temp.GetProperties()[1].GetGetMethod().Invoke( <<<< "hello world", {})
119 [12] » $temp.GetProperties()[1].GetGetMethod().Invoke("hello world", $Null)
120 11
121 [13] »
122

Note - I used the :toHTML command from Vim along with Peter Provost's Powershell syntax file to get the color. Powershell doesn't support color at the console like this yet without explicitly passing color arguments to Write-Host.

posted on Wednesday, May 07, 2008 7:51:00 AM (Central Standard Time, UTC-06:00)  #    Comments [0]