Spy++ Internals

Spy++ Internals

Rate This
  • Comments 25

Hi, my name is Pat Brenner and I’m a software design engineer on the Visual C++ libraries team.  I recently rejoined the Visual C++ team after almost 10 years in the Visual Studio environment team.  I am also the owner of Spy++.  While I did not originally write Spy++, I owned it from around 1993 until around 2003, and now that I am back on the Visual C++ team it has been returned to me.  Today I’m going to spend some time talking about Spy++ internals.

 

Spy++ is made to be an observer (and not a modifier) of the system around it.  While it can do other things as well, the main purpose of Spy++ is to log messages that are being passed around in Windows.  Spy++ accomplishes this by the use of three global message hooks: a WH_GETMESSAGE hook, which hooks a message posted to a window (via PostMessage); a WH_CALLWNDPROC hook, which hooks a message sent to a window (via SendMessage); and a WH_CALLWNDPROCRET hook, which hooks the return of a message sent to a window (via SendMessage).

 

Because the hooks must be global (so that they can hook messages to any window in the system) they must reside in a DLL.  This DLL is loaded into every running process as soon as the hooks are set (via SetWindowsHookEx), and remains loaded in the processes until the hooks are unhooked (via UnhookWindowsHookEx).

 

The hook DLL communicates the message information with the Spy++ application via a circular queue, so there are a number of synchronization requirements.

·         Since the hook DLL is loaded into every process, the circular queue resides in a shared data section, and there is a pair of mutexes that controls access to the queue.  A writer mutex is used to synchronize write accesses between the loaded copies of the hook DLL, and an access mutex is used to synchronize access between the writers and the Spy++ application itself, which is reading from the queue.

·         Because the queue is circular, there needs to be synchronization between the reading and the writing, so there are two events used for this purpose.  A read event is signaled when the Spy++ application reads a packet of message data from the queue, and a written event is signaled when a copy of the hook DLL writes a packet of message data to the queue.

·         Synchronization is also required with regard to the current read and write locations in the queue, so there are offsets maintained (as shared data) which specify the next read location and the next write location in the queue.  There is also a count maintained (in shared data) which is the number of packets that have been written but not yet read.

 

So the writers (in the hook DLL), when a message comes through one of their hooks, do the following:

·         Take the writer mutex.

·         Take the access mutex.

·         Obtain a message packet (section of shared queue) large enough to store the data for the message.

·         Copy the message data to the packet.

·         Increment the count of message packets in the queue.

·         Increment the write offset by the size of the message packet taken.

·         Set the written event.

·         Release the access mutex.

·         Release the writer mutex.

 

The reader, which resides in a loop in a thread in the Spy++ application, does the following.

·         Check the written event.  Once it is set:

·         Take the access mutex.

·         Copy the message data from the message packet in the shared queue.

·         Decrement the count of message packets in the queue.

·         If the message packet count is now zero, reset the written event.

·         Release the access mutex.

·         Set the read event.

·         Sends the copied packet data in a message to a hidden window owned by the main thread.

 

The hidden window processes the message by looping through the current message loggers (since there may be more than one message logger open) and calling each active logger.  The logger will then apply any filtering (since the message may have been for a window or message that the logger is not interested in) and if necessary will display the message in its view.

 

Once Spy++ starts hooking the messages in the system, we want the writers to stay ahead of the reader in the queue, but we don’t want the writers to overtake and pass the reader.  So if obtaining a new packet for a writer would overtake the reader, the writer has to loop, doing the following:

·         Reset the read event.

·         Wait for the read event to be set.

·         Check for either of the following conditions:

  1. The message packet count has returned to zero (the reader is fully caught up).
  2. The read offset is far enough ahead (write offset plus new packet size does not pass read offset).

 

That pretty much covers what I wanted to talk about today.  There are a couple of other things worth mentioning.

·         You may notice that Spy++ does not log any messages for its own windows.  Obviously, this is because this would cause an infinite set of messages to be sent throughout the system.  So Spy++ knows its own process and thread identifiers and filters out any messages sent to windows owned by them.

·         All the data that Spy++ displays is a copy of the original data.  Since much of the data passed in messages may only be valid for the lifetime of the message, Spy++ copies any data that it needs to display into the queue, and then copies it again when the message is placed in the message log, since the data in the queue will be overwritten as well.

·         Ever since Spy++ was first written, using it has occasionally resulted in the hanging of the system, with the only recourse being to cold-boot the computer. After a recent conversation with a co-worker, I (finally!) realized that this was because all the waits (for mutexes or events) were infinite, rather than using timeouts.  This could easily cause a hang, particularly if the Spy++ application crashed while it held the access mutex, since then all the copies of the hook DLL would block in their hooks waiting for the access mutex.  So I have made a fix for this (by using timeouts on the waits) which should substantially reduce the trouble that Spy++ can cause in the system.  This also made Spy++ much easier for me to debug, since I no longer have to resort to powering off/on my computer when a bug is encountered in the message processing.

 

Thanks, and I hope you found this interesting.  I welcome any questions you might have about Spy++.  I welcome feature requests for future versions as well, but keep in mind the “Spy++ is an observer, not a modifier” statement I made earlier.

 

Pat Brenner

Visual C++ Libraries Team

  • Ah, the shared mutex usage explains why I've seen Spy++ lock up the UI occasionally. It's probably the same problem as Text and Speech -- debugger stops all threads on a process, one of which has the Spy++ lock, and the whole UI grinds to a halt.

  • May i suggest using a fixed-size lock-free queue plus an overflow counter?

  • Cool - thanks for the explanation and taking requests.

    1) I'd like the ability track which window has input focus.  This would help greatly with "lost" focus.  e.g. sometimes an offscreen or hidden window gets focus while tabbing.

    2) How about some ability to spy on messages for windows as they are created.  e.g. perhaps a window tracking WNDCLASS registrations, window creates coupled with the ability to spy by WNDCLASS name instead of having to select a window.

    John

  • A belated Happy New Year to everyone. I'm finally back from vacation. Catching up: VCBlog: A couple of

  • I encountered a system hang on Windows Vista while using Spy++:

    1. Open Spy++ (8.0)

    2. Open notepad

    3. Select Spy->Log messages... and target to notepad window

    4. Then open Internet Explorer

    The system hang.

  • I have wondered what caused Spy++ to hang my system for a long time; thanks for explaining it.  I knew it was interaction between the debugger and Spy++ (in my case), but wasn't sure what.

    To try and mitigate this problem, I wrote a small program that runs on startup, which will always return FALSE in response to WM_QUERYENDSESSION.  This allows me to log off the system, which will in turn usually allow me to kill devenv (or msdev), without actually letting the logoff complete.

    I will definitely be glad to get rid of this little program when the new version is available.

    ChrisR

  • When and where can we get the fixed version??

  • How can I used Spy++ with Visual Basic rather than Visual C++? Please Help me ASAP.

  • In reference to the question above about logging messages on windows as they are created:

    Spy++ can log messages for all windows in a process.  This can be used to log all the creation messages, if you use the following trick.  Step into your application in the debugger, and once you have the process identifier, start Spy++.  Open a processes tree view (Spy.Processes).  Select your process ID in the tree, and then do Spy.Messages.  In the Message Options dialog, on the Windows tab, your process will already be the selected object.  Select the messages you want to see on the Messages tab.  OK the dialog, and then let your process run.  Now you should see all the window creation messages for windows in your process.

  • pagine piuttosto informative, piacevoli =)

  • luogo grande:) nessun osservazioni!

  • Great site! Good luck to it's owner!

  • Hi, Pat Brenner again. I recently posted an article about Spy++ internals . I have some updates that

  • Thanks for the response about watching the messages during window creation.  I'll give it a try.

    John

  • Luogo molto buon:) Buona fortuna!

Page 1 of 2 (25 items) 12