#include "common.h"

#include "pdbaccess.cpp"
#include "pdblocal.cpp"
#include "pdbreg.cpp"

// We only enable remote PDB fetching in case
// we are building the plugin, for the moment.
// While this is an annoying limitation, it's mostly
// because the pdbremote code requires that
// the 'win32' (stub) debugger be loadable, in order
// to work: Ideally, we should only use an rpc_client
// instance, but currently we channel PDB requests
// through the remote debugger connection.
// (Neither efd.exe, nor tilib.exe can use of a
//  running win32_remote.exe debugger instance for the
//  moment)
#ifdef __IDP__
#ifdef ENABLE_REMOTEPDB
#include "pdbremote.cpp"
#endif
#endif

// In case we are building EFD, we don't include the
// til_builder_t type.
// (efd.exe doesn't walk down the PDB in the same
//  way as the PDB plugin, or tilib.exe do.)
#ifndef BUILDING_EFD
#include "tilbuild.cpp"
#endif



#ifdef __NT__

//lint -esym(843, g_diadlls, g_pdb_errors) could be declared as const

static const char *pdberr(int code);

//----------------------------------------------------------------------
// Common code for PDB handling
//----------------------------------------------------------------------
class CCallback : public IDiaLoadCallback2,
                  public IDiaReadExeAtRVACallback,
                  public IDiaReadExeAtOffsetCallback
{
  int m_nRefCount;
  ea_t m_load_address;
  HANDLE hFile;
  input_exe_reader_t exe_reader;
  input_mem_reader_t mem_reader;
  void              *user_data;
public:
  CCallback(input_exe_reader_t _exe_reader, input_mem_reader_t _mem_reader, void *_user_data):
      exe_reader(_exe_reader), mem_reader(_mem_reader),
      m_load_address(BADADDR), m_nRefCount(0), hFile(INVALID_HANDLE_VALUE),
      user_data(_user_data) { }

  virtual ~CCallback()
  {
    if (hFile != INVALID_HANDLE_VALUE)
      CloseHandle(hFile);
  }

  void SetLoadAddress(ea_t load_address)
  {
    m_load_address = load_address;
  }

  void OpenExe(LPCWSTR FileName)
  {
#ifdef PDBTOTIL
    if ( exe_reader != NULL )
      return;
#endif
    hFile = CreateFileW(
      FileName,
      GENERIC_READ,
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL,
      OPEN_EXISTING,
      0,
      NULL);
  }

  // IUnknown
  ULONG STDMETHODCALLTYPE AddRef()
  {
    return ++m_nRefCount;
  }

  ULONG STDMETHODCALLTYPE Release()
  {
    if ( --m_nRefCount == 0 )
    {
      delete this;
      return 0;
    }
    return m_nRefCount;
  }

  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID rid, void **ppUnk)
  {
    if ( ppUnk == NULL )
      return E_INVALIDARG;
    if ( rid == __uuidof(IDiaLoadCallback2) || rid == __uuidof(IDiaLoadCallback) )
      *ppUnk = (IDiaLoadCallback2 *)this;
#ifndef BUILDING_EFD
    else if ( rid == __uuidof( IDiaReadExeAtRVACallback ) && m_load_address != BADADDR )
      *ppUnk = (IDiaReadExeAtRVACallback *)this;
#endif
    else if ( rid == __uuidof(IDiaReadExeAtOffsetCallback) )
    {
      if ( hFile != INVALID_HANDLE_VALUE
#ifdef PDBTOTIL
        || exe_reader != NULL
#endif
      )
      {
        // we have a real file or an EXE reader
        *ppUnk = (IDiaReadExeAtOffsetCallback *)this;
      }
      else
      {
        *ppUnk = NULL;
      }
    }
    else if ( rid == __uuidof(IUnknown) )
        *ppUnk = (IUnknown *)(IDiaLoadCallback *)this;
    else
        *ppUnk = NULL;
    if ( *ppUnk == NULL )
      return E_NOINTERFACE;
    AddRef();
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE NotifyDebugDir(
              BOOL /* fExecutable */,
              DWORD /* cbData */,
              BYTE /* data */ [])
  {
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE NotifyOpenDBG(
              LPCOLESTR dbgPath,
              HRESULT resultCode)
  {
    qstring spath;
    u2cstr(dbgPath, &spath);
    if ( SUCCEEDED(resultCode) ) 
      deb(IDA_DEBUG_DEBUGGER, "PDB: found DBG file: '%s'\n", spath.c_str());
    else
      deb(IDA_DEBUG_DEBUGGER, "PDB: '%s': %s\n", spath.c_str(), pdberr(resultCode));
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE NotifyOpenPDB(
              LPCOLESTR pdbPath,
              HRESULT resultCode)
  {
    qstring spath;
    u2cstr(pdbPath, &spath);
    if ( SUCCEEDED(resultCode) ) 
      deb(IDA_DEBUG_DEBUGGER, "PDB: '%s' matched.\n", spath.c_str());
    else
      deb(IDA_DEBUG_DEBUGGER, "PDB: '%s': %s\n", spath.c_str(), pdberr(resultCode));
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE RestrictRegistryAccess()
  {
    // return hr != S_OK to prevent querying the registry for symbol search paths
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE RestrictSymbolServerAccess()
  {
    // return hr != S_OK to prevent accessing a symbol server
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE RestrictOriginalPathAccess()
  {
    // return hr != S_OK to prevent querying the registry for symbol search paths
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE RestrictReferencePathAccess()
  {
    // return hr != S_OK to prevent accessing a symbol server
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE RestrictDBGAccess()
  {
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE RestrictSystemRootAccess()
  {
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE ReadExecutableAtRVA(
   DWORD  relativeVirtualAddress,
   DWORD  cbData,
   DWORD* pcbData,
   BYTE   data[] )
  {
#ifdef PDBTOTIL
    if ( mem_reader != NULL )
    {
      uint32 read;
      bool ok = mem_reader(m_load_address + relativeVirtualAddress, cbData, data, &read, user_data);
      if ( !ok )
        return E_FAIL;
      *pcbData = read;
      return S_OK;
    }
#endif
#ifndef BUILDING_EFD
#ifndef PDBTOTIL
    if ( get_many_bytes(m_load_address + relativeVirtualAddress, data, cbData) )
    {
      *pcbData = cbData;
      return S_OK;
    }
#endif
#else
    qnotused(relativeVirtualAddress);
    qnotused(cbData);
    qnotused(pcbData);
    qnotused(data);
#endif
    return S_FALSE;
  }

  // IDiaReadExeAtOffsetCallback
  HRESULT STDMETHODCALLTYPE ReadExecutableAt(
    DWORDLONG fileOffset,
    DWORD cbData,
    DWORD *pcbData,
    BYTE *pbData)
  {
#ifdef PDBTOTIL
    if ( exe_reader != NULL )
    {
      uint32 read;
      bool ok = exe_reader(fileOffset, cbData, pbData, &read, user_data);
      if ( !ok )
        return E_FAIL;
      *pcbData = read;
      return S_OK;
    }
#endif
    LARGE_INTEGER pos;
    pos.QuadPart = (LONGLONG)fileOffset;
    return hFile == INVALID_HANDLE_VALUE ||
        !SetFilePointerEx(hFile, pos, NULL, FILE_BEGIN) ||
        !ReadFile(hFile, pbData, cbData, pcbData, NULL) ? E_FAIL : S_OK;
  }
};

//---------------------------------------------------------------------------
template<class T> void print_generic(T t)
{
  IDiaPropertyStorage *pPropertyStorage;
  HRESULT hr = t->QueryInterface(__uuidof(IDiaPropertyStorage), (void **)&pPropertyStorage);
  if ( hr == S_OK )
  {
    print_property_storage(pPropertyStorage);
    pPropertyStorage->Release();
  }
}

static const char *const g_pdb_errors[] =
{
  "Operation successful (E_PDB_OK)",
  "(E_PDB_USAGE)",
  "Out of memory (E_PDB_OUT_OF_MEMORY)",
  "(E_PDB_FILE_SYSTEM)",
  "Failed to open the file, or the file has an invalid format (E_PDB_NOT_FOUND)",
  "Signature does not match (E_PDB_INVALID_SIG)",
  "Age does not match (E_PDB_INVALID_AGE)",
  "(E_PDB_PRECOMP_REQUIRED)",
  "(E_PDB_OUT_OF_TI)",
  "(E_PDB_NOT_IMPLEMENTED)",
  "(E_PDB_V1_PDB)",
  "Attempted to access a file with an obsolete format (E_PDB_FORMAT)",
  "(E_PDB_LIMIT)",
  "(E_PDB_CORRUPT)",
  "(E_PDB_TI16)",
  "(E_PDB_ACCESS_DENIED)",
  "(E_PDB_ILLEGAL_TYPE_EDIT)",
  "(E_PDB_INVALID_EXECUTABLE)",
  "(E_PDB_DBG_NOT_FOUND)",
  "(E_PDB_NO_DEBUG_INFO)",
  "(E_PDB_INVALID_EXE_TIMESTAMP)",
  "(E_PDB_RESERVED)",
  "(E_PDB_DEBUG_INFO_NOT_IN_PDB)",
  "(E_PDB_SYMSRV_BAD_CACHE_PATH)",
  "(E_PDB_SYMSRV_CACHE_FULL)",
};

//---------------------------------------------------------------------------
inline void pdberr_suggest_vs_runtime(HRESULT hr)
{
  if ( hr == E_NOINTERFACE )
  {
    msg("<< It appears that MS DIA SDK is not installed.\n"
        "Please try installing \"Microsoft Visual C++ 2008 Redistributable Package / x86\" >>\n");
  }
}

//---------------------------------------------------------------------------
static const char *pdberr(int code)
{
  switch ( code )
  {                         // tab in first pos is flag for replace warning to msg
    case E_INVALIDARG:      return "Invalid parameter.";
    case E_UNEXPECTED:      return "Data source has already been prepared.";
    default:
      if ( code >= E_PDB_OK && (code - E_PDB_OK) < qnumber(g_pdb_errors) )
        return g_pdb_errors[code - E_PDB_OK];
  }
  return winerr(code);
}


//----------------------------------------------------------------------
size_t get_symbol_length(IDiaSymbol *sym)
{
  DWORD64 size = 0;
  DWORD tag = 0;
  sym->get_symTag(&tag);
  if ( tag == SymTagData )
  {
    IDiaSymbol *pType;
    if ( sym->get_type(&pType) == S_OK )
    {
      pType->get_length(&size);
      pType->Release();
    }
  }
  else
  {
    sym->get_length(&size);
  }
  return size_t(size);
}

#include "../../ldr/windmp/common.h" // for get_special_folder

//----------------------------------------------------------------------
class DECLSPEC_UUID("4C41678E-887B-4365-A09E-925D28DB33C2") DiaSource90;
class DECLSPEC_UUID("1fbd5ec4-b8e4-4d94-9efe-7ccaf9132c98") DiaSource80;
class DECLSPEC_UUID("31495af6-0897-4f1e-8dac-1447f10174a1") DiaSource71;
static const GUID *const g_d90 = &__uuidof(DiaSource90);  // msdia90.dll
static const GUID *const g_d80 = &__uuidof(DiaSource80);  // msdia80.dll
static const GUID *const g_d71 = &__uuidof(DiaSource71);  // msdia71.dll
static const GUID *const g_msdiav[] = { g_d90, g_d80, g_d71 };
static const int         g_diaver[] = { 900,   800,   710 };
static const char *g_diadlls[] = { "msdia90.dll", "msdia80.dll", "msdia71.dll"};

//----------------------------------------------------------------------
HRESULT __stdcall CoCreateInstanceNoReg(
  LPCTSTR szDllName,
  IN REFCLSID rclsid,
  IUnknown* pUnkOuter,
  IN REFIID riid,
  OUT LPVOID FAR* ppv,
  OUT HMODULE *phMod)
{
  // http://lallousx86.wordpress.com/2007/01/29/emulating-cocreateinstance/
  HRESULT hr = REGDB_E_CLASSNOTREG;
  HMODULE hDll;
  do
  {
    hDll = LoadLibrary(szDllName);
    if ( hDll == NULL )
      break;

    HRESULT (__stdcall *GetClassObject)(REFCLSID rclsid, REFIID riid, LPVOID FAR* ppv);
    *(FARPROC*)&GetClassObject = GetProcAddress(hDll, "DllGetClassObject");
    if ( GetClassObject == NULL )
      break;

    IClassFactory *pIFactory;
    hr = GetClassObject(rclsid, IID_IClassFactory, (LPVOID *)&pIFactory);
    if ( FAILED(hr) )
      break;

    hr = pIFactory->CreateInstance(pUnkOuter, riid, ppv);
    pIFactory->Release();
  } while (false);

  if ( FAILED(hr) && hDll != NULL )
    FreeLibrary(hDll);
  else
    *phMod = hDll;

  return hr;
}

//----------------------------------------------------------------------
static void get_input_and_sym_path(
        const char *input_file,
        const char *user_spath,
        qwstring &winput,
        qwstring &wspath)
{
  char env_sympath[4096];
  char temp_path[QMAXPATH];
  char spath[sizeof(spath_prefix)+sizeof(temp_path)+sizeof(spath_suffix)];
  // no symbol path passed? let us compute default values
  if ( user_spath == NULL || user_spath[0] == '\0' )
  {
    // no env var?
    if ( GetEnvironmentVariable("_NT_SYMBOL_PATH", env_sympath, sizeof(env_sympath)) == 0
      || GetLastError() == ERROR_ENVVAR_NOT_FOUND )
    {
      if ( !GetTempPath(sizeof(temp_path), temp_path) )
        temp_path[0] = '\0';
      else
        qstrncat(temp_path, "ida", sizeof(temp_path));
      qsnprintf(spath, sizeof(spath), "%s%s%s", spath_prefix, temp_path, spath_suffix);
      user_spath = spath;
    }
    else
    {
      user_spath = env_sympath;
    }
  }
  c2ustr(user_spath, &wspath);
  c2ustr(input_file, &winput);
}

// ---------------------------------------------------------------------------
static DWORD get_machine_type(DWORD dwMachType)
{
  DWORD machine;
  switch ( dwMachType )
  {
    default:
      machine = CV_CFL_80386;
      break;
    case IMAGE_FILE_MACHINE_IA64:
      machine = CV_CFL_IA64;
      break;
    case IMAGE_FILE_MACHINE_AMD64:
      machine = CV_CFL_AMD64;
      break;
    case IMAGE_FILE_MACHINE_THUMB:
    case IMAGE_FILE_MACHINE_ARM:
      machine = CV_CFL_ARM6;
      break;
    case PECPU_ARMV7:
      machine = CV_CFL_ARM7;
      break;
    case PECPU_PPC:
      machine = CV_CFL_PPC620;
      break;
    case PECPU_PPCFP:
      machine = CV_CFL_PPCFP;
      break;
    case PECPU_PPCBE:
      machine = CV_CFL_PPCBE;
      break;
  }
  return machine;
}


//----------------------------------------------------------------------
void pdb_session_t::close()
{
  if ( pdb_access != NULL )
  {
    delete pdb_access;
    pdb_access = NULL;
  }

  if ( dia_hmod != NULL )
  {
    FreeLibrary(dia_hmod);
    dia_hmod = NULL;
  }
}

// ---------------------------------------------------------------------------
HRESULT pdb_session_t::open(const char *input_file,
                            const char *user_spath,
                            ea_t load_address,
                            input_exe_reader_t exe_reader,
                            input_mem_reader_t mem_reader,
                            void *user_data)
{
  // Already open?
  if ( pdb_access != NULL )
    return S_OK;

  // When the debugger is active, first try to load debug directory from the memory
  ea_t load_address_order[2];
  // when remote debugging, don't use files on disk
  bool remote_debug = false;
#ifndef BUILDING_EFD
#ifndef PDBTOTIL
  if ( get_process_state() != DSTATE_NOTASK )
  {
    load_address_order[0] = load_address;
    load_address_order[1] = BADADDR;
    remote_debug = dbg->is_remote();
  }
  else
#endif
#endif
  {
    load_address_order[0] = BADADDR;
    load_address_order[1] = load_address;
  }

  int dia_version;
  HRESULT hr;
  IDiaDataSource *pSource  = NULL;
  IDiaSession    *pSession = NULL;
  IDiaSymbol     *pGlobal  = NULL;
  do
  {
    // No interface was created?
    hr = create_dia_source(&pSource, &dia_version);
    if ( FAILED(hr) )
      goto CLEANUP;

    qwstring wpath, winput;
    get_input_and_sym_path(input_file, user_spath, winput, wpath);

    if ( exe_reader == NULL && mem_reader == NULL && !remote_debug )
      // Try to load input file as PDB
      hr = pSource->loadDataFromPdb(winput.c_str());
    else
      hr = E_FAIL;

    // Failed? Try to load as EXE
    if ( FAILED(hr) )
    {
      CCallback callback(exe_reader, mem_reader, user_data);
      callback.AddRef();

      if ( !remote_debug )
      {
        // Open the executable
        callback.OpenExe(winput.c_str());
      }

      for ( int i=0; i < qnumber(load_address_order); i++ )
      {
        callback.SetLoadAddress(load_address_order[i]);
        hr = pSource->loadDataForExe(winput.c_str(), wpath.c_str(), (IDiaLoadCallback *)&callback);
        if ( SUCCEEDED(hr) )
          break;
        // if pdb is not found, is there any sense of trying again with
        // another address?
        if ( hr == E_PDB_NOT_FOUND )
          break;
      }
    }

    // Failed? Then nothing else to try, quit
    if ( FAILED(hr) )
      goto CLEANUP;

    // Open a session for querying symbols
    hr = pSource->openSession(&pSession);
    if ( FAILED(hr) )
      goto CLEANUP;

    // Set load address
    if ( load_address != BADADDR )
      pSession->put_loadAddress(load_address);

    // Retrieve a reference to the global scope
    hr = pSession->get_globalScope(&pGlobal);
    if ( FAILED(hr) )
      goto CLEANUP;

    hr = S_OK;
  } while ( false );

  // Make sure we cleanup
  if ( FAILED(hr) )
    goto CLEANUP;

  pdb_access = new local_pdb_access_t
    (pSource,
     pSession,
     pGlobal,
     true);

  DWORD pdb_machType, machType;
  if ( pGlobal->get_machineType(&pdb_machType) != S_OK )
    pdb_machType = IMAGE_FILE_MACHINE_I386;
  machType = get_machine_type(pdb_machType);

  pdb_access->set_machine_type(machType);
  pdb_access->set_dia_version(dia_version);

  hr = pdb_access->init();
  if ( FAILED(hr) )
    goto CLEANUP;

  return hr;

 CLEANUP:
  // In the event of an error, this will be reached.
  if ( pdb_access == NULL )
  {
    if ( pGlobal != NULL )
      pGlobal->Release();
    if ( pSession != NULL )
      pSession->Release();
    if ( pSource != NULL )
      pSource->Release();
  }
  close();
  return hr;
}

//----------------------------------------------------------------------
HRESULT pdb_session_t::create_dia_source(IDiaDataSource **pSource, int *dia_version)
{
  HRESULT hr;
  // VC80/90 CRT installs msdiaNN.dll in this folder:
  // "C:\Program Files (x86)\Common Files\microsoft shared\VC"
  char common_files[QMAXPATH];
  qstring vc_shared;
  if ( get_special_folder(CSIDL_PROGRAM_FILES_COMMON, common_files, sizeof(common_files)) )
  {
    vc_shared = common_files;
    vc_shared.append("\\Microsoft Shared\\VC");
  }

  for ( size_t i=0; i < qnumber(g_msdiav); i++ )
  {
    // Try to create using CoCreateInstance()
    hr = CoCreateInstance(*g_msdiav[i],
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          __uuidof(IDiaDataSource),
                          (void**)pSource);

    // Try to create with CoCreateInstanceNoReg()
    if ( FAILED(hr) )
    {
      // Search for this interface in DIA dlls
      char path[QMAXPATH];
      if ( !search_path(g_diadlls[i], path, sizeof(path), false)
           && (vc_shared.empty() || SearchPathA(vc_shared.c_str(), g_diadlls[i], NULL, qnumber(path), path, NULL) == 0) )
        continue;

      for ( size_t j=0; j < qnumber(g_msdiav); j++ )
      {
        hr = CoCreateInstanceNoReg(path,
                                   *g_msdiav[j],
                                   NULL,
                                   __uuidof(IDiaDataSource),
                                   (void**)pSource,
                                   &dia_hmod);

        if ( SUCCEEDED(hr) )
        {
          static bool displayed = false;
          if ( !displayed )
          {
            displayed = true;
            msg("PDB: using DIA dll \"%s\"\n", path);
          }
          i = j;
          break;
        }
      }
    }

    if ( SUCCEEDED(hr) )
    {
      *dia_version = g_diaver[i];
      static bool displayed = false;
      if ( !displayed )
      {
        displayed = true;
        msg("PDB: DIA interface version %d.%d\n", (*dia_version)/100, (*dia_version)%100);
      }
      return hr;
    }
    else
    {
      *dia_version = 0;
    }
  }
  return E_NOINTERFACE;
}

//----------------------------------------------------------------------
pdb_session_ref_t::pdb_session_ref_t(void)
{
  session = new pdb_session_t();
  sessions_count++;
}

//----------------------------------------------------------------------
pdb_session_ref_t::pdb_session_ref_t(const pdb_session_ref_t &r)
{
  session = r.session;
  session->refcount++;
}

//----------------------------------------------------------------------
pdb_session_ref_t::~pdb_session_ref_t()
{
  destroy();
}

// ---------------------------------------------------------------------------
void pdb_session_ref_t::destroy()
{
  if ( session->refcount > 1 )
  {
    session->refcount--;
  }
  else
  {
    session->close();
    delete session;
    session = NULL;
    sessions_count--;
  }

  if ( co_initialized && sessions_count == 0 )
  {
    CoUninitialize();
    co_initialized = false;
  }
}

//----------------------------------------------------------------------
pdb_session_ref_t &pdb_session_ref_t::operator=(const pdb_session_ref_t &r)
{
  if ( &r != this )
  {
    if ( session->refcount > 1 )
    {
      // unlink
      session->refcount--;
    }
    else
    {
      session->close();
      sessions_count--;
    }
    session = r.session;
    session->refcount++;
  }
  return *this;
}

//----------------------------------------------------------------------
void pdb_session_ref_t::close()
{
  // shared instance? then detach
  if ( session->refcount > 1 )
  {
    // unlink
    session->refcount--;
    session = new pdb_session_t();
    sessions_count++;
  }
  else
  {
    session->close();
  }
}

//----------------------------------------------------------------------
const bool pdb_session_ref_t::opened() const
{
  return session->pdb_access != NULL;
}

// //----------------------------------------------------------------------
// DWORD pdb_session_ref_t::get_machine_type(DWORD dwMachType)
// {
//   DWORD machine;
//   switch ( dwMachType )
//   {
//     default:
//       machine = CV_CFL_80386;
//       break;
//     case IMAGE_FILE_MACHINE_IA64:
//       machine = CV_CFL_IA64;
//       break;
//     case IMAGE_FILE_MACHINE_AMD64:
//       machine = CV_CFL_AMD64;
//       break;
//     case IMAGE_FILE_MACHINE_THUMB:
//     case IMAGE_FILE_MACHINE_ARM:
//       machine = CV_CFL_ARM6;
//       break;
//     case PECPU_ARMV7:
//       machine = CV_CFL_ARM7;
//       break;
//     case PECPU_PPC:
//       machine = CV_CFL_PPC620;
//       break;
//     case PECPU_PPCFP:
//       machine = CV_CFL_PPCFP;
//       break;
//     case PECPU_PPCBE:
//       machine = CV_CFL_PPCBE;
//       break;
//   }
//   return machine;
// }

//----------------------------------------------------------------------
HRESULT pdb_session_ref_t::open(const char *input_file,
                                const char *user_spath,
                                ea_t load_address,
                                input_exe_reader_t exe_reader,
                                input_mem_reader_t mem_reader,
                                void *user_data)
{
  if ( opened() )
    return S_OK;

  // Not initialized yet?
  if ( !co_initialized )
  {
    // Initialize COM
    CoInitialize(NULL);
    co_initialized = true;
  }
  return session->open(input_file, user_spath, load_address, exe_reader, mem_reader, user_data);
}

// ---------------------------------------------------------------------------
bool pdb_session_ref_t::co_initialized = false;

// ---------------------------------------------------------------------------
int pdb_session_ref_t::sessions_count = 0;

//----------------------------------------------------------------------
HRESULT handle_pdb_file(
        const char *input_file,
        const char *user_spath,
        HRESULT handler(pdb_session_ref_t pdb_session_ref, void *ud),
        ea_t load_address,
        input_exe_reader_t exe_reader,
        input_mem_reader_t mem_reader,
        void *ud)
{
  pdb_session_ref_t ref;
  HRESULT hr = ref.open(input_file, user_spath, load_address, exe_reader, mem_reader, ud);
  if ( FAILED(hr) )
    return hr;
  else
    return handler(ref, ud);
}

#endif
