3

I have the following code:

#include "stdafx.h"
#include <process.h>
#include <iostream>
#include <Windows.h>
#include "dbghelp.h"

using namespace std;

int LogStackTrace()
{
    void *stack[1024];
    HANDLE process = GetCurrentProcess();
    SymInitialize(process, NULL, TRUE);
    WORD numberOfFrames = CaptureStackBackTrace(0, 1000, stack, NULL);
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO));
    symbol->MaxNameLen = 1024;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    IMAGEHLP_LINE *line = (IMAGEHLP_LINE *)malloc(sizeof(IMAGEHLP_LINE));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE);
    printf("Caught exception ");
    for (int i = 0; i < numberOfFrames; i++)
    {
        SymFromAddr(process, (DWORD64)(stack[i]), NULL, symbol);
        SymGetLineFromAddr(process, (DWORD)(stack[i]), NULL, line);
        printf("at %s in %s, address 0x%0X\n", symbol->Name, line->FileName, symbol->Address);
    }
    return 0;
}

void function2()
{
    int a = 0;
    int b = 0;
    throw new exception("Expected exception.");
}

void function1()
{
    int a = 0;
    function2();
}

void function0()
{
    function1();
}

static void threadFunction(void *param)
{
    try
    {
        function0();
    }
    catch (...)
    {
        LogStackTrace();
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
        _beginthread(threadFunction, 0, NULL);
    }
    catch (...)
    {
        LogStackTrace();
    }
    printf("Press any key to exit.\n");
    cin.get();
    return 0;
}

The problem is that it always errors out at this line: printf("at %s in %s, address 0x%0X\n", symbol->Name, line->FileName, symbol->Address);

The reason is because line's FileName seems to be NULL. Actually, the entire line structure is messed up. I am trying to write an application to show a stack trace on an error. But why is that? Shouldn't it be working using the above code? PS I compiled it against Win32, as a simple MSVC++ Console application.

Alexandru
  • 12,264
  • 17
  • 113
  • 208
  • Update your question with version information. Windows XP, or Windows Seven? 32 bits or 64 bits? MBCS or Unicode? – manuell Mar 17 '14 at 21:33
  • Windows 8.1, Using Unicode Character Set, Win32 App. – Alexandru Mar 17 '14 at 21:35
  • @manuell Hey, I wrote a blog post on all of this and made a nice wrapper class that people can use to trace the stack, and went into things like Windows Error Reporting (which is honestly a life-saver): http://www.dima.to/blog/?p=13 – Alexandru May 31 '14 at 17:06

2 Answers2

4

Had the same problem with your code (Windows Seven 64b, Unicode 32 bits build, VS2012 Express)

Fixed it with :

DWORD dwDisplacement;
SymGetLineFromAddr(process, (DWORD)(stack[i]), &dwDisplacement, line);
manuell
  • 7,528
  • 5
  • 31
  • 58
  • so much for optional values eh? this might explain the The error code 3221225477 is 0xC0000005 in hex, which on Windows is: #define STATUS_ACCESS_VIOLATION ((NTSTATUS)0xC0000005L) Access violation is Windows' version of "segmentation fault", which simply said means that the program tried to access a memory which is not allocated. This can happen for a lot of different reasons, but mostly (if not always) is a bug in the program. – Alexandru Mar 17 '14 at 21:46
  • 1
    Well, it's not really documented as optional :-) the 'or zero' is to be interpreted for the output value... – manuell Mar 17 '14 at 21:49
  • I made the mistake of thinking the displacement was optional like in SymFromAddr – Alexandru Mar 17 '14 at 21:49
  • 1
    Thanks to you, I learned something today :-). Didn't know about that kind of APIs. Happy hacking! – manuell Mar 17 '14 at 21:53
  • Do you know how uh...surprised I am that I haven't found such an example of this elsewhere? I keep trying to pioneer these crazy APIs. You might find some of my other Stack questions very interesting, but in particular: read this http://stackoverflow.com/questions/20061459/why-wont-my-solution-work-to-p-invoke-notifyservicestatuschange-in-c which leads to http://stackoverflow.com/questions/20103529/how-do-asynchronous-procedure-calls-handle-marshaled-delegates-when-you-p-invoke – Alexandru Mar 17 '14 at 21:57
  • Shit, I think there's still something wrong because the line number doesn't always come through. First 2 loops it works, then SymGetLineFromAddr64 returns false. The reason why you still see the file name come up in the trace is because its piggybacking off the last struct for line that properly came through...I'm trying to see where its failing, but for sure calling GetLastError() shows us that error code is 487 (access violation?) – Alexandru Mar 17 '14 at 22:52
  • Made a spin-off of this (PS still not entirely sure why the line numbers weren't working for me before but it seems to work with the code I supplied in this thread): http://stackoverflow.com/questions/22467604/how-can-you-use-capturestackbacktrace-to-capture-the-exception-stack-not-the-ca – Alexandru Mar 18 '14 at 01:21
  • Hey, I wrote a blog post on all of this and made a nice wrapper class that people can use to trace the stack, and went into things like Windows Error Reporting (which is honestly a life-saver): http://www.dima.to/blog/?p=13 – Alexandru May 31 '14 at 17:05
2
SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO));
symbol->MaxNameLen = 1024;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

The documentation for SizeOfStruct states:

The size of the structure, in bytes. This member must be set to sizeof(SYMBOL_INFO). Note that the total size of the data is the SizeOfStruct + (MaxNameLen - 1) * sizeof(TCHAR). The reason to subtract one is that the first character in the name is accounted for in the size of the structure.

Emphasis mine. You must allocate storage of at least sizeof(SYMBOL_INFO) + MaxNameLen + 1 bytes. You are only allocating sizeof(SYMBOL_INFO) bytes.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • That's not the issue. The struct returns addresses correctly. No problem with the SYMBOL_INFO, although I could increase the size there of that struct, sure. The problem is with SymGetLineFromAddr and the IMAGEHLP_LINE structure. Even by increasing the size of SYMBOL_INFO, the problem still exists with the IMAGEHLP_LINE structure not being correctly set. – Alexandru Mar 17 '14 at 21:22
  • 1
    It isn't enough, core problem is that he's ignoring the return value and not calling GetLastError() to find out what went wrong. – Hans Passant Mar 17 '14 at 21:23
  • 2
    @HansPassant True. As soon as I see that code may be smashing the heap I tend to stop looking for other problems since the code is already broken... – James McNellis Mar 17 '14 at 21:26
  • @HansPassant That makes no sense. Why would calling GetLastError() in this case prevent stack smashing? – Alexandru Mar 17 '14 at 21:32
  • Its unreliable anyways, according to docs: "some functions set the last-error code to 0 on success and others do not." – Alexandru Mar 17 '14 at 21:38
  • 1
    It's not unreliable. The documentation for [`SymGetLineFromAddr`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms681330.aspx) expressly states "If the function fails, the return value is `FALSE`. To retrieve extended error information, call `GetLastError`." – James McNellis Mar 17 '14 at 21:40
  • Oh, you're talking about the return type of SymGetLineFromAddr. I thought you guys meant to check the last error on the start of the method lol – Alexandru Mar 17 '14 at 21:42
  • error is The error code 3221225477 is 0xC0000005 in hex, which on Windows is: #define STATUS_ACCESS_VIOLATION ((NTSTATUS)0xC0000005L) Access violation is Windows' version of "segmentation fault", which simply said means that the program tried to access a memory which is not allocated. This can happen for a lot of different reasons, but mostly (if not always) is a bug in the program. – Alexandru Mar 17 '14 at 21:46
  • 2
    Yes, well, as I already pointed out, your program is smashing the heap. – James McNellis Mar 17 '14 at 21:47
  • No its not see the answer above – Alexandru Mar 17 '14 at 21:48
  • sorry it is. i forgot to actually include the out argument when it should have been there. – Alexandru Mar 17 '14 at 21:48
  • When I saw it as an optional value in the result of SymFromAddr i must have assumed it was also optional in the SymGetLineFromAddr – Alexandru Mar 17 '14 at 21:49
  • If you're using C++ and would rather use the `new` operator instead, you can allocate the block as a `char` array and use `reinterpret_cast(block)` – solstice333 Sep 16 '17 at 15:48