/*
 *  smoke16/src/emu.c - The user-mode SMOKE-16 emulator.
 *  $Revision: 1.65 $
 *  Copyright 1997-2002, Benjamin Sittler
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* #define DEBUG_OBJ */
/* #define DEBUG_PC */
#define DEBUG_DUMP
#define DEBUG_OP

#ifdef DEBUG_OP
int debug_op = 0;
int debug_sys = 0;
#endif

#ifdef DEBUG_DUMP
int debug_dump = 0;
#endif

#include <assert.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>

#undef raise
#define raise(x) (exit((x)-128),1)

#include <smoke16/fcntl.h>
#ifndef EXCLUDE_GETOPT
#include "getopt.h"
#endif
#include <smoke16/a_out.h>
#include <smoke16/regs.h>
#include <smoke16/syscall.h>
#include <smoke16/libObj.h>

#ifndef SIGBUS
#define SIGBUS SIGSEGV
#endif

#if defined(__GNUC__) && (__GNUC__ > 1)
#define unused __attribute__((unused))
#else
#define unused
#endif

static char unused RCSID[] = "$Id: emu.c,v 1.65 2002/07/28 23:26:33 bsittler Exp $";

const char *sregname[4] = {
  "%pc", "%ps", "%pt", "%tt"
};

const char *regname[16] = {
  "%0",
  "%t0", "%t1", "%t2", "%t3", "%t4", "%t5", "%t6",
  "%a0", "%a1", "%a2", "%a3",
  "%fp", "%sp", "%ra", "%gp"
};

#define STACK_SAFETY 1024

struct smoke16_task {
  struct smoke16_regs regs;
  u16 min_text;
  u16 max_text;
  u16 min_data;
  u16 max_data;
  u16 min_bss;
  u16 max_bss;
  u16 min_heap;
  u16 max_heap;
  u16 min_stack;
  u16 max_stack;
  u8 *icore, *core;
  char *strs;
  struct a_out16_nlist *syms;
  struct a_out16_exec *exec;
};

int show_numbers = 1;
int show_symbols = 1;

void put_symbolic(struct smoke16_task *task, FILE *stream, u16 addr) {
  u16 i, value = 0x0000U;
  char *str = 0;

  if (show_symbols) {
    if ((addr >= task -> min_text) &&
	(addr < task -> max_text) &&
	(task -> exec -> a_magic != SMOKE16_JMAGIC)) {
      str = ".text";
      value = task -> min_text;
    } else if ((addr >= task -> min_data) &&
	       (addr < task -> max_data)) {
      str = ".data";
      value = task -> min_data;
    } else if ((addr >= task -> min_bss) &&
	       (addr < task -> max_bss)) {
      str = ".bss";
      value = task -> min_bss;
    }
    for (i = 0;
	 i < (task -> exec -> a_syms /
	      fsz16nlist(SMOKE16_A_TOOLVERSION(*task -> exec)));
	 i++) {
      struct a_out16_nlist nlist = task -> syms[i];

      if (((nlist . n_type & (SMOKE16_N_EXT | SMOKE16_N_TYPE)) ==
	   (SMOKE16_N_EXT | SMOKE16_N_TEXT)) &&
	  (task -> exec -> a_magic != SMOKE16_JMAGIC)) {
	if (nlist . n_value + task -> min_text <= addr)
	  if (nlist . n_strx && task -> strs[nlist . n_strx])
	    if ((nlist . n_value + task -> min_text >= value) &&
		(task -> max_text > addr)) {
	      value = nlist . n_value + task -> min_text;
	      str = task -> strs + nlist . n_strx;
	    }
      } else if ((nlist . n_type & (SMOKE16_N_EXT | SMOKE16_N_TYPE)) ==
		 (SMOKE16_N_EXT | SMOKE16_N_DATA)) {
	if (nlist . n_value + task -> min_data <= addr)
	  if (nlist . n_strx && task -> strs[nlist . n_strx])
	    if ((nlist . n_value + task -> min_data >= value) &&
		(task -> max_data > addr)) {
	      value = nlist . n_value + task -> min_data;
	      str = task -> strs + nlist . n_strx;
	    }
      } else if ((nlist . n_type & (SMOKE16_N_EXT | SMOKE16_N_TYPE)) ==
		 (SMOKE16_N_EXT | SMOKE16_N_BSS)) {
	if (nlist . n_value + task -> min_bss <= addr)
	  if (nlist . n_strx && task -> strs[nlist . n_strx])
	    if ((nlist . n_value + task -> min_bss >= value) &&
		(task -> max_bss > addr)) {
	      value = nlist . n_value + task -> min_bss;
	      str = task -> strs + nlist . n_strx;
	    }
      } else if ((nlist . n_type & (SMOKE16_N_EXT | SMOKE16_N_TYPE)) ==
		 (SMOKE16_N_EXT | SMOKE16_N_ABS)) {
	if (nlist . n_value <= addr)
	  if (nlist . n_strx && task -> strs[nlist . n_strx])
	    if (nlist . n_value >= value) {
	      value = nlist . n_value;
	      str = task -> strs + nlist . n_strx;
	    }
      }
    }
  }
  if (! str)
    fprintf(stream, "%#4.4" FMT_x16, addr);
  else if (addr - value) {
    if (show_numbers)
      fprintf(stream, "%#4.4" FMT_x16 " <%s+%#" FMT_x16 ">", addr, str, addr - value);
    else
      fprintf(stream, "%s+%#" FMT_x16, str, addr - value);
  } else {
    if (show_numbers)
      fprintf(stream, "%#4.4" FMT_x16 " <%s>", addr, str);
    else
      fprintf(stream, "%s", str);
  }
}

void put_isymbolic(struct smoke16_task *task, FILE *stream, u16 addr) {
  u16 i, value = 0x0000U;
  char *str = 0;

  if (task -> exec -> a_magic != SMOKE16_JMAGIC) {
    put_symbolic(task, stream, addr);
    return;
  }
  if (show_symbols) {
    if ((addr >= task -> min_text) &&
	(addr < task -> max_text)) {
      str = ".text";
      value = task -> min_text;
    }
    for (i = 0;
	 i < (task -> exec -> a_syms /
	      fsz16nlist(SMOKE16_A_TOOLVERSION(*task -> exec)));
	 i++) {
      struct a_out16_nlist nlist = task -> syms[i];

      if ((nlist . n_type & (SMOKE16_N_EXT | SMOKE16_N_TYPE)) ==
	  (SMOKE16_N_EXT | SMOKE16_N_TEXT)) {
	if ((nlist . n_value + task -> min_text <= addr) &&
	    (task -> max_text > addr))
	  if (nlist . n_strx && task -> strs[nlist . n_strx])
	    if (nlist . n_value + task -> min_text >= value) {
	      value = nlist . n_value + task -> min_text;
	      str = task -> strs + nlist . n_strx;
	    }
      } else if ((nlist . n_type & (SMOKE16_N_EXT | SMOKE16_N_TYPE)) ==
		 (SMOKE16_N_EXT | SMOKE16_N_ABS)) {
	if (nlist . n_value <= addr)
	  if (nlist . n_strx && task -> strs[nlist . n_strx])
	    if (nlist . n_value >= value) {
	      value = nlist . n_value;
	      str = task -> strs + nlist . n_strx;
	    }
      }
    }
  }
  if (! str)
    fprintf(stream, "%#4.4" FMT_x16, addr);
  else if (addr - value) {
    if (show_numbers)
      fprintf(stream, "%#4.4" FMT_x16 " <%s+%#" FMT_x16 ">", addr, str, addr - value);
    else
      fprintf(stream, "%s+%#" FMT_x16, str, addr - value);
  } else {
    if (show_numbers)
      fprintf(stream, "%#4.4" FMT_x16 " <%s>", addr, str);
    else
      fprintf(stream, "%s", str);
  }
}

typedef enum { CHECK_READ, CHECK_WRITE, CHECK_EXEC } CheckType;

/* verify an address for a given operation */
int check_ok(CheckType which, struct smoke16_task *task,
	     u16 addr) {
  if ((addr >= task -> min_text) &&
      (addr < task -> max_text) &&
      ((which == CHECK_EXEC) ||
       (task -> exec -> a_magic != SMOKE16_JMAGIC)))
    switch (task -> exec -> a_magic) {
    case SMOKE16_JMAGIC:
      if (which == CHECK_EXEC)
	return 1;
      break;
    case SMOKE16_OMAGIC:
      return 1;
      break;
    default:
      if ((which == CHECK_READ) ||
	  (which == CHECK_EXEC))
	return 1;
      break;
    }
  if ((which != CHECK_EXEC) ||
      (task -> exec -> a_magic != SMOKE16_JMAGIC)) {
    if ((addr >= task -> min_data) &&
	(addr < task -> max_data))
      return 1;
    if ((addr >= task -> min_bss) &&
	(addr < task -> max_bss))
      return 1;
    if ((addr >= task -> min_heap) &&
	(addr < task -> max_heap))
      return 1;
    if ((addr >= task -> min_stack) &&
	(addr <= task -> max_stack))
      return 1;
    if ((addr >= task -> min_stack - STACK_SAFETY) &&
	(addr < task -> max_stack)) {
      if (task -> min_stack - task -> max_heap >= 2 * STACK_SAFETY) {
	task -> min_stack -= STACK_SAFETY;
	return 1;
      }
    }
  }
  return 0;
}

void dump(struct smoke16_task *task) {
  int i;

  fputs(" __S-REGS___ __________________REGISTERS_________________  _Top of stack_\n", stderr);
  for(i = 0; i < 4; i++) {
    int j = (i + 1) << 1;
    fprintf(stderr, "| %3.3s: %4.4" FMT_x16, sregname[i], task -> regs . s[i]);
    fprintf(stderr, " | %3.3s: %4.4" FMT_x16, regname[i], task -> regs . r[i]);
    fprintf(stderr, "  %3.3s: %4.4" FMT_x16, regname[i + 4], task -> regs . r[i + 4]);
    fprintf(stderr, "  %3.3s: %4.4" FMT_x16, regname[i + 8], task -> regs . r[i + 8]);
    fprintf(stderr, "  %3.3s: %4.4" FMT_x16, regname[i + 12], task -> regs . r[i + 12]);
    fprintf(stderr, " || %d(%3.3s): ",
	    j, regname[13]);
    if(! check_ok(CHECK_READ, task, j + task -> regs . _sp))
      fputs("..", stderr);
    else
      fprintf(stderr, "%2.2" FMT_x8, task -> core[j + task -> regs . _sp]);
    if(! check_ok(CHECK_READ, task, j + task -> regs . _sp + 1))
      fputs("..", stderr);
    else
      fprintf(stderr, "%2.2" FMT_x8, task -> core[j + task -> regs . _sp + 1]);
    fputs(" |\n", stderr);
  }
  fprintf(stderr, "|___________|____________________________________________||______________|\n");
  fprintf(stderr, "Insn at ");
  put_isymbolic(task, stderr, task -> regs . _pc);
  fputs(": ", stderr);
  if(! check_ok(CHECK_EXEC, task, task -> regs . _pc))
    fputs("..", stderr);
  else
    fprintf(stderr, "%2.2" FMT_x8, task -> icore[task -> regs . _pc]);
  if(! check_ok(CHECK_EXEC, task, task -> regs . _pc + 1))
    fputs("..", stderr);
  else
    fprintf(stderr, "%2.2" FMT_x8, task -> icore[task -> regs . _pc + 1]);
  fputc('\n', stderr);
  fflush(stderr);
}

void check(CheckType which, struct smoke16_task *task,
	   u16 addr) {
  if (check_ok(which, task, addr))
    return;
  fprintf(stderr, "SMOKE-16: Segmentation fault\n");
  dump(task), raise(SIGSEGV);
}

/* verify a null-terminated string for a given operation */
void checks(CheckType which, struct smoke16_task *task,
	    u16 addr) {
  do
    check(which, task, addr);
  while (task -> core[addr ++]);
}

/* verify a fixed-size buffer for a given operation */
void checkbuf(CheckType which, struct smoke16_task *task,
	      u16 addr, u16 len) {
  while (len) {
    len --;
    check(which, task, addr + len);
  }
}

#ifndef O_BINARY
#define O_BINARY 0
#endif

#ifndef O_NOCTTY
#define O_NOCTTY 0
#endif

#ifndef O_EXCL
#define O_EXCL 0
#endif

#ifndef O_NDELAY
#ifndef O_NONBLOCK
#define O_NDELAY 0
#else
#define O_NDELAY O_NONBLOCK
#endif
#endif

#ifndef O_SYNC
#ifndef O_DIRECT
#define O_SYNC 0
#else
#define O_SYNC O_DIRECT
#endif
#endif

int host_flags(u16 smoke_flags) {
  int flags = O_BINARY;

  if (smoke_flags & SMOKE16_O_RDONLY)
    flags |= O_RDONLY;
  if (smoke_flags & SMOKE16_O_WRONLY)
    flags |= O_WRONLY;
  if (smoke_flags & SMOKE16_O_RDWR)
    flags |= O_RDWR;
  if (smoke_flags & SMOKE16_O_NDELAY)
    flags |= O_NDELAY;
  if (smoke_flags & SMOKE16_O_APPEND)
    flags |= O_APPEND;
  if (smoke_flags & SMOKE16_O_CREAT)
    flags |= O_CREAT;
  if (smoke_flags & SMOKE16_O_TRUNC)
    flags |= O_TRUNC;
  if (smoke_flags & SMOKE16_O_NOCTTY)
    flags |= O_NOCTTY;
  if (smoke_flags & SMOKE16_O_EXCL)
    flags |= O_EXCL;
  if (smoke_flags & SMOKE16_O_SYNC)
    flags |= O_SYNC;
  return flags;
}

mode_t host_perms(u16 smoke_perms) {
  mode_t perms = 0;

  if (smoke_perms & 04000)
    perms |= S_ISUID;
  if (smoke_perms & 02000)
    perms |= S_ISGID;
#ifdef S_ISVTX
  if (smoke_perms & 01000)
    perms |= S_ISVTX;
#endif
  if (smoke_perms & 00400)
    perms |= S_IRUSR;
  if (smoke_perms & 00200)
    perms |= S_IWUSR;
  if (smoke_perms & 00100)
    perms |= S_IXUSR;
  if (smoke_perms & 00040)
    perms |= S_IRGRP;
  if (smoke_perms & 00020)
    perms |= S_IWGRP;
  if (smoke_perms & 00010)
    perms |= S_IXGRP;
  if (smoke_perms & 00004)
    perms |= S_IROTH;
  if (smoke_perms & 00002)
    perms |= S_IWOTH;
  if (smoke_perms & 00001)
    perms |= S_IXOTH;
  return perms;
}

void interrupt(struct smoke16_task *task, u8 intnum) {
  int tmp;
  s16 signed_arg, signed_arg2;

  switch(intnum) {
  case SMOKE16_SYS__EXIT:
      signed_arg = u16_to_s16(task -> regs . _a0);
#ifdef DEBUG_OP
      if (debug_sys) {
          fprintf(stderr, "sys__exit(%" FMT_s16 ") = \?\n",
                  signed_arg);
          fflush(stderr);
      }
#endif
      exit(signed_arg);
      break;
  case SMOKE16_SYS_RAISE:
      signed_arg = u16_to_s16(task -> regs . _a0);
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_raise(%" FMT_s16,
	      signed_arg);
      fflush(stderr);
    }
#endif
    tmp = errno;
    errno = task -> regs . _a1;
    task -> regs . _a0 = raise(signed_arg);
    task -> regs . _a1 = errno;
    errno = tmp;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  case SMOKE16_SYS_OPEN:
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_open(");
      put_symbolic(task, stderr, task -> regs . _a0);
      fprintf(stderr, ", %#" FMT_x16 ", %#" FMT_o16,
	      task -> regs . _a1,
	      task -> regs . _a2);
      fflush(stderr);
    }
#endif
    tmp = errno;
    errno = task -> regs . _a3;
    checks(CHECK_READ, task, task -> regs . _a0);
    task -> regs . _a0 = open((char *)task -> core + task -> regs . _a0,
			    host_flags(task -> regs . _a1),
			    host_perms(task -> regs . _a2));
    task -> regs . _a1 = errno;
    errno = tmp;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  case SMOKE16_SYS_CLOSE:
      signed_arg = u16_to_s16(task -> regs . _a0);
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_close(%" FMT_s16,
	      signed_arg);
      fflush(stderr);
    }
#endif
    tmp = errno;
    errno = task -> regs . _a1;
    task -> regs . _a0 = close(signed_arg);
    task -> regs . _a1 = errno;
    errno = tmp;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  case SMOKE16_SYS_WRITE:
      signed_arg = u16_to_s16(task -> regs . _a0);
      signed_arg2 = u16_to_s16(task -> regs . _a2);
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_write(%" FMT_s16 ", ",
	      signed_arg);
      put_symbolic(task, stderr, task -> regs . _a1);
      fprintf(stderr, ", %" FMT_s16,
	      signed_arg2);
      fflush(stderr);
    }
#endif
    tmp = errno;
    errno = task -> regs . _a3;
    checkbuf(CHECK_READ, task, task -> regs . _a1, task -> regs . _a2);
    task -> regs . _a0 = write(signed_arg,
                               (char *)(task -> core + task -> regs . _a1),
                               signed_arg2);
    task -> regs . _a1 = errno;
    errno = tmp;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  case SMOKE16_SYS_READ:
      signed_arg = u16_to_s16(task -> regs . _a0);
      signed_arg2 = u16_to_s16(task -> regs . _a2);
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_read(%" FMT_s16 ", ",
	      signed_arg);
      put_symbolic(task, stderr, task -> regs . _a1);
      fprintf(stderr, ", %" FMT_s16,
	      signed_arg2);
      fflush(stderr);
    }
#endif
    tmp = errno;
    errno = task -> regs . _a3;
    checkbuf(CHECK_WRITE, task, task -> regs . _a1, task -> regs . _a2);
    task -> regs . _a0 = read(signed_arg,
                              (char *)(task -> core + task -> regs . _a1),
                              signed_arg2);
    task -> regs . _a1 = errno;
    errno = tmp;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  case SMOKE16__SYS_REGDUMP:
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_regdump(\n");
      fflush(stderr);
    }
#endif
    fputs("\n*** SYS_REGDUMP ***\n", stderr);
    dump(task);
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ")\n");
#endif
    break;
  case SMOKE16__SYS_PERROR:
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_perror(");
      put_symbolic(task, stderr, task -> regs . _a0);
      fflush(stderr);
    }
#endif
    tmp = errno;
    errno = task -> regs . _a1;
    checks(CHECK_READ, task, task -> regs . _a0);
    perror(task -> regs . _a0 ?
	   ((char *)(task -> core + task -> regs . _a0)) :
	   (char *)0);
    task -> regs . _a0 = errno;
    errno = tmp;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  case SMOKE16__SYS_BRK: /* brk(0) is a hack */
#ifdef DEBUG_OP
    if (debug_sys) {
      fprintf(stderr, "sys_brk(");
      put_symbolic(task, stderr, task -> regs . _a0);
      fflush(stderr);
    }
#endif
    if (task -> regs . _a0)
      if ((task -> regs . _a0 + STACK_SAFETY < task -> min_stack) &&
	  (task -> regs . _a0 >= task -> min_heap))
      {
	task -> regs . _a0 =
            (task -> max_heap = task -> regs . _a0);
      }
      else
      {
	task -> regs . _a0 = (u16) -1;
	task -> regs . _a1 = ENOMEM;
      }
    else
      task -> regs . _a0 = task -> max_heap;
#ifdef DEBUG_OP
    if (debug_sys)
      fprintf(stderr, ") = %" FMT_s16 "\n",
	      u16_to_s16(task -> regs . _a0));
#endif
    break;
  default:
    fprintf(stderr, "SMOKE-16: Illegal instruction\n");
    dump(task), raise(SIGILL);
    break;
  }
}

void emulate(u8 *icore,
	     u8 *core,
	     struct a_out16_exec *exec,
	     u16 max_stack,
	     u16 tos,
	     u16 argc, u16 argv, u16 envp,
	     struct a_out16_nlist *syms,
	     char *strs,
	     u16 Ttext,
	     u16 Tdata,
	     u16 Tbss) {
  struct smoke16_task
    task[1];
  int i;
#ifdef DEBUG_OP
  int joined = 0; /* for debugging sethi/movb pairs */
  int joined_addr = 0;
  u16 joined_word = 0x0000U;
#endif

  task -> icore = icore;
  task -> core = core;
  task -> syms = syms;
  task -> strs = strs;
  task -> exec = exec;
  task -> min_text = Ttext;
  task -> max_text = Ttext + exec -> a_text;
  task -> min_data = Tdata;
  task -> max_data = Tdata + exec -> a_data;
  task -> min_bss = Tbss;
  task -> max_bss = Tbss + exec -> a_bss;
  task -> min_heap = task -> max_bss;
  if (task -> max_data > task -> min_heap)
    task -> min_heap = task -> max_data;
  if (task -> max_text > task -> min_heap)
    task -> min_heap = task -> max_text;
  task -> max_heap = task -> min_heap;
  task -> max_stack = max_stack;
  task -> min_stack = tos - STACK_SAFETY;
  task -> regs . _pc = exec -> a_entry;
  task -> regs . _ps = 0;
  task -> regs . _pt = 0;
  task -> regs . _tt = 0;
  for(i = 0; i < 16; i++)
    task -> regs . r[i] = 0;
  task -> regs . _sp = tos;
  task -> regs . _a0 = argc;
  task -> regs . _a1 = argv;
  task -> regs . _a2 = envp;
  while(1) {
    u16 insn = 0x0000U, addr = 0x0000U;
    u8 op, immed, src1, src2, dest;
    unsigned long result;

    checkbuf(CHECK_EXEC, task, task -> regs . _pc, 2);
    insn = (icore[task -> regs . _pc] << 8) | icore[task -> regs . _pc + 1];
    if (task -> regs . _pc & 1) {
      fprintf(stderr, "SMOKE-16: Bus error.\n");
      dump(task), raise(SIGBUS);
    }
    immed = insn & 0xffU;
    src2 = immed & 0xfU;
    src1 = immed >> 4;
    dest = (insn >> 8) & 0xfU;
    op = (insn >> 12) & 0xfU;

#ifdef DEBUG_PC
    fprintf(stderr, "%s: %4.4" FMT_x16 " Op: %4.4" FMT_x16 "%c", sregname[0], task -> regs . _pc, insn,
#ifdef DEBUG_OP
	    debug_op ? ' ' :
#endif
	    '\r'
	    );
#endif

#ifdef DEBUG_DUMP
    if(debug_dump)
      dump(task);
#endif

#ifdef DEBUG_OP
    if (debug_op) {
      put_isymbolic(task, stderr, task -> regs . _pc);
      fputs(":\t", stderr);
    }
#endif

    /* : OP :DEST:SRC1:SRC2: */
    switch(op) /* opcode */ {
    case 0: /* add rs1, rs2, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"add %s, %s, %s\n",
		regname[src1], regname[src2], regname[dest]);
#endif
      result = (unsigned long)task -> regs . r[src1] + (unsigned long)task -> regs . r[src2];
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      if (dest)
	task -> regs . r[dest] = (u16)(result & 0xffffU);
      break;
    case 1: /* sub rs1, rs2, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"sub %s, %s, %s\n",
		regname[src1], regname[src2], regname[dest]);
#endif
      result = (unsigned long)task -> regs . r[src1] - (unsigned long)task -> regs . r[src2];
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      if (dest)
	task -> regs . r[dest] = (u16)(result & 0xffffU);
      break;
    case 2: /* rol rs, amt, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"rol %s, %" FMT_u8 ", %s\n",
		regname[src1], src2, regname[dest]);
#endif
      result =
	(unsigned long)(task -> regs . r[src1] << src2) |
	(unsigned long)(task -> regs . r[src1] >> (16 - src2));
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      if (dest)
	task -> regs . r[dest] = (u16)(result & 0xffffU);
      break;
    case 3: /* ror rs, amt, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"ror %s, %" FMT_u8 ", %s\n",
		regname[src1], src2, regname[dest]);
#endif
      result =
	((unsigned long)task -> regs . r[src1] >> src2) |
	((unsigned long)task -> regs . r[src1] << (16 - src2));
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      if (dest)
	task -> regs . r[dest] = (u16)(result & 0xffffU);
      break;
    case 4: /* and rs1, rs2, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"and %s, %s, %s\n",
		regname[src1], regname[src2], regname[dest]);
#endif
      if (dest)
	task -> regs . r[dest] = task -> regs . r[src1] & task -> regs . r[src2];
      break;
    case 5: /* or rs1, rs2, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"or %s, %s, %s\n",
		regname[src1], regname[src2], regname[dest]);
#endif
      if (dest)
	task -> regs . r[dest] = task -> regs . r[src1] | task -> regs . r[src2];
      break;
    case 6: /* mov [raddr + i4], rd */
      addr = task -> regs . r[src2] + (((u16) src1) << 1);
#ifdef DEBUG_OP
      if(debug_op) {
	fprintf(stderr,
		"mov [%s + %" FMT_u16 "], %s\n",
		regname[src2], ((u16) src1) << 1, regname[dest]);
      }
#endif
      checkbuf(CHECK_READ, task, addr, 2);
      if (dest)
	task -> regs . r[dest] =
	  (core[addr] << 8) |
	  core[addr + 1];
      break;
    case 7: /* sethi i8, rd */
#ifdef DEBUG_OP
      if(debug_op) {
	u16 next_insn = 0x0000U;

	if (check_ok(CHECK_EXEC, task, task -> regs . _pc + 2) &&
	    check_ok(CHECK_EXEC, task, task -> regs . _pc + 3))
	  next_insn = (icore[task -> regs . _pc + 2] << 8) | icore[task -> regs . _pc + 3];

	if (((next_insn >> 12) == 14) &&
	    ((next_insn & 0x0f00U) == (insn & 0x0f00U))) {
	  u16 next_next_insn = 0x0000U;

	  if (check_ok(CHECK_EXEC, task, task -> regs . _pc + 4) &&
	      check_ok(CHECK_EXEC, task, task -> regs . _pc + 5))
	    next_next_insn = (icore[task -> regs . _pc + 4] << 8) | icore[task -> regs . _pc + 5];
	
	  joined = 1;
	  joined_word = (((u16) immed) << 8) | (next_insn & 0x00ffU);
	  /* heuristic to decide whether to print a word symbolically or numerically */
	  if (((next_next_insn & 0xf00fU) ==
               (0x6000U | dest))     /* IF following insn is mov/ld */
	      || ((next_next_insn & 0xf00fU) ==
                  (0xd000U | dest))  /* OR following insn is mov/st */
	      || ((next_next_insn & 0xff0fU) ==
                  (0xf200U | dest))  /* OR following insn is movb/ldb */
	      || ((next_next_insn & 0xff0fU) ==
                  (0xf300U | dest))) /* OR following insn is movb/stb */
	    joined_addr = 1; /* print the word symbolically */
	  else if((next_next_insn & 0xf00fU) ==
                  (0xc000U | dest)) /* IF following insn is jal */
	    joined_addr = 2; /* print the word insn-symbolically */
	  else
	    joined_addr = 0; /* otherwise, print the word numerically */
	  if (joined_addr) {
	    fputs("sethi %hi(", stderr);
	    if (joined_addr == 2)
	      put_isymbolic(task, stderr, joined_word);
	    else
	      put_symbolic(task, stderr, joined_word);
	    fprintf(stderr, "), %s\n",
		    regname[dest]);
	  } else
	    fprintf(stderr, "sethi %%hi(%#4.4" FMT_x16 "), %s\n",
		    joined_word, regname[dest]);
	} else {
	  joined = 0;
	  fprintf(stderr,
		  "sethi %%hi(%#2.2" FMT_x8 "00), %s\n",
		  immed, regname[dest]);
	}
      }
#endif
      if (dest) {
	task -> regs . r[dest] = (((u16) immed) << 8);
      }
      break;
    case 8: /* sl rs, amt, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"sl %s, %" FMT_u8 ", %s\n",
		regname[src1], src2, regname[dest]);
#endif
      result = (unsigned long)(task -> regs . r[src1] << src2);
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      if (dest)
	task -> regs . r[dest] = (u16)(result & 0xffffU);
      break;
    case 9: /* sr rs, amt, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"sr %s, %" FMT_u8 ", %s\n",
		regname[src1], src2, regname[dest]);
#endif
      result = task -> regs . r[src1];
      /* sign-extend the operand to 32 bits */
      if (result & 0x8000UL)
          result |= 0xFFFF0000UL;
      /* shift by all but the last position (so we can set the C flag) */
      result >>= (src2 - 1);
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 1) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      /* shift by the last position and truncate to 16 bits */
      if (dest)
	task -> regs . r[dest] = (u16)((result >> 1) & 0xFFFFUL);
      break;
    case 10: /* srl rs, amt, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"srl %s, %" FMT_u8 ", %s\n",
		regname[src1], src2, regname[dest]);
#endif
      result = (unsigned long)(task -> regs . r[src1] >> (src2 - 1));
      task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
      task -> regs . s[1] |=
	((result & 1) ? SMOKE16_PS_C : 0) |
	(result ? 0 : SMOKE16_PS_Z);
      if (dest)
	task -> regs . r[dest] = (u16)((result >> 1) & 0xffffU);
      break;
    case 11: /* xor rs1, rs2, rd */
#ifdef DEBUG_OP
      if(debug_op)
	fprintf(stderr,
		"xor %s, %s, %s\n",
		regname[src1], regname[src2], regname[dest]);
#endif
      if (dest)
	task -> regs . r[dest] = task -> regs . r[src1] ^ task -> regs . r[src2];
      break;
    case 12: /* jal raddr + roff, rret */
#ifdef DEBUG_OP
      if(debug_op) {
	fprintf(stderr,
		"jal %s + %s, %s\n",
		regname[src2], regname[src1], regname[dest]);
      }
#endif
      if(dest)
	task -> regs . r[dest] = task -> regs . _pc + 2;
      task -> regs . _pc = task -> regs . r[src1] + task -> regs . r[src2] - 2;
      break;
    case 13: /* mov rd, [raddr + i4] */
      addr = task -> regs . r[src2] + (((u16) src1) << 1);
#ifdef DEBUG_OP
      if(debug_op) {
	fprintf(stderr,
		"mov %s, [%s + %" FMT_u16 "]\n",
		regname[dest], regname[src2], ((u16) src1) << 1);
      }
#endif
      checkbuf(CHECK_WRITE, task, addr, 2);
      core[addr] = task -> regs . r[dest] >> 8;
      core[addr + 1] = task -> regs . r[dest] & 0xffU;
      break;
    case 14: /* movb i8, rd */
#ifdef DEBUG_OP
      if(debug_op) {
	if (joined) {
	  if (joined_addr) {
	    fputs("movb %lo(", stderr);
	    if (joined_addr == 2)
	      put_isymbolic(task, stderr, joined_word);
	    else
	      put_symbolic(task, stderr, joined_word);
	    fprintf(stderr, "), %s\n",
		    regname[dest]);
	  } else
	    fprintf(stderr, "movb %%lo(%#4.4" FMT_x16 "), %s\n", joined_word, regname[dest]);
	  joined = 0;
	} else {
	  fprintf(stderr,
		  "movb %#2.2" FMT_x8 ", %s",
		  immed, regname[dest]);
	  if (isprint(immed))
	    fprintf(stderr, " // '%c'", (char) immed);
	  else if (immed == '\b')
	    fputs(" // '\\b'", stderr);
	  else if (immed == '\f')
	    fputs(" // '\\f'", stderr);
	  else if (immed == '\n')
	    fputs(" // '\\n'", stderr);
	  else if (immed == '\r')
	    fputs(" // '\\r'", stderr);
	  else if (immed == '\t')
	    fputs(" // '\\t'", stderr);
	  else if (immed == '\v')
	    fputs(" // '\\v'", stderr);
	  else if (immed == '\0')
	    fputs(" // '\\0'", stderr);
	  else
	    fprintf(stderr, " // '\\%#" FMT_o8 "'", immed);
	  fputc('\n', stderr);
	}
      }
#endif
      if (dest) {
	task -> regs . r[dest] &= 0xff00U;
	task -> regs . r[dest] |= immed;
      }
      break;
    case 15:
      /* : 15 : OP :DEST:SRC1: */
      op = dest;
      dest = src1;
      src1 = src2;
      switch(op) /* opcode */ {
      case 0: /* not rs, rd */
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "not %s, %s\n",
		  regname[src1], regname[dest]);
#endif
	if (dest)
	  task -> regs . r[dest] = task -> regs . r[src1] ^ 0xFFFFU;
	break;
      case 1: /* cmp rs1, rs2 */
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "cmp %s, %s\n",
		  regname[dest], regname[src1]);
#endif
	task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
	if (task -> regs . r[dest] == task -> regs . r[src1])
	  task -> regs . s[1] |= SMOKE16_PS_Z;
	if (u16_to_s16(task -> regs . r[dest]) < u16_to_s16(task -> regs . r[src1]))
	  task -> regs . s[1] |= SMOKE16_PS_C;
	break;
      case 2: /* movb [raddr], rd */
#ifdef DEBUG_OP
	if(debug_op) {
	  fprintf(stderr,
		  "movb [%s], %s\n",
		  regname[src1], regname[dest]);
	}
#endif
	check(CHECK_READ, task, task -> regs . r[src1]);
	if (dest)
	  task -> regs . r[dest] &= 0xff00U;
	  task -> regs . r[dest] |= core[task -> regs . r[src1]];
	break;
      case 3: /* movb rs, [raddr] */
#ifdef DEBUG_OP
	if(debug_op) {
	  fprintf(stderr,
		  "movb %s, [%s]\n",
		  regname[dest], regname[src1]);
	}
#endif
	check(CHECK_WRITE, task, task -> regs . r[src1]);
	core[task -> regs . r[src1]] = task -> regs . r[dest];
	break;
      case 4: /* mov ss, rd */
	if(src1 > 3) {
	  fprintf(stderr, "SMOKE-16: Illegal instruction\n");
	  dump(task), raise(SIGILL);
	}
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "mov %s, %s\n",
		  sregname[src1], regname[dest]);
#endif
	fprintf(stderr, "SMOKE-16: Illegal instruction\n");
	dump(task), raise(SIGILL);
	break;
      case 5: /* mov rs, sd */
	if(src1 > 3) {
	  fprintf(stderr, "SMOKE-16: Illegal instruction\n");
	  dump(task), raise(SIGILL);
	}
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "mov %s, %s\n",
		  regname[dest], sregname[src1]);
#endif
	fprintf(stderr, "SMOKE-16: Illegal instruction\n");
	dump(task), raise(SIGILL);
	break;
      case 6: /* bl i8 */
#ifdef DEBUG_OP
	if(debug_op) {
	  fputs("bl ", stderr);
	  put_isymbolic(task, stderr,
			task -> regs . _pc + 2 + (((int) u8_to_s8(immed)) << 1));
	  fputc('\n', stderr);
	
	}
#endif
	if(task -> regs . s[1] & SMOKE16_PS_C)
	  task -> regs . _pc += ((int) u8_to_s8(immed)) << 1;
	break;
      case 7: /* be i8 */
#ifdef DEBUG_OP
	if(debug_op) {
	  fputs("be ", stderr);
	  put_isymbolic(task, stderr,
			task -> regs . _pc + 2 + (((int) u8_to_s8(immed)) << 1));
	  fputc('\n', stderr);
	
	}
#endif
	if(task -> regs . s[1] & SMOKE16_PS_Z)
	  task -> regs . _pc += ((int) u8_to_s8(immed)) << 1;
	break;
      case 8: /* bne i8 */
#ifdef DEBUG_OP
	if(debug_op) {
	  fputs("bne ", stderr);
	  put_isymbolic(task, stderr,
			task -> regs . _pc + 2 + (((int) u8_to_s8(immed)) << 1));
	  fputc('\n', stderr);

	}
#endif
	if(!(task -> regs . s[1] & SMOKE16_PS_Z))
	  task -> regs . _pc += (((int) u8_to_s8(immed)) << 1);
	break;
      case 9: /* bge i8 */
#ifdef DEBUG_OP
	if(debug_op) {
	  fputs("bge ", stderr);
	  put_isymbolic(task, stderr,
			task -> regs . _pc + 2 + (((int) u8_to_s8(immed)) << 1));
	  fputc('\n', stderr);
	
	}
#endif
	if(! (task -> regs . s[1] & SMOKE16_PS_C))
	  task -> regs . _pc += (((int) u8_to_s8(immed)) << 1);
	break;
      case 10: /* bg i8 */
#ifdef DEBUG_OP
	if(debug_op) {
	  fputs("bg ", stderr);
	  put_isymbolic(task, stderr,
			task -> regs . _pc + 2 + (((int) u8_to_s8(immed)) << 1));
	  fputc('\n', stderr);
	
	}
#endif
	if(!((task -> regs . s[1] & SMOKE16_PS_C) ||
	     (task -> regs . s[1] & SMOKE16_PS_Z)))
	  task -> regs . _pc += (((int) u8_to_s8(immed)) << 1);
	break;
      case 11: /* b i8 */
#ifdef DEBUG_OP
	if(debug_op) {
	  fputs("b ", stderr);
	  put_isymbolic(task, stderr,
			task -> regs . _pc + 2 + (((int) u8_to_s8(immed)) << 1));
	  fputc('\n', stderr);
	
	}
#endif
	task -> regs . _pc += (((int) u8_to_s8(immed)) << 1);
	break;
      case 12: /* int i8 */
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "int %#" FMT_x8 "\n",
		  immed);
#endif
	interrupt(task, immed);
	break;
      case 13: /* sub i4, rd */
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "sub %" FMT_u8 ", %s\n",
		  src1, regname[dest]);
#endif
	result = (unsigned long)task -> regs . r[dest] - (unsigned long)src1;
	task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
	task -> regs . s[1] |=
	  ((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	  (result ? 0 : SMOKE16_PS_Z);
	if (dest)
	  task -> regs . r[dest] = (u16)(result & 0xffffU);
	break;
    case 14: /* add i4, rd */
#ifdef DEBUG_OP
	if(debug_op)
	  fprintf(stderr,
		  "add %" FMT_u8 ", %s\n",
		  src1, regname[dest]);
#endif
	result = (unsigned long)task -> regs . r[dest] + (unsigned long)src1;
	task -> regs . s[1] ^= task -> regs . s[1] & SMOKE16_PS_CMP;
	task -> regs . s[1] |=
	  ((result & 0x10000UL) ? SMOKE16_PS_C : 0) |
	  (result ? 0 : SMOKE16_PS_Z);
	if (dest)
	  task -> regs . r[dest] = (u16)(result & 0xffffU);
	break;
      case 15:
	switch(immed) {
	case 0xf0U: /* nop */
#ifdef DEBUG_OP
	  if (debug_op)
	    fprintf(stderr,
		    "nop\n");
#endif
	  break;
	case 0xffU: /* iret */
#ifdef DEBUG_OP
	  if (debug_op)
	    fprintf(stderr,
		    "iret\n");
#endif
	default:
	  fprintf(stderr, "SMOKE-16: Illegal instruction\n");
	  dump(task), raise(SIGILL);
	}
	break;
      default:
	fprintf(stderr, "SMOKE-16: Illegal instruction\n");
	dump(task), raise(SIGILL);
      }
      break;
    default:
      fprintf(stderr, "SMOKE-16: Illegal instruction\n");
      dump(task), raise(SIGILL);
    }
    task -> regs . _pc += 2;
  }
}

#define SUMMARY \
   "Summary: %s [-help] [-version] [options] image [image-args]\n"

#ifdef DEBUG_OP
#define DEBUG_OP_USAGE "\
-disassemble            show instructions as they are executed\n\
-trace                  show system calls as they are executed\n\
"
#else
#define DEBUG_OP_USAGE
#endif

#ifdef DEBUG_DUMP
#define DEBUG_DUMP_USAGE "\
-registers              show register contents at every tick\n\
"
#else
#define DEBUG_DUMP_USAGE
#endif

#ifndef EXCLUDE_GETOPT

#define USAGE "\
Usage: %s [options] image [image-args]\n\
Options:\n\
-h or\n\
-help                   show this message and exit\n\
-E [0x]HEX              override the entry point\n\
-M [0x]HEX              set the maximum accessible address\n\
-N or -numeric          show addresses as numbers\n\
-S or -symbolic         show addresses as symbols with offsets\n\
-Tbss [0x]HEX           set the load address of the BSS section\n\
-Tdata [0x]HEX          set the load address of the data section\n\
-T[text] [0x]HEX        set the load address of the text section\n\
-V or -version          print emulator version and exit\n\
" DEBUG_OP_USAGE DEBUG_DUMP_USAGE

#else /* defined(EXCLUDE_GETOPT) */

#define USAGE "\
Usage: %s image [image-args]\n\
\n\
This version was built without getopt() support, for use\n\
with the Linux binfmt_misc binary emulation subsystem.\n\
\n"

#endif /* EXCLUDE_GETOPT */

#define VERSION \
   "SMOKE-16 emulator $Revision: 1.65 $\n" \
   SMOKE16_WARRANTY

int main(int argc, char * argv[], char * envp[]) {
  FILE *image;
  u8 *icore, *core;
  struct a_out16_nlist *syms = 0;
  char *strs = 0;
  u16 Tbss = 0;
  int Tbss_p = 0;
  u16 Tdata = 0;
  int Tdata_p = 0;
  u16 Ttext = 0x0400U; /* 0x0000U for "v0" executables */
  int Ttext_p = 0;
  u16 Entry = 0x0000U;
  int Entry_p = 0;
  u16 nstrs = 0;
  u16 envsize = 0;
  u16 maxcore = 0xffffU;
  struct a_out16_exec exec;

#ifndef EXCLUDE_GETOPT

  int i, option_index;
  static struct option long_options[] = {
    {"Tbss", 1, 0, 0},
    {"Tdata", 1, 0, 0},
#ifdef DEBUG_OP
    {"disassemble", 0, 0, 0},
    {"trace", 0, 0, 0},
#endif
#ifdef DEBUG_DUMP
    {"registers", 0, 0, 0},
#endif
    {"Ttext", 1, 0, 'T'},
    {"help", 0, 0, 'h'},
    {"numeric", 0, 0, 'N'},
    {"symbolic", 0, 0, 'S'},
    {"version", 0, 0, 'V'},
    {0, 0, 0, 0}
  };

  while((i = getopt_long_only(argc, argv, "+hE:M:NST:V", long_options, &option_index)) != -1)
    switch(i) {
    case 0:
      switch(option_index) {
      case 0:
	Tbss_p = 1;
	if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	  optarg += 2;
	if (sscanf(optarg, "%" FMT_x16 "%s", &Tbss, optarg) != 1)
	  fprintf(stderr,
		  "%s: %s: not a valid hexadecimal number\n",
		  argv[0],
		  optarg),
	    exit(1);
	break;
      case 1:
	Tdata_p = 1;
	if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	  optarg += 2;
	if (sscanf(optarg, "%" FMT_x16 "%s", &Tdata, optarg) != 1)
	  fprintf(stderr,
		  "%s: %s: not a valid hexadecimal number\n",
		  argv[0],
		  optarg),
	    exit(1);
	break;
#ifdef DEBUG_OP
      case 2:
	debug_op = 1;
	break;
      case 3:
	debug_sys = 1;
	break;
#ifdef DEBUG_DUMP
      case 4:
#endif
#else
#ifdef DEBUG_DUMP
      case 2:
#endif
#endif
#ifdef DEBUG_DUMP
	debug_dump = 1;
	break;
#endif
      default:
        fprintf(stderr, SUMMARY, argv[0]);
        exit(2);
      }
      break;
    case 'h':
      printf(USAGE, argv[0]);
      exit(0);
      break;
    case 'N':
      show_numbers = 1;
      show_symbols = 0;
      break;
    case 'S':
      show_numbers = 0;
      show_symbols = 1;
      break;
    case 'T':
      Ttext_p = 1;
      if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	optarg += 2;
      if (sscanf(optarg, "%" FMT_x16 "%s", &Ttext, optarg) != 1)
	fprintf(stderr,
		"%s: %s: not a valid hexadecimal number\n",
		argv[0],
		optarg),
	  exit(1);
      break;
    case 'E':
      Entry_p = 1;
      if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	optarg += 2;
      if (sscanf(optarg, "%" FMT_x16 "%s", &Entry, optarg) != 1)
	fprintf(stderr,
		"%s: %s: not a valid hexadecimal number\n",
		argv[0],
		optarg),
	  exit(1);
      break;
    case 'M':
      if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	optarg += 2;
      if (sscanf(optarg, "%" FMT_x16 "%s", &maxcore, optarg) != 1)
	fprintf(stderr,
		"%s: %s: not a valid hexadecimal number\n",
		argv[0],
		optarg),
	  exit(1);
      break;
    case 'V':
      printf(VERSION, SMOKE16_TOOLVERSION);
      exit(0);
      break;
    default:
      fprintf(stderr, SUMMARY, argv[0]);
      exit(2);
    }

  if((argc - optind) < 1) {
    fprintf(stderr, "%s: need at least one non-option argument.\n", argv[0]);
    fprintf(stderr, SUMMARY, argv[0]);
    exit(2);
  }
  if(argv[optind][0] == '-' && argv[optind][1] == '\0') {
    image = stdin;
    argv[optind] = "stdin";
  } else if(!(image = fopen(argv[optind], "rb")))
    perror(argv[optind]), exit(1);

#else /* defined(EXCLUDE_GETOPT) */

#undef optind
#define optind 1

  if(argc - optind < 1) {
    fprintf(stderr, "%s: image required.\n", argv[0]);
    fprintf(stderr, USAGE, argv[0]);
    fprintf(stderr, VERSION, SMOKE16_TOOLVERSION);
    exit(2);
  }
  if(!(image = fopen(argv[optind], "rb")))
    perror(argv[optind]), exit(1);

#endif /* EXCLUDE_GETOPT */

/*
 * FIXME: Some parts of the emulator (system calls, for example) still
 *        assume that 'u8' is the same size as 'char'. The rest of the
 *        toolchain should work fine with larger-than-necessary types.
 */

  assert(sizeof(char) == sizeof(u8));

  if(!fget16exec(&exec, 1, image)) {
    if(!feof(image))
      perror(argv[optind]);
    else
      fprintf(stderr, "%s: premature EOF in header\n", argv[optind]);
    exit(1);
  }
  if(SMOKE16_N_BADMAG(exec))
    fprintf(stderr,
	    "%s: bad magic number (%#" FMT_o16 ")\n",
	    argv[optind],
	    exec . a_magic),
    exit(1);
  if(SMOKE16_A_TOOLVERSION(exec) > SMOKE16_TOOLVERSION) {
    fprintf(stderr,
	    "%s: v%" FMT_u16 " toolset can't read file written by v%" FMT_u16 " toolset\n",
	    argv[optind],
	    SMOKE16_TOOLVERSION,
	    SMOKE16_A_TOOLVERSION(exec));
    exit(1);
  }
  if(! (syms = (struct a_out16_nlist *)
	calloc(sizeof(*syms),
	       exec . a_syms / fsz16nlist(SMOKE16_A_TOOLVERSION(exec))))) {
    perror("calloc");
    exit(1);
  }
  if((exec . a_magic != SMOKE16_OMAGIC) &&
     (exec . a_magic != SMOKE16_NMAGIC) &&
     (exec . a_magic != SMOKE16_JMAGIC))
    fprintf(stderr,
	    "%s: magic number (%#" FMT_o16 ") is not executable (OMAGIC, NMAGIC or JMAGIC)\n",
	    argv[optind],
	    exec . a_magic),
    exit(1);
  if(SMOKE16_A_MACHTYPE(exec) != M_SMOKE16)
    fprintf(stderr,
	    "%s: unsupported machtype %#" FMT_x16 " is not M_SMOKE16 (%#" FMT_x16 ")\n",
	    argv[optind],
	    SMOKE16_A_MACHTYPE(exec),
	    M_SMOKE16),
    exit(1);
  /* if(exec . a_trsize || exec . a_drsize) */
  /*   fprintf(stderr, "%s: executable needs relocation\n", argv[optind]), exit(1); */
  if(SMOKE16_A_DYNAMIC(exec))
    fprintf(stderr, "%s: unsupported a_dynamic flag\n", argv[optind]), exit(1);
  if ((SMOKE16_A_TOOLVERSION(exec) == 0) && (! Ttext_p))
    Ttext = 0x0000U;
  if (exec . a_magic != SMOKE16_JMAGIC) {
    if (! Tdata_p)
      Tdata = Ttext + exec . a_text;
    if ((Tdata < Ttext + exec . a_text) &&
	(Tdata + exec . a_data >= Ttext)) {
      fprintf(stderr, "%s: text and data sections overlap\n", argv[optind]);
      exit(1);
    }
    if (! Tbss_p)
      Tbss = Tdata + exec . a_data;
    if ((Tbss < Ttext + exec . a_text) &&
	(Tbss + exec . a_bss >= Ttext)) {
      fprintf(stderr, "%s: text and bss sections overlap\n", argv[optind]);
      exit(1);
    }
  } else {
    if (! Tdata_p) {
      if (SMOKE16_A_TOOLVERSION(exec) == 0)
        Tdata = 0x0000U;
      else
        Tdata = 0x0400U;
    }
    if (! Tbss_p)
      Tbss = Tdata + exec . a_data;
  }
  if ((Tbss < Tdata + exec . a_data) &&
      (Tbss + exec . a_bss >= Tdata)) {
    fprintf(stderr, "%s: data and bss sections overlap\n", argv[optind]);
    exit(1);
  }
  if ((((u32) Tbss) + exec . a_bss > ((u32) maxcore) + 2) ||
      (((u32) Tdata) + exec . a_data > ((u32) maxcore) + 2) ||
      (((u32) Ttext) + exec . a_text > ((u32) maxcore) + 2)) {
    fprintf(stderr, "%s: Cannot emulate: out of SMOKE-16 memory\n",
	    argv[optind]);
    exit(1);
  }
  if (! (core = (u8 *) malloc((((u32) maxcore) + 2) * sizeof(u8)))) {
    perror("malloc");
    return 1;
  }
  /* initialize data address space to NULs */
  memset(core, '\0', ((u32) maxcore) + 2);
  if (exec . a_magic != SMOKE16_JMAGIC) {
    icore = core;
  } else {
    if (! (icore = (u8 *) malloc((((u32) maxcore) + 2) * sizeof(u8)))) {
      perror("malloc");
      return 1;
    }
    /* initialize instruction address space to IRETs */
    memset(icore, '\xff', ((u32) maxcore) + 2);
  }
  if(exec . a_text !=
     fget_u8(icore + Ttext,
	     exec . a_text,
	     image)) {
    if(!feof(image))
      perror(argv[optind]);
    else
      fprintf(stderr, "%s: premature EOF in text section\n", argv[optind]);
    exit(1);
  }
  if(exec . a_data !=
     fget_u8(core + Tdata,
	     exec . a_data,
	     image)) {
    if(!feof(image))
      perror(argv[optind]);
    else
      fprintf(stderr, "%s: premature EOF in data section\n", argv[optind]);
    exit(1);
  }

  /* throw away the text relocations */
  while(exec . a_trsize) {
    u8 tmp;

    fget_u8(&tmp,
	    1,
	    image);
    exec . a_trsize --;
  }

  /* throw away the data relocations */
  while(exec . a_drsize) {
    u8 tmp;

    fget_u8(&tmp,
	    1,
	    image);
    exec . a_drsize --;
  }

  /* read the string table */
  if(exec . a_syms) {
    if((exec . a_syms / fsz16nlist(SMOKE16_A_TOOLVERSION(exec))) !=
       fget16nlist(syms,
		   exec . a_syms / fsz16nlist(SMOKE16_A_TOOLVERSION(exec)),
		   image)) {
      if(!feof(image))
	perror(argv[optind]);
      else
	fprintf(stderr, "%s: premature EOF in symbol table\n", argv[optind]);
      exit(1);
    } else if(!fget_u16(&nstrs, 1, image)) {
      if(!feof(image))
	perror(argv[optind]);
      else
	fprintf(stderr, "%s: premature EOF in string table header\n", argv[optind]);
      exit(1);
    }
    if(! (strs = (char *) malloc((size_t) nstrs + 1))) {
      perror("malloc");
      exit(1);
    }
    strs[nstrs] = '\0';
    if(((size_t) nstrs - 2) !=
       fget_as(strs + 2,
	       (size_t) nstrs - 2,
	       image)) {
      if(ferror(image))
	perror(argv[optind]);
      else if(feof(image))
	fprintf(stderr, "%s: premature EOF in string table\n", argv[optind]);
      else
	fprintf(stderr, "%s: couldn't convert string table to native encoding\n",
		argv[optind]);
      exit(1);
    }
  }
  fclose(image);
  if (Entry_p)
    exec . a_entry = Entry;
#ifdef DEBUG_OBJ
  fprintf(stderr,
	  "%s: text %#" FMT_x32 " data %#" FMT_x32 ", "
          "bss %#" FMT_x16 ", entry %#" FMT_x16 " (%sstripped)\n",
	  argv[optind], exec . a_text, exec . a_data, exec . a_bss, exec . a_entry,
	  (exec . a_syms ? "not " : ""));
#endif
  /* pass arguments and environment */
  {
    int i, j;
    u16 cp_argc = argc - optind;
    u16 cp_argv;
    u16 cp_envp;
    u32 top = ((u32) maxcore) + 1, bottom;

    /* first, tally up the space needed for args + environment */
    envsize += 5;
    for (i = optind; i < argc; i ++)
      envsize += 3 + strlen(argv[i]);
    for (i = 0; envp[i]; i ++)
      envsize += 3 + strlen(envp[i]);
    bottom = top - envsize;
    if ((top < envsize) ||
        (bottom < Ttext + exec . a_text) ||
	(bottom < Tdata + exec . a_data) ||
	(bottom < ((u32) Tbss) + exec . a_bss)) {
      fprintf(stderr,
	      "%s: Cannot emulate: out of SMOKE-16 environment space\n",
	      argv[optind]);
    } else {
      cp_argv = bottom;
      /* next, copy them into the SMOKE-16 memory */
      for (i = 0; (top > bottom) && (i < u16_to_s16(cp_argc)); i++) {
	char * s = argv[optind + i];
	
	core[--top] = '\0';
	for (j = strlen(s); (top > bottom) && j;)
	  core[--top] = s[--j];
	core[bottom++] = top >> 8;
	core[bottom++] = top & 0xffU;
      }
      core[bottom++] = '\0';
      core[bottom++] = '\0';
      cp_envp = bottom;
      for (i = 0; (top > bottom) && envp[i]; i++) {
	char * s = envp[i];
	
	core[--top] = '\0';
	for (j = strlen(s); (top > bottom) && j;)
	  core[--top] = s[--j];
	core[bottom++] = top >> 8;
	core[bottom++] = top & 0xffU;
      }
      core[bottom++] = '\0';
      core[bottom++] = '\0';
      /* finally, execute the SMOKE-16 image */
      if (top > bottom)
	emulate(icore,
		core,
		&exec,
		maxcore,
		maxcore - 1 - envsize,
		cp_argc,
		cp_argv,
		cp_envp,
		syms,
		strs,
		Ttext,
		Tdata,
		Tbss);
      else
	fprintf(stderr,
		"%s: Cannot emulate: out of SMOKE-16 environment space\n",
		argv[optind]);
    }
  }
  return 1;
}
