0

Following advice from here and here, I created a class to print the call stack, with line numbers, just by inserting a call at a point in my code. It works well enough, but, I am confused by one aspect of the output - the line number. My test code:

#define _WIN32 1
#define DEBUG 1

#include "C:\git\StackTrace\StackTrace.h"

int main()
{
    MyCompany::PrintStackTrace();
    return 0;
}

The call to print the stack trace is on line 8, however, the output indicates it's on line 9:

*** 0: main in c:\git\tests\stacktrace\stacktrace\st_test.cpp: line: 9: address: 0x7FF629172070
*** 1: invoke_main in f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl: line: 41: address: 0x7FF629173710
*** 2: __scrt_common_main_seh in f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl: line: FD: address: 0x7FF6291734C0
*** 3: __scrt_common_main in f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl: line: 128: address: 0x7FF6291734A0
*** 4: mainCRTStartup in f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp: line: 11: address: 0x7FF629173760
*** SymGetLineFromAddr64 returned error code 487
*** at BaseThreadInitThunk address 0x7FFA09648350
*** SymGetLineFromAddr64 returned error code 487
*** at RtlUserThreadStart address 0x7FFA0BF070B0

Please help me understand what I am missing here?

My StackTrace.h code:

#ifdef _WIN32
#ifdef DEBUG

#include <Windows.h>
#include <fstream>
#include <iostream>
#include <DbgHelp.h>
#include <WinBase.h>

#pragma comment(lib, "Dbghelp.lib")

using namespace std;

namespace MyCompany
{
    class StackTrace
    {
    private:
        static bool instanceFlag;
        static StackTrace *single;
        StackTrace() { }
    public:
        static StackTrace* GetInstance();
        void PrintStack();
        void PrintStack(ostream& out, int skipFrames);
        ~StackTrace()
        {
            instanceFlag = false;
        }
    };

    bool StackTrace::instanceFlag = false;
    StackTrace* StackTrace::single = NULL;

    /**
     * Returns the instance of the StackTrace class, generating the instance if needed.
     *
     * @return A pointer to the instance of the StackTrace class.
     */
    StackTrace* StackTrace::GetInstance()
    {
        if (!instanceFlag)
        {
            single = new StackTrace();
            instanceFlag = true;
            return single;
        }
        else
        {
            return single;
        }
    }

    /**
     * Prints the call stack to stdout.
     *
     * @param sourceStr
     *        The multibyte character string.
     */
    void StackTrace::PrintStack()
    {
        // If PrintStackTrace() was called with no parameters, there is one more 
        // wrapper in the stack (this one) to skip.
        int skipFrames = 3;

        PrintStack(cout, skipFrames);
    }

    /**
     * Prints the call stack to the given stream.
     *
     * @param out
     *        The already-opened stream where the call stack will be printed.
     *
     * @param skipFrames
     *        The number of frames to skip when capturing the call stack.
     */
    void StackTrace::PrintStack(ostream& out, int skipFrames = 2)
    {
        const int maxCallers = 1024; // Not worried about size limits of OS's older than Windows 7.
        void * callers_stack[maxCallers];
        unsigned short frames;
        SYMBOL_INFO * symbol;
        HANDLE process;
        DWORD displacement;

        IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
        if (NULL == line)
        {
            return;
        }

        process = GetCurrentProcess();
        SymInitialize(process, NULL, TRUE);
        frames = CaptureStackBackTrace(skipFrames, maxCallers, callers_stack, NULL);

        symbol = (SYMBOL_INFO *)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
        if (NULL == symbol)
        {
            return;
        }

        symbol->MaxNameLen = 255;
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

        out << uppercase;

        const unsigned short  MAX_CALLERS_SHOWN = 64;
        frames = frames < MAX_CALLERS_SHOWN ? frames : MAX_CALLERS_SHOWN;

        for (unsigned int i = 0; i < frames; i++)
        {
            DWORD64 address = (DWORD64)(callers_stack[i]);
            SymFromAddr(process, address, 0, symbol);

            if (SymGetLineFromAddr64(process, address, &displacement, line))
            {
                out << "*** " << dec << i << ": " << symbol->Name << " in " << line->FileName 
                    << ": line: " << line->LineNumber << ": address: 0x" << hex << symbol->Address << endl;
            }
            else
            {
                out << "*** SymGetLineFromAddr64 returned error code " << dec << GetLastError() << endl;
                out << "*** at " << symbol->Name << " address 0x" << hex << symbol->Address << endl;
            }
        }

        if (symbol) free(symbol);
        if (line) free(line);
    }

    /**
     * Print the call stack to stdout.
     */
    void PrintStackTrace()
    {
        StackTrace::GetInstance()->PrintStack();
    }

    /**
     * Print the call stack to the given stream.
     *
     * @param out
     *        The already-opened stream where the call stack will be printed.
     */
    void PrintStackTrace(ostream& out)
    {
        StackTrace::GetInstance()->PrintStack(out);
    }
}

#endif // DEBUG
#endif // _WIN32
Community
  • 1
  • 1
Jon
  • 1,675
  • 26
  • 57

1 Answers1

0

You can't expect line numbers to be completely reliable - not even the stack trace itself for that matter. Once the optimizer is done chewing on your code it will look very different to what you actually wrote and there's only so much info that can be kept in debug info to map it back to source.

You have to learn to read stack traces with a grain of salt.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • The compiler is supposed to keep track of line numbers even as it's optimizing. – Barmar Mar 15 '17 at 19:33
  • @Barmar It does. But only to a certain extend. *Especially* when optimizing it can't do it reliably. If you doubt me, try building any non-trivial program with gcc, clang or VS at the highest optimization level (include debug info) and then cause it to crash somewhere with a deep stack and take a look at the core dump. – Jesper Juhl Mar 15 '17 at 19:36
  • What of the case where I am building a DEBUG binary and optimization is turned off via `/Od`? – Jon Mar 15 '17 at 21:40
  • @Jon then you can usually trust stack traces a lot more (although compilers still do sometimes change stuff even in that case). In any case; it's pretty common (in my experience) for compilers to assign something to the line just *after* a statement. So seeing "foo()" reported as "foo.cc line just after the semi-colon" wouldn't surprise me at all. – Jesper Juhl Mar 15 '17 at 21:43
  • So, for a vanilla VC2015 C++ WIn32 Console App project, building the code above, you don't see any reason for the discrepancy, other than the compiler doing some funny tricks (IOW, it's not something I did or did not do)? – Jon Mar 16 '17 at 15:41