
// IDA plugin to load function name information from PDB files
//      26-02-2008 Complete rewrite to use DIA API

#include <set>
#include <map>
#include <algorithm>

#ifndef __NT__
#  define ENABLE_REMOTEPDB
#endif

#include "cvconst.h"

#include <ida.hpp>
#include <idp.hpp>
#include <err.h>
#include <md5.h>
#include <dbg.hpp>
#include <auto.hpp>
#include <name.hpp>
#include <frame.hpp>
#include <loader.hpp>
#include <diskio.hpp>
#include <struct.hpp>
#include <typeinf.hpp>
#include <demangle.hpp>
#include "../../ldr/pe/pe.h"
#include "../../include/intel.hpp"
#include "common.h"

// PDB search path (in _NT_SYMBOL_PATH format)
static char full_sympath[QMAXPATH];
static void init_sympaths(void);

#ifndef ENABLE_REMOTEPDB
#  include "oldpdb.h"
#  ifdef ENABLE_SRCDBG
#    include "sip.cpp"
#  endif
#else
#  include "../../ldr/pe/mycor.h"
#endif
#include "common.cpp"

#include "pdbreg.hpp"
#include "pdb.hpp"

static peheader_t pe;
static char download_path[QMAXPATH];
static char pdb_remote_server[QMAXPATH];
//lint -esym(551,pdb_remote_port)
static int  pdb_remote_port = DEBUGGER_PORT_NUMBER;
static char pdb_remote_passwd[QMAXPATH];
static PDB_CALLCODE call_code;

typedef std::map<ea_t, qstring> namelist_t;
static namelist_t namelist;

#ifndef ENABLE_REMOTEPDB

inline bool is_mips(DWORD machineType)
{
  return machineType == CV_CFL_MIPS
      || machineType == CV_CFL_MIPSR4000
      || machineType == CV_CFL_MIPS16
      || machineType == CV_CFL_MIPS32
      || machineType == CV_CFL_MIPS64
      || machineType == CV_CFL_MIPSI
      || machineType == CV_CFL_MIPSII
      || machineType == CV_CFL_MIPSIII
      || machineType == CV_CFL_MIPSIV
      || machineType == CV_CFL_MIPSV;
}

#endif // #ifndef ENABLE_REMOTEPDB

//-------------------------------------------------------------------------
//#define  CHECK_CREATED_TYPES
#ifdef CHECK_CREATED_TYPES
static bool is_good_argloc(argloc_t ida_argloc, size_t size)
{
  if ( ida_argloc != BAD_ARGLOC && is_reg_argloc(ida_argloc) )
  {
    int r1, r2;
    split_argloc(ida_argloc, &r1, &r2);
    if ( r1 != -1 )
    {
      if ( get_reg_name(r1, size, NULL, 0, r2) <= 0 )
        return false;
    }
  }
  return true;
}

//-------------------------------------------------------------------------
inline bool append_to_regargs(intvec_t &regargs, int reg, int)
{
  return regargs.add_unique(reg);
}

//-------------------------------------------------------------------------
inline bool mop_is_valid_size(int s) { return s > 0 && s < 16 && (s&(s-1))==0; }

//-------------------------------------------------------------------------
static bool is_type_correct(til_t *ti, const type_t *ptr)
{
  bool must_be_complete = true;
  while ( true )
  {
    const type_t *p2 = resolve_typedef2(ti, ptr);
    if ( p2 == NULL )
      return !must_be_complete;
    if ( p2[0] == '\0' )
      return false;
    if ( p2 != ptr )
    { // resolved!
      // check if the original type has any garbage at the end
      if ( !is_valid_full_type(ti, ptr) )
        return false;
      ptr = p2;
    }
    const type_t *end = ptr;
    int s = get_type_size(ti, end);
    if ( s == BADSIZE )
    {
      end = ptr;
      if ( is_type_func(*ptr) )
      {
        // check function arguments
        func_type_info_t fti;
        int n = build_funcarg_info(ti, ptr, NULL, &fti, 0);
        if ( n < 0 || fti.cc == CM_CC_INVALID )
          return false;
        if ( n == 0 && is_vararg_cc(fti.cc) )
          return false;
        if ( !is_type_correct(ti, fti.rettype.begin()) )
          return false;
        if ( is_user_cc(fti.cc) && !is_restype_void(ti, fti.rettype.begin()) )
        {
          if ( fti.retloc == BAD_ARGLOC )
            return false;
          int retsize = get_type_size0(ti, fti.rettype.begin());
          if ( !is_good_argloc(fti.retloc, retsize) )
            return false;
        }
        intvec_t regargs;
        for ( int i=0; i < n; i++ )
        {
          if ( !is_type_correct(ti, fti[i].type.begin()) )
            return false;
          if ( is_user_cc(fti.cc) )
          {
            int ts = get_type_size0(ti, fti[i].type.begin());
            if ( ts <= 0 )
              return false;
            argloc_t al = fti[i].argloc;
            if ( !is_good_argloc(al, ts) )
              return false;
            if ( is_reg_argloc(al) )
            {
              if ( is_reg2_argloc(al) )
              {
                ts /= 2;
                if ( !append_to_regargs(regargs, get_argloc_r2(al), ts) )
                  return false;
              }
              if ( !append_to_regargs(regargs, get_argloc_r1(al), ts) )
                return false;
            }
          }
        }
      }
      else if ( !is_type_void(*ptr) && must_be_complete )
      {
        return false;
      }
      if ( !is_valid_full_type(ti, end) )
        return false;
    }
    else
    {
      if ( *end )
        return false;
      if ( is_type_ptr(*ptr) )
      {
        ptr = skip_ptr_type_header(ptr);
        must_be_complete = false;
        continue;
      }
      else if ( is_type_array(*ptr) )
      {
        ptr = skip_array_type_header(ptr);
        must_be_complete = false;
        continue;
      }
      else if ( is_type_enum(*ptr) && !mop_is_valid_size(s) )
      {
        return false;
      }
      else if ( is_type_struni(*ptr) && s == 0 )
      {
        return false;
      }
    }
    return true;
  }
}

//----------------------------------------------------------------------
struct type_to_check_t
{
  // one of the following 3 will be valid:
  ea_t ea;
  int id;
  qstring name;

  // the type itself
  qtype type;
};
DECLARE_MOVABLE_TYPE(type_to_check_t);

static qvector<type_to_check_t> types_to_check;

static void check_tinfo(ea_t ea, int id, const char *name, const type_t *ptr)
{
  type_to_check_t &tc = types_to_check.push_back();
  tc.ea = ea;
  tc.id = id;
  tc.name = name;
  tc.type = ptr;
}

static void check_added_types(void)
{
  static int n = 0;
  for ( int i=0; i < types_to_check.size(); i++ )
  {
    type_to_check_t &tc = types_to_check[i];
    if ( !is_type_correct(idati, tc.type.begin()) )
    {
      qstring res;
      print_type_to_qstring(&res, NULL, 0, 0, 0, idati, tc.type.begin());
      msg("%d: INCORRECT TYPE ", n);
      if ( !tc.name.empty() )
        msg("%s", tc.name.begin());
      else if ( tc.ea != BADADDR )
        msg("%a", tc.ea);
      else
        msg("#%d", tc.id);
      msg(": %s\n", res.c_str());
      n++;
    }
  }
}
#else
inline void check_tinfo(ea_t,int,const char*,const type_t *) {}
inline void check_added_types(void) {}
#endif

//----------------------------------------------------------------------
bool create_func_if_necessary(ea_t ea, const char *name)
{
  int stype = segtype(ea);
  if ( stype != SEG_NORM && stype != SEG_CODE ) // only for code or normal segments
    return false;

  if ( get_mangled_name_type(name) == MANGLED_DATA )
    return false;

  if ( !decode_insn(ea) )
    return false;

  if ( !ph.notify(ph.is_sane_insn, 1) )
    return false;

  auto_make_proc(ea);
  return true;
}

//----------------------------------------------------------------------
bool looks_like_function_name(const char *name)
{
  // this is not quite correct: the presence of an opening brace
  // in the demangled name indicates a function
  // we can have a pointer to a function and there will be a brace
  // but this logic is not applied to data segments
  if ( strchr(name, '(') != NULL )
    return true;

  // check various function keywords
  static const char *const keywords[] =
  {
    "__cdecl ",
    "public: ",
    "virtual ",
    "operator ",
    "__pascal ",
    "__stdcall ",
    "__thiscall ",
  };
  for ( int i=0; i < qnumber(keywords); i++ )
    if ( strstr(name, keywords[i]) != NULL )
      return true;
  return false;
}

//----------------------------------------------------------------------
bool check_for_ids(ea_t ea, const char *name)
{
  // Seems to be a GUID?
  const char *ptr = name;
  while ( *ptr == '_' )
    ptr++;

  static const char *const guids[] = { "IID", "DIID", "GUID", "CLSID", "LIBID", NULL };
  static const char *const sids[] = { "SID", NULL };

  struct id_info_t
  {
    const char *const *names;
    const char *type;
  };
  static const id_info_t ids[] =
  {
    { guids, "GUID x;" },
    { sids,  "SID x;" },
  };
  static bool checked_types = false;
  static bool has_sid       = false;
  if ( !checked_types )
  {
    if ( default_compiler() == COMP_UNK )
      set_compiler_id(COMP_MS);
    if ( get_named_type(idati, "GUID", NTF_TYPE) == 0 )
    {
      static const char decl[] = "typedef struct _GUID { unsigned long  Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[8];} GUID;";
      h2ti(idati, NULL, decl, HTI_DCL, NULL, NULL, msg);
    }
    // SID type is pretty complex, so we won't add it manually but just check if it exists
    has_sid = get_named_type(idati, "SID", NTF_TYPE) != 0;
    checked_types = true;
  }
  for ( int k=0; k < qnumber(ids); k++ )
  {
    if ( k == 1 && !has_sid )
      continue;
    for ( const char *const *p2=ids[k].names; *p2; p2++ )
    {
      const char *guid = *p2;
      size_t len = strlen(guid);
      if ( strncmp(ptr, guid, len) == 0
        && (ptr[len] == '_' || ptr[len] == ' ') ) // space can be in demangled names
      {
        apply_cdecl2(idati, ea, ids[k].type);
        return true;
      }
    }
  }
  if ( strncmp(name, "_guid", 5) == 0 )
  {
    apply_cdecl2(idati, ea, ids[0].type);
    return true;
  }
  return false;
}

//----------------------------------------------------------------------
static bool is_data_prefix(ea_t ea, const char *name)
{
  static const char *const data_prefixes[] =
  {
    "__IMPORT_DESCRIPTOR",
    //"__imp_",             //imported function pointer
  };
  for ( int i=0; i < qnumber(data_prefixes); i++ )
    if ( strncmp(name, data_prefixes[i], strlen(data_prefixes[i])) == 0 )
      return true;

  // __real@xxxxxxxx            - floating point number, 4 bytes
  // __real@xxxxxxxxxxxxxxxx    - floating point number, 8 bytes
  if ( strncmp(name, "__real@", 7) == 0 )
  {
    const char *ptr = name + 7;
    const char *hex = ptr;
    while ( qisxdigit(*ptr) )
      ptr++;
    size_t len = ptr - hex;
    if ( len == 8 )
    {
      doFloat(ea, 4);
      return true;
    }
    if ( len == 16 )
    {
      doDouble(ea, 8);
      return true;
    }
    if ( len == 20 )
    { // i haven't seen this, but probably it exists too
      doTbyt(ea, 10);
      return true;
    }
  }
  return false;
}

//----------------------------------------------------------------------
// maybe_func: -1:no, 0-maybe, 1-yes
bool apply_name_in_idb(ea_t ea, const qstring &name, int maybe_func, uint32 the_machine_type)
{
  showAddr(ea); // so the user doesn't get bored

  char buf[MAXSTR];
  buf[0] = '\0';
  // check for meaningless 'string' names
  if ( strncmp(name.c_str(), "??_C@_", 6) == 0 )
  {
      // ansi:    ??_C@_0<len>@xxx
      // unicode: ??_C@_1<len>@xxx
      // TODO: parse length?
      make_ascii_string(ea, 0, name[6]=='1' ? ASCSTR_UNICODE : ASCSTR_C );
      return true;
  }
  if ( maybe_func <= 0 && demangle(buf, sizeof(buf), name.c_str(), MNG_SHORT_FORM) > 0 )
  {
    if ( strcmp(buf, "`string'") == 0 )
    {
      size_t s1 = get_max_ascii_length(ea, ASCSTR_C);
      size_t s2 = get_max_ascii_length(ea, ASCSTR_UNICODE);
      make_ascii_string(ea, 0, s1 >= s2 ? ASCSTR_C : ASCSTR_UNICODE);
      return true;
    }
  }

  // Renaming things immediately right here can lead to bad things.
  // For example, if the name is a well known function name, then
  // ida will immediately try to create a function. This is a bad idea
  // because IDA does not know exact function boundaries and will try
  // to guess them. Since the database has little information yet, there
  // is a big chance that the function will end up to be way too long.
  // That's why we collect names here and will rename them later.
  namelist[ea] = name;

  if ( check_for_ids(ea, name.c_str()) )
    return true;
  if ( check_for_ids(ea, buf) )
    return true;

  if ( is_data_prefix(ea, name.c_str()) )
    return true;

  // do not automatically create functions in debugger segments
  segment_t *s = getseg(ea);
  if ( s == NULL || !s->is_loader_segm() )
    return true;

  // ARMv7 PDBs don't use bit 0 for Thumb mode
  if ( (ph.id == PLFM_ARM && the_machine_type != CV_CFL_ARM7) || ph.id == PLFM_MIPS )
  {
    // low bit is Thumb/MIPS16 mode
    bool func16 = (ea & 1) != 0;
    ea &= ~1;
    if ( func16 )
    {
      // move the entry in namelist
      namelist.erase(ea|1);
      namelist[ea] = name;
    }
  }
  // check for function telltales
  if ( maybe_func == 0
    && segtype(ea) != SEG_DATA
    && demangle(buf, sizeof(buf), name.c_str(), MNG_LONG_FORM) > 0
    && looks_like_function_name(buf) )
  {
    auto_make_proc(ea); // fixme: when we will implement lvars, we have to process these request
                        // before handling lvars
    return true;
  }

  if ( maybe_func == 0 )
    create_func_if_necessary(ea, name.c_str());
  return true;
}


// Because we need to be able to call the 'old' pdb plugin
// code, which knows nothing about the til_builder_t (and
// thus its 'machine_type' field, and also because, at the
// very time we call the old pdb code, our til_builder_t
// instance will have been long forgotten and destroyed,
// we must keep this machine type information somewhere.
static uint32 g_machine_type = CV_CFL_80386;

// ---------------------------------------------------------------------------
bool apply_name(ea_t ea, const qstring &name, int maybe_func)
{
  return apply_name_in_idb(ea, name, maybe_func, g_machine_type);
}

//----------------------------------------------------------------------
void load_vc_til(void)
{
  // We managed to load the PDB file.
  // It is very probably that the file comes from VC
  // Load the corresponding type library immediately
  if ( ph.id == PLFM_386 && pe.signature == PEEXE_ID )
  {
    if ( pe.is_userland() )
      add_til2(pe.is_pe_plus() ? "vc10_64" : "mssdk", ADDTIL_INCOMP);
    else
      add_til2(pe.is_pe_plus() ? "ntddk64" : "ntddk", ADDTIL_INCOMP);
  }
}

// ---------------------------------------------------------------------------
class pdb_til_builder_t : public til_builder_t
{
public:
  pdb_til_builder_t(til_t *_ti, pdb_access_t *_pa)
    : til_builder_t(_ti, _pa) {}

  virtual int     before_iterating();
  virtual void    type_created(ea_t ea, int id, const char *name, const type_t *ptr) const;
  virtual bool    handle_symbol_at_ea(pdb_sym_t &sym, DWORD tag, DWORD id, ea_t ea, qstring &name);
  virtual void    handle_function_type(pdb_sym_t &fun_sym, ea_t ea);
  virtual HRESULT handle_function_type_child(pdb_sym_t &fun_sym,
                                             ea_t ea,
                                             pdb_sym_t &child_sym,
                                             DWORD child_tag,
                                             DWORD child_loc_type);
};

// ---------------------------------------------------------------------------
int pdb_til_builder_t::before_iterating()
{
  load_vc_til();
  return 0;
}

// ---------------------------------------------------------------------------
void pdb_til_builder_t::type_created(ea_t ea, int id, const char *name, const type_t *ptr) const
{
  check_tinfo(ea, id, name, ptr);
}

// ---------------------------------------------------------------------------
bool pdb_til_builder_t::handle_symbol_at_ea(pdb_sym_t &sym, DWORD tag, DWORD /*id*/, ea_t ea, qstring &name)
{
  int maybe_func = 0;
  switch ( tag )
  {
    case SymTagFunction:
    case SymTagThunk:
      maybe_func = 1;
      break;
    case SymTagBlock:
    case SymTagData:
    case SymTagLabel:
    case SymTagFuncDebugStart:
    case SymTagFuncDebugEnd:
    case SymTagVTable:
      maybe_func = -1;
      break;
    case SymTagPublicSymbol:
      {
        BOOL b;
        if ( sym.get_function(&b) == S_OK && b )
          maybe_func = 1;
      }
      break;
    default:
      break;
  }

  // symbols starting with __imp__ can not be functions
  if ( strncmp(name.c_str(), "__imp__", 7) == 0 )
  {
    doDwrd(ea, 4);
    maybe_func = -1;
  }

  BOOL is_code;
  if ( sym.get_code(&is_code) == S_OK && !is_code )
  {
    // not a function
    maybe_func = -1;
  }

  qtype type, fields;
  if ( get_symbol_type(sym, type, &fields, NULL) )
  {
    // Apparently _NAME_ is a wrong symbol generated for file names
    // It has wrong type information, so correct it
    if ( tag == SymTagData && name == "_NAME_" && type == (type_t*)"2" )
    {
      static const type_t array_of_chars[] = { BT_ARRAY|BTMT_NONBASED, 1, BT_INT8|BTMT_CHAR, 0 };
      type = array_of_chars;
    }
    if ( tag == SymTagFunction )
    {
      // convert the type again, this time passing function symbol
      // this allows us to get parameter names and handle static class methods
      pdb_sym_t func_sym(*pdb_access);
      if ( sym.get_type(&func_sym) == S_OK )
      {
        qtype itp2, fields2;
        if ( really_convert_type(func_sym, &sym, SymTagFunctionType, itp2, &fields2) == cvt_ok )
        {
          // successfully retrieved
          type = itp2;
          fields = fields2;
        }
      }
    }
    if ( is_restype_func(ti, type.begin()) )
    {
      maybe_func = 1;
      handle_function_type(sym, ea);
    }
    else
    {
      maybe_func = -1;
    }
    const type_t *t2 = type.begin();
    qtype ret;
    cm_t cc;
    int n = get_func_rettype(ti, &t2, NULL, &ret, NULL, NULL, &cc);
    // sometimes there are functions with linked FunctionType but no parameter or return type info in it
    // we get better results by not forcing type info on them
    bool no_ti = n == 0 && ret.length() == 1 && is_type_void(ret[0]) && fields.empty();
    if ( !no_ti )
    {
      type_created(ea, 0, NULL, type.begin());
      apply_tinfo(ti, ea, type.begin(), fields.begin(), TINFO_DEFINITE);
    }
  }
  else if ( maybe_func > 0 )
  {
    auto_make_proc(ea); // certainly a func
  }
  apply_name_in_idb(ea, name, maybe_func, pdb_access->get_machine_type());
  return true;
}

// ---------------------------------------------------------------------------
HRESULT pdb_til_builder_t::handle_function_type_child(
        pdb_sym_t &fun_sym,
        ea_t ea,
        pdb_sym_t &child_sym,
        DWORD child_tag,
        DWORD child_loc_type)
{
  LONG offset;
  DWORD reg_id;
  switch ( child_loc_type )
  {
    case LocIsEnregistered:
      if ( child_sym.get_registerId(&reg_id) == S_OK )
      {
        if ( enregistered_bug && reg_id > 0 )
          reg_id--;
        func_t *pfn = get_func(ea);
        qstring name;
        child_sym.get_name(&name);
        const char *canon = print_pdb_register(pdb_access->get_machine_type(), reg_id);
        if ( pfn != NULL )
          add_regvar(pfn, pfn->startEA, pfn->endEA, canon, name.c_str(), NULL);
      }
      break;

    case LocIsRegRel:
      if ( child_sym.get_registerId(&reg_id) == S_OK
        && child_sym.get_offset(&offset) == S_OK
        && reg_id == CV_REG_EBP )     // we can handle only ebp for the moment
      {
        func_t *pfn = get_func(ea);
        if ( pfn != NULL )
        {
          qstring name;
          child_sym.get_name(&name);
          qtype type, fields;
          if ( get_symbol_type(child_sym, type, &fields, NULL) )
          {
            const type_t *ptr = type.begin();
            opinfo_t mt;
            size_t size;
            flags_t flags;
            if ( get_idainfo_by_type2(ti, ptr, fields.begin(), &size, &flags, &mt, NULL) )
            {
              // DIA's offset is bp-based, not frame-based like in IDA
              offset -= pfn->fpd;
              if ( is_arm(pdb_access->get_machine_type()) && reg_id == CV_ARM_SP )
                offset -= pfn->frsize;
              // make sure the new variable is not overwriting the return address
              // for some reason some PDBs have bogus offsets for some params/locals...
              if ( pdb_access->get_machine_type() != CV_CFL_80386 || offset > 0 || size <= -offset )
              {
                add_stkvar2(pfn, name.c_str(), offset, flags, &mt, size);
                cmd.ea = BADADDR;
                cmd.Op1.type = o_void; // make sure is_sp_based fails
                member_t *mptr = get_stkvar(cmd.Op1, offset, NULL);
                if ( mptr != NULL )
                {
                  struc_t *sptr = get_frame(pfn);
                  set_member_tinfo(ti, sptr, mptr, 0, type.begin(), fields.begin(), 0);
                  set_userti(mptr->id);
                }
              }
            }
          }
          else // no type info...
          {
            msg("%a: stkvar '%s' with no type info\n", ea, name.c_str());
          }
        }
      }
      break;
    default:
      return til_builder_t::handle_function_type_child
        (fun_sym, ea, child_sym, child_tag, child_loc_type);
  }
  return S_OK;
}

// ---------------------------------------------------------------------------
void pdb_til_builder_t::handle_function_type(pdb_sym_t &sym, ea_t ea)
{
  // create the function - normally we know its size
  ea_t end = BADADDR;
  DWORD64 ulLen;
  if ( sym.get_length(&ulLen) == S_OK )
    end = ea + asize_t(ulLen);
  if ( !create_insn(ea) )
    return;

  // before adding a function, try to create all its instructions.
  // without this the frame analysis may fail.
  func_t fn;
  clear_func_struct(&fn);
  fn.startEA = ea;
  find_func_bounds(ea, &fn, FIND_FUNC_DEFINE);
  if ( !add_func(ea, end) ) // end is wrong for fragmented functions
    add_func(ea, BADADDR);

  til_builder_t::handle_function_type(sym, ea);
}


// ---------------------------------------------------------------------------
static HRESULT handler_common(pdb_access_t &pdb_access)
{
  pdb_til_builder_t builder(idati, &pdb_access);
  pdb_sym_t global(pdb_access, pdb_access.get_global_symbol_id());
  return builder.build(global);
}

// ---------------------------------------------------------------------------
#ifdef ENABLE_REMOTEPDB

static HRESULT handler_remote(
        const char *input_file,
        const char *user_spath,
        ea_t base_ea)
{
// static char pdb_remote_server[QMAXPATH];
// //lint -esym(551,pdb_remote_port)
// static int  pdb_remote_port = DEBUGGER_PORT_NUMBER;
// static char pdb_remote_passwd[QMAXPATH];
  remote_pdb_access_t access(input_file,
                             user_spath,
                             pdb_remote_server,
                             pdb_remote_port,
                             pdb_remote_passwd,
                             base_ea);
  HRESULT hr = access.init();
  if ( hr == S_OK )
    return handler_common(access);
  else
    return hr;
}

#else

static HRESULT handler_local(pdb_session_ref_t session_ref, void *)
{
  return handler_common(*session_ref.session->pdb_access);
}

#endif


/*====================================================================
                      IDA PRO INTERFACE START HERE
====================================================================*/

//--------------------------------------------------------------------------
// terminate
void idaapi term(void)
{
  namelist.clear();
#ifndef ENABLE_REMOTEPDB
#ifdef ENABLE_SRCDBG
  unregister_srcinfo_provider(&g_pdb_provider);
#endif
#endif
}

//--------------------------------------------------------------------------
// callback for parsing config file
const char *idaapi parse_options(
        const char *keyword,
        int value_type,
        const void *value)
{
  if ( strcmp(keyword, "PDB_REMOTE_PORT") == 0 )
  {
    if ( value_type != IDPOPT_NUM )
      return IDPOPT_BADTYPE;
    uval_t val = *(uval_t*)value;
    if ( val == 0 || val > 65535 )
      return IDPOPT_BADVALUE;
    pdb_remote_port = int(val);
    return IDPOPT_OK;
  }

  if ( value_type != IDPOPT_STR )
    return IDPOPT_BADTYPE;

  if ( strcmp(keyword, "PDBSYM_DOWNLOAD_PATH") == 0 )
  {
    qstrncpy(download_path, (const char *)value, sizeof(download_path));
    // empty string used for ida program directory
    if ( download_path[0] != '\0' && !qisdir(download_path) )
      return IDPOPT_BADVALUE;
  }
  else if ( strcmp(keyword, "PDBSYM_SYMPATH") == 0 )
  {
    qstrncpy(full_sympath, (const char *)value, sizeof(full_sympath));
    return IDPOPT_OK;
  }
  else if ( strcmp(keyword, "PDB_REMOTE_SERVER") == 0 )
  {
    qstrncpy(pdb_remote_server, (const char *)value, sizeof(pdb_remote_server));
    // empty string used for ida program directory
    if ( pdb_remote_server[0] == '\0' )
      return IDPOPT_BADVALUE;
  }
  else if ( strcmp(keyword, "PDB_REMOTE_PASSWD") == 0 )
  {
    qstrncpy(pdb_remote_passwd, (const char *)value, sizeof(pdb_remote_passwd));
    // empty string used for ida program directory
    if ( pdb_remote_server[0] == '\0' )
      return IDPOPT_BADVALUE;
  }
  else
  {
    return IDPOPT_BADKEY;
  }


  return IDPOPT_OK;
}

uint32 get_machine_from_idb()
{
  uint32 mt;
  switch ( ph.id )
  {
    case PLFM_ARM:
      mt = CV_CFL_ARM6;
      break;
    case PLFM_MIPS:
      mt = CV_CFL_MIPSR4000;
      break;
    case PLFM_PPC:
      mt = inf.mf ? CV_CFL_PPCBE : CV_CFL_PPCFP;
      break;
    case PLFM_SH:
      mt = CV_CFL_SH4;
      break;
    case PLFM_IA64:
      mt = CV_CFL_IA64;
      break;
    case PLFM_386:
    default:
      mt = CV_CFL_80386;
      break;
  }
  return mt;
}

//----------------------------------------------------------------------
static void init_sympaths()
{
  // user specified symbol path?
  download_path[0] = '\0';
  full_sympath[0] = '\0';
  read_user_config_file("pdb", parse_options);

  // if download path is set, format the path for Microsoft symbol server
  if ( full_sympath[0] == '\0' && download_path[0] != '\0' )
    qsnprintf(full_sympath, sizeof(full_sympath), "%s%s%s", spath_prefix, download_path, spath_suffix);
}

//----------------------------------------------------------------------
// Main function: do the real job here
void idaapi run(int _call_code)
{
  ea_t loaded_base;
  char input_path[QMAXPATH];
  sval_t typesonly = inf.filetype != f_PE && !is_miniidb();
  bool was_load_error = false;
  bool verbose = false;

  netnode pdbnode;
  pdbnode.create(PDB_NODE_NAME);

  netnode penode(PE_NODE);
  penode.valobj(&pe, sizeof(pe));

  bool ok = true;

  call_code = (PDB_CALLCODE)_call_code;
  // loading additional dll?
  if ( call_code == PDB_CC_ADDITIONAL )
  {
    loaded_base = pdbnode.altval(PDB_DLLBASE_NODE_IDX);
    pdbnode.supstr(PDB_DLLNAME_NODE_IDX, input_path, sizeof(input_path));
    QASSERT(30168, loaded_base != 0);
    PLUGIN.flags &= ~PLUGIN_UNL;
  }
  else
  {
    loaded_base = penode.altval(PE_ALT_IMAGEBASE);
    get_input_file_path(input_path, sizeof(input_path));

    if ( pdbnode.alt1st() != BADNODE ) // pdb plugin has been run at least once
    {
      // user explicitly invoked the plugin?
      if ( call_code == PDB_CC_USER )
        verbose = true;
    }
    if ( verbose || !qfileexist(input_path) )
    {
      static const char form[] =
        "Load PDB file\n"
        "<#Specify the path to the file to load symbols for#Input file:f:0:64::>\n"
        "<#Specify the loading address of the exe/dll file#Address   :N:64:64::>\n"
        "<#Load only types, do not rename program locations#Types only:C::::>>\n"
        "\n";
      if ( !AskUsingForm_c(form, input_path, &loaded_base, &typesonly) )
        return;
    }

    if ( !was_load_error
      && call_code == PDB_CC_IDA
      && askyn_c(1,
                 "AUTOHIDE REGISTRY\nHIDECANCEL\n"
                 "IDA has determined that the input file %s\n"
                 "was linked with debug information.\n"
                 "Do you want to look for the corresponding PDB file at the local symbol store\n"
                 "and the Microsoft Symbol Server?\n", input_path) <= 0 )
    {
      return;
    }
  }

LOAD_PDB:

  init_sympaths();
  const char *spath = NULL;
  if ( full_sympath[0] != '\0' )
    spath = full_sympath;

#ifdef ENABLE_REMOTEPDB
  HRESULT hr = handler_remote(input_path, spath, loaded_base);
#else
  HRESULT hr;
  try
  {
    hr = handle_pdb_file(
            input_path,
            spath,
            handler_local,
            loaded_base,
            NULL,
            NULL,
            NULL,
            typesonly);
  }
  catch ( const std::bad_alloc & )
  {
    warning("It appears IDA has run out of memory while loading the PDB file.\n"
            "This can happen when using the DIA SDK dll with big and/or corrupt PDBs.\n"
            "While you will now be able to continue your work, IDA cannot proceed with PDB parsing, sorry.\n\n"
            "It is also HIGHLY recommended that you save the database as soon as possible,\n"
            "quit, and restart IDA with that database.");
    hr = E_PDB_OUT_OF_MEMORY;
    was_load_error = true;
  }
#endif

  if ( FAILED(hr) )
  {
    ok = false;
#ifdef ENABLE_REMOTEPDB
    const char *err_str = NULL;
#else
    const char *err_str = pdberr(hr);
    msg("PDB: could not process file %s with DIA: %s\n", input_path, err_str);
    pdberr_suggest_vs_runtime(hr);

    // DIA interface failed, try the old methods
    if ( hr != E_PDB_INVALID_SIG
      && hr != E_PDB_INVALID_AGE
      && hr != E_PDB_NOT_FOUND
      && hr != E_PDB_INVALID_EXECUTABLE )
    {
      g_machine_type = get_machine_from_idb(); // See 'g_machine_type' comment above
      ok = old_pdb_plugin(loaded_base, input_path, spath);
      if ( ok )
        msg("Old method of loading PDB files (dbghelp) was successful\n");
    }
#endif
    if ( !was_load_error && !ok )
    {
      was_load_error = true;
      if ( askyn_c(ASKBTN_YES,
                   "HIDECANCEL\nAUTOHIDE REGISTRY\nPDB %s Load failed.\n%s\n"
                   "Do you want to browse for a .pdb on disk?",
                   input_path,
                   err_str == NULL ? "" : err_str) == ASKBTN_YES )
      {
        char *pdb_file = askfile_c(false, "*.pdb", "Choose PDB file");
        if ( pdb_file != NULL )
        {
          qstrncpy(input_path, pdb_file, sizeof(input_path));
          goto LOAD_PDB;
        }
      }
    }
  }

  if ( ok && !typesonly )
  {
    // Now all information is loaded into the database (except names)
    // We are ready to use names.
    int counter = 0;
    for ( namelist_t::iterator p=namelist.begin(); p != namelist.end(); ++p )
    {
      if ( call_code == PDB_CC_ADDITIONAL )
        counter += set_debug_name(p->first, p->second.c_str());
      else
        counter += do_name_anyway(p->first, p->second.c_str());
    }
    namelist.clear();
    msg("PDB: total %d symbol%s loaded for %s\n", counter, counter!=1 ? "s" : "", input_path);
  }

  pdbnode.altset(PDB_DLLBASE_NODE_IDX, ok);
  check_added_types();
}

//--------------------------------------------------------------------------
// initialize plugin
int idaapi init(void)
{
  const char *opts = get_plugin_options("pdb");
  if ( opts != NULL && strcmp(opts, "off") == 0 )
    return PLUGIN_SKIP;
#if !defined(ENABLE_REMOTEPDB) && defined(ENABLE_SRCDBG)
  if ( register_srcinfo_provider(&g_pdb_provider) )
    return PLUGIN_KEEP;
  else
#endif
    return PLUGIN_OK;
}

//--------------------------------------------------------------------------
//
//      PLUGIN DESCRIPTION BLOCK
//
//--------------------------------------------------------------------------
plugin_t PLUGIN =
{
  IDP_INTERFACE_VERSION,
  PLUGIN_MOD | PLUGIN_HIDE, // plugin flags:
  init,                 // initialize

  term,                 // terminate. this pointer may be NULL.

  run,          // invoke plugin

  // long comment about the plugin
  // it could appear in the status line
  // or as a hint
  "Load debug information from a PDB file",

  // multiline help about the plugin
  "PDB file loader\n"
  "\n"
  "This module allows you to load debug information about function names\n"
  "from a PDB file.\n"
  "\n"
  "The PDB file should be in the same directory as the input file\n",

  // the preferred short name of the plugin
  "Load PDB file (dbghelp 4.1+)",
  // the preferred hotkey to run the plugin
  ""
};


//lint -esym(766, md5.h, diskio.hpp) Unused header files.
