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

https://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?