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

  • pagine piuttosto informative, piacevoli =)

  • 9 su 10! Ottenerlo! Siete buoni!

  • Hi Pat- nice to see some explanation of how these kind of tools work.

    However, thats not the reason i happened onto this blogpost... i am currently trying to figure out why my WM_HELP messages that i am sending to myself seem to go missing when certain forms of my .NET application are activated - particularly one that contains th IE ActiveX control.  so i thought, i'll open spy++ and check on it, no problem, right?

    So it turns out that when Spy++ is running, none of my WM_HELP messages are getting through at all, no matter whether the IE control is open or not!  its bizarre. i can be running fine, then open spy++, and suddenly no more messages, then close spy++, and my messages return!

    Now, i'm sure its something to do with how i am sending this message, but can you tell me if there are cases where Spy++ would actually remove messages from an application queue completely, or handle them for the applicaton?

    tx,

    tim.

  • Tim,

    There are no instances I know of where Spy++ would remove messages from an application's message queue.  Spy++ simply installs a hook that the messages go through so Spy++ can examine them, but it should have no effect on the actual message queue behavior.

    Sorry I can't be of more help!

    Pat.

  • There are several critical bugs in Spy++ that need to be fixed:

    (a) it MUST have an "always on top" capability.  This is about 15 years overdue.  It takes such an infinitesimal number of lines to add this that there is no reason to not add it.

    (b) it MUST have the ability to report WM_APP-based messages AS WM_APP-based messages, not WM_USER+huge number

    (c) it MUST have a way to save named profiles so that an appropriate set of message options can be reloaded on demand.  It is EXTREMELY annoying to have disable WM_SETCURSOR, WM_MOUSEMOVE and WM_NCHITTEST messages during some debugging sessions.

    I submitted a much larger list of problems at the MVP Summit.  If you want a copy, feel free to email me (my email link is on my Web site)

  • Thanks for explaining ...

    I'm curious what the writer mutex is good for - from the above I cannot see anything happening with the writer mutex locked and the access mutex unlocked. Is it really required or would the access mutex handle be good enough?

  • To reply to Joseph Newcomer:

    Thanks for the list above and the additional requests.  I've implemented several of these for Visual Studio 2008 (though they may not appear in Beta2, they will be in the final version).

    1) "Always on top" capability is now available.

    2) User message (those above WM_APP) are now reported as WM_APP+n rather than WM_USER+n.

    3) The treeviews and message logs support the context menu key.

    4) Changing the message options is allowed at any time, not just if the logging is stopped.

  • To reply to Bertram above:

    Thanks for the suggestion.  I will take another look to make sure that I actually need both mutexes.  I believe that now that I have reduced the size of the circular queue to hold only one message (see http://blogs.msdn.com/vcblog/archive/2007/03/22/spy-update.aspx) I may only need one mutex.

  • So where do we *get* this fixed version?

  • Did you ever get a chance to blankly stare at a screen similar to the above, trying to recollect what

Page 2 of 2 (25 items) 12