4 ms·
About a million years ago (OK, more like 20) I was making casual videogames in C++ and I wanted a cross-platform (Linux, Mac, Windows) way to get a stack trace
by ggambetta 2y ago
About a million years ago (OK, more like 20) I was making casual videogames in C++ and I wanted a cross-platform (Linux, Mac, Windows) way to get a stack trace whenever a game crashed. What I ended up doing was adding a macro to the first line of every function, let's call it STACKTRACE, which was something like
#define STACKTRACE GLOBAL_STACK_FILE[GLOBAL_STACK_IDX] = __FILE__; GLOBAL_STACK_LINE[GLOBAL_STACK_IDX++] = __LINE__; StackTraceCleaner stc;
StackTraceCleaner was a class that didn't do anything but execute GLOBAL_STACK_IDX-- in its destructor.
So at any point in time I could inspect GLOBAL_STACK_FILE and GLOBAL_STACK_LINE and have a complete stack trace of the game.
Obviously this only worked because these games weren't performance-critical and because they were essentially single-threaded, but it did the job at the time. We're talking about a time when Visual Studio 6's support for templates was half-broken, and the STL wasn't exactly S, to the point that I had to roll out my own string, smart pointers, containers, etc -- made twice as hard because of the aforementioned broken template support in VS6 :(
I do miss these simpler, more innocent times, though.
- VikingCoder 2y agoBack in VS6 days, I did a similar thing, but I generated a GUID for the first line of every function. STACKTRACE("e0957136-fed3-414d-80b9-8bbf84f3fa03"); With the GUID, I could see where functions moved as they were refactored. I would write out the GUID to a thread-local file handle, along with a time-stamp, and an "enter" or "exit" when an RAII object left the stack. Then I could retrospectively debug after running my program. I could see the callstack, and step through in time. I would walk my source and record GUID-to-filename/linenumber in a map. Then I could dump out a Visual Studio output that had the file name and line number, and execution time... allowing me to step forward and back through the execution. Stone knives and bearskins.
- trentnelson 2y agoThat's neat. The modern equivalent to that these days, on Windows, is to leverage ETW and Windows Performance Analyzer. Potentially with a custom plugin that can visualize your specific perf data as a first-class WPA citizen (i.e. indistinguishable from any other perf data being analyzed, which means you can group/query/filter etc. just like anything else). I wrote a plugin for a past employer to visualize our internal product event hierarchy performance as if it were a normal C/C++ call stack, it was pretty cool. ETW and WPA are phenomenal tools. I miss them both dearly when on Linux.
- arjvik 2y agoMy C++ is quite rusty, but why not have StackTraceCleaner's constructor take __FILE__ and __LINE__ as arguments and update the file and line arrays there?
- FooBarWidget 2y agoThis is the same strategy I used in the Passenger application server.
- pjmlp 2y agoWe used a similar technique on a CRM server, UNIX based, back in 1999 - 2001. Another techique is that all key allocations were handled based, so we could also easily dump what the whole process map was about.
- deleted 2y ago[deleted]
- pjc50 2y agoI had to do that when dealing with a device that was only debuggable over USB. When it crashed, that also took out the uplink and the debugger. So I had to record the stack trace in an area of memory specified as uninitialized, then dump it out as one of the first things to happen after a reboot.
- olvy0 2y agoI did a more general thing: At work we have a small "scripting" language (sort of a DSL), this DSL is translated into C++ and then the code is compiled before loading the program. Users work through a dedicated IDE. During the translation phase, we inject a call between every 2 lines (of the original script language) similar in spirit to the macro above, that updates a global array with a counter. This global array is also built during the translation phase. There's a separate thread which polls this array and sends updates to the IDE, so users can see in real-time what code was run and when, without needing to stop and debug the code or insert printing statement. There's a somewhat complicated algorithm which during translation gives a unique ID to each callee in the call tree, so that thread sends to the IDE basically just a number with a counter. This doesn't deal with recursion BTW, I just limited reporting on recursion to a shallow depth. It still runs but just isn't reported. It's not a complete replacement for a debugger (we also have a debugger) but it's good enough for most simple cases. There's a similar hack for variables (including local variables). Recently, some of my less technical users have been disappointed to discover this feature isn't present in other languages / IDEs... You can also sort of do the same trick in any language, if compiled without optimizations the compiler will usually insert NOP assembly statements between each source line (disclaimer: not all compilers, not all languages, depends on a lot of factors). This gives you both runtime visualization of running code and potentially time-travel debugging. So one might be able to run a post-build patching phase and replace these NOPs with a call to a reporting function as above. Out of curiosity I actually did this with C#, replacing NOPs in the MSIL level, since MSIL is easier to reason about than pure assembly, and it worked very nicely, I got a full "log" of program execution, including all lines executed, when, and all values of local and global variables. I used the Cecil library to make it slightly more pleasant to read and manipulate IL. Didn't go on with it other than writing a basic proof of concept. There are of course tools which do the same for C++, like undo.io and RR, but I'm not aware of any tool doing this for C# / dotnet code. I'm not sure why since while not trivial, it isn't very difficult (and I'm not an expert by any means in this sort of thing). Roslyn (C# compiler infrastructure) has code generators now, which is nicer than working with IL, but as far as I was able to see they don't support this scenario.
- plingbang 2y ago> so users can see in real-time what code was run and when I'm really curious how this was presented to the user. A table with timestamp, filename, line number, and line contents? Or something more advanced?