Understanding Your DirectShow Filter Graph

Many questions in DirectShow development are caused by lack of developer’s understanding what topology his code effectively built. Intelligent Connect and RenderXxx methods help adding and connecting filters and in the end a developer does not have a faintest idea what the pipeline looks like.

DirectShow API provides methods to enumerate filters, pins, connection and obtained detailed information about the filter graph. The API is well-documented. Then Windows SDK is shipped with GraphEdit which helps building graphs interactively. Ability to publish a graph on ROT and review it from GraphEdit is nothing but powerful. And then we have GraphStudioNext which makes everything even more convenient.

This does not seem sufficient and clear as many new questions and misunderstanding show that developers have false assumptions on graphs their applications use.

DirectShowSpy goes one step further with debugging options. With DirectShowSpy one can embed reviewing UI right into the developed application and either generate detailed textual description of filters, connections, media types as well as pass filter graph to GraphEdit/GraphStudioNext for interactive review with visualized topology. No excuses left any longer for misunderstanding built topologies.

Steps below explain in detail how to visualize your application DirectShow filter graph and generate a textual report on graph details.

1. For starters, one needs to intall DirectShowSpy in target system. Standard installation is mentioned in original post.

  • It is necessary that DirectShowSpy of correct/matching bitness is installed. 32-bit applications use 32-bit DirectShowSpy and 64-bit applications – 64-bit DirectShowSpy. .NET applications built as “Any CPU” are effectively either 32 or 64 bit processes and respectively need a matching spy as well.
  • To cut long story short, simply download DirectShow*.* from Toolbox and use DirectShowSpy-Win32-reg-ui.bat or DirectShowSpy-x64-reg-ui.bat to pop up registration UI. You need local administrator privileges for the registration step (or spy is usable through COM otherwise but it’s beyond scope of this post).

2. DirectShowSpy’s FilterGraphHelper object (already mentioned earlier) offers DoPropertyFrameModal method to pop up diagnostic UI. The helper needs prior initialization with either graph, filter or pin interface. C++ code snippet:

#import "libid:B9EC374B-834B-4DA9-BFB5-C1872CE736FF" raw_interfaces_only // AlaxInfoDirectShowSpy
// ...
CComPtr<IFilterGraph2> pFilterGraph;
// ...
CComPtr<AlaxInfoDirectShowSpy::IFilterGraphHelper> pFilterGraphHelper;
ATLENSURE_SUCCEEDED(pFilterGraphHelper.CoCreateInstance(__uuidof(AlaxInfoDirectShowSpy::FilterGraphHelper)));
ATLENSURE_SUCCEEDED(pFilterGraphHelper->put_FilterGraph(pFilterGraph));
ATLENSURE_SUCCEEDED(pFilterGraphHelper->DoPropertyFrameModal(NULL));

C# code snippet:

IFilterGraph2 graph = new FilterGraph() as IFilterGraph2;
// ...
FilterGraphHelper helper = new FilterGraphHelper();
helper.FilterGraph = graph;
helper.DoPropertyFrameModal(0);

Downloadable sample projects (FilterGraphHelperDialog for C# and FilterGraphHelperDialog2 for C++) are available in Subversion repository or Trac.

3. DoPropertyFrameModal methods opens a window (it’s argument is parent window handle, optional) with details about the graph, including copyable diagnostic text, filters and their property pages all gathered in single window.

FilterGraphHelper.DoPropertyFrameModal UI

NOTE: With root tree element “Filters” selected, the right-side pane contains the text that provides filter graph description (see image above)!

4. Additionally, it is possible to launch GraphEdit/GraphStudioNext with a hotkey and open – through ROT – the graph visually.

FilterGraphHelper.DoPropertyFrameModal UI (Actions)

Remote Graph in GraphStudioNext

This requires that Windows SDK proppage.dll is available. It is normally registered with Windows SDK, and otherwise can be copied from SDK into target system and COM-registered using regsvr32. Or copied into the folder of DirectShowSpy in which case DirectShowSpy-Win32-reg-ui.bat (see item 1 above) file will see it and offer additional property page for registration.

5. When no longer needed, DirectShowSpy can be removed from system using the batch file mentioned above in item 1.

Whatever debugging you do with DirectShow filter graph, you need a complete understanding what filter graph you deal with. If you want to provide additional information to certain DirectShow related question, a copy/pasted diagnostic information needs to be attached to such question so that others understand what you are dealing with exactly.

Log Process Exceptions: Filters and Email Notification

Moving on with LogProcessExceptions tool which externally monitors (debugs, to be more specific) an application of interest and captures its exceptions writing minidump files for further analysis.

This updates adds two features:

  1. Ability to filter out the exceptions of interest
  2. An email notification on exception, with or without minidump file

Filter

A new property page in the wizard provides one with an option to specify filters/rules to identify exceptions of interest. One rule per line, once exception occurred its codes are compared against rules top to bottom. The first matching rule results in decision whether to log the minidump (and issue a notification) or not. If no filter/rule applicable found, or the entire filter is empty, the exception is logged (positive).

The rule syntax is the following:

  • empty lines are ignored
  • anything to the right from // is considered to be a comment
  • meaningful line has one or more items separated by spaces
  • first item on the line is either - or + to indicate whether the rule will result in skipping the exception (minus sign) or logging in (plus sign)
  • second item is exception code, e.g. 0xC0000005 for memory access violation, 0xE06D7363 for native C++ exception (this constant has a dedicated alias “C++”
  • third item applies to C++ exceptions and matches ATL CAtlException codes, and this allows to filter out specific HRESULTs; it is also possible to specify a range of codes as shown on the screenshot above

Email Notification

Why? Some issues are long to wait, so you don’t want to have it in front of you until the issue takes place. This is when a friendly email from the application would be so much appreciated.

The application gets you that by queuing an email once exception of interest takes place. The email settings are basically those of class described on an earlier EmailTools.dll post. The whole SMTP email class is embedded into this application, and it actually deserves a dedicated post on explaining how to embed ATL COM class with persistence based on COM map, which in turn depends on type library expected to be registered, into another application which additionally runs without registration.

There is a default preset tuned for Google Mail, but there are other options as well. Note that both filter and email settings are saved into registry under HKCU to be reused in next runs of the application.

Small minidump files are attached right to the emails. Larger ones (typically those with Full Memory option checked) are only mentioned. There is a threshold setting that defines how many megabytes is OK to attach the most.

Download links:

DirectShow Spy: Who Sent EC_ERRORABORT?

persiflage@stackoverflow asks if there is a chance to use DirectShow Spy see who sent an EC_ERRORABORT notification, which filter exactly. Let us see first why there is no way to find this out, and then we will see what we can do.

DirectShow Filter Graph Manager accepts events from filters via its IMediaEventSink interface. The conversation taking place around event notifications is like this:

  • Filter: Hey, Graph Manager! Can I call you IMediaEventSink?
  • Manager: Yes, you can.
  • F: I notify you on EC_ERRORABORT event, here is HRESULT that I have: VFW_E_SOMETHING.
  • M: OK.

Filter graph manager (FGM) does not ask “Who’s taking?”. It does not need to know, it accepts information anonymously. Can a non-filter post an event? Absolutely, FGM does not have to care. This is simple, but when a question raised who posted the event, there is no answer for it – there was no such information in first place.

The good news through is that a developer does not need one hundred percent precision. The source of the event is important to understand which of the filters aborted streaming, and any information is helpful. Spy impersonates the whole FGM and as such it is capable of covering IMediaEventSink interface as well, in order to trace calls to the log file, and, even more helpful, trace call stack of the call which brought specific event in.

With the call stack information at the time of event notification, the filter of interest can be identified pretty precisely. Especially, having debug symbols available, so that Spy could provide symbols for the code locations on stack.

For instance, let us looks at Windows SDK AMCap Sample which previews video and uses video renderer, and hence has EC_VIDEO_SIZE_CHANGED event involved (just an example, spy from now on traces EC_ERRORABORT call stack only). Once this event reaches FGM, the call stack logged is:

FilterGraphSpy.h(850): CSpyT<class CSpy,&struct _GUID const CLSID_FilterGraph>::Notify: nEventCode EC_VIDEO_SIZE_CHANGED (0x0A), nParameter1 0x00F00140, nParameter2 0x00000000
  DirectShowSpy!6a3ba3b4 CSpyT<CSpy,&CLSID_FilterGraph>::Notify (+ 337) [d:\projects\alax.info\repository-public\directshowspy\filtergraphspy.h, 859] (+ 13) @6a3a0000
  quartz!6a243188 CBaseFilter::NotifyEvent (+ 46) @6a220000
  quartz!6a3394f6 CBaseControlVideo::OnVideoSizeChange (+ 56) @6a220000
  quartz!6a2a2f9a CRenderer::CompleteConnect (+ 175) @6a220000
  quartz!6a337668 CRendererInputPin::CompleteConnect (+ 25) @6a220000
  quartz!6a23a470 CBasePin::ReceiveConnection (+ 213) @6a220000
  quartz!6a2a3741 CVideoInputPin::ReceiveConnection (+ 92) @6a220000
  ksproxy!65e94dc0 CBasePin::AttemptConnection (+ 84) @65e70000
  ksproxy!65e94e81 CBasePin::TryMediaTypes (+ 104) @65e70000
  ksproxy!65e94f68 CBasePin::AgreeMediaType (+ 115) @65e70000
  ksproxy!65e966a0 CBasePin::Connect (+ 100) @65e70000
  ksproxy!65e7d711 CKsOutputPin::Connect (+ 381) @65e70000
  quartz!6a23252e CFilterGraph::ConnectDirectInternal (+ 233) @6a220000
  quartz!6a23847c CFilterGraph::ConnectRecursively (+ 44) @6a220000
  quartz!6a238d09 CFilterGraph::ConnectInternal (+ 331) @6a220000
  quartz!6a238c22 CFilterGraph::Connect (+ 23) @6a220000
  DirectShowSpy!6a3b8686 CSpyT<CSpy,&CLSID_FilterGraph>::Connect (+ 881) [d:\projects\alax.info\repository-public\directshowspy\filtergraphspy.h, 672] (+ 0) @6a3a0000
  quartz!6a2322f0 CEnumMediaTypes::Release (+ 39) @6a220000
  qcap!6a6c7a31 CBuilder2_2::DoesCategoryAndTypeMatch (+ 408) @6a6b0000
  qcap!6a6b3424 _GUID_00000000_0000_0000_0000_000000000000 (+ 4) @6a6b0000
  qcap!6a6cb9cb CBuilder2_2::RenderStream (+ 5294) @6a6b0000
  AMCap!01009723 @01000000
  AMCap!010041be @01000000
  AMCap!01005e27 @01000000
  AMCap!0100611c @01000000
  AMCap!01007600 @01000000
  AMCap!010076ba @01000000
  AMCap!0100a90d @01000000

It does not take a rocket scientist to see that event is posted by video renderer hosted by quartz.dll, which was a part of pin connection handling, where a pin of ksproxy’s filter – which has to be WDM Video Capture Filter – was connected to video renderer input pin.

DirectShow Spy started logging new items:

  • COM interface calls on filter graph IMediaEvent, IMediaEventEx, IMediaEventSink interfaces
  • Call staclk on IMediaEventSink::Notify call, with EC_ERRORABORT code (other codes are logged without call stack to reduce hook overhead and avoid logging stuff for no reason)

Download links:

Endangered species – Debugging Tools for Windows

A standalone redistributable installation before, Debugging Tools for Windows was finally absorbed into Windows SDK. MSDN quote from Download and Install Debugging Tools for Windows:

Install Debugging Tools for Windows as a Standalone Component

If you do not want an entire kit (WDK or SDK), you can install the Debugging Tools as a standalone component from the Windows SDK.

TO INTSTALL JUST DEBUGGING TOOLS: In the SDK installation wizard, select Debugging Tools, and clear other components that you don’t want. Note that .NET Framework 4.0 also will be installed.

  • Install Debugging Tools for Windows as a Standalone Component

Install Debugging Tools for Windows without Installing .NET Framework

If you do NOT want to install the .NET Framework, there are additional steps needed.

Start the install process on a different computer where it is okay to install the .NET Framework. The installer requires .NET Framework 4.0 or higher, and will install .NET if it is not already installed.

Install the Debugging Tools as a standalone component from the Windows SDK. In the installation wizard, select Debugging Tools, and clear other components that you don’t want.

After installation is complete, go to the program files directory and look for (%Program Files%)\Windows Kits\8.0\Debuggers\Redist.
Copy and run the applicable MSIs on the computer that cannot have .NET.

Also, Windows SDK for Windows 8 Consumer Preview is not available as ISO download. So you have to use web downloader, install the gear, and having gone through this, the .MSI of interest are finally there.

You gotta be kidding me, though still thanks for not removing those completely.

LogProcessExceptions: Log Service Process Exceptions

One of the nasty issues with LogProcessExceptions utility was that it was unable to attach to service processes and track them to catch their exceptions.

The actual problem was that the processes were not listed in first place, so there was nothing to attach to. Access and security requirements necessary for a process to debug another process are listed in MSDN DebugActiveProcess article:

The debugger must have appropriate access to the target process, and it must be able to open the process for PROCESS_ALL_ACCESS. DebugActiveProcess can fail if the target process is created with a security descriptor that grants the debugger anything less than full access. If the debugging process has the SE_DEBUG_NAME privilege granted and enabled, it can debug any process.

The utility did enable the SE_DEBUG_NAME privilege, however it was doing it prior to starting debugging session and after the process of interest was already pointed to by user.

This was insufficient because EnumProcesses only lists service processes (not actually exactly services, but processes running in different security context) in case debug privilege is already enable by the time of the API call. The utility now enabled the privilege well in advance and list the services, so can be effectively applied to those.

Download links:

Enabling ATLTRACE output in Release configuration builds

The original intent is pretty clear, as MSDN states:

In release builds, ATLTRACE2 compiles to (void) 0.

As simple as this, but once in a while you are in a situation where release build fails to work for unknown reason and you need additional information for troubleshooting, and then you remember that you had debug tracing code still nicely available in the source, it is just being stripped out by definition of ATLTRACE/ATLTRACE2 macros for release builds.

To avoid reinvention of the wheel and putting new tracing, it might make sense to just re-enable existing tracing (certainly, if putting debug build binary is out of question, which might be the case in production environment and/or to avoid the hassle of installing additional runtime).

The macros need to be #undef’ined and redefined appropriately with or without limiting scope by push_macro/pop_macro #pragma’s. The trick with macro has to reach two goals, to pick file name, line and current symbol name using __FILE__ and friend macros, and also accept variable number of arguments.

The trick ATL does and we can use too is to define a helper class, with constructor taking file name, line and symbol name values, and cast operator () taking actual tracing parameters and arguments. Internally the output can be mapped to OutputDebugString API so that output could be seen using external tool such as DebugView.

When everything is well set, new the macros in question can be defined as follows:

#pragma push_macro("ATLTRACE")
#pragma push_macro("ATLTRACE2")

#undef ATLTRACE
#undef ATLTRACE2

#define ATLTRACE2 CAtlTrace(__FILE__, __LINE__, __FUNCTION__)
#define ATLTRACE ATLTRACE2

int _tmain(int argc, _TCHAR* argv[])
{
    ATLTRACE("First: %d\n", __LINE__);
    ATLTRACE(L"Second: %d\n", __LINE__);
    ATLTRACE2(atlTraceGeneral, 2, "Third: %d\n", __LINE__);
    ATLTRACE2(atlTraceGeneral, 2, L"Fourth: %d\n", __LINE__);
    return 0;
}

#pragma pop_macro("ATLTRACE2")
#pragma pop_macro("ATLTRACE")

And the Release configuration output will be:

Visual C++ .NET 2010 source code is available from SVN; in particular CAtlTrace class is here.

Bonus reading:

Hardware assisted memory corruption detection

So you got a memory corruption issue with a piece of software. It comes in a unique scenario along the line of having a huge pile of weird code running well most of the time and then, right out of the blue, a corruption takes place followed by unexpected code execution and unstable software state in general.

The biggest problem with memory corruption is that a fragment of code is modifying a memory block which it does not own, and it has no idea who actually is the owner of the block, while the real owner has no timely way to detect the modification. You only face the consequences being unable to capture the modification moment in first place.

To get back to the original cause, an engineer has to drop into a time machine, turn back time and step back to where the trouble took originally place. As developers are not actually given state-of-the-art time machines, the time turning step is speculative.

CVirtualHeapPtr Class: Memory with Exception-on-Write access mode

At the same time a Windows platform developer is or might be aware of virtual memory API which among other things provides user mode application with capabilities to define memory protection modes. Having this on hands opens unique opportunity to apply read-only protection (PAGE_READONLY) onto a memory block and have exception raised at the very moment of unexpected memory modification, having call stack showing up a source of the problem. I refer to this mode of operation as “hardware assisted” because the access violation exception/condition would be generated purely in hardware without any need to additionally do any address comparison in code.

Needless to say that this way is completely convenient for the developer as he does not need to patch the monstrous application all around in order to compare access addresses against read-only fragment. Instead, a block defined as read-only will be immediately available as such for the whole process almost without any performance overhead.

As ATL provides a set of memory allocator templates (CHeapPtr for heap backed memory blocks, allocated with CCRTAllocator, alternate options include CComHeapPtr with CComAllocator wrapping CoTaskMemAlloc/CoTaskMemFree API), let us make an alternate allocator option that mimic well-known class interface and would facilitate corruption detection.

Because virtual memory allocation unit is a page, and protection mode is defined for the whole page, this would be the allocation granularity. For a single allocated byte we would need to request SYSTEM_INFO::dwPageSize bytes of virtual memory. Unlike normal memory heap manager, we have no way to share pages between allocations as we would be unable to effectively apply protection modes. This would definitely increase application pressure onto virtual memory, but is still acceptable for the sacred task of troubleshooting.

We define a CVirtualAllocator class to be compatible with ATL’s CCRTAllocator, however based on VirtualAlloc/VirtualFree API. The smart pointer class over memory pointer would be defined as follows:

template <typename T>
class CVirtualHeapPtr :
    public CHeapPtr<T, CVirtualAllocator>
{
public:
// CVirtualHeapPtr
    CVirtualHeapPtr() throw();
    explicit CVirtualHeapPtr(_In_ T* pData) throw();
    VOID SetProtection(DWORD nProtection)
    {
        // TODO: ...
    }
};

The SetProtection method is to define memory protection for the memory block. Full code for the classes is available on Trac here (lines 9-132):

  • CGlobalVirtualAllocator class is a singleton querying operating system for virtual memory page size, and provides alignment method
  • CVirtualAllocator class is a CCRTAllocator-compatible allocator class
  • CVirtualHeapPtr class is smart template class wrapping a pointer to allocated memory

Use case code will be as follows. “SetProtection(PAGE_READONLY)” enables protection on memory block and turns on exception generation at the moment memory block modification attempt. “SetProtection(PAGE_READWRITE)” would restore normal mode of memory operation.

CVirtualHeapPtr<BYTE> p;
p.Allocate(2);
p[1] = 0x01;
p.SetProtection(PAGE_READONLY);
// NOTE: Compile with /EHa on order to catch the exception
_ATLTRY
{
    p[1] = 0x02;
    // NOTE: We never reach here due to exception
}
_ATLCATCHALL()
{
    // NOTE: Catching the access violation for now to be able to continue execution
}
p.SetProtection(PAGE_READWRITE);
p[1] = 0x03;

Given the information what data gets corrupt, the pointer allocator provides an efficient opportunity to detect the violation attempt. The only thing remained is to keep memory read-only, and temporarily revert to write access when the “legal” memory modification code is about to be executed.

Continue reading →