/*
    gendef - Generate list of exported symbols from a Portable Executable.
    Copyright (C) 2009-2016  mingw-w64 project

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include "compat_string.h"
#include "gendef.h"
#include "fsredir.h"
#ifdef HAVE_LIBMANGLE
#include <libmangle.h>
#endif

#define ENABLE_DEBUG 0

#if ENABLE_DEBUG == 1
#define PRDEBUG(ARG...)  fprintf(stderr,ARG)
#else
#define PRDEBUG(ARG...) do { } while(0)
#endif

static void decode_mangle (FILE *fp, const char *n);
static int load_pep (void);
static void do_pedef (void);
static void do_pepdef (void);
static void do_import_read32 (uint32_t va_imp, uint32_t sz_imp);
static void do_export_read (uint32_t va_exp,uint32_t sz_exp,int be64);
static void add_export_list (uint32_t ord,uint32_t func,const char *name, const char *forward,int be64,int beData);
static void dump_def (void);
static int disassembleRet (uint32_t func,uint32_t *retpop,const char *name, sImportname **ppimpname, int *seen_ret);
static size_t getMemonic (int *aCode,uint32_t pc,volatile uint32_t *jmp_pc,const char *name, sImportname **ppimpname);

static sImportname *imp32_add (const char *dll, const char *name, uint32_t addr, uint16_t ord);
static void imp32_free (void);
static sImportname *imp32_findbyaddress (uint32_t addr);

static sImportname *theImports = NULL;

static void *map_va (uint32_t va);
static int is_data (uint32_t va);
static int is_reloc (uint32_t va);
static int has_atdecoration (void);

static int disassembleRetIntern (uint32_t pc, uint32_t *retpop, sAddresses *seen, sAddresses *stack,
				 int *hasret, int *atleast_one, const char *name, sImportname **ppimpname);
static sAddresses*init_addr (void);
static void dest_addr (sAddresses *ad);
static int push_addr (sAddresses *ad,uint32_t val);
static int pop_addr (sAddresses *ad,uint32_t *val);

static sExportName *gExp = NULL;
static sExportName *gExpTail = NULL;
char *fninput;
char *fnoutput;
char *fndllname;

size_t gDta_size;
unsigned char *gDta;
PIMAGE_DOS_HEADER gMZDta;
PIMAGE_NT_HEADERS32 gPEDta;
PIMAGE_NT_HEADERS64 gPEPDta;

#ifdef REDIRECTOR
static int use_redirector = 0; /* Use/Disable FS redirector */
#endif

static int std_output = 0;
static int assume_stdcall = 0; /* Set to one, if function symbols should be assumed to have stdcall.  */
static int no_forward_output = 0; /* Set to one, if in .def files forwarders shouldn't be displayed.  */

static Gendefopts *chain_ptr = NULL;
 __attribute__((noreturn)) static void show_usage (void);
static int opt_chain (const char *, const char *);

static int
opt_chain (const char *opts, const char *next)
{
  static Gendefopts *prev, *current;
  char *r1, *r2;
  
  if (!strncmp (opts, "-", 2))
    {
      std_output = 1;
      return 0;
    }
  if (!strcmp (opts, "--help") || !strcmp (opts, "-h"))
    {
      show_usage();
      return 0;
    }
  if (!strcmp (opts, "--assume-stdcall") || !strcmp (opts, "-a"))
    {
      assume_stdcall = 1;
      return 0;
    }
  if (!strcmp (opts, "--include-def-path") || !strcmp (opts, "-I"))
    {
      if (!next)
        {
	  fprintf (stderr, "Error: %s expects path as next arguement.", opts);
	  return 0;
        }
      gendef_addpath_def (next);
      return 1;
    }
  if (!strcmp (opts, "--no-forward-output") || !strcmp (opts, "-f"))
    {
      no_forward_output = 1;
      return 0;
    }

#ifdef REDIRECTOR
  if (!strcmp (opts, "--disable-fs-redirector") || !strcmp (opts, "-r"))
    {
      use_redirector = 1;
      return 0;
    }
#endif

  current = malloc (sizeof(Gendefopts));
  if (current)
    {
      memset (current, 0, sizeof (Gendefopts));
      current->next = NULL;

      if (!prev)
        chain_ptr = current;
      else
        prev->next = current;
      r1 = strrchr (opts,'/');
      r2 = strrchr (opts,'\\');
      current->fninput = strdup (opts);
      
      if (!r1 && r2 != NULL)
        r1 = r2 + 1;
      else if(r1 == NULL && r2 == NULL)
        r1 = current->fninput;
      else if (r2 != NULL && r1 != NULL && r1 < r2)
        r1 = r2 + 1;
      else
        r1++;
      current->fnoutput = (char *) malloc (strlen (current->fninput) + 5);
      strcpy (current->fnoutput,r1);

      r1 = strrchr (current->fnoutput,'.');
      if (r1)
        strcpy (r1,".def");
      else
        strcat (current->fnoutput,".def");
      prev = current;
   }
  return 0;
}

void
show_usage (void)
{
  fprintf (stderr, "Usage: gendef [OPTION]... [DLL]...\n");
  fprintf (stderr, "Dumps DLL exports information from PE32/PE32+ executables\n");
  fprintf (stderr, "\n");
  fprintf (stderr, "Options:\n"
    "  -                        Dump to stdout\n"
    "  -h, --help               Show this help.\n"
    "  -a, --assume-stdcall     Assume functions with ambiguous call\n"
    "                           convention as stdcall.\n"
    "  -I, --include-def-path <path>\n"
    "                           Add additional search paths to find\n"
    "                           hint .def files.\n"
    "  -f, --no-forward-output  Don't output forwarders in .def file\n"
#ifdef REDIRECTOR
    "  -r, --disable-fs-redirector\n"
    "                           Disable Win64 FS redirection, for 32-bit\n"
    "                           gendef on 64-bit Windows\n"
#endif
  );
  fprintf (stderr, "\n");
  fprintf (stderr, "Usage example: \n"
                   "  By default, the output files are named after their DLL counterparts\n"
                   "  gendef MYDLL.DLL     Produces MYDLL.def\n"
                   "  gendef - MYDLL.DLL   Prints the exports to stdout\n");
  fprintf (stderr, "\nBuilt on %s\n", __DATE__);
  fprintf (stderr, "\nReport bugs to <mingw-w64-public@lists.sourceforge.net>\n");
  exit (0);
}

int main(int argc,char **argv)
{
  int i;
  Gendefopts *opt, *next;

  if (argc < 2)
  {
    show_usage ();
    return 0;
  }

  for (i = 1; i < argc; i++)
    i += opt_chain (argv[i], ((i+1) < argc ? argv[i+1] : NULL));
#ifdef REDIRECTOR
  doredirect(use_redirector);
#endif
  opt = chain_ptr;
  while (opt)
    {
      fninput = opt->fninput;
      fnoutput = opt->fnoutput;

      if (load_pep ())
        {
	  if (gPEDta || gPEPDta)
	    {
	      if (gPEDta)
		do_pedef ();
	      else
		do_pepdef ();
	      dump_def ();
	    }
	}
      if (fndllname)
	free (fndllname);
      fndllname = NULL;
      if (gDta)
	{
	  free (gDta);
	  gDta = NULL;
	}
      next = opt->next;
      free(opt->fninput);
      free(opt->fnoutput);
      free(opt);
      opt = next;
    }
  imp32_free ();
  return 0;
}

static int
load_pep (void)
{
  FILE *fp = fopen (fninput, "rb");

  if (!fp)
    {
      fprintf (stderr, "*** [%s] failed to open()\n", fninput);
      return 0;
    }
  fseek (fp, 0, SEEK_END);
  gDta_size = (size_t) ftell (fp);
  if (gDta_size > 0) {
    fseek (fp,0,SEEK_SET);
    gDta = (unsigned char *) malloc (gDta_size + 1);
    if (gDta)
      {
        if (fread (gDta, gDta_size, 1, fp) != 1)
	  {
	    free (gDta);
	    gDta = NULL;
	  }
	else
          gDta[gDta_size] = 0;
    }
  }
  fclose (fp);
  if (!gDta)
    {
      fprintf (stderr, "*** [%s] unable to allocate %lu bytes\n", fninput,
	       (unsigned long) gDta_size);
      return 0;
    }
  gMZDta = (PIMAGE_DOS_HEADER) gDta;
  if (gDta_size < sizeof(IMAGE_DOS_HEADER) || gDta[0]!='M' || gDta[1]!='Z'
      || gMZDta->e_lfanew <= 0
      || gMZDta->e_lfanew >= (int32_t) gDta_size)
  {
    fprintf(stderr,"*** [%s] not a PE(+) file\n", fninput);
    free(gDta);
    gDta = NULL;
    return 0;
  }
  gPEDta = (PIMAGE_NT_HEADERS32) &gDta[gMZDta->e_lfanew];
  gPEPDta = (PIMAGE_NT_HEADERS64) gPEDta;
  if (gPEDta->Signature != IMAGE_NT_SIGNATURE)
    {
      fprintf (stderr, "*** [%s] no PE(+) signature\n", fninput);
      free (gDta);
      gDta = NULL;
      gPEPDta = NULL;
      gPEDta = NULL;
      return 0;
    }
  if (gPEDta->FileHeader.SizeOfOptionalHeader == IMAGE_SIZEOF_NT_OPTIONAL32_HEADER
      && gPEDta->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
    {
      gPEPDta = NULL;
      fprintf (stderr, " * [%s] Found PE image\n", fninput);
    }
  else if (gPEPDta->FileHeader.SizeOfOptionalHeader == IMAGE_SIZEOF_NT_OPTIONAL64_HEADER
    && gPEPDta->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
    {
      gPEDta = NULL;
      fprintf (stderr, " * [%s] Found PE+ image\n", fninput);
    }
  else
    {
      free (gDta);
      gDta = NULL;
      fprintf (stderr, "*** [%s] no PE(+) optional header\n", fninput);
      gPEPDta = NULL;
      gPEDta = NULL;
      return 0;
    }
  return 1;
}

static int
is_data (uint32_t va)
{
  PIMAGE_SECTION_HEADER sec;
  uint32_t sec_cnt,i;

  /* If export va is directly relocated, so it must be data.  */  
  if (is_reloc (va))
    return 1;
  
  if (gPEDta)
    {
      sec_cnt = gPEDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEDta);
    }
  else
    {
      sec_cnt = gPEPDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEPDta);
    }
  if (!sec)
    return 0;
  for (i = 0;i < sec_cnt;i++)
    {
      if (va >= sec[i].VirtualAddress && va < (sec[i].VirtualAddress+sec[i].Misc.VirtualSize))
        break;
    }
  if (i == sec_cnt)
    return 0; 
  if ((sec[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE)) != 0)
    return 0;
  if ((sec[i].Characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0)
    return 1;
  if ((sec[i].Characteristics & IMAGE_SCN_MEM_READ) ==0)
    return 0;
  if ((sec[i].Characteristics & (IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_LNK_COMDAT)) != 0)
    return 1;
  return 1;
}

static int
is_reloc (uint32_t va)
{
  uint32_t va_rel, sz_rel, pos;
  unsigned char *p;
  PIMAGE_BASE_RELOCATION brel;

  if (gPEDta)
    {
      va_rel = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
      sz_rel = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
    }
  else
    {
      va_rel = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
      sz_rel = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
    }
  if (va_rel == 0 || sz_rel < IMAGE_SIZEOF_BASE_RELOCATION)
    return 0;
  p = (unsigned char *) map_va (va_rel);
  for (pos = 0; pos < sz_rel;)
    {
      uint16_t *r;
      uint32_t nums, j;
      if ((sz_rel - pos) < IMAGE_SIZEOF_BASE_RELOCATION)
        break;
      brel = (PIMAGE_BASE_RELOCATION) &p[pos];
      if (brel->SizeOfBlock == 0)
         break;
      pos += IMAGE_SIZEOF_BASE_RELOCATION;
      nums = (brel->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION) / 2;
      r = (uint16_t *) &p[pos];
      if (va >= brel->VirtualAddress && va < (brel->VirtualAddress + 0x1008))
        {
          for (j = 0; j < nums; j++)
            {
              uint32_t relsz = 0;
              uint32_t offs = (uint32_t) (r[j] & 0xfff) + brel->VirtualAddress;
              uint16_t typ = (r[j] >> 12) & 0xf;
              if (typ == IMAGE_REL_BASED_HIGHADJ)
                j++;
              switch (typ)
                {
                case IMAGE_REL_BASED_HIGHLOW:
                  relsz = 4;
                  break;
                case IMAGE_REL_BASED_DIR64:
                  relsz = 8;
                  break;
                }
              if (relsz != 0 && va >= offs && va < (offs + relsz))
                return 1;
            }
        }
      pos += (brel->SizeOfBlock - IMAGE_SIZEOF_BASE_RELOCATION);
    }
  return 0;
}

static void *
map_va (uint32_t va)
{
  PIMAGE_SECTION_HEADER sec;
  uint32_t sec_cnt,sz,i;
  char *dptr;

  if (gPEDta)
    {
      sec_cnt = gPEDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEDta);
    }
  else
    {
      sec_cnt = gPEPDta->FileHeader.NumberOfSections;
      sec = IMAGE_FIRST_SECTION(gPEPDta);
    }
  for (i = 0;i < sec_cnt;i++)
    {
      sz = sec[i].Misc.VirtualSize;
      if (!sz) sz = sec[i].SizeOfRawData;
      if (va >= sec[i].VirtualAddress && va < (sec[i].VirtualAddress+sz))
        {
          dptr = (char *) &gDta[va-sec[i].VirtualAddress+sec[i].PointerToRawData];
          return (void *)dptr;
        }
    }
  return NULL;
}

/* For pep we can take the exports itself, there is no additional decoration necessary.  */
static void
do_pepdef (void)
{
  uint32_t va_exp = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  uint32_t sz_exp = gPEPDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

  do_export_read (va_exp, sz_exp, 1);
}

static void
do_pedef (void)
{
  uint32_t va_exp = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  uint32_t sz_exp = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
  uint32_t va_imp = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
  uint32_t sz_imp = gPEDta->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size;

  imp32_free ();
  do_import_read32 (va_imp, sz_imp);
  do_export_read (va_exp, sz_exp, 0);
}

static void
do_import_read32 (uint32_t va_imp, uint32_t sz_imp)
{
  IMAGE_IMPORT_DESCRIPTOR *pid;
  if (!sz_imp || !va_imp)
    return;
  pid = (IMAGE_IMPORT_DESCRIPTOR *) map_va (va_imp);
  while (pid != NULL && sz_imp >= 20 && pid->Name != 0 && pid->OriginalFirstThunk != 0)
    {
      uint32_t index = 0;
      PIMAGE_THUNK_DATA32 pIAT;
      PIMAGE_THUNK_DATA32 pFT;
      char *imp_name = (char *) map_va (pid->Name);

      for (;;) {
	char *fctname;
	pIAT = (PIMAGE_THUNK_DATA32) map_va (pid->OriginalFirstThunk + index);
	pFT = (PIMAGE_THUNK_DATA32) map_va (pid->FirstThunk + index);
	if (pIAT->u1.Ordinal == 0 || pFT->u1.Ordinal == 0)
	  break;
	if (IMAGE_SNAP_BY_ORDINAL32 (pIAT->u1.Ordinal))
	  fctname = NULL;
	else
	  fctname = (char *) map_va (pIAT->u1.Function + 2);
	if (fctname)
	  imp32_add (imp_name, fctname,
	  //pid->OriginalFirstThunk + index + gPEDta->OptionalHeader.ImageBase,
	  pid->FirstThunk + index + gPEDta->OptionalHeader.ImageBase,
	    *((uint16_t *) map_va (pIAT->u1.Function)));
	index += 4;
      }
      sz_imp -= 20;
      va_imp += 20;
      if (sz_imp >= 20)
	pid = (IMAGE_IMPORT_DESCRIPTOR *) map_va (va_imp);
    }
}

static sImportname *
imp32_findbyaddress (uint32_t addr)
{
  sImportname *h = theImports;
  while (h != NULL && h->addr_iat != addr)
    h = h->next;
  return h;
}

static void
imp32_free (void)
{
  while (theImports != NULL)
    {
      sImportname *h = theImports;
      theImports = theImports->next;
      if (h->dll)
	free (h->dll);
      if (h->name)
	free (h->name);
      free (h);
    }
}

static sImportname *
imp32_add (const char *dll, const char *name, uint32_t addr, uint16_t ord)
{
  sImportname *n = (sImportname *) malloc (sizeof (sImportname));
  memset (n, 0, sizeof (sImportname));
  n->dll = strdup (dll);
  n->name = strdup (name);
  n->ord = ord;
  n->addr_iat = addr;
  n->next = theImports;
  theImports = n;
  return n;
}

static void
do_export_read (uint32_t va_exp, uint32_t sz_exp, int be64)
{
  uint32_t i;
  PIMAGE_EXPORT_DIRECTORY exp_dir;
  uint32_t *functions;
  uint16_t *ordinals;
  uint32_t *name;

  if (va_exp == 0 || sz_exp == 0)
    return;
  exp_dir = (PIMAGE_EXPORT_DIRECTORY) map_va (va_exp);
  PRDEBUG(" * export directory at VA = 0x%x size=0x%x\n", (unsigned int) va_exp, (unsigned int) sz_exp);
  fndllname = strdup ((char *) map_va (exp_dir->Name));
  PRDEBUG(" * Name: %s\n * Base: %u\n", fndllname, (unsigned int) exp_dir->Base);
  functions = (uint32_t *) map_va (exp_dir->AddressOfFunctions);
  ordinals = (uint16_t *) map_va (exp_dir->AddressOfNameOrdinals);
  name = (uint32_t *) map_va (exp_dir->AddressOfNames);
  
  for (i = 0;i < exp_dir->NumberOfFunctions;i++)
    {
      uint32_t entryPointRVA = functions[i];
      uint32_t j;
      char *fname;
      uint32_t ord;
      fname = NULL;
      if (!entryPointRVA)
        continue;
      ord = i + exp_dir->Base;
      for (j = 0;j < exp_dir->NumberOfNames;j++)
        if (ordinals[j]==i)
          fname = (char *) map_va (name[j]);
      if (entryPointRVA >= va_exp && entryPointRVA <= (va_exp + sz_exp))
        add_export_list (ord, 0, fname,(char *) map_va (entryPointRVA), be64, 0);
      else
        add_export_list(ord, entryPointRVA, fname, NULL, be64, is_data (entryPointRVA));
    }
}

static void
add_export_list(uint32_t ord,uint32_t func,const char *name, const char *forward,int be64,int beData)
{
  sExportName *exp = NULL;

  if (!name)
    name = "";
  if (!forward)
    forward = "";
  exp = (sExportName *) malloc (sizeof (sExportName) + strlen (name) + strlen (forward) + 2);
  if (!exp)
    return;
  exp->name = (char *) &exp[1];
  exp->forward = exp->name + strlen (name) + 1;
  strcpy (exp->name, name);
  strcpy (exp->forward, forward);
  exp->next = NULL;
  exp->ord = ord;
  exp->func = func;
  exp->be64 = be64;
  exp->beData = beData;
  exp->retpop = (uint32_t)-1;
  if (gExpTail)
    gExpTail->next = exp;
  else
    gExp = exp;
  gExpTail = exp;
}

static void
dump_def (void)
{
  sExportName *exp;
  FILE *fp;

  if (!fndllname || gExp == NULL)
    return;
  if (!std_output)
    fp = fopen(fnoutput,"wt");
  else
    fp = stdout;
  if(!fp) {
    fprintf(stderr," * failed to create %s ...\n",fnoutput);
    return;
  }
  fprintf (fp,";\n; Definition file of %s\n; Automatic generated by gendef\n; written by Kai Tietz 2008\n;\n",
    fndllname);
  fprintf (fp,"LIBRARY \"%s\"\nEXPORTS\n",fndllname);
  while ((exp = gExp) != NULL)
    {
      sImportname *pimpname;
      int seen_ret;
      seen_ret = 1;
      gExp = exp->next;
      if (exp->name[0] == '?')
        {
          decode_mangle (fp, exp->name);
        }
      if (exp->name[0] == 0)
        fprintf (fp, "ord_%u", (unsigned int) exp->ord);
      else
        {
          const char *quote = strchr (exp->name, '.') ? "\"" : "";
          fprintf (fp, "%s%s%s", quote, exp->name, quote);
        }
      if (exp->name[0] == '?' && exp->name[1] == '?')
        {
          if (!strncmp (exp->name, "??_7", 4))
	    exp->beData = 1;
        }
      pimpname = NULL;
      if (!exp->beData && !exp->be64 && exp->func != 0)
	{
	  seen_ret = 0;
	  exp->beData = disassembleRet (exp->func, &exp->retpop, exp->name, &pimpname, &seen_ret);
        }
      if (!exp->be64 && exp->retpop == (uint32_t) -1 && pimpname)
	{
	  int isD = 0;
	  uint32_t at = 0;
	  if (gendef_getsymbol_info (pimpname->dll, pimpname->name, &isD, &at))
	    {
	      exp->beData = isD;
	      if (!isD)
		exp->retpop = at;
	    }
	}
      else if (exp->func == 0 && !exp->beData)
	{
	  int isD = 0;
	  uint32_t at = 0;
	  if (gendef_getsymbol_info (exp->forward, NULL, &isD, &at))
	    {
	      exp->beData = isD;
	      if (!isD)
	        exp->retpop = at;
	    }
	}

      if (exp->be64)
        exp->retpop = 0;

      if (exp->retpop != (uint32_t) -1 && !exp->be64 && has_atdecoration())
        {
          if (exp->name[0]=='?')
            fprintf(fp," ; has WINAPI (@%u)", (unsigned int) exp->retpop);
          else
            fprintf(fp,"@%u", (unsigned int) exp->retpop);
        }
      if (exp->func == 0 && no_forward_output == 0)
	fprintf (fp, " = %s", exp->forward);
      if (exp->name[0] == 0)
        fprintf(fp," @%u", (unsigned int) exp->ord);
      if (exp->beData)
        fprintf(fp," DATA");

      if (exp->retpop != (uint32_t) -1 || (exp->retpop == 0 && exp->be64) || !has_atdecoration ())
	{
	}
      else if (pimpname)
        {
	  fprintf (fp, " ; Check!!! forwards to %s in %s (ordinal %u)",
	    pimpname->name, pimpname->dll, pimpname->ord);
        }
      else if (exp->func == 0 && !exp->beData)
	{
	  fprintf (fp, " ; Check!!! forwards to %s", exp->forward);
	}
      else if (seen_ret == 0 && !exp->beData)
        {
	  fprintf (fp, " ; Check!!! Couldn't determine function argument count. Function doesn't return. ");
        }
      fprintf(fp,"\n");
      free (exp);
    }
  gExpTail=NULL;
  if (!std_output)
    fclose(fp);
}

static sAddresses *
init_addr (void)
{
  sAddresses *r = (sAddresses*) malloc (sizeof (sAddresses));
  r->max = 8;
  r->cnt = 0;
  r->ptrs = (uint32_t *) malloc (sizeof (uint32_t) * 8);
  r->idx = 0;
  return r;
}

static void
dest_addr (sAddresses *ad)
{
  free (ad->ptrs);
  free (ad);
}

static int
push_addr (sAddresses *ad, uint32_t val)
{
  uint32_t i;

  for (i = 0;i < ad->cnt; i++)
    {
      if (ad->ptrs[i] == val)
        return 0;
    }
  if (ad->max == ad->cnt)
    {
      uint32_t *p = (uint32_t *) malloc (sizeof (uint32_t) * (ad->max + 8));

      memcpy (p, ad->ptrs, sizeof (uint32_t) * ad->max);
      ad->max += 8;
      free (ad->ptrs);
      ad->ptrs = p;
    }
  ad->ptrs[ad->cnt++] = val;
  return 1;
}

static int
pop_addr (sAddresses *ad, uint32_t *val)
{
  if (!ad || ad->idx == ad->cnt)
    return 0;
  ad->idx++;
  *val = ad->ptrs[ad->idx-1];
  return 1;
}

static int
has_atdecoration (void)
{
  return (gPEDta && gPEDta->FileHeader.Machine == 0x014c /* IMAGE_FILE_MACHINE_I386 */);
}

/* exp->beData */
static int
disassembleRet (uint32_t func, uint32_t *retpop, const char *name, sImportname **ppimpname, int *seen_ret)
{
  sAddresses *seen = init_addr ();
  sAddresses *stack = init_addr ();
  uint32_t pc;
  int hasret = 0;
  int atleast_one = 0;

  if (!has_atdecoration())
  {
    *retpop = 0;
    return 0;
  }
  *retpop = (uint32_t) -1;
  push_addr (stack, func);

  while (!hasret && pop_addr (stack,&pc))
    {
      if (disassembleRetIntern (pc, retpop, seen, stack, &hasret, &atleast_one, name, ppimpname))
        break;
    }
  *seen_ret = hasret;
  dest_addr (seen);
  dest_addr (stack);
  return (atleast_one ? 0 : 1);
}

static int
disassembleRetIntern (uint32_t pc, uint32_t *retpop, sAddresses *seen, sAddresses *stack,
		      int *hasret, int *atleast_one, const char *name, sImportname **ppimpname)
{
  size_t sz = 0;
  int code = 0,break_it = 0;
  volatile uint32_t tojmp = 0;

  while(1)
    {
      if (!push_addr (seen, pc))
        return 0;
      sz = getMemonic (&code, pc, &tojmp, name, ppimpname) & 0xffffffff;
      if (!sz || code == c_ill)
        {
          PRDEBUG(" %s = 0x08%x ILL (%u) at least one==%d\n",name,
		  (unsigned int) pc, (unsigned int) sz,atleast_one[0]);
#if ENABLE_DEBUG == 1
      {
        unsigned char *ppc = (unsigned char *) map_va (pc);
        size_t i;

        fprintf (stderr, "%s(0x%x): ",name, (unsigned int) pc);
        for (i = 0;i < sz; i++)
          {
            fprintf (stderr, "%s0x%x", (i == 0 ? " ":","), ppc[i]);
          }
        fprintf (stderr, "\n");
      }
      exit (0);
#endif
          break;
        }
#if ENABLE_DEBUG == 1
      {
        unsigned char *ppc = (unsigned char *) map_va (pc);
        size_t i;

        fprintf (stderr, "%s(0x%x): ",name, (unsigned int) pc);
        for (i = 0;i < sz; i++)
          {
            fprintf (stderr, "%s0x%x", (i == 0 ? " ":","), ppc[i]);
          }
        fprintf (stderr, "\n");
      }
#endif
      atleast_one[0] += 1;
      break_it = 0;
      pc += sz;
      switch(code)
        {
        case c_jmpnjb: case c_jmpnjv:
          pc = tojmp;
          break;
        case c_jxx:
          push_addr (stack, tojmp);
          break;
        case c_jmpfap: case c_int3:
          break_it = 1;
          break;
        case c_iret: case c_retf: case c_retn:
          *hasret = 1;
          if (assume_stdcall)
            *retpop = 0;
          return 1;
        case c_retflw: case c_retnlw:
          *hasret = 1;
          *retpop = tojmp;
          return 1;
        }
      if (break_it)
        break;
    }

  return 0;
}

static int opMap2[256] = {
  c_EG,c_EG,c_EG,c_EG,c_1,c_1,c_ill,c_ill, /* 0x00-0x07 */
  c_1,c_1,c_ill,c_ill,c_ill,c_ill,c_ill,c_ill, /* 0x08-0x0f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x10-0x17 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x18-0x1f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_ill,c_EG,c_ill, /* 0x20-0x27 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x28-0x2f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_ill,c_1, /* 0x30-0x37 */
  c_ill,c_ill,c_ill,c_ill,c_ill,c_ill,c_ill,c_ill, /* 0x38-0x3f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x40-0x47 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x48-0x4f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x50-0x57 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x58-0x5f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x60-0x67 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x68-0x6f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x70-0x77 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x78-0x7f */
  c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv, /* 0x80-0x87 */
  c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv,c_jxxv, /* 0x88-0x8f */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x90-0x97 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x98-0x9f */
  c_1,c_1,c_1,c_EG,c_EGlb,c_EG,c_ill,c_ill, /* 0xa0-0xa7 */
  c_1,c_1,c_1,c_EG,c_EGlb,c_EG,c_EG,c_EG, /* 0xa8-0xaf */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xb0-0xb7 */
  c_EG,c_1,c_EGlb,c_EG, c_EG,c_EG,c_EG,c_EG, /* 0xb8-0xbf */
  c_EG,c_EG,c_EGlb,c_EG,c_EGlb,c_EGlb,c_EGlb,c_EG, /* 0xc0-0xc7 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0xc8-0xcf */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xd0-0xd7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xd8-0xdf */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xe0-0xe7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xe8-0xef */
  c_ill,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xf0-0xf7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_ill /* 0xf8-0xff */
};

static int opMap1[256] = {
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x00-0x07 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_0f, /* 0x08-0x0f */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x10-0x17 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x18-0x1f */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x20-0x27 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x28-0x2f */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x30-0x37 */
  c_EG,c_EG,c_EG,c_EG,c_lb,c_lv,c_1,c_1, /* 0x38-0x3f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x40-0x47 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x48-0x4f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x50-0x57 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x58-0x5f */
  c_1,c_1,c_EG,c_EG,c_1,c_1,c_op,c_ad, /* 0x60-0x67 */
  c_lv,c_EGlv,c_lb,c_EGlb,c_1,c_1,c_1,c_1, /* 0x68-0x6f */
  c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx, /* 0x70-0x77 */
  c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx,c_jxx, /* 0x78-0x7f */
  c_EGlb,c_EGlv,c_EGlb,c_EGlb,c_EG,c_EG,c_EG,c_EG, /* 0x80-0x87 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0x88-0x8f */
  c_1,c_1,c_1,c_1,c_1,c_1,c_1,c_1, /* 0x90-0x97 */
  c_1,c_1,c_callfar,c_1,c_1,c_1,c_1,c_1, /* 0x98-0x9f */
  c_O,c_O,c_O,c_O,c_1,c_1,c_1,c_1, /* 0xa0-0xa7 */
  c_lb,c_lv,c_1,c_1,c_1,c_1,c_1,c_1, /* 0xa8- 0xaf */
  c_2,c_2,c_2,c_2,c_2,c_2,c_2,c_2, /* 0xb0-0xb7 */
  c_lv,c_lv,c_lv,c_lv,c_lv,c_lv,c_lv,c_lv, /* 0xb8-0xbf */
  c_EGlb,c_EGlb,c_retnlw,c_retn,c_EG,c_EG,c_EGlb,c_EGlv, /* 0xc0-0xc7 */
  c_4,c_1,c_retflw,c_retf,c_int3,c_2,c_1,c_iret, /* 0xc8-0xcf */
  c_EG,c_EG,c_EG,c_EG,c_2,c_2,c_1,c_1, /* 0xd0-0xd7 */
  c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG,c_EG, /* 0xd8-0xdf */
  c_jxx,c_jxx,c_jxx,c_jxx,c_2,c_2,c_2,c_2, /* 0xe0-0xe7 */
  c_calljv,c_jmpnjv,c_jmpfap,c_jmpnjb,c_1,c_1,c_1,c_1, /* 0xe8-0xef */
  c_1,c_1,c_1,c_1, c_1,c_1,c_EGg3b,c_EGg3v, /* 0xf0-0xf7 */
  c_1,c_1,c_1,c_1,c_1,c_1,c_g4,c_g4 /* 0xf8-0xff */
};

#if ENABLE_DEBUG == 1

#define MAX_INSN_SAVE	20

static void
enter_save_insn (unsigned char *s, unsigned char b)
{
  int i;
  for (i=0;i<MAX_INSN_SAVE-1;i++)
    s[i]=s[i+1];
  s[MAX_INSN_SAVE-1]=b;
}

static void
print_save_insn (const char *name, unsigned char *s)
{
  int i;
  
  PRDEBUG("From %s: ",name);
  for (i=0;i<MAX_INSN_SAVE;i++)
  {
    PRDEBUG("%s0x%x",(i!=0 ? "," : ""), (unsigned int) s[i]);
  }
}
#endif

static size_t
getMemonic(int *aCode,uint32_t pc,volatile uint32_t *jmp_pc, __attribute__((unused)) const char *name, sImportname **ppimpname)
{
#if ENABLE_DEBUG == 1
  static unsigned char lw[MAX_INSN_SAVE];
#endif
  unsigned char *p;
  int addr_mode = 1;
  int oper_mode = 1;
  size_t sz = 0;
  unsigned char b;
  int tb1;

  for(;;) {
    p = (unsigned char *) map_va (pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
    if (b==0x26 || b==0x2e || b==0x36 || b==0x3e || b==0x64 || b==0x65)
      ++sz;
    else if (b==0x66) { oper_mode^=1; sz++; }
    else if (b==0x67) { addr_mode^=1; sz++; }
    else if (b==0xf2 || b==0xf3 || b==0xf0) sz++;
    else break;
  }
  sz++;
  tb1=opMap1[(unsigned int) b];
  
redo_switch:
#if ENABLE_DEBUG == 1
  if (tb1!=c_ill) { enter_save_insn(lw,b); }
#endif
  switch (tb1) {
  case c_ill:
#if ENABLE_DEBUG == 1
    print_save_insn (name, lw);
    PRDEBUG(" 0x%x illegal ", (unsigned int) b);
#endif
    *aCode=c_ill; return 0;
  case c_4: sz++;/* fallthru */
  case c_3: sz++;/* fallthru */
  case c_lb:
  case c_2: sz++;/* fallthru */
  case c_retn: case c_retf:
  case c_iret: case c_int3:
  case c_ad: case c_op:
  case c_1: *aCode=tb1; return sz;
  case c_lv:
    if (oper_mode) sz+=4;
    else sz+=2;
    *aCode=tb1; return sz;
  case c_O: case c_calljv:
    if (addr_mode) sz+=4;
    else sz+=2;
    *aCode=tb1; return sz;
  case c_EG: case c_EGlv: case c_EGlb: case c_g4: case c_EGg3v: case c_EGg3b:
    p = (unsigned char *) map_va (pc + sz);
    sz++;
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
#if ENABLE_DEBUG == 1
    enter_save_insn(lw,b);
#endif
    if (addr_mode) {
      if((b&0xc0)!=0xc0 && (b&0x7)==4)
        {
          p = (unsigned char *) map_va (pc + sz);
          if (!p) { *aCode=c_ill; return 0; }
#if ENABLE_DEBUG == 1
	  enter_save_insn(lw,p[0]);
#endif
          b&=~0x7; b|=(p[0]&7);
	  sz+=1;
	}
      if((b&0xc0)==0 && (b&7)==5)
        {
	  /* Here we check if for jmp instruction it points to an IAT entry.  */
	  if(tb1==c_g4 && ((b&0x38)==0x20 || (b&0x38)==0x28))
	    {
	      uint32_t vaddr;
	      sImportname *inss;
	      vaddr = *((uint32_t *) map_va (pc + sz));
	      inss = imp32_findbyaddress (vaddr);
	      if (inss)
		*ppimpname = inss;
	    }
	  sz+=4;
        }
      else if((b&0xc0)==0x40)
	sz+=1;
      else if((b&0xc0)==0x80)
	sz+=4;
    } else {
      if((b&0xc0)==0) {
	if((b&0x07)==6) sz+=2;
      } else if((b&0xc0)==0x40)
	sz+=1;
      else if((b&0xc0)==0x80)
	sz+=2;
    }
    if (tb1==c_EGlv) sz+=(oper_mode ? 4 : 2);
    else if(tb1==c_EGlb) sz++;
    else if(tb1==c_g4) {
      if ((b&0x38)==0x20 || (b&0x38)==0x28)
	tb1=c_int3;
      else if((b&0x38)==0x38)
        tb1=c_ill;
    } else if (tb1==c_EGg3v || tb1==c_EGg3b) {
      switch (((b&0x38)>>3)) {
      case 1:
      case 0: sz+= (tb1==c_EGg3v ? (oper_mode ? 4 : 2) : 1); break;
      default: break;
      }
    }
    *aCode=tb1; return sz;
  case c_jxx: case c_jmpnjb:
    p = (unsigned char *) map_va (pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
    sz++;
    jmp_pc[0]=(uint32_t) pc + (uint32_t) sz;
    if ((b&0x80)!=0)
      jmp_pc[0] = jmp_pc[0] + (((uint32_t) b) | 0xffffff00);
    else
      jmp_pc[0] = jmp_pc[0] + (uint32_t) b;
    *aCode=tb1; return sz;
  case c_jmpnjv:
  case c_jxxv: 
    p = (unsigned char *) map_va (pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    if (oper_mode) { jmp_pc[0]=*((uint32_t *)p); sz+=4; }
    else {
      jmp_pc[0]=(uint32_t) *((uint16_t *)p);
      if ((jmp_pc[0]&0x8000)!=0) jmp_pc[0]|=0xffff0000;
      sz+=2;
    }
    jmp_pc[0]+=(uint32_t) pc+(uint32_t) sz;
#if ENABLE_DEBUG == 1
    if ((jmp_pc[0]&0xff000000)!=0) {
      print_save_insn (name, lw);
      PRDEBUG(" 0x%x illegal ", (unsigned int) b);
      PRDEBUG("jmp(cond) 0x%x (sz=%x,pc=%x,off=%x) ",
	      jmp_pc[0], (unsigned int) sz,(unsigned int) pc,
              (unsigned int) (jmp_pc[0]-(sz+pc)));
    }
#endif
    *aCode=(tb1==c_jxxv ? c_jxx : tb1);
    return sz;
  case c_0f:
    p = (unsigned char *) map_va (pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    b = p[0];
    sz++;
    tb1=opMap2[b];
    goto redo_switch;
  case c_jmpfap:
    sz+=4; if(oper_mode) sz+=2;
    *aCode=tb1; return sz;
  case c_callfar:
    sz+=4; if(oper_mode) sz+=2;
    *aCode=tb1; return sz;
  case c_retflw: case c_retnlw:
    p = (unsigned char *) map_va (pc + sz);
    if (!p) { *aCode=c_ill; return 0; }
    jmp_pc[0]=*((uint16_t*) p);
    sz+=2;
    *aCode=tb1;
#if ENABLE_DEBUG == 1
    if (jmp_pc[0]>0x100 || jmp_pc[0]&3) {
      print_save_insn (name, lw);
      PRDEBUG(" 0x%x illegal ", (unsigned int) b);
      PRDEBUG("ret dw 0x%x (sz=%x) ", (unsigned int) jmp_pc[0], (unsigned int) sz);
    }
#endif
    return sz;
  default:
    PRDEBUG(" * opcode 0x%x (tbl=%d) unknown\n", (unsigned int) b, tb1);
    sz=0; *aCode=c_ill; break;
  }
  return sz;
}

static void
decode_mangle (FILE *fp, const char *n)
{
#ifdef HAVE_LIBMANGLE
  libmangle_gc_context_t *gc = libmangle_generate_gc ();
  libmangle_tokens_t ptok;
#endif
  if (!fp || !n || *n == 0)
    return;
#ifdef HAVE_LIBMANGLE
  ptok = libmangle_decode_ms_name (gc, n);
  if (ptok)
    {
      char *h = libmangle_sprint_decl (ptok);
      if (h)
	{
	  fprintf (fp, "; %s\n", h);
	  free (h);
	}
    }
  libmangle_release_gc (gc);
#endif
}
