Skip to main content
gamedev.net
🔒 Locked

Call stack log on crash?

Started by Hedos Jun 25, 2004 at 11:00 PM 25 replies 7k views
Original Post
Hedos
Hedos
Hey, I'm wondering how would it be possible to have a system in my application that would log ( in a .txt file for example ) the call stack, when the application crash? So I can see where does my application crashes on other people computers. My application is written in C++. Thanks a lot
Hedos
Hedos
Thanks a lot for the reply, but unfortunatly, it looks like your website doesn't work..

The URL doesn't look wrong, I guess their server is just temporary down, right?

edit: The website works now, I'm gonna take a look.
Kibble
Kibble
I do something like:
class CException{ ...   virtual CString GetMessage();};class CStackTraceException : public CException{   CString m_StackTrace;public:   ...   void Push(const TCHAR * Function); // add function to stack here, I do m_StackTrace += Function + '\n';   CString GetMessage() { /* Build and return message with call stack here */ }};

then:
#define PushCall(FuncName)    const TCHAR FUNCTION_NAME[] = FuncName;    try    { #define PopCall()    }    catch(CStackTraceException & Ex)    {       Ex.Push(FUNCTION_NAME);       throw Ex;    }

to use it:
void FuncA(){   PushCall("FuncA");   throw CStackTraceException("I farted");   PopCall();}void FuncB(){   PushCall("FuncB");   FuncA();   PopCall();}void FuncC(){   PushCall("FuncC");   FuncB();   PopCall();}int main(int ArgC, char ** argv){   try   {      PushCall("main");      FuncC();      PopCall();   }   catch(CException & Ex)   {      MessageBox(Ex.GetMessage());   }}/*which prints out:I Farted:FuncAFuncBFuncCmain*/


I have a preprocessor flag to simply define PushCall/PopCall as empty, as I'm not sure what kind of performance hit they incur. I haven't noticed much of a speed difference it causes, so it can't be that bad, but it does work, and I like the flexibility to leave out helper functions that always appear in the same place in the call stack.

I got the idea looking at some source code quite a long time ago, I think it may have been Unreal's mod headers. if it was unreal, they leave this enabled in their games, so its probably not too bad of a speed hit. Of course, the call stack their errors spit out might be from the scripting system, and not this hackery.
antareus
antareus
Kibble, that assumes that control always leaves the function through the 'bottom' of it. This is almost always the case, but it seems like its easy to forget. Additionally, you can't really use something like a scope guard to do it for you.
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
C-Junkie
C-Junkie
On the offhand chance the OP isn't refering to windows,

Linux, BSD, and I assume MacOS:

tell your users to enable core dumps, and send the core file to you. It will contain a call stack and the entire environment at the time of the crash.

I believe that on a bash console, it can be done with

ulimit -c unlimited

Or you could enable core dumps yourself within your application using the rlimit syscall (iirc)
Kibble
Kibble
Quote:
Original post by antareus
Kibble, that assumes that control always leaves the function through the 'bottom' of it. This is almost always the case, but it seems like its easy to forget. Additionally, you can't really use something like a scope guard to do it for you.

true, but when would you want the call stack when you haven't terminated the program and are making an error message? and, just to be stupid:
extern CString g_CallStack;#define PushCall(FuncName) { g_CallStack += FuncName + "\n"; static int FUNCTION_NAME_LENGTH = strlen(FuncName); #define PopCall() g_CallStack[g_CallStack.GetLength() - FUNCTION_NAME_LENGTH] = '\0'; }

definitely not fast though...

Promit
Promit
Quote:
Original post by antareus
Kibble, that assumes that control always leaves the function through the 'bottom' of it. This is almost always the case, but it seems like its easy to forget. Additionally, you can't really use something like a scope guard to do it for you.


You could define a macro to push the stack and return, I suppose.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
C-Junkie
C-Junkie
or um, you could make jsut one macro...

stack g_calls;
class asdf { asdf(string s) {g_calls.push(s);} ~asdf(){g_calls.pop();} };
#define FUNCTION(x) asdf asldkfjl(#x)

void function() {
FUNCTION(function);
//blah blah blah
} // destructor pops the stack

S1CA
S1CA
I made a small utility function a while ago that uses the StackWalk function to walk down the stack by a specified number of levels to get the symbol information about that level of the stack. Might be of use to someone:

http://www.sc3d.com/thought/snippets/debugging/GetFunctionCaller/

The main difference compared to similar stuff I found on the internet is that it doesn't need to be called from within an exception handler (getting EIP is the tricky part).
Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site
antareus
antareus
Quote:
Original post by C-Junkie
or um, you could make jsut one macro...

stack g_calls;
class asdf { asdf(string s) {g_calls.push(s);} ~asdf(){g_calls.pop();} };
#define FUNCTION(x) asdf asldkfjl(#x)

void function() {
FUNCTION(function);
//blah blah blah
} // destructor pops the stack

Nope. If an exception is thrown the call stack will be destroyed when the exception leaves the scope because that will fire the local's destructor.

BTW most compilers can inject the name of the current function in there, too. :)

S1CA that code snippet is AWESOME, do you mind if I rip it out, modify it to dynamically load dbghelp.dll, and put it in a public domain library? Full credit will be left in the files.
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
C-Junkie
C-Junkie
Quote:
Original post by antareus
Nope. If an exception is thrown the call stack will be destroyed when the exception leaves the scope because that will fire the local's destructor.
Oh, that what you guys were trying to do. Should have read those earlier posts.

That's why I hate exceptions. abort() works much better.
Kibble
Kibble
Quote:
Original post by antareus
Quote:
Original post by C-Junkie
or um, you could make jsut one macro...

stack g_calls;
class asdf { asdf(string s) {g_calls.push(s);} ~asdf(){g_calls.pop();} };
#define FUNCTION(x) asdf asldkfjl(#x)

void function() {
FUNCTION(function);
//blah blah blah
} // destructor pops the stack

Nope. If an exception is thrown the call stack will be destroyed when the exception leaves the scope because that will fire the local's destructor.

If you wanted to use that method, you would have to have the exception's constructor grab the current call stack, and store it for when it pops out the bottom of the stack.
Kibble
Kibble
Quote:
Original post by C-JunkieThat's why I hate exceptions. abort() works much better.

Umm, the whole stack unwinding + destructor calling thing is exactly why exceptions kick ass.
S1CA
S1CA
Quote:
Original post by antareus
Quote:
Original post by C-Junkie
or um, you could make jsut one macro...

stack g_calls;
class asdf { asdf(string s) {g_calls.push(s);} ~asdf(){g_calls.pop();} };
#define FUNCTION(x) asdf asldkfjl(#x)

void function() {
FUNCTION(function);
//blah blah blah
} // destructor pops the stack

Nope. If an exception is thrown the call stack will be destroyed when the exception leaves the scope because that will fire the local's destructor.

BTW most compilers can inject the name of the current function in there, too. :)

S1CA that code snippet is AWESOME, do you mind if I rip it out, modify it to dynamically load dbghelp.dll, and put it in a public domain library? Full credit will be left in the files.



No worries, do anything you want with it, including commercial use - its not significant enough to impose any restrictions on![smile]

The legal statement at the top is really mainly just to disclaim any liability (e.g. if NASA use it in their next bit of kit and it doesn't work as expected, don't sue me! [smile])


There's a few things you might want to do with it though:

- proper error handling (it was more of an experiment to help with a specific problem than a robust library).

- some pre-processor #ifdefs so it will compile with older versions of the Platform SDK - it uses the 64bit compatible stack tracing functions. The PSDK which comes with MSVC 6 for example probably won't contain those.

- make it 64-bit CPU (and non-x86 Windows) friendly - the EIP/ESP stuff isn't likely to play nicely on a 64-bit CPU running in native 64-bit mode. One idea might be to set a temporary exception handler and deliberately throw an exception so that Windows works out the EIP/ESP/EBP for you in a CPU specific way for you.
Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site
kdogg
kdogg
Unix (and linux) produce core files when you crash that contain the entire call stack as well as the value of all your variables on the stack and so forth. I'd think that there must be a way to create core files in windows too. It seems like a waste to create complicated functions to print out your stack when the system will do it automatically.
DrPizza
DrPizza
Quote:
S1CA that code snippet is AWESOME, do you mind if I rip it out, modify it to dynamically load dbghelp.dll, and put it in a public domain library? Full credit will be left in the files.

It worked for you? It wouldn't for me. The SizeOfStruct and MaxNameLen members of the SYMBOL_INFO needs to be set appropriately, and the FileName member of IMAGEHLP_LINE64 needs to point to a buffer.

Setting AddrStack is unnecessary. Retrieving EBP and EIP can be done through calling GetThreadContext(GetCurrentThread(), ...). Though this is documented as not returning meaningful data, those particular members of the CONTEXT object seem to be properly populated. Failing that, EIP can be retrieved by calling the compiler intrinsic _ReturnAddress() (which is equivalent to the asm, but built-in). EBP's value can be obtained from _AddressOfReturnAddress() - 4.
char a[99999],*p=a;int main(int c,char**V){char*v=c>0?1[V]:(char*)V;if(c>=0)for(;*v&&93!=*v;){62==*v&&++p||60==*v&&--p||43==*v&&++*p||45==*v&&--*p||44==*v&&(*p=getchar())||46==*v&&putchar(*p)||91==*v&&(*p&&main(0,(char**)(--v+2))||(v=(char*)main(-1,(char**)++v)-1));++v;}else for(c=1;c;c+=(91==*v)-(93==*v),++v);return(int)v;} /*** drpizza@battleaxe.net ***/
S1CA
S1CA
DrPizza:

Like I said earlier, "it was more of an experiment to help with a specific problem than a robust library" [smile].

That code was actually written late at night to help track down who was making anonymous memory allocations in a large project I was debugging rather than for any production use or even tutorial use.

If someone wants to fix it and make it production quality, or make their own code to do the same publicly available they're more than welcome [smile].


Yep, you're right about the SizeOfStruct and MaxNameLen members of SYMBOL_INFO. However you're wrong about the FileName pointer in IMAGEHLP_LINE64 - SymGetLineFromAddr64() always sets that pointer, regardless of its value (and will overwrite any pointer you specify).

Personally I'd be extremely wary about relying on GetThreadContext() returning valid data for the currently running thread - its documented as not returning valid information in that case, I'd trust that to be true (though my plain asm is probably risky too for other reasons). There's too many things to bite you when relying on undocumented "features" (hyper-threading, SMP etc).


I wasn't aware of the _ReturnAddress and _AddressOfReturnAddress intrinsics - thanks for enlightening me!. I'll bear them in mind if I'm ever doing anything like this again.


BTW: the code runs fine on the WindowsXP and Windows2000 machines I used it on (built with .NET 2003 and an up to date PSDK).

Simon O'Connor | Technical Director (Newcastle) Lockwood Publishing | LinkedIn | Personal site
msn12b
msn12b
On Windows, generating minidumps scales better and is more reliable. You don't need symbols on the machine that crashed; you can even spawn a separate process to inspect the crashed application's memory and generate the minidump.

Couple that with the freely available debugging tools from Microsoft, most notably the symbols server, you'll be able to easily debug crashes from remote machines with a minimal hit on runtime dependencies.

MSN

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.