EACallstackRecorder

Introduction

The CallstackRecorder records call stacks. By this we mean that the call stack in effect at any given location in code can be recorded. It can also subsequently report the call stack history that was recorded.

The most common use of CallstackRecorder is to aid in debugging, particularly reference count mismatch tracking. It often happens that trying to track the calls to a given AddRef and Release function by using breakpoints in the debugger is very tedious and having this automated saves a lot of time. CallstackRecorder can also track the number of unique ways that a given function is called so you can tell what all the code is that calls some function.

CallstackRecorder is usually intrusive to source code; that is, you usually need to insert calls to it in your source code for it to work. As such, it is much like profiling statements that users often put into source code to profile segments of code. One way to avoid modifying source code is to use the ability of many compilers to set up a callback function of your choice whenever any function is called. This is called /callcap and /fastcap under Microsoft compilers, for example.

Report output includes file/line information and also source code if available. See below for example output which demonstrates this.

Example usage

Here's example usage for how to use CallstackRecorder to trace a refcount history for a class:

CallstackRecorder gRecorder; // Call gRecorder.SetPredicate(pSomeClass) to track a single SomcClass instance.
 
int SomeClass::AddRef()
{     Callstack::RecordCurrentCallstack(gRecorder, "SomeClass::AddRef", (uintptr_t)this);     return ++mRefCount; } int SomeClass::Release() {
    Callstack::RecordCurrentCallstack(gRecorder, "SomeClass::Release", (uintptr_t)this);
    if(mRefCount > 1)         return --mRefCount;     delete this;     return 0; } void AppShutdown() { Callstack::TraceCallstackRecording(gRecorder, pStream, false); // True = detailed output, false = summarized. }

Example Output

There two forms of output, detailed and summarized. The detailed output displays every callstack event that occurred in order. The summarized output displays every unique callstack event and the number of times each occurred. The summarized output is usually more compact and readable; the detailed output is useful for when you want to know the order of calls.

Here is example detailed output from the above example code:

Callstack recording trace for Test:
   0: SomeClass::AddRef
      f:\Test\Source\EACallstackTest.cpp(287): p->AddRef();
      f:\Test\Source\EACallstackTest.cpp(355): TestAddRef1();
      f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c(398)
      0x77e8141a

   1: SomeClass::AddRef
      f:\Test\Source\EACallstackTest.cpp(287): p->AddRef();
      f:\Test\Source\EACallstackTest.cpp(355): TestAddRef1();
      f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c(398)
      0x77e8141a

   2: SomeClass::Release
      f:\Test\Source\EACallstackTest.cpp(269): p->Release();
      f:\Test\Source\EACallstackTest.cpp(289): TestRelease1();
      f:\Test\Source\EACallstackTest.cpp(355): return gErrorCount;
      f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c(398)
      0x77e8141a

   3: SomeClass::AddRef
      f:\Test\Source\EACallstackTest.cpp(287): p->AddRef();
      f:\Test\Source\EACallstackTest.cpp(355): TestAddRef1();
      f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c(398)
      0x77e8141a

   ...

Here is the equivalent summarized output:

Callstack recording trace for Test:
   SomeClass::AddRef, call count: 3
      f:\Test\Source\EACallstackTest.cpp(287): p->AddRef();
      f:\Test\Source\EACallstackTest.cpp(355): TestAddRef1();
      f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c(398)
      0x77e8141a

   SomeClass::Release, call count: 1
      f:\Test\Source\EACallstackTest.cpp(269): p->Release();
      f:\Test\Source\EACallstackTest.cpp(289): TestRelease1();
      f:\Test\Source\EACallstackTest.cpp(355): return gErrorCount;
      f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c(398)
      0x77e8141a

   ...

Interface

The CallstackRecorder has a high level interface and a low level interface. The example code shown earlier demonstrates the high level interface, which is often fine for many recording and reporting uses. The low level interface is useful for exerting more control over the recorder input or output.

High level interface:

/// RecordCurrentCallstack
///
/// Adds the current callstack to a given CallstackRecorder.
/// A typical use of this is to implement AddRef/Release tracking for 
/// some C++ class object that is leaking due to refcounting mismatches.
/// 
/// The return value is the same as with the CallstackRecorder::AddEntry
/// function.
///
bool RecordCurrentCallstack(CallstackRecorder& csr, const char* pName, uintptr_t predicate = 0);


/// TraceCallstackRecording
///
/// Writes a recording to a given output stream. 
/// If bDetail is true, every individual call is traced in order.
/// If bDetail is false, only the unique set of calls are traced, 
/// and they are traced in order of first appearance.
///
bool TraceCallstackRecording(CallstackRecorder& csr, EA::IO::IStream* pOutput, bool bDetail);

Low level interface:

/// callstack_hash_t
/// Defines the integral hash value used for callstack hashes.
typedef uint32_t callstack_hash_t;


/// GetCallstackHash
/// Returns a hash value for a callstack.
callstack_hash_t GetCallstackHash(const void* callstack[], size_t entryCount);


/// CallstackEntry
/// Defines a given callstack with a name and a hash value.
class CallstackEntry
{
public:
    typedef fixed_vector<const void*, 16, true> Callstack;

public:
    CallstackEntry();
    CallstackEntry(const char* pName, const void* pCallstack[], size_t entryCount);
    CallstackEntry(const CallstackEntry& ce);
    CallstackEntry& operator=(const CallstackEntry& ce);

    callstack_hash_t GetHash() const;
    void             SetHash(callstack_hash_t h);
    const char*      GetName() const;
    const Callstack& GetCallstack() const;
    uint32_t         GetCount() const;
    uint32_t         IncrementCount();

    bool operator==(const CallstackEntry& ce) const;
    bool operator!=(const CallstackEntry& ce) const;
    bool operator< (const CallstackEntry& ce) const;
};



/// CallstackRecorder
///
/// Implements a record of callstacks.
/// A common usage of this is to track down all the calls to a given 
/// function in order reference-counting debug memory leaks.
///
class CallstackRecorder
{
public:
    typedef hash_set<CallstackEntry> CallstackEntrySet;
    typedef deque<callstack_hash_t>  CallstackEntryHistory;

public:
    /// CallstackRecorder
    /// Default constructor.
    CallstackRecorder(Allocator::ICoreAllocator* pCoreAllocator = NULL);

    /// ~CallstackRecorder
    /// Virtual destructor.
    virtual ~CallstackRecorder();

    /// SetAllocator
    /// Sets the memory allocator to use with this class.
    /// This allocator is used to allocate CallstackEntrySet elements and 
    /// CallstackEntryHistory elements.
    /// This function must be called before any entries are added.
    virtual void SetAllocator(Allocator::ICoreAllocator* pCoreAllocator);

    /// AddEntry
    /// Adds a new callstack entry to our recording. Returns true if the 
    /// entry was added. It would only fail to be added if the predicate 
    /// was specified and didn't match the expected value.
    ///
    /// Example usage:
    ///     void* callstack[32];
    ///     size_t n = GetCallstack(callstack, 32);
    ///     pRecorder->AddEntry("AddRef", &(const void*&)*callstack, n);
    ///
    virtual bool AddEntry(const char* pName, const void* callstack[], 
                             size_t entryCount, uintptr_t predicate = 0);

    /// GetEntryCount
    /// Returns the number of entries in our recording.
    virtual size_t GetEntryCount() const;

    /// GetEntry
    /// Returns the given historical entry, where the valid entry index is 
    /// in the range of [0, GetEntryCount).
    virtual const CallstackEntry* GetEntry(size_t i);

    /// GetCallstackEntrySet
    /// Returns the set of unique callstack entries. This is useful for 
    /// enumerating the set of unique call pathways to a given location.
    const CallstackEntrySet& GetCallstackEntrySet() const;

    /// GetName
    /// Returns the name set for this recorder by SetName. If no name was
    /// set, then an empty string is returned (and not a NULL pointer).
    const char* GetName() const;

    /// SetName
    /// Sets a name for this recorder. The pName argument must not be NULL.
    /// The name should be less than 24 characters for it to be stored without
    /// doing a memory allocation.
    void SetName(const char* pName);

    /// GetPredicate
    /// Returns the value set by SetPredicate. Will return 0 if the 
    /// SetPredicate function wasn't called.
    uintptr_t GetPredicate() const;

    /// SetPredicate
    /// Sets a value whereby if it is nonzero, then AddEntry functions will
    /// not add entries unless the predicate matches. A more complicated 
    /// mechanism for filtering entry additions required subclassing this
    /// CallstackRecorder. 
    void SetPredicate(uintptr_t predicate);
};