Icon 17 Line Markov Chain

 

Icon 14 Character Random Number Generator

 

Icon Simple Two Joint IK

 

Icon Generating Icons with Pixel Sorting

 

Icon Neural Network Ambient Occlusion

 

Icon Three Short Stories about the East Coast Main Line

 

Icon The New Alphabet

 

Icon "The Color Munifni Exists"

 

Icon A Deep Learning Framework For Character Motion Synthesis and Editing

 

Icon The Halting Problem and The Moral Arbitrator

 

Icon The Witness

 

Icon Four Seasons Crisp Omelette

 

Icon At the Bottom of the Elevator

 

Icon Tracing Functions in Python

 

Icon Still Things and Moving Things

 

Icon water.cpp

 

Icon Making Poetry in Piet

 

Icon Learning Motion Manifolds with Convolutional Autoencoders

 

Icon Learning an Inverse Rig Mapping for Character Animation

 

Icon Infinity Doesn't Exist

 

Icon Polyconf

 

Icon Raleigh

 

Icon The Skagerrak

 

Icon Printing a Stack Trace with MinGW

 

Icon The Border Pines

 

Icon You could have invented Parser Combinators

 

Icon Ready for the Fight

 

Icon Earthbound

 

Icon Turing Drawings

 

Icon Lost Child Announcement

 

Icon Shelter

 

Icon Data Science, how hard can it be?

 

Icon Denki Furo

 

Icon In Defence of the Unitype

 

Icon Maya Velocity Node

 

Icon Sandy Denny

 

Icon What type of Machine is the C Preprocessor?

 

Icon Which AI is more human?

 

Icon Gone Home

 

Icon Thoughts on Japan

 

Icon Can Computers Think?

 

Icon Counting Sheep & Infinity

 

Icon How Nature Builds Computers

 

Icon Painkillers

 

Icon Correct Box Sphere Intersection

 

Icon Avoiding Shader Conditionals

 

Icon Writing Portable OpenGL

 

Icon The Only Cable Car in Ireland

 

Icon Is the C Preprocessor Turing Complete?

 

Icon The aesthetics of code

 

Icon Issues with SDL on iOS and Android

 

Icon How I learned to stop worrying and love statistics

 

Icon PyMark

 

Icon AutoC Tools

 

Icon Scripting xNormal with Python

 

Icon Six Myths About Ray Tracing

 

Icon The Web Giants Will Fall

 

Icon PyAutoC

 

Icon The Pirate Song

 

Icon Dear Esther

 

Icon Unsharp Anti Aliasing

 

Icon The First Boy

 

Icon Parallel programming isn't hard, optimisation is.

 

Icon Skyrim

 

Icon Recognizing a language is solving a problem

 

Icon Could an animal learn to program?

 

Icon RAGE

 

Icon Pure Depth SSAO

 

Icon Synchronized in Python

 

Icon 3d Printing

 

Icon Real Time Graphics is Virtual Reality

 

Icon Painting Style Renderer

 

Icon A very hard problem

 

Icon Indie Development vs Modding

 

Icon Corange

 

Icon 3ds Max PLY Exporter

 

Icon A Case for the Technical Artist

 

Icon Enums

 

Icon Scorpions have won evolution

 

Icon Dirt and Ashes

 

Icon Lazy Python

 

Icon Subdivision Modelling

 

Icon The Owl

 

Icon Mouse Traps

 

Icon Updated Art Reel

 

Icon Tech Reel

 

Icon Graphics Aren't the Enemy

 

Icon On Being A Games Artist

 

Icon The Bluebird

 

Icon Everything2

 

Icon Duck Engine

 

Icon Boarding Preview

 

Icon Sailing Preview

 

Icon Exodus Village Flyover

 

Icon Art Reel

 

Icon LOL I DREW THIS DRAGON

 

Icon One Cat Just Leads To Another

Printing a Stack Trace with MinGW

Created on Feb. 18, 2015, 9:19 p.m.

If you're developing a C application on linux you can print a backtrace of the program using the backtrace library. Unfortunately this library isn't avaliable for Windows. If you're developing using MinGW you can use gdb to get a backtrace of a crashing program, but if you want to actually print out a backtrace to your users (or to file) when something goes wrong you are going to find it a bit more difficult.

There are two main reasons. The first is that the Windows stack walk API is extremely sensitive and fails under the most minor misconfiguration. And the second is that the Windows debugging symbols format is different to that used by linux and MinGW, and so you need to do the conversion before you can display them.

Here are the steps required to print out a stacktrace on Windows using MinGW:

  1. Walk the stack using the StackWalk64 API to print out the results.
  2. Compile your executable using MinGW, including debug information.
  3. Convert the debugging information to the Windows format using cv2pdb.
  4. Run your program and produce stack trace.

The most common way to walk the stack on Windows is the StackWalk64 function. There are plenty of resources online for this function which you are advised to read over before getting started. There is also an unofficial, but definitive, resource on using StackWalk64 which contains lots of answers to smaller details, and solved lots of my issues. It can be found here.

So let's assume we want to write a function which will print out a stack trace for the running program. The first step is to get references to the current thread, process, and context. Getting references to the current thread and process is easy.

HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();

But getting a reference to the current context is more difficult. On 64 bit windows you need to use the RtlCaptureContext function. Don't try using the GetThreadContext function on the current thread - it wont work. There are some workarounds for getting the context on 32-bit windows, but it isn't pretty.

CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);

Once the context is loaded we need to initalize the symbol hander. This assumes our debugging information is somewhere on the symbol search path. The easiest way to ensure this is the case is to put the generated debugging file in the same directory as the executable.

SymInitialize(process, NULL, TRUE);

We then need to prepare a STACKFRAME64 struct for the StackWalk64 function. This structure essentially tells the function what state the stack is in I.E where the stack and frame pointers are located. This is what we needed the current context for as it contains this information. This part of the code is platform dependant, so needs to be done differently for 32-bit and 64-bit. It can be done like this:

DWORD image;
STACKFRAME64 stackframe;
ZeroMemory(&stackframe, sizeof(STACKFRAME64));

#ifdef _M_IX86
image = IMAGE_FILE_MACHINE_I386;
stackframe.AddrPC.Offset = context.Eip;
stackframe.AddrPC.Mode = AddrModeFlat;
stackframe.AddrFrame.Offset = context.Ebp;
stackframe.AddrFrame.Mode = AddrModeFlat;
stackframe.AddrStack.Offset = context.Esp;
stackframe.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
image = IMAGE_FILE_MACHINE_AMD64;
stackframe.AddrPC.Offset = context.Rip;
stackframe.AddrPC.Mode = AddrModeFlat;
stackframe.AddrFrame.Offset = context.Rsp;
stackframe.AddrFrame.Mode = AddrModeFlat;
stackframe.AddrStack.Offset = context.Rsp;
stackframe.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
image = IMAGE_FILE_MACHINE_IA64;
stackframe.AddrPC.Offset = context.StIIP;
stackframe.AddrPC.Mode = AddrModeFlat;
stackframe.AddrFrame.Offset = context.IntSp;
stackframe.AddrFrame.Mode = AddrModeFlat;
stackframe.AddrBStore.Offset = context.RsBSP;
stackframe.AddrBStore.Mode = AddrModeFlat;
stackframe.AddrStack.Offset = context.IntSp;
stackframe.AddrStack.Mode = AddrModeFlat;
#endif

As a side note, in researching for this problem I found people using lots of different intialisations for this structure - but this is the only one I've found that I am certain works. So please look carefully if copying code from the internet that the configuration matches this one.

Now it's time to walk the stack. To do this we repeatedly call StackWalk64 until it returns FALSE (or we reach some maximum depth). Each time we call it we print out the symbol name for the function address. To do this we construct a buffer the size of SYMBOL_INFO plus some space for the symbol name MAX_SYM_NAME. We then pass this to the function SymFromAddr, which should get the symbol name for that address using the debugging information. If we can't find the symbol for any reason then SymFromAddr will return FALSE and we'll just print out ??? to signify the missing symbol.

for (size_t i = 0; i < 25; i++) {
  
  BOOL result = StackWalk64(
    image, process, thread,
    &stackframe, &context, NULL, 
    SymFunctionTableAccess64, SymGetModuleBase64, NULL);
  
  if (!result) { break; }
  
  char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
  PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;
  symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
  symbol->MaxNameLen = MAX_SYM_NAME;
  
  DWORD64 displacement = 0;
  if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) {
    printf("[%i] %s\n", i, symbol->Name);
  } else {
    printf("[%i] ???\n", i);
  }
  
}

All this leaves is for us to clean up the symbol handler.

SymCleanup(process);

Here is the code for the full program:

#include <windows.h>
#include <DbgHelp.h>

#include <stdio.h>
#include <stdlib.h>

static void stack_trace(void) {

  HANDLE process = GetCurrentProcess();
  HANDLE thread = GetCurrentThread();
  
  CONTEXT context;
  memset(&context, 0, sizeof(CONTEXT));
  context.ContextFlags = CONTEXT_FULL;
  RtlCaptureContext(&context);
  
  SymInitialize(process, NULL, TRUE);
  
  DWORD image;
  STACKFRAME64 stackframe;
  ZeroMemory(&stackframe, sizeof(STACKFRAME64));
  
#ifdef _M_IX86
  image = IMAGE_FILE_MACHINE_I386;
  stackframe.AddrPC.Offset = context.Eip;
  stackframe.AddrPC.Mode = AddrModeFlat;
  stackframe.AddrFrame.Offset = context.Ebp;
  stackframe.AddrFrame.Mode = AddrModeFlat;
  stackframe.AddrStack.Offset = context.Esp;
  stackframe.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
  image = IMAGE_FILE_MACHINE_AMD64;
  stackframe.AddrPC.Offset = context.Rip;
  stackframe.AddrPC.Mode = AddrModeFlat;
  stackframe.AddrFrame.Offset = context.Rsp;
  stackframe.AddrFrame.Mode = AddrModeFlat;
  stackframe.AddrStack.Offset = context.Rsp;
  stackframe.AddrStack.Mode = AddrModeFlat;
#elif _M_IA64
  image = IMAGE_FILE_MACHINE_IA64;
  stackframe.AddrPC.Offset = context.StIIP;
  stackframe.AddrPC.Mode = AddrModeFlat;
  stackframe.AddrFrame.Offset = context.IntSp;
  stackframe.AddrFrame.Mode = AddrModeFlat;
  stackframe.AddrBStore.Offset = context.RsBSP;
  stackframe.AddrBStore.Mode = AddrModeFlat;
  stackframe.AddrStack.Offset = context.IntSp;
  stackframe.AddrStack.Mode = AddrModeFlat;
#endif

  for (size_t i = 0; i < 25; i++) {
    
    BOOL result = StackWalk64(
      image, process, thread,
      &stackframe, &context, NULL, 
      SymFunctionTableAccess64, SymGetModuleBase64, NULL);
    
    if (!result) { break; }
    
    char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
    PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    symbol->MaxNameLen = MAX_SYM_NAME;
    
    DWORD64 displacement = 0;
    if (SymFromAddr(process, stackframe.AddrPC.Offset, &displacement, symbol)) {
      printf("[%i] %s\n", i, symbol->Name);
    } else {
      printf("[%i] ???\n", i);
    }
    
  }
  
  SymCleanup(process);

}

static void function_c(void) {
  stack_trace();
}

static void function_b(void) {
  function_c();
}

static void function_a(void) {
  function_b();
}

int main(int argc, char *argv[]) {
  function_a();
}

We can compile this program with the following command.

gcc -std=c99 -g test.c -lDbgHelp -o test.exe

Now initially when we run it we'll just get ??? for all the locations where our program's symbols would be. This is because we still need to create the windows debugging file for our program...

[0] ???
[1] ???
[2] ???
[3] ???
[4] ???
[5] ???
[6] ???
[7] BaseThreadInitThunk
[8] RtlUserThreadStart

When you compile a program using gcc with the -g flag it embeds debugging information such as function names and line numbers into the executable in a format called DWARF. This can be read by programs such as gdb to give you human readable information in situations such as backtraces. But native windows applications take a different approach - they create a separate file with the .pdb extension that contains all of the debugging information.

If we want to use the Windows APIs for debugging we need to first convert the debugging information to the correct format. To do this you can use the program cv2pdb.

I found it easiest to compile this program from source and add it to somewhere on my PATH. Make sure to compile it 64-bit if you are making 64-bit applications. If you compile it succesfully you should be able to run it on your executable and it should create a matching .pdb file of the same name.

cv2pdb test.exe

If it gives an error like "cannot load PDB helper DLL" try running it from the Visual Studio Developer Command Prompt. Now when you run your program because there is a .pdb file present with the symbols for your program the symbol handler will correctly find the names for the backtrace and print them out.

[0] stack_trace
[1] function_c
[2] function_b
[3] function_a
[4] main
[5] __tmainCRTStartup
[6] mainCRTStartup
[7] BaseThreadInitThunk
[8] RtlUserThreadStart

Voila!

github twitter rss