/*
     RUN_ELF - Load and execute big ending ix86 ELF binaries
     Copyright (C) 2001-2002 Martin Blom <martin@blom.org>
     
     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 2
     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, write to the Free Software
     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <sys/mman.h>
#include <byteswap.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "dis-asm.h"
#include "run_elf86.h"

#define PAGE_SIZE 4096

/******************************************************************************/

#define SAVESIGS 0

#define HOOK( ret, func ) ret func
#define NATMEM_OFFSET (0)
#define REGPARAM __attribute__((regparm(3)))
#define STATIC_INLINE static __inline__

typedef unsigned char uae_u8;
typedef unsigned short uae_u16;
typedef unsigned long uae_u32;
typedef unsigned long uaecptr;
typedef unsigned long uae_cptr;
typedef char flagtype;

typedef uae_u32 (*mem_get_func)(uaecptr) REGPARAM;
typedef void (*mem_put_func)(uaecptr, uae_u32) REGPARAM;
typedef uae_u8 *(*xlate_func)(uaecptr) REGPARAM;
typedef int (*check_func)(uaecptr, uae_u32) REGPARAM;

#define call_mem_get_func(func,addr) ((*func)(addr))
#define call_mem_put_func(func,addr,v) ((*func)(addr,v))

typedef struct {
    /* These ones should be self-explanatory... */
    mem_get_func lget, wget, bget;
    mem_put_func lput, wput, bput;
    /* Use xlateaddr to translate an Amiga address to a uae_u8 * that can
     * be used to address memory without calling the wget/wput functions.
     * This doesn't work for all memory banks, so this function may call
     * abort(). */
    xlate_func xlateaddr;
    /* To prevent calls to abort(), use check before calling xlateaddr.
     * It checks not only that the memory bank can do xlateaddr, but also
     * that the pointer points to an area of at least the specified size.
     * This is used for example to translate bitplane pointers in custom.c */
    check_func check;
    /* For those banks that refer to real memory, we can save the whole trouble
       of going through function calls, and instead simply grab the memory
       ourselves. This holds the memory address where the start of memory is
       for this particular bank. */
    uae_u8* baseaddr;
} addrbank;

extern addrbank* mem_banks[65536];
#define bankindex(addr) (((uaecptr)(addr)) >> 16)
#define get_mem_bank(addr) (mem_banks[bankindex(addr)])

#define longget(addr) (call_mem_get_func(get_mem_bank(addr)->lget, addr))
#define wordget(addr) (call_mem_get_func(get_mem_bank(addr)->wget, addr))
#define byteget(addr) (call_mem_get_func(get_mem_bank(addr)->bget, addr))
#define longput(addr,l) (call_mem_put_func(get_mem_bank(addr)->lput, addr, l))
#define wordput(addr,w) (call_mem_put_func(get_mem_bank(addr)->wput, addr, w))
#define byteput(addr,b) (call_mem_put_func(get_mem_bank(addr)->bput, addr, b))

#define longget_1 longget
#define wordget_1 wordget
#define byteget_1 byteget
#define longput_1 longput
#define wordput_1 wordput
#define byteput_1 byteput


STATIC_INLINE uae_u32 get_long(uaecptr addr)
{
  return longget_1(addr);
}
STATIC_INLINE uae_u32 get_word(uaecptr addr)
{
  return wordget_1(addr);
}
STATIC_INLINE uae_u32 get_byte(uaecptr addr)
{
  return byteget_1(addr);
}
STATIC_INLINE void put_long(uaecptr addr, uae_u32 l)
{
  longput_1(addr, l);
}
STATIC_INLINE void put_word(uaecptr addr, uae_u32 w)
{
  wordput_1(addr, w);
}
STATIC_INLINE void put_byte(uaecptr addr, uae_u32 b)
{
  byteput_1(addr, b);
}

STATIC_INLINE uae_u8 *get_real_address(uaecptr addr)
{
  return get_mem_bank(addr)->xlateaddr(addr);
}

STATIC_INLINE int valid_address(uaecptr addr, uae_u32 size)
{
  return get_mem_bank(addr)->check(addr, size);
}

#define m68k_setpc( pc ) regs.regs[22] = regs.regs[23] = regs.regs[24] = pc;

extern jmp_buf m68k;
extern uae_u32 m68kvalid;

#ifndef m68k_dreg
#define m68k_dreg(r,num) ((r).regs[(num)])
#endif

#ifndef m68k_areg
#define m68k_areg(r,num) (((r).regs + 8)[(num)])
#endif

extern struct regstruct
{
  uae_u32 regs[16];
  uaecptr  usp,isp,msp;
  uae_u16 sr;
  flagtype t1;
  flagtype t0;
  flagtype s;
  flagtype m;
  flagtype x;
  flagtype stopped;
  int intmask;
  
  uae_u32 pc;
  uae_u8 *pc_p;
  uae_u8 *pc_oldp;
} regs;


/******************************************************************************/

__asm__("
	.globl	CallNativeInitFunction
	.type	CallNativeInitFunction,@function

	.balign	4
CallNativeInitFunction:
	bswapl	%ecx
	pushl	%ecx		# push d2 (ExecBase) as big endian
	bswapl	%edx
	pushl	%edx		# push d1 (segList) as big endian
	bswapl	%eax
	pushl	%eax		# push d0 (library base) as big endian
	call	*%ebx		# call a0 (ix86 native libinit function)
	addl	$12,%esp
	ret
");


/******************************************************************************/

static struct sigaction     Act;
static struct sigaction     OldAct;
static struct GuardContext* GuardContext = NULL;

static int
MySPrintF( char *str, const char *format, ... )
{
  va_list ap;
  
  va_start( ap, format );
  return vsprintf( str + strlen( str ) , format, ap );
}

static int
Disassemble86( char*         dst,
	       unsigned long addr )
{
  disassemble_info info;
  char             tmp[ 128 ];
  int              len;
  int              i;

  tmp[ 0 ] = 0;
  INIT_DISASSEMBLE_INFO( info, (FILE*) tmp, (fprintf_ftype) MySPrintF );
  info.buffer_length = 0xffffffff;
  
  len = print_insn_i386( addr, &info );

  for( i = 0; i < len; ++i )
  {
    if( i == 0 )
    {
      MySPrintF( dst, "%08X:", addr );
    }
    else if( i == info.bytes_per_line )
    {
      MySPrintF( dst, "    %s\n         ", tmp );
    }
    else if( i % info.bytes_per_line == 0 )
    {
      strcat( dst, "\n         " );
    }

    MySPrintF( dst, " %02x", ((unsigned char*) addr)[ i ] );
  }

  if( i <= info.bytes_per_line )
  {
    while( i < info.bytes_per_line )
    {
      strcat( dst, "   " );
      ++i;
    }

    MySPrintF( dst, "    %s", tmp );
  }

  strcat( dst, "\n" );

  return len;
}


// print_insn_m68k() needs floatformat_ieee_double_big from libiberty,
// which declares a COMMON symbol. For some reason, Amithlon refuses
// to load objects with common symbols. This line makes ld resolve
// _xexit_cleanup:

void (*_xexit_cleanup) PARAMS ((void)) = NULL;

static int
Disassemble68( char*         dst,
	       unsigned long addr )
{
  disassemble_info info;
  char             tmp[ 128 ];
  int              len;
  int              i;

  tmp[ 0 ] = 0;
  INIT_DISASSEMBLE_INFO( info, (FILE*) tmp, (fprintf_ftype) MySPrintF );
  info.buffer_length = 0xffffffff;
  
  len = print_insn_m68k( addr, &info );

  for( i = 0; i < len; i += 2 )
  {
    if( i == 0 )
    {
      MySPrintF( dst, "%08X:", addr );
    }
    else if( i == info.bytes_per_line )
    {
      MySPrintF( dst, "    %s\n         ", tmp );
    }
    else if( i % info.bytes_per_line == 0 )
    {
      strcat( dst, "\n         " );
    }

    MySPrintF( dst, " %02x%02x",
	       ((unsigned char*) addr)[ i ],
	       ((unsigned char*) addr)[ i + 1 ] );
  }

  if( i <= info.bytes_per_line )
  {
    while( i < info.bytes_per_line )
    {
      strcat( dst, "     " );
      i += 2;
    }

    MySPrintF( dst, "    %s", tmp );
  }

  strcat( dst, "\n" );

  return len;
}


extern void RestoreProtection(void);

__asm__( "
RestoreProtection:
	pushf
	pushl	%eax
	pushl	%ecx
	pushl	%edx

	movl	16(%esp),%eax
	pushl	$0		# No access
	pushl	$0x1000		# PAGE_SIZE
	pushl	%eax		# Page address
	call	mprotect
	addl	$12,%esp

	popl	%edx
	popl	%ecx
	popl	%eax
	popf
	addl	$4,%esp		# Skip page address
	ret
");


static void
GuardHandler( int        sig,
	      siginfo_t* siginfo,
	      void*      context )
{
  struct sigcontext* scp = (struct sigcontext*) ((unsigned long) context + 20);
  int                insn_skip;
  unsigned long      insn_start;
  int                i;
  struct Hit*        hit = &GuardContext->hits[ GuardContext->write_index ];

  if( (unsigned long) siginfo->si_addr < PAGE_SIZE )
  {
    hit->disasm[ 0 ] = 0;
    insn_skip = Disassemble86( hit->disasm, scp->eip );
    
    // Report all write hits and read hits from any address but 4.

    if( scp->err & 2 || siginfo->si_addr != (void*) 4 )
    {
      unsigned long* execbase;

      mprotect( (void*) 0, PAGE_SIZE, PROT_READ );
      execbase = (unsigned long*) bswap_32( *(unsigned long*) 4 );
      mprotect( (void*) 0, PAGE_SIZE, 0 );

      hit->addr    = bswap_32( (unsigned long) siginfo->si_addr );
      hit->task    = execbase[ 69 ];
      hit->mode    = ( scp->err & 2 ) ? MODE_WRITE : MODE_READ;

      if( (unsigned long) regs.pc_p & 1 )
      {
	hit->cpu = CPU_IX86;
	hit->pc = bswap_32( scp->eip );
      
	hit->c.ix86.eflags  = bswap_32( scp->eflags );
	hit->c.ix86.eax     = bswap_32( scp->eax );
	hit->c.ix86.ebx     = bswap_32( scp->ebx );
	hit->c.ix86.ecx     = bswap_32( scp->ecx );
	hit->c.ix86.edx     = bswap_32( scp->edx );
	hit->c.ix86.esi     = bswap_32( scp->esi );
	hit->c.ix86.edi     = bswap_32( scp->edi );
	hit->c.ix86.esp     = bswap_32( scp->esp );
	hit->c.ix86.ebp     = bswap_32( scp->ebp );
      
	for( i = 0; i < 16; ++i )
	{
	  hit->stack[ i ] = ((unsigned long*) scp->esp)[ i ];
	}

	insn_start = scp->eip + insn_skip;
	insn_start += Disassemble86( hit->disasm, insn_start );
	insn_start += Disassemble86( hit->disasm, insn_start );
	insn_start += Disassemble86( hit->disasm, insn_start );
      }
      else
      {
	unsigned long* sp = (unsigned long*) m68k_areg( regs, 7 );
	
	hit->cpu = CPU_M68K;
	hit->pc = bswap_32( (unsigned long) regs.pc_p );

	for( i = 0; i < 8; ++i )
	{
	  hit->c.m68k.dregs[ i ] = bswap_32( m68k_dreg( regs, i ) );
	}
      
	for( i = 0; i < 8; ++i )
	{
	  hit->c.m68k.aregs[ i ] = bswap_32( m68k_areg( regs, i ) );
	}

	hit->c.m68k.sr  = bswap_16( regs.sr );

	for( i = 0; i < 16; ++i )
	{
	  hit->stack[ i ] = sp[ i ];
	}

	// Overwrite old ix86 disassembly
	hit->disasm[ 0 ] = 0; 
      
	// Now disassemble the m68k code
	insn_start = (unsigned long) regs.pc_p;
	insn_start += Disassemble68( hit->disasm, insn_start );
	insn_start += Disassemble68( hit->disasm, insn_start );
	insn_start += Disassemble68( hit->disasm, insn_start );
	insn_start += Disassemble68( hit->disasm, insn_start );
      }

      GuardContext->write_index++;
    }

    if( (scp->err & 2) == 0 )
    {
      // If reading, emulate it
      
      unsigned long page = (unsigned long) siginfo->si_addr & ~(PAGE_SIZE-1);
      char* read_insn = hit->insn.push_ret - insn_skip;

      // Allow reads from the page in question
      mprotect( (void*) page, PAGE_SIZE, PROT_READ );

      memcpy( read_insn, (const char*) scp->eip, insn_skip );
      hit->insn.push_ret[0] = 0x68;
      *(unsigned long*)( &hit->insn.push_ret[1] ) = scp->eip + insn_skip;
      hit->insn.push_page[0] = 0x68;
      *(unsigned long*)( &hit->insn.push_page[1] ) = page;
      hit->insn.jmp[0] = 0xe9;
      *(unsigned long*)( &hit->insn.jmp[1] ) =
	(unsigned long) RestoreProtection -
	(unsigned long) &hit->insn.jmp[5];

      scp->eip = (unsigned long) read_insn;
    }
    else
    {
      // Skip all writes
      scp->eip += insn_skip;
    }
  }
  else
  {
    if ( OldAct.sa_flags & SA_SIGINFO )
    {
      if( OldAct.sa_sigaction == (void (*)(int, siginfo_t*, void*)) SIG_DFL )
      {
	abort();
      }
      else if( OldAct.sa_sigaction != (void (*)(int, siginfo_t*, void*)) SIG_IGN )
      {
	OldAct.sa_sigaction( sig, siginfo, context );
      }
    }
    else
    {
      if( OldAct.sa_handler == SIG_DFL )
      {
	abort();
      }
      else if( OldAct.sa_handler != SIG_IGN )
      {
	// Do "OldAct.sa_handler( sig, *scp )" but also update *scp
	
	__asm__( "subl	%0,%%esp         # Allocate sigcontext on stack

                  movl  %%esp, %%ebx     # Save new sigcontext*
                  pushl %0               # sizeof struct sigcontext
                  pushl %1               # From original sigcontext*
                  pushl %%ebx            # To new sigcontext*
                  call  memcpy
                  addl  $12,%%esp

                  pushl %2               # Push sig
                  call  *%3              # Call sa_handler
                  addl  $4,%%esp

                  pushl %0               # sizeof struct sigcontext
                  pushl %%ebx            # From new sigcontext*
                  pushl %1               # To original sigcontext*
                  call  memcpy
                  addl  %0,%%esp"
                 :
		 : "rio" (sizeof *scp), "ro" (scp),
		   "ro" (sig), "r" (OldAct.sa_handler)
		 : "cc", "eax", "ebx", "ecx", "edx" );

      }
    }
  }
}
  
void
GuardDisable( struct GuardContext* c ) REGPARAM;

void
GuardDisable( struct GuardContext* c )
{
  if( c->zero_page )
  {
    mprotect( 0, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC );
  }

  if( c->installed )
  {
    sigaction( SIGSEGV, &OldAct, NULL );
  }

  GuardContext = NULL;
  free( c );
}

struct GuardContext*
GuardEnable( void ) REGPARAM;

struct GuardContext*
GuardEnable( void )
{
  struct GuardContext* c;

  Act.sa_handler   = 0;
  Act.sa_sigaction = GuardHandler;
  sigfillset( &Act.sa_mask );
  Act.sa_flags = SA_SIGINFO;

  c = malloc( sizeof( struct GuardContext ) );

  if( c != NULL )
  {
    if( sigaction( SIGSEGV, &Act, &OldAct ) == 0 )
    {
      c->installed = 1;
      
      if( mprotect( (void*) 0, PAGE_SIZE, 0 ) == 0 )
      {
	c->zero_page = 1;
      }
      else
      {
	GuardDisable( c );
	c = NULL;
      }
    }
    else
    {
      GuardDisable( c );
      c = NULL;
    }
  }

  GuardContext = c;
  return c;
}


/******************************************************************************/

static uae_u32
call68k_array( uae_u32* args, uae_u32 what ) REGPARAM __attribute__((unused));

static uae_u32
call68k_void( uae_u32 what ) REGPARAM;

static uae_u32
call68k_array( uae_u32* args, uae_u32 what )
{
  uae_u32 answer;
  uae_u32 saved_regs[16];
  int     i;
  
  for( i = 0; i < 15; ++i )
  {
    saved_regs[ i ] = regs.regs[ i ];
    regs.regs[ i ]  = args[ i ];
  }

  answer = call68k_void( what );
  
  for( i = 0; i < 15; ++i )
  {
    regs.regs[ i ] = saved_regs[ i ];
  }

  return answer;
}


static uae_u32
call68k_void( uae_u32 what )
{
  uae_u32 A7=m68kvalid;
  uae_u32 oldA7;
  uae_u32 addr;
  uae_u32 new68kstack[10];
  uae_u32 answer;
    
  jmp_buf* x86;

  if (!m68kvalid) {
    fprintf(stderr,"_CallLib68k/_CallFunc68k without 68k context!\n");
    abort();
  }

  m68kvalid=0;
  oldA7=get_long(A7+4);
  addr=get_long(A7);

  m68k_setpc(what);

  x86=(void*)(oldA7-sizeof(jmp_buf)+NATMEM_OFFSET);

  if (!sigsetjmp(*x86,SAVESIGS)) { /* original, head to 68k thread */
    uae_u32 newA7=((uae_u32)new68kstack) - NATMEM_OFFSET;
    put_long(newA7+4,addr);
    put_long(newA7+8,oldA7);
    put_long(newA7,addr+1); /* This one is an x86-holder return address */
        
    m68k_areg(regs,7)=(uae_u32)newA7;
            
    siglongjmp(m68k,-1);
    abort();
  }
  else {
    /* Im baaaaaaaaaack */
    answer=m68k_dreg(regs,0);
  }

  return answer;
}


/******************************************************************************/

__asm__("
	.globl	_CallLib68k
	.type	_CallLib68k,@function

	.globl	_CallFunc68k
	.type	_CallFunc68k,@function

	.balign	4
	
# struct _Regs* in eax, negative offset in edx.
# eax->a6 + edx is *always* even and a real, non-magic address.
_CallLib68k:
	addl	0x38(%eax),%edx
	cmpw	$0xf94e,(%edx)		# Check for (byteswapped) JMP op-code
	jne	call68k_array
	movl	2(%edx),%edx
	bswap	%edx
	testl	$0x80000001,%edx
	jns	1f			# Jump if bit 31 not set
	jp	1f			# Jump if bit 0 not set
	andl	$0x7ffffffc,%edx	# Mask bslowcall/bfastcall bits
	jmp	*%edx

	.balign	4
	
# struct _Regs* in eax, address in edx
# edx may be odd or have the two topmost bits set
_CallFunc68k:
	testl	$0x80000001,%edx
	jns	1f			# Jump if bit 31 not set
	jp	1f			# Jump if bit 0 not set
	andl	$0x7ffffffc,%edx	# Mask bslowcall/bfastcall bits
	jmp	*%edx
1:
	movl	%edx,%ecx
	andl	$3,%ecx
	decl	%ecx
	jnz	call68k_array
	andl	$0xfffffffc,%edx

	# emulate a slowcall (should regs be saved/loaded/restored as well?)

	pushl	%ebp
	pushl	%ebx
	pushl	%esi
	pushl	%edi

	movl	%edx,%edi
	
        pushl	0x38(%eax)
        pushl	0x34(%eax)
        pushl	0x30(%eax)
        pushl	0x2c(%eax)
        pushl	0x28(%eax)
        pushl	0x24(%eax)
        pushl	0x20(%eax)
        movl	0x28(%eax),%esi
        movl	0x24(%eax),%ebp
        movl	0x20(%eax),%ebx
        movl	0x08(%eax),%ecx
        movl	0x04(%eax),%edx
        movl	0x00(%eax),%eax
        call	*%edi
        addl	$28,%esp
	popl	%edi
	popl	%esi
	popl	%ebx
        popl	%ebp
	ret
");
