/* ====================================================================
 * Copyright (c) 2003-2006, Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

// sc
#include "Stackwalk.h"

// sys
#include <dbghelp.h>

///////////////////////////////////////////////////////////////////////
// a few helper

template<typename T> static void clear( T &t )
{
  memset( &t, 0, sizeof(T) );
}

const long SyminfoSizeName = 256;

typedef struct _SW_SYMBOL_INFO : IMAGEHLP_SYMBOL64
{
  char _name[SyminfoSizeName];

} SW_SYMBOL_INFO;

static void init( SW_SYMBOL_INFO &si, DWORD64 addr )
{
  clear(si);
  si.Address       = addr;
  si.SizeOfStruct  = sizeof(SW_SYMBOL_INFO);
  si.MaxNameLength = SyminfoSizeName;
}

static void init( IMAGEHLP_MODULE64& mi )
{
  clear(mi);
  mi.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
}

static void init( IMAGEHLP_LINE64& li )
{
  clear(li);
  li.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
}

static void init( STACKFRAME64& sf, CONTEXT& c )
{
  clear(sf);

  // x86
  sf.AddrPC.Offset    = c.Eip;
  sf.AddrPC.Mode      = AddrModeFlat;
  sf.AddrFrame.Offset = c.Ebp;
  sf.AddrFrame.Mode   = AddrModeFlat;
  sf.AddrStack.Offset = c.Esp;
  sf.AddrStack.Mode   = AddrModeFlat;
}


static BOOL CALLBACK ReadProcessMemoryProc( HANDLE hProcess, DWORD64 lpBaseAddress,
  PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead )
{
  return ReadProcessMemory( GetCurrentProcess(), (LPCVOID)lpBaseAddress, lpBuffer,
    nSize, lpNumberOfBytesRead );
}

static bool StackWalk( STACKFRAME64* sf, CONTEXT* c )
{
  return StackWalk64( IMAGE_FILE_MACHINE_I386, GetCurrentProcess(), GetCurrentThread(),
    sf, c, ReadProcessMemoryProc, SymFunctionTableAccess64, SymGetModuleBase64, 0 )
    == TRUE;
}

static bool GetSymFromAddr( DWORD64 addr, SW_SYMBOL_INFO &si, DWORD64 *disp )
{
  return SymGetSymFromAddr64( GetCurrentProcess(), addr, disp, (PIMAGEHLP_SYMBOL64)&si )
    == TRUE;
}

static bool GetModuleFromAddr( DWORD64 addr, IMAGEHLP_MODULE64& mi )
{
  DWORD64 baseAddr = SymGetModuleBase64( GetCurrentProcess(), addr );
  return SymGetModuleInfo64( GetCurrentProcess(), baseAddr, &mi ) == TRUE; 
}

static bool GetLineFromAddr( DWORD64 addr, IMAGEHLP_LINE64 &li, DWORD *disp )
{
  return SymGetLineFromAddr64( GetCurrentProcess(), addr, disp, &li ) == TRUE;
}


///////////////////////////////////////////////////////////////////////


Stackwalk::Stackwalk( EXCEPTION_POINTERS* pExp ) : _exp(pExp)
{
}

void Stackwalk::walk()
{
  try
  {
    STACKFRAME64 csf;                           // current stack frame
    CONTEXT      ctxt = *_exp->ContextRecord;   // copy the context

    // initialize the current stackframe.
    init( csf, ctxt );

    // walk the stack...
    bool success = true;
    while(success)
    {
      success = StackWalk( &csf, &ctxt );

      if( success )
      {
        Stackframe frame;

        frame._addr    = csf.AddrPC.Offset;
        frame._addrSeg = ctxt.SegCs;

        fillModuleInfo( frame );
        fillSymbolInfo( frame );
        fillLineInfo( frame );

        _frames.push_back(frame);
      }
    }
  }
  catch(...)
  {
    Stackframe frame;
    frame._error = true;
    _frames.push_back(frame);
  }
}

const Stackframes& Stackwalk::getFrames() const
{
  return _frames;
}

void Stackwalk::fillLineInfo( Stackframe& f )
{
  IMAGEHLP_LINE64 li;
  init(li);
  DWORD dispLine = 0;

  bool b = GetLineFromAddr( f._addr, li, &dispLine );

  if( b ) 
  {
    f._line = true;
    f._fileName = li.FileName;
    f._lineNr   = li.LineNumber;
    f._lineDisp = dispLine;
  }
  else
  {
    f._line = false;
  }
}

void Stackwalk::fillModuleInfo( Stackframe& f )
{
  IMAGEHLP_MODULE64 mi;
  init(mi);

  bool b = GetModuleFromAddr( f._addr, mi );

  if( b )
  {
    f._module = true;
    f._moduleName = mi.ModuleName;
  }
  else
  {
    f._module = false;
  }
}

void Stackwalk::fillSymbolInfo( Stackframe& f )
{
  SW_SYMBOL_INFO si;
  init(si,f._addr);

  DWORD64 disp = 0;
  bool b = GetSymFromAddr( f._addr, si, &disp );

  if( b )
  {
    f._symbol = true;
    f._symbolName = si.Name;
    f._symbolDisp = disp;
  }
  else
  {
    f._symbol = false;
  }
}


syntax highlighted by Code2HTML, v. 0.9.1