You can use Visual Studio to debug itself!


How do you find out why your computer or a running program is so slow? Here’s one way.


 


Let’s attach the VS debugger to VS itself. The main executable for VS is devenv.exe.


 


Start Visual Studio 2008. This will be the “debugger”


 


Choose File->Open Project    C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe


 


(You can also choose Debug->Attach to process to debug another instance of devenv.exe, or any other EXE, like Foxpro.exe or Excel.exe)


 


 


Hit F5, and a dialog pops up:


“Debugging information for ‘devenv.exe’ cannot be found or does not match. Symbols not loaded. Do you want to continue debugging?”. 


Answer yes, and Visual Studio starts. This will be the “debuggee”


 


Do anything you like in the debuggee, such as create or load a project. Hit F12 to cause an asynchronous breakpoint (or go to the debugger and choose Debug->Break All).


 


That will freeze all the debuggee threads and put you in the debugger.


 


You can then examine the Threads window (Debug->Windows->Threads) and see what threads are running.  There are several. You can dbl-click various threads and look at the Call stack for it (Debug->Windows->Call stack).


 


You’ll probably see that most threads are just waiting for something to happen.


 


Choose the main thread. When VS is idling, the stack will look like this:


 


            ntdll.dll!7c90eb94()       


            [Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]


            ntdll.dll!7c90e9ab()       


            kernel32.dll!7c8094e2()


>          msvcr90.dll!_onexit_nolock(int (void)* func=0x0072006f)  Line 157 + 0x6 bytes    C


            00660072()       


 


Each stack entry shows the module and address that called the next stack entry. This isn’t very useful, so you need to load symbols. You can use the public Microsoft Symbol Server:


 


Tools->Options->Debug->Symbols


 


http://msdl.microsoft.com/download/symbols


 


Cache the symbols to a local dir, like C:\Symbols


 


Right click on the various modules (like ntdll.dll, kernel32.dll, msenv.dll etc.) in the call stack to load symbols. Now it’s a little more intelligible:


 


>          ntdll.dll!_KiFastSystemCallRet@0()       


            user32.dll!_NtUserKillTimer@8()  + 0xc bytes     


            msenv.dll!CMsoCMHandler::FPushMessageLoop()  + 0x36 bytes           


            msenv.dll!SCM::FPushMessageLoop()  + 0x4f bytes     


            msenv.dll!SCM_MsoCompMgr::FPushMessageLoop()  + 0x28 bytes      


            msenv.dll!CMsoComponent::PushMsgLoop()  + 0x28 bytes       


            msenv.dll!VStudioMainLogged()  + 0x19b bytes


            msenv.dll!_VStudioMain()  + 0x7d bytes


            devenv.exe!util_CallVsMain()  + 0xd8 bytes       


            devenv.exe!CDevEnvAppId::Run()  + 0x5cb bytes         


            devenv.exe!_WinMain@16()  + 0x60 bytes         


            devenv.exe!License::GetPID()  – 0x4cf9 bytes    


            kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes         


 


You can see the WinMain calls a MessageLoop.


 


 


Let’s make the foreground thread busy. Create a VB console application. Add an XML literal:


 


Module Module1


 


    Sub Main()


        Dim bigxml = <xml>


 


                  </xml>


    End Sub


 


End Module


 


Make the XML literal big: open the file C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5\System.Data.Linq.xml and copy everything except the <?xml version=”1.0″ encoding=”utf-8″?> into the literal (between the <xml> and the </xml>), so you have about 2000 lines. 


 


Start Task Manager (Ctrl-Shift->Escape, or right click on the task bar and choose Task Manager). Observe the little tray icon in the System tray. It will indicate how busy your computer is.


 


For example, if you have 2 processors and 1 thread is very busy, it’ll show 50% busy.


 


Now, if you hover your mouse over the “bigxml”, you’ll trigger VB to create a Quick Info tooltip, but it takes quite a lot of calculation to figure out the tip.


 


Do your asynchronous breakpoint trick and you’ll see something like this:


 


>          msvb7.dll!BCSYM::IsNamedRoot()          


            msvb7.dll!BCSYM::AreTypesEqual()     


            msvb7.dll!EquivalentTypes()  + 0x17 bytes        


            msvb7.dll!ClassifyPredefinedCLRConversion()  + 0x22 bytes     


            msvb7.dll!Semantics::ClassifyPredefinedCLRConversion()  + 0x28 bytes


            msvb7.dll!Semantics::ClassifyPredefinedConversion()  + 0xbf bytes      


            msvb7.dll!Semantics::ResolveConversion()  + 0x234 bytes        


            msvb7.dll!Semantics::ClassifyUserDefinedConversion()  + 0x40685 bytes          


            msvb7.dll!Semantics::ClassifyConversion()  + 0x21e31 bytes     


            msvb7.dll!Semantics::CompareParameterTypeSpecificity()  + 0x57 bytes           


            msvb7.dll!Semantics::CompareParameterSpecificity()  + 0xa3 bytes       


            msvb7.dll!Semantics::InsertIfMethodAvailable()  + 0x461 bytes  


            msvb7.dll!Semantics::CollectOverloadCandidates()  + 0x1e6 bytes         


            msvb7.dll!Semantics::ResolveOverloading()  + 0xd9 bytes         


            msvb7.dll!Semantics::ResolveOverloadedCall()  + 0x5e bytes    


            msvb7.dll!Semantics::InterpretCallExpression()  + 0x2656f bytes


            msvb7.dll!Semantics::CreateConstructedInstance()  + 0xfa bytes


            msvb7.dll!Semantics::CreateConstructedInstance()  + 0x5b bytes           


            msvb7.dll!Semantics::InterpretXmlElement()  + 0x241 bytes       


            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          


            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       


            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          


            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       


            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          


            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       


            msvb7.dll!Semantics::InterpretXmlContent()  + 0x56 bytes          


            msvb7.dll!Semantics::InterpretXmlElement()  + 0x27c bytes       


            msvb7.dll!Semantics::InterpretXmlExpression()  – 0x13d bytes   


            msvb7.dll!Semantics::InterpretXmlExpression()  + 0xb0 bytes    


            msvb7.dll!Semantics::InterpretXmlExpression()  + 0xc3 bytes    


            msvb7.dll!Semantics::InterpretExpression()  – 0x1fc bytes          


            msvb7.dll!Semantics::InterpretExpressionWithTargetType()  + 0x43 bytes           


            msvb7.dll!Semantics::InterpretInitializer()  + 0x36 bytes  


            msvb7.dll!Semantics::InterpretInitializer()  + 0x1a bytes  


            msvb7.dll!Semantics::InterpretInitializer()  + 0x127 bytes 


            msvb7.dll!Semantics::InterpretVariableDeclarationStatement()  + 0x158c bytes    


            msvb7.dll!Semantics::InterpretStatement()  + 0x7b2f bytes         


            msvb7.dll!Semantics::InterpretStatementSequence()  + 0x2f bytes          


            msvb7.dll!Semantics::InterpretBlock()  + 0x24 bytes      


            msvb7.dll!Semantics::InterpretMethodBody()  + 0x1fa bytes      


            msvb7.dll!SourceFile::GetBoundMethodBodyTrees()  + 0x126 bytes       


            msvb7.dll!CBaseSymbolLocator::GetBoundMethodBody()  + 0xb6 bytes           


            msvb7.dll!CSymbolLocator::LocateSymbolInMethodImpl()  + 0x29 bytes


            msvb7.dll!CSymbolLocator::LocateSymbol()  + 0x81f bytes       


            msvb7.dll!CIntelliSense::GenQuickInfo()  + 0x52399 bytes          


            msvb7.dll!CIntelliSense::HandleQuickInfo()  + 0x29 bytes           


            msvb7.dll!CIntelliSense::ProcessCompletionInfo()  + 0x66cda bytes       


            msvb7.dll!CIntelliSense::GenIntelliSenseInfo()  + 0x402 bytes     


            msvb7.dll!SourceFileView::GenIntelliSenseInfo()  + 0x94 bytes   


            msvb7.dll!SourceFileView::GetDataTip()  + 0x743 bytes 


            msvb7.dll!CVBLangService::GetDataTip()  + 0x11d bytes           


            msenv.dll!CEditView::GetFilterDataTipText()  + 0x37 bytes         


            msenv.dll!CEditView::HandleHoverWaitTimer()  + 0x213 bytes    


            msenv.dll!CEditView::TimerTick()  + 0x7d bytes 


            msenv.dll!CEditView::WndProc()  + 0x1685b9 bytes      


            msenv.dll!CEditView::StaticWndProc()  + 0x39 bytes     


            user32.dll!_InternalCallWinProc@20()  + 0x28 bytes        


            user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes       


            user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes


            user32.dll!_DispatchMessageW@4()  + 0xf bytes          


            msenv.dll!EnvironmentMsgLoop()  + 0xb6 bytes


            msenv.dll!CMsoCMHandler::FPushMessageLoop()  + 0x36 bytes           


            msenv.dll!SCM::FPushMessageLoop()  + 0x4f bytes     


            msenv.dll!SCM_MsoCompMgr::FPushMessageLoop()  + 0x28 bytes      


            msenv.dll!CMsoComponent::PushMsgLoop()  + 0x28 bytes       


            msenv.dll!VStudioMainLogged()  + 0x19b bytes


            msenv.dll!_VStudioMain()  + 0x7d bytes


            devenv.exe!util_CallVsMain()  + 0xd8 bytes       


            devenv.exe!CDevEnvAppId::Run()  + 0x5cb bytes         


            devenv.exe!_WinMain@16()  + 0x60 bytes         


            devenv.exe!License::GetPID()  – 0x4cf9 bytes    


            kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes         


 


Note how it’s easy to read the stack. The bottom of the stack (the first thing put on it) is _BaseProcessStart, which starts the process. You can see that a timer tick caused GetDataTip to call GenIntelliSenseInfo, which calls GenQuickInfo, -> LocateSymbol, etc.


 


Each stack entry indicates the name of the routine being executed. The “+ 0x23 bytes” means the # of bytes into the routine that the call occurred. A low number means near the beginning of that method.


 


Because XML is a tree, and is thus a recursive data structure, you see that XMLContent can contain an XMLExpression and vice versa. The depth of the recursion reflects the actual XML being processed.


 


 


BTW, the VB background compiler thread is:


 


0          >          4620     Worker Thread   ThreadSyncManager::ThreadProc           _KiFastSystemCallRet@0            Normal  0


 


And it’s stack at idle:


 


>          ntdll.dll!_KiFastSystemCallRet@0()       


            ntdll.dll!_ZwWaitForMultipleObjects@20()  + 0xc bytes   


            kernel32.dll!_WaitForMultipleObjectsEx@20()  – 0x48 bytes        


            user32.dll!_RealMsgWaitForMultipleObjectsEx@20()  + 0xd9 bytes        


            ole32.dll!CCliModalLoop::BlockFn()  + 0x76 bytes         


            ole32.dll!_CoWaitForMultipleHandles@20()  + 0xe6 bytes           


            msvb7.dll!ThreadSyncManager::ThreadProc()  + 0x98 bytes       


            kernel32.dll!_BaseThreadStart@8()  + 0x37 bytes           


 


 


 


See also:


Dynamically attaching a debugger


Is a process hijacking your machine?

Comments (2)

  1. Greg says:

    I used this to find out why the VS designer could not load/edit an ASP VB.NET page.  It would give an exception when loading the page in the designer.

    I used VS to debug itself and set it to break on all exceptions.

    The problem was a constant in the ASP VB.NET page called a .net function to read a value from the config file.  This code was executed before the rest of the page was processed.  When running in the VS designer, there is no web config file to read and therefore an exception was thrown.

    We found this when cleaning up a 200,000+ line ASP VB.net application built by our offshore team.  It was found when we tried to load each and every GUI element in the application in the designer and found that a few of them had issues due to faulty hand tweaking of the VS generated GUI code.