/* ====================================================================
 * Copyright (c) 2003-2006, 2008  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
#define _NO_CVCONST_H 1
#include <dbghelp.h>

// dia
enum BasicType { 
  btNoType   = 0,
  btVoid     = 1,
  btChar     = 2,
  btWChar    = 3,
  btInt      = 6,
  btUInt     = 7,
  btFloat    = 8,
  btBCD      = 9,
  btBool     = 10,
  btLong     = 13,
  btULong    = 14,
  btCurrency = 25,
  btDate     = 26,
  btVariant  = 27,
  btComplex  = 28,
  btBit      = 29,
  btBSTR     = 30,
  btHresult  = 31
};

///////////////////////////////////////////////////////////////////////
// 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 : SYMBOL_INFO
{
  char _name[SyminfoSizeName];

} SW_SYMBOL_INFO;

static void init( SW_SYMBOL_INFO& si, DWORD64 addr )
{
  clear(si);
  si.SizeOfStruct = sizeof(SYMBOL_INFO);
  si.MaxNameLen   = 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 void init( IMAGEHLP_STACK_FRAME& sf, DWORD64 addr )
{
  clear(sf);
  sf.InstructionOffset = addr;
}

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

static bool GetSymFromAddr( DWORD64 addr, SW_SYMBOL_INFO& si, DWORD64 *disp )
{
  return SymFromAddr( GetCurrentProcess(), addr, disp, &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;
}

static bool SetContext( IMAGEHLP_STACK_FRAME& sf )
{
  return SymSetContext( GetCurrentProcess(), &sf, NULL ) == TRUE;
}

static bool EnumSymbols( PSYM_ENUMERATESYMBOLS_CALLBACK cb, PVOID userContext )
{
  return SymEnumSymbols( GetCurrentProcess(), 0, NULL, cb, userContext ) == TRUE;
}

static bool GetTypeInfo( DWORD64 mod, ULONG typeId, IMAGEHLP_SYMBOL_TYPE_INFO typeInfo, PVOID pInfo )
{
  return SymGetTypeInfo( GetCurrentProcess(), mod, typeId, typeInfo, pInfo ) == TRUE;
}

static void AddVariable( PSYMBOL_INFO pSymInfo, const Variable& var, Stackframe& frame )
{
  if( pSymInfo->Flags & SYMFLAG_PARAMETER )
    frame._params.push_back(var);

  else if( pSymInfo->Flags & SYMFLAG_LOCAL )
    frame._locals.push_back(var);
}

static void HandleBaseType( PSYMBOL_INFO pSymInfo, ULONG64 addr, Stackframe& frame )
{
  DWORD baseType;
  bool bt = GetTypeInfo(pSymInfo->ModBase, pSymInfo->TypeIndex, TI_GET_BASETYPE, &baseType);

  ULONG64 length;
  bool bl = GetTypeInfo(pSymInfo->ModBase, pSymInfo->TypeIndex, TI_GET_LENGTH, &length);

  switch( baseType )
  {
  case btInt:
    {
      Variable var(vtInt,(sc::Size)length);
      var.name(pSymInfo->Name,pSymInfo->NameLen);
      var.value((void*)addr);

      AddVariable(pSymInfo,var,frame);
    }
  }
}

static BOOL CALLBACK EnumSymbolsProc( PSYMBOL_INFO pSymInfo, ULONG symSize, PVOID userContext )
{
  Stackframe& frame = *(Stackframe*)userContext;

  if( ! (pSymInfo->Flags & SYMFLAG_REGREL) )
    return FALSE;

  ULONG64 addr = frame._addrFrame + pSymInfo->Address;


  DWORD symtag;
  bool b = GetTypeInfo(pSymInfo->ModBase, pSymInfo->TypeIndex, TI_GET_SYMTAG, &symtag);

  switch(symtag)
  {
  case SymTagBaseType:
    {
      HandleBaseType( pSymInfo, addr, frame );
    }
  }

  printf(">\n");

  if( pSymInfo->Flags & SYMFLAG_FRAMEREL )
    printf("FRAMEREL\n");

  if( pSymInfo->Flags & SYMFLAG_LOCAL )
    printf("LOCAL\n");

  if( pSymInfo->Flags & SYMFLAG_PARAMETER )
    printf("PARAMETER\n");

  // SYMFLAG_FRAMEREL, SYMFLAG_REGREL
  // SYMFLAG_LOCAL, SYMFLAG_PARAMETER

  printf( "%s\n", pSymInfo->Name );
  //printf("%08X (%4u) (%s)\n", 
    //       pSymInfo->Address, pSymInfo->Size, pSymInfo->Name);

  return 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;
        frame._addrFrame = csf.AddrFrame.Offset;

        fillModuleInfo( frame );
        fillSymbolInfo( frame );
        fillLineInfo( frame );
        fillParameterInfo( 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
  {
    DWORD error = GetLastError();
    f._symbol = false;
  }
}

void Stackwalk::fillParameterInfo( Stackframe& f )
{
  IMAGEHLP_STACK_FRAME sf;
  init(sf,f._addr);

  bool b = SetContext(sf);
  if(b)
  {
    b = EnumSymbols( EnumSymbolsProc, &f );
  }
}