/*
 *  This is the mach (MAC OS X) debugger module
 *
 *  Functions unique for Mach (MAC OS X)
 *
 */

#include <pro.h>
#include <fpro.h>
#include <err.h>
#include <ida.hpp>
#include <idp.hpp>
#include <idd.hpp>
#include <name.hpp>
#include <bytes.hpp>
#include <loader.hpp>
#include <diskio.hpp>

#undef INTERR
#undef QASSERT
#define INTERR() interr(__FILE__, __LINE__, __FUNCTION__)
#define QASSERT(cond) do if ( !(cond) ) INTERR(); while (0)
#define MD msg("at line %d\n", __LINE__);

#define processor_t mach_processor_t

#include <grp.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <mach-o/loader.h>
#include <mach-o/reloc.h>
#include <mach-o/nlist.h>
#include <mach-o/fat.h>
#include <mach/mach.h>
#include <mach/shared_memory_server.h>

#include <map>
#include <set>
#include <deque>
#include <vector>
#include <algorithm>

using std::vector;
using std::pair;
using std::make_pair;

#include "idarpc.hpp"
#include "deb_pc.hpp"

#include "pc_remote.cpp"
#include "common_remote.cpp"

//--------------------------------------------------------------------------
//
//      DEBUGGER INTERNAL DATA
//
//--------------------------------------------------------------------------
// processes list information
typedef std::vector<process_info_t> processes_t;
static processes_t processes;

// debugged process information
static mach_port_t task;        // debugged application's task port
static int pid;                 // process id

static bool is_dll;             // Is dynamic library?

enum run_state_t { rs_running, rs_pausing, rs_exiting, rs_exited };
static run_state_t run_state;
inline bool exited(void) { return run_state == rs_exited; }

// Macho dyld information
typedef ea_t CORE_ADDR;
struct dyld_raw_infos
{
  uint32_t version;             /* MacOS X 10.4 == 1 */
  uint32_t num_info;            /* Number of elements in the following array */

  /* Array of images (struct dyld_raw_info here in gdb) that are loaded
     in the inferior process.
     Note that this address may change over the lifetime of a process;
     as the array grows, dyld may need to realloc () the array.  So don't
     cache the value of info_array except while the inferior is stopped.
     This is either 4 or 8 bytes in the inferior, depending on wordsize.
     This value can be 0 (NULL) if dyld is in the middle of updating the
     array.  Currently, we'll just fail in that (unlikely) circumstance.  */

  CORE_ADDR info_array;

  /* Function called by dyld after a new dylib/bundle (or group of
     dylib/bundles) has been loaded, but before those images have had
     their initializer functions run.  This function has a prototype of

     void dyld_image_notifier (enum dyld_image_mode mode, uint32_t infoCount,
     const struct dyld_image_info info[]);

     Where mode is either dyld_image_adding (0) or dyld_image_removing (1).
     This is either 4 or 8 bytes in the inferior, depending on wordsize. */

  CORE_ADDR dyld_notify;
};

/* A structure filled in by dyld in the inferior process.
   Each dylib/bundle loaded has one of these structures allocated
   for it.
   Each field is either 4 or 8 bytes, depending on the wordsize of
   the inferior process.  (including the modtime field - size_t goes to
   64 bits in the 64 bit ABIs).  */

struct dyld_raw_info
{
  CORE_ADDR addr;               /* struct mach_header *imageLoadAddress */
  CORE_ADDR name;               /* const char *imageFilePath */
  CORE_ADDR modtime;            /* time_t imageFileModDate */
};

static ea_t dylib;              // address of dylib mach-o header
static ea_t dylib_infos;        // address of _dyld_all_image_infos
static dyld_raw_infos dyri;

// image information
struct image_info_t
{
  image_info_t() : base(BADADDR), imagesize(0) {}
  image_info_t(ea_t _base, ulong _imagesize, string _name) : base(_base), imagesize(_imagesize), name(_name) {}
  ea_t base;
  ulong imagesize;
  string name;
};

typedef std::map<ea_t, image_info_t> images_t; // key: image base address
static images_t dlls; // list of loaded dynamic libraries

typedef std::set<ea_t> easet_t;         // set of addresses
static easet_t dlls_to_import;          // list of dlls to import information from

union my_mach_msg_t
{
  mach_msg_header_t hdr;
  char data[1024];
  void display(const char *header);
};

enum block_type_t
{
  bl_none,                      // process is running
  bl_signal,                    // blocked due to a signal (must say PTRACE_CONT)
  bl_exception,                 // blocked due to an exception (must say task_resume())
};

// thread information
struct ida_thread_info_t
{
  ida_thread_info_t(thid_t t, mach_port_t p)
    : tid(t), port(p), child_signum(0), single_step(false),
      pending_sigstop(false) {}
  int tid;
  mach_port_t port;
  int child_signum;
  bool single_step;
  bool pending_sigstop;
  my_mach_msg_t reply_msg;
  block_type_t block;
  bool blocked(void) const { return block != bl_none; }
};

typedef std::map<int, ida_thread_info_t> threads_t; // (tid -> info)
static threads_t threads;

typedef std::deque<debug_event_t> eventlist_t;
static eventlist_t events;

static std::set<ea_t> bpts;     // breakpoint list

static bool attaching;          // Handling events linked to PTRACE_ATTACH, don't run the program

struct mach_exception_port_info_t
{
  exception_mask_t masks[EXC_TYPES_COUNT];
  mach_port_t ports[EXC_TYPES_COUNT];
  exception_behavior_t behaviors[EXC_TYPES_COUNT];
  thread_state_flavor_t flavors[EXC_TYPES_COUNT];
  mach_msg_type_number_t count;
};
static mach_port_t exc_port = MACH_PORT_NULL;
static mach_exception_port_info_t saved_exceptions;

typedef qvector<uchar> ucharbuf_t;
typedef qvector<struct nlist> nlists_t;
typedef qvector<dyld_raw_info> dyriv_t;

extern boolean_t exc_server(
                mach_msg_header_t *InHeadP,
                mach_msg_header_t *OutHeadP);

static bool set_hwbpts(int hThread);
static int get_memory_info(memory_info_t **areas, int *qty, bool suspend);
static bool my_resume_thread(ida_thread_info_t &ti);
static void update_dylib(void);
static asize_t calc_image_size(const char *fname, ea_t *expected_base);
static bool read_macho_commands(linput_t *li, ulong *p_off, mach_header &mh, ucharbuf_t &commands);
static kern_return_t write_mem(ea_t ea, void *buffer, int size);
static kern_return_t read_mem(ea_t ea, void *buffer, int size);
static ea_t get_ip(thid_t tid);
static void update_threads(void);
static void term_exception_ports(void);
//--------------------------------------------------------------------------
static void swap_fat_header(fat_header *fh)
{
  fh->magic     = swap32(fh->magic);
  fh->nfat_arch = swap32(fh->nfat_arch);
}

//--------------------------------------------------------------------------
static void swap_fat_arch(fat_arch *fa)
{
  fa->cputype    = NXSwapInt(fa->cputype);
  fa->cpusubtype = NXSwapInt(fa->cpusubtype);
  fa->offset     = swap32(fa->offset);
  fa->size       = swap32(fa->size);
  fa->align      = swap32(fa->align);
}

//--------------------------------------------------------------------------
static const char *get_ptrace_name(int request)
{
  switch ( request )
  {
    case PT_TRACE_ME:    return "PT_TRACE_ME";    /* child declares it's being traced */
    case PT_READ_I:      return "PT_READ_I";      /* read word in child's I space */
    case PT_READ_D:      return "PT_READ_D";      /* read word in child's D space */
    case PT_READ_U:      return "PT_READ_U";      /* read word in child's user structure */
    case PT_WRITE_I:     return "PT_WRITE_I";     /* write word in child's I space */
    case PT_WRITE_D:     return "PT_WRITE_D";     /* write word in child's D space */
    case PT_WRITE_U:     return "PT_WRITE_U";     /* write word in child's user structure */
    case PT_CONTINUE:    return "PT_CONTINUE";    /* continue the child */
    case PT_KILL:        return "PT_KILL";        /* kill the child process */
    case PT_STEP:        return "PT_STEP";        /* single step the child */
    case PT_ATTACH:      return "PT_ATTACH";      /* trace some running process */
    case PT_DETACH:      return "PT_DETACH";      /* stop tracing a process */
    case PT_SIGEXC:      return "PT_SIGEXC";      /* signals as exceptions for current_proc */
    case PT_THUPDATE:    return "PT_THUPDATE";    /* signal for thread# */
    case PT_ATTACHEXC:   return "PT_ATTACHEXC";   /* attach to running process with signal exception */
    case PT_FORCEQUOTA:  return "PT_FORCEQUOTA";  /* Enforce quota for root */
    case PT_DENY_ATTACH: return "PT_DENY_ATTACH";
  }
  return "?";
}

//--------------------------------------------------------------------------
static long qptrace(int request, pid_t pid, caddr_t addr, int data)
{
  long code = ptrace(request, pid, addr, data);
  int saved_errno = errno;
//  if ( (request == PT_CONTINUE || request == PT_STEP) && int(addr) == 1 )
//    addr = (caddr_t)get_ip(pid);
  debdeb("%s(%u, 0x%X, 0x%X) => 0x%X", get_ptrace_name(request), pid, addr, data, code);
  if ( code == -1 )
    deberr("");
  else
    debdeb("\n");
  errno = saved_errno;
  return code;
}

//--------------------------------------------------------------------------
static ida_thread_info_t *get_thread(thid_t tid)
{
  threads_t::iterator p = threads.find(tid);
  if ( p == threads.end() )
    return NULL;
  return &p->second;
}

//--------------------------------------------------------------------------
inline thid_t maintid(void)
{
  return threads.begin()->first;
}

//--------------------------------------------------------------------------
#define DEFINE_GET_STATE_FUNC(name, type, flavor, count) \
static bool name(thid_t tid, type *state)           \
{                                                        \
  ida_thread_info_t *ti = get_thread(tid);               \
  if ( ti == NULL )                                      \
    return false;                                        \
  mach_port_t port = ti->port;                           \
  mach_msg_type_number_t stateCount = count;             \
  kern_return_t err;                                     \
  err = thread_get_state(port,                           \
                         flavor,                         \
                         (thread_state_t)state,          \
                         &stateCount);                   \
  QASSERT(stateCount == count);                          \
  if ( err != KERN_SUCCESS )                             \
  {                                                      \
    debdeb("tid=%d port=%d: " #name ": %s\n", tid, port, mach_error_string(err)); \
    return false;                                        \
  }                                                      \
  return true;                                           \
}

#define DEFINE_SET_STATE_FUNC(name, type, flavor, count) \
static bool name(thid_t tid, type *state)           \
{                                                        \
  ida_thread_info_t *ti = get_thread(tid);               \
  if ( ti == NULL )                                      \
    return false;                                        \
  mach_port_t port = ti->port;                           \
  mach_msg_type_number_t stateCount = count;             \
  kern_return_t err;                                     \
  err = thread_set_state(port,                           \
                         flavor,                         \
                         (thread_state_t)state,          \
                         stateCount);                    \
  QASSERT(stateCount == count);                          \
  return err == KERN_SUCCESS;                            \
}

//--------------------------------------------------------------------------
DEFINE_GET_STATE_FUNC(get_thread_state, i386_thread_state_t, i386_THREAD_STATE, i386_THREAD_STATE_COUNT)
DEFINE_SET_STATE_FUNC(set_thread_state, const i386_thread_state_t, i386_THREAD_STATE, i386_THREAD_STATE_COUNT)
DEFINE_GET_STATE_FUNC(get_float_state, i386_float_state, i386_FLOAT_STATE, i386_FLOAT_STATE_COUNT)
DEFINE_SET_STATE_FUNC(set_float_state, const i386_float_state, i386_FLOAT_STATE, i386_FLOAT_STATE_COUNT)

//--------------------------------------------------------------------------
static ulong get_dr(thid_t tid, int idx)
{
  return 0;
}

//--------------------------------------------------------------------------
static bool set_dr(thid_t tid, int idx, ulong value)
{
  return false;
}

//--------------------------------------------------------------------------
static ea_t get_ip(thid_t tid)
{
  i386_thread_state_t state;
  if ( !get_thread_state(tid, &state) )
    return BADADDR;
  return state.eip;
}

//--------------------------------------------------------------------------
enum queue_pos_t { IN_FRONT, IN_BACK };
inline void enqueue_event(const debug_event_t &ev, queue_pos_t pos)
{
  if ( pos != IN_BACK )
    events.push_front(ev);
  else
    events.push_back(ev);
}

//--------------------------------------------------------------------------
static pid_t qwait(int *status, bool hang)
{
  int flags = hang ? 0 : WNOHANG;
  pid_t pid = waitpid(-1, status, flags);
/*  if ( (pid == -1 || pid == 0) && errno == ECHILD )
  {
    pid = waitpid(-1, status, flags | __WCLONE);
    if ( pid != -1 && pid != 0 )
      debdeb("------------ __WCLONE %d\n", pid);
  }*/
  return pid;
}

//--------------------------------------------------------------------------
struct mach_exception_info_t
{
  task_t task_port;
  thread_t thread_port;
  exception_type_t exception_type;
  exception_data_t exception_data;
  mach_msg_type_number_t data_count;
};

#ifdef DEBUG_MAC_DEBUGGER
void my_mach_msg_t::display(const char *header)
{
  msg("%s\n", header);
  msg("         msgh_bits       : 0x%x\n", hdr.msgh_bits);
  msg("         msgh_size       : 0x%x\n", hdr.msgh_size);
  msg("         msgh_remote_port: %d\n", hdr.msgh_remote_port);
  msg("         msgh_local_port : %d\n", hdr.msgh_local_port);
  msg("         msgh_reserved   : %d\n", hdr.msgh_reserved);
  msg("         msgh_id         : 0x%x\n", hdr.msgh_id);
  if ( hdr.msgh_size > 24 )
  {
    const ulong *buf = ((ulong *) this) + 6;
    msg("         data            :");
    int cnt = hdr.msgh_size / 4 - 6;
    for ( int i=0; i < cnt; i++ )
      msg(" %08x", buf[i]);
    msg("\n");
  }
}
#endif

static mach_exception_info_t exinf;
static my_mach_msg_t _reply_msg;

// this function won't be called but is declared to avoid linker complaints
kern_return_t catch_exception_raise_state(
        mach_port_t exception_port,
        exception_type_t exception,
        const exception_data_t code,
        mach_msg_type_number_t codeCnt,
        int *flavor,
        const thread_state_t old_state,
        mach_msg_type_number_t old_stateCnt,
        thread_state_t new_state,
        mach_msg_type_number_t *new_stateCnt)
{
  return KERN_FAILURE;
}

// this function won't be called but is declared to avoid linker complaints
kern_return_t catch_exception_raise_state_identity(
        mach_port_t exception_port,
        mach_port_t thread,
        mach_port_t task,
        exception_type_t exception,
        exception_data_t code,
        mach_msg_type_number_t codeCnt,
        int *flavor,
        thread_state_t old_state,
        mach_msg_type_number_t old_stateCnt,
        thread_state_t new_state,
        mach_msg_type_number_t *new_stateCnt)
{
  return KERN_FAILURE;
}

// this function will be called by exc_server()
kern_return_t
catch_exception_raise(mach_port_t exception_port,
                      mach_port_t thread,
                      mach_port_t task,
                      exception_type_t exception,
                      exception_data_t code_vector,
                      mach_msg_type_number_t code_count)
{
  exinf.task_port      = task;
  exinf.thread_port    = thread;
  exinf.exception_type = exception;
  exinf.exception_data = code_vector;
  exinf.data_count     = code_count;
  return KERN_SUCCESS;
}

//--------------------------------------------------------------------------
// event->tid is filled upon entry
static bool handle_signal(int code, debug_event_t *event, block_type_t block)
{
  if ( run_state == rs_exiting && code == SIGKILL )
  {
    qptrace(PT_CONTINUE, pid, caddr_t(1), code);
    return false;
  }
  update_threads();
  event->pid          = pid;
  event->handled      = true;
  event->ea           = get_ip(event->tid);
  event->eid          = EXCEPTION;
  event->exc.code     = code;
  event->exc.can_cont = true;
  event->exc.ea       = BADADDR;

  ida_thread_info_t *ti = get_thread(event->tid);
  QASSERT(ti != NULL);
  ti->block = block;
  if ( block == bl_exception )
    ti->reply_msg = _reply_msg; // save the reply message for later

  if ( code == SIGSTOP )
  {
    if ( ti->pending_sigstop )
    {
      debdeb("got pending SIGSTOP, good!\n");
      ti->pending_sigstop = false;
      return false;
    }
    if ( run_state == rs_pausing )
    {
      debdeb("successfully paused the process, good!\n");
      run_state = rs_running;
      event->eid = NO_EVENT;
    }
  }
  if ( event->eid == EXCEPTION )
  {
    bool suspend;
    const exception_info_t *ei = find_exception(code);
    if ( ei != NULL )
    {
      qsnprintf(event->exc.info, sizeof(event->exc.info), "got %s signal (%s)", ei->name.c_str(), ei->desc.c_str());
      suspend = ei->break_on();
      event->handled = ei->handle();
    }
    else
    {
      qsnprintf(event->exc.info, sizeof(event->exc.info), "got unknown signal #%d", code);
      suspend = true;
    }
    if ( event->exc.code == SIGTRAP )
    {
      ulong dr6 = get_dr(event->tid, 6);
      bool hwbpt_found = false;
      for ( int i=0; i < MAX_BPT; i++ )
      {
        if ( dr6 & (1<<i) )  // Hardware breakpoint 'i'
        {
          if ( hwbpt_ea[i] == get_dr(event->tid, i) )
          {
            event->eid     = BREAKPOINT;
            event->bpt.hea = hwbpt_ea[i];
            event->bpt.kea = BADADDR;
            set_dr(event->tid, 6, 0); // Clear the status bits
            hwbpt_found = true;
            break;
          }
        }
      }
      if ( !hwbpt_found )
      {
        if ( bpts.find(event->ea-1) != bpts.end() )
        {
          event->eid     = BREAKPOINT;
          event->bpt.hea = BADADDR;
          event->bpt.kea = BADADDR;
          event->ea--;
        }
        else
        {
          event->eid = STEP;
        }
      }
      code = 0;
    }
    ti->child_signum = code;
    if ( !suspend && event->eid == EXCEPTION )
    {
      my_resume_thread(*ti);
      return false;
    }
  }
  return true;
}

//--------------------------------------------------------------------------
static bool is_task_valid(task_t task)
{
  kern_return_t err;
  struct task_basic_info info;
  uint info_count = TASK_BASIC_INFO_COUNT;

  err = task_info (task, TASK_BASIC_INFO, (task_info_t)&info, &info_count);
  return err == KERN_SUCCESS;
}

//--------------------------------------------------------------------------
static bool check_for_exception(int timeout)
{
  if ( run_state >= rs_exiting || !is_task_valid(task) )
    return false;

  int flags = MACH_RCV_MSG;
  if ( timeout != -1 )
    flags |= MACH_RCV_TIMEOUT;
  else
    timeout = MACH_MSG_TIMEOUT_NONE;
  my_mach_msg_t mmsg;
  kern_return_t err = mach_msg(&mmsg.hdr,
                               flags,
                               0,               // send size
                               sizeof(mmsg),
                               exc_port,
                               timeout,         // timeout
                               MACH_PORT_NULL); // notify port
  if ( err != MACH_MSG_SUCCESS )
    return false;
//  mmsg.display("received an exception, details:");

  // process it
  memset(&exinf, 0, sizeof(exinf));

  // suspending the task
  err = task_suspend(task);
  QASSERT(err == KERN_SUCCESS);

//  msg("calling exc_server\n");
  bool ok = exc_server(&mmsg.hdr, &_reply_msg.hdr);
  QASSERT(ok);
//  reply_msg.display("reply message:");

//  msg("catch exception raise:\n");
//  msg("      thread_port   : %d\n", exinf.thread_port);
//  msg("      task_port     : %d\n", exinf.task_port);
//  msg("      exception_type: 0x%x\n", exinf.exception_type);
//  msg("      data_count    : 0x%x\n", exinf.data_count);
//  if ( exinf.data_count != 0 )
//  {
//    msg("      data          :");
//    show_hex(exinf.exception_data, exinf.data_count, "      data          :");
//  }
  // send the exception code back to the task?
  // not right now, wait for the user to react

  return true;
}

//--------------------------------------------------------------------------
static bool resume_after_exception(ida_thread_info_t &ti)
{
  tid_t tid = ti.tid;

  i386_thread_state_t cpu;
  if ( !get_thread_state(tid, &cpu) )
    return false;

  if ( ti.single_step )
    cpu.eflags |= EFLAGS_TRAP_FLAG;
  else
    cpu.eflags &= ~EFLAGS_TRAP_FLAG;

//  msg("resume after exception: tid=%d, step=%d, ip=%x\n", tid, ti.single_step, cpu.eip);

  if ( !set_thread_state(tid, &cpu) )
    return false;

  kern_return_t err = mach_msg(&ti.reply_msg.hdr,
                               MACH_SEND_MSG,
                               ti.reply_msg.hdr.msgh_size, // send size
                               0,
                               ti.reply_msg.hdr.msgh_remote_port,
                               0,                  // timeout
                               MACH_PORT_NULL); // notify port
  bool ok = (err == KERN_SUCCESS);
  task_resume(task);
  return ok;
}

//--------------------------------------------------------------------------
static int exception_to_signal(void)
{
  int code = exinf.exception_data[0];
  int sig = 0;
  switch( exinf.exception_type )
  {
    case EXC_BAD_ACCESS:
      if ( code == KERN_INVALID_ADDRESS)
        sig = SIGSEGV;
      else
        sig = SIGBUS;
      break;

    case EXC_BAD_INSTRUCTION:
      sig = SIGILL;
      break;

    case EXC_ARITHMETIC:
      sig = SIGFPE;
      break;

    case EXC_EMULATION:
      sig = SIGEMT;
      break;

    case EXC_SOFTWARE:
      switch (code)
      {
//        case EXC_UNIX_BAD_SYSCALL:
//          sig = SIGSYS;
//          break;
//        case EXC_UNIX_BAD_PIPE:
//          sig = SIGPIPE;
//          break;
//        case EXC_UNIX_ABORT:
//          sig = SIGABRT;
//          break;
        case EXC_SOFT_SIGNAL:
          sig = SIGKILL;
          break;
      }
      break;

    case EXC_BREAKPOINT:
      sig = SIGTRAP;
      break;
  }
  return sig;
}

//--------------------------------------------------------------------------
// timeout in milliseconds
// 0 - no timeout, return immediately
// -1 - wait forever
static void get_debug_events(int timeout)
{
//printf("waiting, block=%d numpend=%d timeout=%d...\n", block, events.size(), timeout);
//  if ( blocked() )
//    debdeb("ERROR: process is BLOCKED before qwait()!!!\n");

  debug_event_t event;

  // receive info about any exceptions in the program
  while ( check_for_exception(timeout) )
  {
    event.tid = exinf.thread_port;
    int sig = exception_to_signal();
//    msg("got exception for tid=%d sig=%s\n", event.tid, strsignal(sig));
    if ( handle_signal(sig, &event, bl_exception) )
      enqueue_event(event, IN_BACK);
    if ( timeout > 0 )
      timeout = 0;
  }

  // check the signals
  int status;
  pid_t wpid;
  while ( true )
  {
    wpid = qwait(&status, false);
    if ( wpid == -1 || wpid == 0 )
      return;
//    msg("qwait returned pid %d\n", wpid);
    if ( wpid == pid )
      break;
  }
  event.tid = maintid();
  if ( WIFSTOPPED(status) )
  {
    int code = WSTOPSIG(status);
    debdeb("%s (stopped)\n", strsignal(code));
    if ( !handle_signal(code, &event, bl_signal) )
      return;
  }
  else
  {
    if ( WIFSIGNALED(status) )
    {
      debdeb("%s (terminated)\n", strsignal(WSTOPSIG(status)));
      event.exit_code = WSTOPSIG(status);
    }
    else
    {
      debdeb("%d (exited)\n", WEXITSTATUS(status));
      event.exit_code = WEXITSTATUS(status);
    }
    event.pid     = pid;
    event.ea      = BADADDR;
    event.handled = true;
    event.eid     = PROCESS_EXIT;
    run_state = rs_exited;
  }
//  debdeb("low got event: %s\n", debug_event_str(event));
  enqueue_event(event, IN_BACK);
}

//--------------------------------------------------------------------------
static void handle_dyld_bpt(const debug_event_t *event)
{
//  msg("handle dyld bpt, ea=%a\n", event->ea);
  update_dylib();

  i386_thread_state_t state;
  bool ok = get_thread_state(event->tid, &state);
  QASSERT(ok);

  // emulate push ebp
  state.esp -= 4;
  kern_return_t err = write_mem(state.esp, &state.ebp, 4);
  QASSERT(err == KERN_SUCCESS);

  ok = set_thread_state(event->tid, &state);
  QASSERT(ok);

  remote_continue_after_event(event);
}

//--------------------------------------------------------------------------
int idaapi remote_get_debug_event(debug_event_t *event, bool ida_is_idle)
{
  if ( event == NULL )
    return false;

  while ( true )
  {
    // are there any pending events?
    if ( !events.empty() )
    {
      // get the first event and return it
      *event = events.front();
      events.pop_front();
      switch ( event->eid )
      {
        // if this is dyld bpt, do not return it to ida
        case BREAKPOINT:
          if ( event->ea == dyri.dyld_notify )
          {
            handle_dyld_bpt(event);
            continue;
          }
          break;

        case PROCESS_ATTACH:
          attaching = false;        // finally attached to it
          break;

        case THREAD_EXIT:           // thread completely disappeared,
                                    // can remove it from the list
          threads.erase(event->tid);
          break;
      }
      debdeb("GDE1: %s\n", debug_event_str(event));
      return true;
    }

    get_debug_events(ida_is_idle ? TIMEOUT : 0);
    if ( events.empty() )
      break;
  }
  return false;
}

//--------------------------------------------------------------------------
static bool my_resume_thread(ida_thread_info_t &ti)
{
  switch ( ti.block )
  {
    case bl_signal:
      int request = ti.single_step ? PT_STEP : PT_CONTINUE;
//      msg("continuing from %a by calling qptrace()\n", get_ip(ti.tid));
      if ( qptrace(request, pid, (caddr_t)1, ti.child_signum) != 0 )
        return false;
      break;

    case bl_exception:
      resume_after_exception(ti);
      break;

    default:  // nothing to do, the process is already running
      break;
  }
  ti.block = bl_none;
  ti.single_step = false;
  return true;
}

//--------------------------------------------------------------------------
inline bool suspend_all_threads(void)
{
  /* Suspend the target process */
  kern_return_t err = task_suspend(task);
  return err == KERN_SUCCESS;
}

//--------------------------------------------------------------------------
inline void resume_all_threads(void)
{
  kern_return_t err = task_resume(task);
  QASSERT(err == KERN_SUCCESS);
}

//--------------------------------------------------------------------------
static void unblock_all_threads(void)
{
  for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p )
    my_resume_thread(p->second);
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_continue_after_event(const debug_event_t *event)
{
  if ( event == NULL )
    return false;

  ida_thread_info_t *t = get_thread(event->tid);
  if ( debug_debugger )
  {
    debdeb("continue after event %s (%d pending, block type %d, sig#=%d)\n",
           debug_event_str(event),
           events.size(),
           t == NULL ? 0 : t->block,
           t == NULL ? 0 : t->child_signum);
  }

  if ( event->eid != THREAD_START
    && event->eid != THREAD_EXIT
    && event->eid != LIBRARY_LOAD
    && event->eid != LIBRARY_UNLOAD
    && (event->eid != EXCEPTION || event->handled) )
  {
    debdeb("event->eid=%d, erasing child_signum\n", event->eid);
    QASSERT(t != NULL);
    t->child_signum = 0;
  }

  if ( events.empty() && !attaching )
  {
    // if the event queue is empty, we can resume all blocked threads
    // here we resume only the threads blocked because of exceptions or signals
    // if the debugger kernel has suspended a thread for another reason, it
    // will stay suspended.
    unblock_all_threads();
  }
  return true;
}

//--------------------------------------------------------------------------
static kern_return_t read_mem(ea_t ea, void *buffer, int size)
{
  if ( ea == 0 )
    return KERN_INVALID_ADDRESS;
  vm_address_t data;
  vm_size_t data_count = 0;
  kern_return_t err = vm_read(task, ea, size, &data, &data_count);
  if ( err == KERN_SUCCESS )
  {
    if ( data_count != size )
    {
      err = KERN_INVALID_ADDRESS;
    }
    else
    {
      memcpy(buffer, (void*)data, size);
      err = vm_deallocate(mach_task_self(), data, data_count);
      QASSERT(err == KERN_SUCCESS);
    }
  }
  if ( err != KERN_SUCCESS )
    debdeb("vm_read %d: ea=%x size=%d => (%s)\n", task, ea, size, mach_error_string(err));
//  show_hex(buffer, size, "data:\n");
  return err;
}

//--------------------------------------------------------------------------
static kern_return_t write_mem(ea_t ea, void *buffer, int size)
{
  kern_return_t err;
/*  vm_machine_attribute_val_t flush = MATTR_VAL_CACHE_FLUSH;
printf("buffer=%x size=%x\n", buffer, size);
  err = vm_machine_attribute (mach_task_self(), (vm_offset_t)buffer, size, MATTR_CACHE, &flush);
  QASSERT(err == KERN_SUCCESS); // must succeed since it is our memory
*/
  err = vm_write(task, ea, (vm_offset_t)buffer, size);
  if ( err != KERN_SUCCESS && err != KERN_PROTECTION_FAILURE )
    debdeb("vm_write %d: ea=%x, size=%d => %s\n", task, ea, size, mach_error_string(err));
  return err;
}

//--------------------------------------------------------------------------
static bool xfer_page(ea_t ea, void *buffer, int size, bool write)
{
  // get old memory protection
  vm_address_t r_start = ea;
  vm_size_t r_size;
  mach_port_t r_object_name;
  vm_region_basic_info_data_64_t r_data;
  mach_msg_type_number_t r_info_size = VM_REGION_BASIC_INFO_COUNT_64;
  kern_return_t err = vm_region(task, &r_start, &r_size,
                                VM_REGION_BASIC_INFO_64,
                                (vm_region_info_t)&r_data, &r_info_size,
                                &r_object_name);
  if ( err != KERN_SUCCESS )
  {
    // this call fails for the commpage segment
    debdeb("%a: vm_region: %s\n", r_start, mach_error_string(err));
    return false;
  }

  if ( r_start > ea )
    return false;

//  msg("%a: current=%d max=%d\n", ea, r_data.protection, r_data.max_protection);
  int bit = write ? VM_PROT_WRITE : VM_PROT_READ;
  // max permissions do not allow it? fail
  // strangely enough the kernel allows us to set any protection,
  // including protections bigger than max_protection. but after that it crashes
  // we have to verify it ourselves here.
  if ( (r_data.max_protection & bit) == 0 )
    return false;

  if ( (r_data.protection & bit) == 0 )
  {
    int b2 = bit | (write ? VM_PROT_READ : 0);
    // set the desired bit
    err = vm_protect(task, r_start, r_size, 0, b2);
    // if failed, make a copy of the page
    if ( err != KERN_SUCCESS && write )
      err = vm_protect(task, r_start, r_size, 0, VM_PROT_COPY | VM_PROT_READ | VM_PROT_WRITE);
    if ( err != KERN_SUCCESS )
    {
      debdeb("%d: could not set write permission at %x\n", task, r_start);
      return false;
    }
  }

  // attempt to xfer
  err = (write ? write_mem : read_mem)(ea, buffer, size);
  bool ok = (err == KERN_SUCCESS);
#if 1
  if ( ok && write )
  {
    // flush the cache
//    vm_machine_attribute_val_t flush = MATTR_VAL_CACHE_FLUSH;
    vm_machine_attribute_val_t flush = MATTR_VAL_OFF;
    err = vm_machine_attribute(task, r_start, r_size, MATTR_CACHE, &flush);
    if ( err != KERN_SUCCESS )
    {
      static bool complained = false;
      if ( !complained )
      {
//        complained = true;
        msg("Unable to flush data/instruction cache ea=0x%a size=%d: %s\n", r_start, r_size, mach_error_string(err));
      }
    }
    else
    {
//      msg("Success cache ea=0x%a size=%d\n", r_start, r_size);
    }
  }
#endif
  // restore old memory protection
  if ( (r_data.protection & bit) == 0 )
  {
    err = vm_protect(task, r_start, r_size, 0, r_data.protection);
    QASSERT(err == KERN_SUCCESS);
  }
  return ok;
}

//--------------------------------------------------------------------------
static unsigned int get_pagesize(void)
{
  static vm_size_t page = 0;
  if ( page == 0 )
  {
    kern_return_t err = host_page_size(mach_host_self(), &page);
    QASSERT(err == KERN_SUCCESS);
    QASSERT(page > 0);
    QASSERT((page & (page-1)) == 0);  // power of 2
  }
  return page;
}

//--------------------------------------------------------------------------
static bool xfer_memory(ea_t ea, void *buffer, int size, bool write)
{
  bool ok;
  char *ptr = (char *)buffer;
  int pagesize = get_pagesize();
  int delta = ea & (pagesize-1);
  ea_t base = ea - delta;
  if ( delta != 0 )
  {
    static char *page = NULL;
    if ( page == NULL )
    {
      page = (char*)qalloc(pagesize);
      if ( page == NULL )
        nomem("vm_write");
    }
    int s2 = pagesize - delta;
    if ( s2 < pagesize || !write )
      ok = xfer_page(base, page, pagesize, false);
    if ( ok )
    {
      int rest = qmin(s2, size);
      if ( write )
      {
        memcpy(page+delta, ptr, rest);
        ok = xfer_page(base, page, pagesize, true);
      }
      else
      {
        memcpy(ptr, page+delta, rest);
      }
      ptr += rest;
      size -= rest;
      base += pagesize;
      delta = 0;
    }
  }
  if ( ok && size > 0 )
    ok = xfer_page(base, ptr, size, write);
  return ok;
}

//--------------------------------------------------------------------------
static int _read_memory(ea_t ea, void *buffer, int size, bool suspend=false)
{
  if ( exited() || pid <= 0 || size <= 0 )
    return -1;

//  debdeb("READ MEMORY %a:%d: START\n", ea, size);
  // stop all threads before accessing the process memory
  if ( suspend && !suspend_all_threads() )
    return -1;
  if ( exited() )
    return -1;

//  bool ok = xfer_memory(ea, buffer, size, false);
  kern_return_t err = read_mem(ea, buffer, size);
  bool ok = err == KERN_SUCCESS;

  if ( suspend )
    resume_all_threads();
//  debdeb("READ MEMORY %a:%d: END\n", ea, size);
  return ok ? size : 0;
}

//--------------------------------------------------------------------------
static int _write_memory(ea_t ea, const void *buffer, int size, bool suspend=false)
{
  if ( exited() || pid <= 0 || size <= 0 )
    return -1;

  // stop all threads before accessing the process memory
  if ( suspend && !suspend_all_threads() )
    return -1;
  if ( exited() )
    return -1;

  if ( size == 1 )      // might be a breakpoint add/del
  {
    if ( size >= sizeof(bpt_code) && memcmp(buffer, bpt_code, sizeof(bpt_code)) == 0 )
    {
      bpts.insert(ea);
    }
    else if ( bpts.find(ea) != bpts.end() )
    {
      bpts.erase(ea);
    }
  }

  bool ok = xfer_memory(ea, (void*)buffer, size, true);

  if ( suspend )
    resume_all_threads();

  return ok ? size : 0;
}

//--------------------------------------------------------------------------
ssize_t idaapi remote_write_memory(ea_t ea, const void *buffer, size_t size)
{
  return _write_memory(ea, buffer, size, true);
}

//--------------------------------------------------------------------------
ssize_t idaapi remote_read_memory(ea_t ea, void *buffer, size_t size)
{
  return _read_memory(ea, buffer, size, true);
}

//--------------------------------------------------------------------------
static void add_dll(ea_t addr, const char *fname)
{
  asize_t size = calc_image_size(fname, NULL);

  debug_event_t ev;
  ev.eid     = LIBRARY_LOAD;
  ev.pid     = pid;
  ev.tid     = maintid();
  ev.ea      = addr;
  ev.handled = true;
  qstrncpy(ev.modinfo.name, fname, sizeof(ev.modinfo.name));
  ev.modinfo.base = addr;
  ev.modinfo.size = size;
  ev.modinfo.rebase_to = BADADDR;
  if ( is_dll && stricmp(fname, input_file_path) == 0 )
    ev.modinfo.rebase_to = addr;
  enqueue_event(ev, IN_FRONT);

  image_info_t ii(addr, size, fname);
  dlls.insert(make_pair(addr, ii));
  dlls_to_import.insert(addr);
}

//--------------------------------------------------------------------------
struct name_info_t
{
  ea_t imagebase;
  vector<ea_t> addrs;
  vector<char *> names; // we need to keep is char* because it will be passed to
                        // rpc_set_debug_names()
};

inline void add_macho_symbol(ea_t ea, const char *name, name_info_t &ni)
{
  ni.addrs.push_back(ea);
  ni.names.push_back(qstrdup(name));
//  msg("add name %x -> %s\n", ea, name);
}

//--------------------------------------------------------------------------
inline bool is_zeropage(const segment_command &sg)
{
  return sg.vmaddr == 0 && sg.fileoff == 0 && sg.initprot == 0;
}

//--------------------------------------------------------------------------
inline bool is_text_segment(const segment_command &sg)
{
  if ( is_zeropage(sg) )
    return false;
  const char *name = sg.segname;
  for ( int i=0; i < sizeof(sg.segname); i++, name++ )
    if ( *name != '_' )
      break;
  return strnicmp(name, "TEXT", 4) == 0;
}

//--------------------------------------------------------------------------
// returns expected program base
static ea_t get_symbol_table_info(
        linput_t *li,
        long off,
        const mach_header &mh,
        const ucharbuf_t &load_commands,
        nlists_t &symbols,
        ucharbuf_t &strings)
{

  const uchar *begin = &load_commands[0];
  const uchar *end = &load_commands[load_commands.size()];
  const uchar *ptr = begin;
  sval_t expected_base = -1;
  for ( int i=0; i < mh.ncmds; i++ )
  {
    load_command lc = *(load_command*)ptr;
    const uchar *lend = ptr + lc.cmdsize;
    if ( lend <= begin || lend > end )
      break;

    if ( lc.cmd == LC_SEGMENT && expected_base == -1 )
    {
      segment_command &sg = *(segment_command*)ptr;
      if ( is_text_segment(sg) )
        expected_base = sg.vmaddr;
    }
    else if ( lc.cmd == LC_SYMTAB )
    {
      symtab_command &st = *(symtab_command*)ptr;
      if ( st.nsyms > 0 )
      {
        size_t nbytes = st.nsyms*sizeof(struct nlist);
        symbols.resize(st.nsyms);
        memset(&symbols[0], 0, nbytes);
        qlseek(li, off + st.symoff, SEEK_SET);
        // we do not check the error code, if fails, we will have zeroes
        qlread(li, &symbols[0], nbytes);
      }
      if ( st.strsize > 0 )
      {
        strings.resize(st.strsize, 0);
        qlseek(li, off + st.stroff, SEEK_SET);
        // we do not check the error code, if fails, we will have zeroes
        qlread(li, &strings[0], st.strsize);
      }
      return expected_base == -1 ? 0 : expected_base;
    }
    ptr = lend;
  }
  return -1;
}

//--------------------------------------------------------------------------
static asize_t calc_image_size(const char *fname, ea_t *p_base)
{
  if ( p_base != NULL )
    *p_base = BADADDR;
  linput_t *li = open_linput(fname, false);
  if ( li == NULL )
    return 0;

  asize_t size = 0;
  ulong off;
  mach_header mh;
  ucharbuf_t commands;
  if ( read_macho_commands(li, &off, mh, commands) )
  {
    const uchar *begin = &commands[0];
    const uchar *end = &commands[commands.size()];
    const uchar *ptr = begin;
    ea_t base = BADADDR;
    ea_t maxea = 0;
    for ( int i=0; i < mh.ncmds; i++ )
    {
      load_command lc = *(load_command*)ptr;
      const uchar *lend = ptr + lc.cmdsize;
      if ( lend <= begin || lend > end )
        break;

      if ( lc.cmd == LC_SEGMENT )
      {
        segment_command &sg = *(segment_command*)ptr;
        // since mac os x scatters application segments over the memory
        // we calculate only the text segment size
        if ( is_text_segment(sg) )
        {
          if ( base == BADADDR )
            base = sg.vmaddr;
          ea_t end = sg.vmaddr + sg.vmsize;
          if ( maxea < end )
            maxea = end;
//          msg("segment %s base %a size %d maxea %a\n", sg.segname, sg.vmaddr, sg.vmsize, maxea);
        }
      }
      ptr = lend;
    }
    size = maxea - base;
    if ( p_base != NULL )
      *p_base = base;
//    msg("%s: base %a size %d\n", fname, base, size);
  }
  close_linput(li);
  return size;
}

//--------------------------------------------------------------------------
static void use_symbol_table(
        const nlists_t &symbols,
        const ucharbuf_t &strings,
        sval_t slide,
        name_info_t &ni)
{
  for ( size_t i=0; i < symbols.size(); i++ )
  {
    const struct nlist &nl = symbols[i];
    if ( nl.n_un.n_strx > strings.size() )
      continue;
    const char *name = (const char*)&strings[0] + nl.n_un.n_strx;

    ea_t ea;
    int type = nl.n_type & N_TYPE;
    switch ( type )
    {
      case N_UNDF:
      case N_PBUD:
      case N_ABS:
        break;
      case N_SECT:
      case N_INDR:
        ea = nl.n_value + slide;
        if ( name[0] != '\0' )
        {
          if ( (nl.n_type & (N_EXT|N_PEXT)) == N_EXT ) // exported
          {
            add_macho_symbol(ea, name, ni);
            if ( dylib_infos == BADADDR && strcmp(name, "_dyld_all_image_infos") == 0 )
              dylib_infos = ea;
          }
        }
        break;
    }
  }
}

//--------------------------------------------------------------------------
static bool read_macho_commands(linput_t *li, ulong *p_off, mach_header &mh, ucharbuf_t &commands)
{
  if ( qlread(li, &mh, sizeof(mh)) != sizeof(mh) )
    return false;

  ulong off = 0;
  if ( mh.magic == FAT_MAGIC || mh.magic == FAT_CIGAM )
  {
    // locate the I386 part of the image
    bool mf = mh.magic == FAT_CIGAM;
    qlseek(li, 0, SEEK_SET);
    fat_header fh;
    if ( qlread(li, &fh, sizeof(fh)) != sizeof(fh) )
      return false;
    if ( mf )
      swap_fat_header(&fh);
    for ( int i=0; i < fh.nfat_arch; i++ )
    {
      fat_arch fa;
      if ( qlread(li, &fa, sizeof(fa)) != sizeof(fa) )
        return false;
      if ( mf )
        swap_fat_arch(&fa);
      if ( fa.cputype == CPU_TYPE_I386 )
      {
        off = fa.offset;
        break;
      }
    }
    if ( off == 0 )
      return false;
    qlseek(li, off, SEEK_SET);
    if ( qlread(li, &mh, sizeof(mh)) != sizeof(mh) )
      return false;
  }
  if ( mh.magic != MH_MAGIC || mh.sizeofcmds <= 0 )
    return false;

  commands.resize(mh.sizeofcmds);
  if ( qlread(li, &commands[0], commands.size()) != commands.size() )
    return false;

  if ( p_off != NULL )
    *p_off = off;
  return true;
}

//--------------------------------------------------------------------------
static bool import_dll(linput_t *li, ea_t base, name_info_t &ni)
{
  ulong off;
  mach_header mh;
  ucharbuf_t commands;
  if ( !read_macho_commands(li, &off, mh, commands) )
    return false;

  // get the symbol table
  nlists_t symbols;
  ucharbuf_t strings;
  ea_t ebase = get_symbol_table_info(li, off, mh, commands, symbols, strings);
  if ( ebase == -1 )
    return false;

  use_symbol_table(symbols, strings, base-ebase, ni);
  return true;
}

//--------------------------------------------------------------------------
static bool import_dll_to_database(ea_t imagebase, name_info_t &ni)
{
  images_t::iterator p = dlls.find(imagebase);
  if ( p == dlls.end() )
    error("import_dll_to_database: can't find dll name for %a", imagebase);
  const char *dllname = p->second.name.c_str();

  linput_t *li = open_linput(dllname, false);
  if ( li == NULL )
    return false;

  bool ok = import_dll(li, imagebase, ni);
  close_linput(li);
  return ok;
}

//--------------------------------------------------------------------------
void idaapi remote_stopped_at_debug_event(void)
{
  // we will take advantage of this event to import information
  // about the exported functions from the loaded dlls
  name_info_t ni;
  easet_t::iterator p;
  for ( p=dlls_to_import.begin(); p != dlls_to_import.end(); )
  {
    import_dll_to_database(*p, ni);
    dlls_to_import.erase(p++);
  }
  rpc_set_debug_names(&ni.addrs[0], &ni.names[0], ni.addrs.size());
  for ( int i=0; i < ni.names.size(); i++ )
    qfree(ni.names[i]);
}

//--------------------------------------------------------------------------
static void cleanup(void)
{
  pid = 0;
  is_dll = false;
  run_state = rs_exited;
  dylib = BADADDR;
  dylib_infos = BADADDR;
  dyri.version = 0;  // not inited
  term_exception_ports();

  threads.clear();
  dlls.clear();
  dlls_to_import.clear();
  events.clear();
  attaching = false;
  bpts.clear();

  save_oldmemcfg(NULL, 0);
}

//--------------------------------------------------------------------------
//
//      DEBUGGER INTERFACE FUNCTIONS
//
//--------------------------------------------------------------------------
static void refresh_process_list(void)
{
  processes.clear();

  int sysControl[4];
  sysControl[0] = CTL_KERN;
  sysControl[1] = KERN_PROC;
  sysControl[2] = KERN_PROC_ALL;

  size_t length;
  sysctl(sysControl, 3, NULL, &length, NULL, 0);
  int count = (length / sizeof (struct kinfo_proc));
  if ( count <= 0 )
    return;
  length = sizeof (struct kinfo_proc) * count;

  qvector<struct kinfo_proc> info;
  info.resize(count);
  sysctl(sysControl, 3, &info[0], &length, NULL, 0);

  for ( int i=0; i < count; i++ )
  {
    mach_port_t port;
    kern_return_t result = task_for_pid(mach_task_self(), info[i].kp_proc.p_pid,  &port);
    if ( result == KERN_SUCCESS )
    {
      process_info_t pi;
      qstrncpy(pi.name, info[i].kp_proc.p_comm, sizeof(pi.name));
      pi.pid = info[i].kp_proc.p_pid;
      processes.push_back(pi);
    }
    else
    {
      debdeb("%d: %s is unavailable for debugging\n", info[i].kp_proc.p_pid, info[i].kp_proc.p_comm);
    }
  }
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
// input is valid only if n==0
int idaapi remote_process_get_info(int n, const char *input, process_info_t *info)
{
  if ( n == 0 )
  {
    qstrncpy(input_file_path, input, sizeof(input_file_path));
    refresh_process_list();
  }

  if ( n < 0 || n >= processes.size() )
    return false;

  if ( info != NULL )
    *info = processes[n];
  return true;
}

//--------------------------------------------------------------------------
static char **prepare_arguments(const char *path, const char *args)
{
  int i = 0;
  char **argv = NULL;
  while ( 1 )
  {
    string s;
    if ( i == 0 )
    {
      s = path;
    }
    else
    {
      while ( isspace(*args) ) args++;
      if ( *args == '\0' ) break;
      char quote = (*args == '"' || *args == '\'') ? *args++ : 0;
      while ( (quote ? *args != quote : !isspace(*args)) && *args != '\0' )
      {
        if ( *args == '\\' && args[1] != '\0' )
          args++;
        s += *args++;
      }
    }
    argv = (char **)qrealloc(argv, sizeof(char *)*(i+2));
    if ( argv == NULL ) nomem("prepare_arguments");
    argv[i++] = qstrdup(s.c_str());
  }
  argv[i] = NULL;
  return argv;
}

//--------------------------------------------------------------------------
// Returns the file name assciated with pid
static char *get_exec_fname(int pid, char *buf, size_t bufsize)
{
  int mib[3];
  mib[0] = CTL_KERN;
  mib[1] = KERN_ARGMAX;

  int argmax = 0;
  size_t size = sizeof(argmax);

  sysctl(mib, 2, &argmax, &size, NULL, 0);
  if ( argmax <= 0 )
    argmax = QMAXPATH;

  char *args = (char *)qalloc(argmax);
  if ( args == NULL )
    nomem("get_exec_fname");

  mib[0] = CTL_KERN;
  mib[1] = KERN_PROCARGS2;
  mib[2] = pid;

  // obtain the arguments for the target process. this will
  // only work for processes that belong to the current uid,
  // so if you want it to work universally, you need to run
  // as root.
  size = argmax;
  buf[0] = '\0';
  if ( sysctl(mib, 3, args, &size, NULL, 0) != -1 )
  {
    char *ptr = args + sizeof(int);
    char *end = args + size;
//    show_hex(ptr, size, "procargs2\n");

    qstrncpy(buf, ptr, bufsize);
  }
  qfree(args);
  return buf;
}

//--------------------------------------------------------------------------
static bool thread_exit_event_planned(tid_t tid)
{
  for ( eventlist_t::iterator p=events.begin(); p != events.end(); ++p )
  {
    if ( p->eid == THREAD_EXIT && p->tid == tid )
      return true;
  }
  return false;
}

//--------------------------------------------------------------------------
static void update_threads(void)
{
  thread_act_port_array_t threadList;
  mach_msg_type_number_t threadCount;
  kern_return_t err = task_threads(task, &threadList, &threadCount);
  std::set<int> live_tids;
  if ( err == KERN_SUCCESS )
  {
    QASSERT(threadCount > 0);
    for ( int i=0; i < threadCount; i++ )
    {
      mach_port_t port = threadList[i];
      int tid = port;
//      msg("thread %d: port %d\n", i, tid);
      threads_t::iterator p = threads.find(tid);
      if ( p == threads.end() )
      {
        debug_event_t ev;
        ev.eid     = THREAD_START;
        ev.pid     = pid;
        ev.tid     = tid;
        ev.ea      = BADADDR;
        ev.handled = true;
        enqueue_event(ev, IN_BACK);
        threads.insert(make_pair(tid, ida_thread_info_t(tid, port)));
      }
      live_tids.insert(tid);
    }
    err = vm_deallocate (mach_task_self(), (vm_address_t)threadList, threadCount * sizeof (thread_t));
    QASSERT(err == KERN_SUCCESS);
    // remove dead threads
    for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p )
    {
      tid_t tid = p->first;
      if ( live_tids.find(tid) == live_tids.end() && !thread_exit_event_planned(tid) )
      {
        debug_event_t ev;
        ev.eid     = THREAD_EXIT;
        ev.pid     = pid;
        ev.tid     = tid;
        ev.ea      = BADADDR;
        ev.handled = true;
        enqueue_event(ev, IN_BACK);
      }
    }
  }
}

//--------------------------------------------------------------------------
static thid_t init_main_thread(void)
{
  thread_act_port_array_t threadList;
  mach_msg_type_number_t threadCount;
  kern_return_t err = task_threads(task, &threadList, &threadCount);
  QASSERT(err == KERN_SUCCESS);
  QASSERT(threadCount > 0);
  mach_port_t port = threadList[0]; // the first thread is the main thread
  thid_t tid = port;
  threads.insert(make_pair(tid, ida_thread_info_t(tid, port)));
  threads.begin()->second.block = bl_signal;
  err = vm_deallocate (mach_task_self(), (vm_address_t)threadList, threadCount * sizeof (thread_t));
  QASSERT(err == KERN_SUCCESS);
  return tid;
}

//--------------------------------------------------------------------------
static kern_return_t save_exception_ports(task_t task, mach_exception_port_info_t *info)
{
  info->count = (sizeof (info->ports) / sizeof (info->ports[0]));
  return task_get_exception_ports(task,
                                  EXC_MASK_ALL,
                                  info->masks,
                                  &info->count,
                                  info->ports,
                                  info->behaviors,
                                  info->flavors);
}

static kern_return_t restore_exception_ports (task_t task, const mach_exception_port_info_t *info)
{
  kern_return_t err = KERN_SUCCESS;
  for ( int i = 0; i < info->count; i++ )
  {
    err = task_set_exception_ports(task,
                                   info->masks[i],
                                   info->ports[i],
                                   info->behaviors[i],
                                   info->flavors[i]);
    if ( err != KERN_SUCCESS )
      break;
  }
  return err;
}

static void init_exception_ports(void)
{
  kern_return_t err;

  // allocate a new port to receive exceptions
  err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exc_port);
  QASSERT(err == KERN_SUCCESS);

  // add the 'send' right to send replys to threads
  err = mach_port_insert_right(mach_task_self(), exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND);
  QASSERT(err == KERN_SUCCESS);

  // save old exception ports
  err = save_exception_ports (task, &saved_exceptions);
  QASSERT(err == KERN_SUCCESS);

  // set new port for all exceptions
  err = task_set_exception_ports(task, EXC_MASK_ALL, exc_port, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
  QASSERT(err == KERN_SUCCESS);

//  msg("set up exception port %d\n", exc_port);
}

static void term_exception_ports(void)
{
  if ( exc_port != MACH_PORT_NULL )
  {
    kern_return_t err = mach_port_deallocate(mach_task_self(), exc_port);
    QASSERT(err == KERN_SUCCESS);
    exc_port = MACH_PORT_NULL;
  }
}

//--------------------------------------------------------------------------
static bool is_setgid_procmod(void)
{
  gid_t gid = getegid();
  struct group *gr = getgrgid(gid);
  if ( gr == NULL )
    return false;
  bool ok = strcmp(gr->gr_name, "procmod") == 0;
  endgrent();
  return ok;
}

//--------------------------------------------------------------------------
static bool handle_process_start(pid_t _pid)
{
  debdeb("handle process start %d\n", _pid);
  pid = _pid;

  /* Get the mach task for the target process */
  kern_return_t err = task_for_pid(mach_task_self(), pid, &task);
  if ( err == KERN_FAILURE ) // no access?
  {
    if ( !is_setgid_procmod() )
    {
      warning("The mac_server file must be setgid procmod to debug Mac OS X applications.\n"
              "Please use the following commands to change its permissions:\n"
              "  sudo chmod +sg mac_server\n"
              "  sudo chgrp procmod mac_server\n");
      return false;
    }
    debdeb("could not determine process %d port: %s\n", pid, mach_error_string(err));
    return false;
  }
  QASSERT(err == KERN_SUCCESS);

  int status;
  waitpid(pid, &status, 0);
  debdeb("first waitpid on %d: %x\n", pid, status);
  if ( !WIFSTOPPED(status) )
  {
    debdeb("not stopped?\n");
    return false;
  }
  if ( WSTOPSIG(status) != SIGTRAP && WSTOPSIG(status) != SIGSTOP )
  {
    debdeb("got signal %d?\n", WSTOPSIG(status));
    return false;
  }
  thid_t tid = init_main_thread();
  debdeb("initially stopped at %a pid=%d tid=%d task=%d\n", get_ip(tid), pid, tid, task);
  run_state = rs_running;

  debug_event_t ev;
  ev.eid     = PROCESS_START;
  ev.pid     = pid;
  ev.tid     = tid;
  ev.ea      = BADADDR;
  ev.handled = true;
  get_exec_fname(pid, ev.modinfo.name, sizeof(ev.modinfo.name));
  debdeb("gotexe: %s\n", ev.modinfo.name);
  ev.modinfo.size = calc_image_size(ev.modinfo.name, &ev.modinfo.base);
  ev.modinfo.rebase_to = BADADDR;

  // find the real executable base
  // also call get_memory_info() the first time
  // this should find dyld and set its bpt
  memory_info_t *miv;
  int qty;
  if ( get_memory_info(&miv, &qty, false) > 0 && !is_dll )
  {
    for ( int i=0; i < qty; i++ )
    {
      if ( strcmp(miv[i].name, ev.modinfo.name) == 0 )
      {
        ev.modinfo.rebase_to = miv[i].startEA;
        break;
      }
    }
    qfree(miv);
  }
  enqueue_event(ev, IN_FRONT);

  init_exception_ports();
  return true;
}

//--------------------------------------------------------------------------
int idaapi remote_start_process(const char *path,
                                const char *args,
                                const char *startdir,
                                int flags,
                                const char *input_path,
                                ulong input_file_crc32)
{
  // input file specified in the database does not exist
  if ( input_path[0] != '\0' && !qfileexist(input_path) )
    return -2;

  // temporary thing, later we will retrieve the real file name
  // based on the process id
  qstrncpy(input_file_path, input_path, sizeof(input_file_path));
  is_dll = (flags & DBG_PROC_IS_DLL) != 0;

  if ( !qfileexist(path) )
    return -1;

  int mismatch = 0;
  if ( !check_input_file_crc32(input_file_crc32) )
    mismatch = CRC32_MISMATCH;

  if ( startdir[0] != '\0' && chdir(startdir) == -1 )
  {
    debdeb("chdir '%s': %s\n", startdir, winerr(errno));
    return -1;
  }

  cleanup();

  int child_pid = fork();
  if ( child_pid == -1 )
  {
    debdeb("fork: %s\n", winerr(errno));
    return -1;
  }
  if ( child_pid == 0 ) // child
  {
    if ( qptrace(PT_TRACE_ME, 0, 0, 0) == -1 )
      error("TRACEME: %s", winerr(errno));
    char **argv = prepare_arguments(path, args);
    for ( char **ptr=argv; *ptr != NULL; ptr++ )
      debdeb("ARG: %s\n", *ptr);

    setpgid (0, 0);
    // drop extra privileges of the debugger server
    setgid(getgid());
    // free ida-resources
    for (int i = getdtablesize(); i > 2; i--)
      close(i);
    int code = execv(path, argv);
    qprintf("execv: %s\n", winerr(errno));
    _exit(1);
  }
  else                  // parent
  {
    if ( !handle_process_start(child_pid) )
      return -1;
  }

  return 1 | mismatch;
}


//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_attach_process(pid_t pid, int /*event_id*/)
{
  if ( qptrace(PT_ATTACH, pid, NULL, NULL) == 0
    && handle_process_start(pid) )
  {
    // generate the attach event
    debug_event_t ev;
    ev.eid     = PROCESS_ATTACH;
    ev.pid     = pid;
    ev.tid     = maintid();
    ev.ea      = get_ip(ev.tid);
    ev.handled = true;
    get_exec_fname(pid, ev.modinfo.name, sizeof(ev.modinfo.name));
    ev.modinfo.base = BADADDR;
    ev.modinfo.size = 0;
    ev.modinfo.rebase_to = BADADDR;
    enqueue_event(ev, IN_BACK);

    // generate THREAD_START events
    update_threads();

    // block the process until all generated events are processed
    attaching = true;
    return true;
  }
  return false;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_detach_process(void)
{
  if ( dyri.dyld_notify != 0 )
  {
    // remove the dyld breakpoint
    uchar push_ebp = 0x55;
    int size = remote_write_memory(dyri.dyld_notify, &push_ebp, 1);
    QASSERT(size == 1);
    dyri.dyld_notify = 0;
  }
  // suspend the process
  suspend_all_threads();
  // exit all exceptions
  for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p )
    if ( p->second.block == bl_exception )
      my_resume_thread(p->second);
  // cleanup exception ports
  term_exception_ports();
  // detach now
  bool ok = qptrace(PT_DETACH, pid, NULL, NULL) == 0;
  resume_all_threads();
  if ( ok )
  {
    debug_event_t ev;
    ev.eid     = PROCESS_DETACH;
    ev.pid     = pid;
    ev.tid     = maintid();
    ev.ea      = BADADDR;
    ev.handled = true;
    enqueue_event(ev, IN_BACK);
    return true;
  }
  return false;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_prepare_to_pause_process(void)
{
  debdeb("remote_prepare_to_pause_process\n");
  if ( run_state >= rs_exiting )
    return false;
  run_state = rs_pausing;
  kill(pid, SIGSTOP);
  return true;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_exit_process(void)
{
  run_state = rs_exiting;
  bool ok = false;
  if ( kill(pid, SIGKILL) == 0 )
  {
    ok = true;
    unblock_all_threads();
  }
  return ok;
}

//--------------------------------------------------------------------------
// Set hardware breakpoints for one thread
static bool set_hwbpts(int hThread)
{
  bool ok;
#if 0
  uchar *offset = (uchar *)qoffsetof(user, u_debugreg);

  ok = set_dr(hThread, 0, hwbpt_ea[0])
    && set_dr(hThread, 1, hwbpt_ea[1])
    && set_dr(hThread, 2, hwbpt_ea[2])
    && set_dr(hThread, 3, hwbpt_ea[3])
    && set_dr(hThread, 6, 0)
    && set_dr(hThread, 7, dr7);
//  printf("set_hwbpts: DR0=%08lX DR1=%08lX DR2=%08lX DR3=%08lX DR7=%08lX => %d\n",
//         hwbpt_ea[0],
//         hwbpt_ea[1],
//         hwbpt_ea[2],
//         hwbpt_ea[3],
//         dr7,
//         ok);
#endif
  return ok;
}

//--------------------------------------------------------------------------
static bool refresh_hwbpts(void)
{
  for ( threads_t::iterator p=threads.begin(); p != threads.end(); ++p )
    if ( !set_hwbpts(p->second.tid) )
      return false;
  return true;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_add_bpt(bpttype_t type, ea_t ea, int len)
{
  if ( type == BPT_SOFT )
  {
    static const uchar cc[] = { 0xCC };
    return remote_write_memory(ea, &cc, 1) == 1;
  }

  int i = find_hwbpt_slot(ea);    // get slot number
  if ( i != -1 )
  {
    hwbpt_ea[i] = ea;

    int lenc;                               // length code used by the processor
    if ( len == 1 ) lenc = 0;
    if ( len == 2 ) lenc = 1;
    if ( len == 4 ) lenc = 3;

    dr7 |= (1 << (i*2));            // enable local breakpoint
    dr7 |= (type << (16+(i*2)));    // set breakpoint type
    dr7 |= (lenc << (18+(i*2)));    // set breakpoint length

    return refresh_hwbpts();
  }
  return false;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_del_bpt(ea_t ea, const uchar *orig_bytes, int len)
{
  if ( orig_bytes != NULL )
    return remote_write_memory(ea, orig_bytes, len) == len;

  for ( int i=0; i < MAX_BPT; i++ )
  {
    if ( hwbpt_ea[i] == ea )
    {
      hwbpt_ea[i] = BADADDR;            // clean the address
      dr7 &= ~(3 << (i*2));             // clean the enable bits
      dr7 &= ~(0xF << (i*2+16));        // clean the length and type
      return refresh_hwbpts();
    }
  }
  return false;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_thread_get_sreg_base(thid_t tid, int sreg_value, ea_t *pea)
{
  return false;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_thread_suspend(thid_t tid)
{
  debdeb("remote_thread_suspend %d\n", tid);
  kern_return_t err = thread_suspend(tid);
  return err == KERN_SUCCESS;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_thread_continue(thid_t tid)
{
  debdeb("remote_thread_continue %d\n", tid);
  kern_return_t err = thread_resume(tid);
  return err == KERN_SUCCESS;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_thread_set_step(thid_t tid)
{
  ida_thread_info_t *t = get_thread(tid);
  if ( t == NULL )
    return false;
  t->single_step = true;
  return true;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_thread_read_registers(thid_t tid, regval_t *values, int count)
{
  if ( values == NULL )
    return false;

  i386_thread_state_t cpu;
  if ( !get_thread_state(tid, &cpu) )
    return false;

  // force null bytes at the end of floating point registers.
  // we need this to properly detect register modifications,
  // as we compare the whole regval_t structure !
  memset(values, 0, count * sizeof(regval_t));

  values[R_EAX   ].ival = ulong(cpu.eax);
  values[R_EBX   ].ival = ulong(cpu.ebx);
  values[R_ECX   ].ival = ulong(cpu.ecx);
  values[R_EDX   ].ival = ulong(cpu.edx);
  values[R_ESI   ].ival = ulong(cpu.esi);
  values[R_EDI   ].ival = ulong(cpu.edi);
  values[R_EBP   ].ival = ulong(cpu.ebp);
  values[R_ESP   ].ival = ulong(cpu.esp);
  values[R_EIP   ].ival = ulong(cpu.eip);
  values[R_EFLAGS].ival = ulong(cpu.eflags);
  values[R_CS    ].ival = ulong(cpu.cs);
  values[R_DS    ].ival = ulong(cpu.ds);
  values[R_ES    ].ival = ulong(cpu.es);
  values[R_FS    ].ival = ulong(cpu.fs);
  values[R_GS    ].ival = ulong(cpu.gs);
  values[R_SS    ].ival = ulong(cpu.ss);

  i386_float_state fpu;
  if ( !get_float_state(tid, &fpu) )
    return false;

  long double *fpr = (long double*)&fpu.fpu_stmm0;
  for (int i = 0; i < FPU_REGS_COUNT; i++)
    *(long double *)values[R_ST0+i].fval = *fpr++;

  values[R_CTRL].ival = *(ushort*)&fpu.fpu_fcw;
  values[R_STAT].ival = *(ushort*)&fpu.fpu_fsw;
  values[R_TAGS].ival = fpu.fpu_ftw;
  return true;
}

//--------------------------------------------------------------------------
// 1-ok, 0-failed
int idaapi remote_thread_write_register(thid_t tid, int reg_idx, const regval_t *value)
{
  if ( value == NULL )
    return false;

  if (reg_idx < R_ST0 || reg_idx >= R_CS)
  {
    // get the original context, to be sure to not modify unwanted registers
    i386_thread_state_t cpu;
    if ( !get_thread_state(tid, &cpu) )
      return false;
    switch (reg_idx)
    {
      case R_CS:     cpu.cs     = value->ival; break;
      case R_DS:     cpu.ds     = value->ival; break;
      case R_ES:     cpu.es     = value->ival; break;
      case R_FS:     cpu.fs     = value->ival; break;
      case R_GS:     cpu.gs     = value->ival; break;
      case R_SS:     cpu.ss     = value->ival; break;
      case R_EAX:    cpu.eax    = value->ival; break;
      case R_EBX:    cpu.ebx    = value->ival; break;
      case R_ECX:    cpu.ecx    = value->ival; break;
      case R_EDX:    cpu.edx    = value->ival; break;
      case R_ESI:    cpu.esi    = value->ival; break;
      case R_EDI:    cpu.edi    = value->ival; break;
      case R_EBP:    cpu.ebp    = value->ival; break;
      case R_ESP:    cpu.esp    = value->ival; break;
      case R_EIP:    cpu.eip    = value->ival; break;
      case R_EFLAGS: cpu.eflags = value->ival; break;
    }

    return set_thread_state(tid, &cpu);
  }
  else // FPU related register
  {
    // get the original context, to be sure to not modify unwanted registers
    i386_float_state fpu;
    if ( get_float_state(tid, &fpu) )
      return false;

    if (reg_idx >= R_ST0+FPU_REGS_COUNT) // FPU status registers
    {
      switch (reg_idx)
      {
        case R_CTRL: *(ushort*)&fpu.fpu_fcw = value->ival; break;
        case R_STAT: *(ushort*)&fpu.fpu_fsw = value->ival; break;
        case R_TAGS:            fpu.fpu_ftw = value->ival; break;
      }
    }
    else // FPU floating point register
    {
      long double *fptr = (long double*)&fpu.fpu_stmm0;
      fptr += reg_idx - R_ST0;
      *fptr = *(long double *)&value->fval;
    }
    return set_float_state(tid, &fpu);
  }
}

//--------------------------------------------------------------------------
static bool is_dylib_header(ea_t base, char *filename, size_t namesize)
{
  mach_header mh;
  if ( read_mem(base, &mh, sizeof(mh)) != KERN_SUCCESS )
    return false;

  if ( mh.magic != MH_MAGIC || mh.filetype != MH_DYLINKER )
    return false;

  // seems to be dylib
  // find its file name
  filename[0] = '\0';
  ea_t ea = base + sizeof(mh);
  for ( int i=0; i < mh.ncmds; i++ )
  {
    struct load_command lc;
    lc.cmd = 0;
    read_mem(ea, &lc, sizeof(lc));
    if ( lc.cmd == LC_ID_DYLIB )
    {
      struct dylib_command dcmd;
      read_mem(ea, &dcmd, sizeof(dcmd));
      read_mem(ea+dcmd.dylib.name.offset, filename, namesize);
      break;
    }
    else if ( lc.cmd == LC_ID_DYLINKER )
    {
      struct dylinker_command dcmd;
      read_mem(ea, &dcmd, sizeof(dcmd));
      read_mem(ea+dcmd.name.offset, filename, namesize);
      break;
    }
    ea += lc.cmdsize;
  }
  return true;
}

//--------------------------------------------------------------------------
// find a dll in the memory information array
static bool exist_dll(const dyriv_t &riv, ea_t base)
{
  // dyld is never unloaded
  if ( base == dylib )
    return true;
  for ( int i=0; i < riv.size(); i++ )
    if ( riv[i].addr == base )
      return true;
  return false;
}

//--------------------------------------------------------------------------
static void update_dylib(void)
{
//  msg("start: update_dylib\n");
  if ( read_mem(dylib_infos, &dyri, sizeof(dyri)) == KERN_SUCCESS )
  {
    QASSERT(dyri.version == 1);

    if ( dyri.num_info > 0 && dyri.info_array != 0 )
    {
      dyriv_t riv;
      riv.resize(dyri.num_info);
      int nbytes = dyri.num_info * sizeof(dyld_raw_info);
      if ( read_mem(dyri.info_array, &riv[0], nbytes) != KERN_SUCCESS )
        return;
//      show_hex(&riv[0], nbytes, "riv:\n");
      // remove unexisting dlls
      images_t::iterator p;
      for ( p=dlls.begin(); p != dlls.end(); )
      {
        if ( !exist_dll(riv, p->first) )
        {
          debug_event_t ev;
          ev.eid     = LIBRARY_UNLOAD;
          ev.pid     = pid;
          ev.tid     = maintid();
          ev.ea      = BADADDR;
          ev.handled = true;
          qstrncpy(ev.info, p->second.name.c_str(), sizeof(ev.info));
          enqueue_event(ev, IN_FRONT);
          dlls.erase(p++);
        }
        else
        {
          ++p;
        }
      }
      // add new dlls
      for ( int i=0; i < riv.size(); i++ )
      {
        // address zero is ignored
        if ( riv[i].addr == 0 )
          continue;
        p = dlls.find(riv[i].addr);
        if ( p == dlls.end() )
        {
          char buf[QMAXPATH];
          memset(buf, 0, sizeof(buf));
          read_mem(riv[i].name, buf, sizeof(buf)); // may fail because we don't know exact size
          buf[sizeof(buf)-1] = '\0';
//          msg("dll name at %a is '%s'\n", riv[i].name, buf);
          add_dll(riv[i].addr, buf);
        }
      }
    }
  }
//  msg("end: update_dylib\n");
}

//--------------------------------------------------------------------------
static void init_dylib(ea_t addr, const char *fname)
{
  msg("%a: located dylib header and file '%s'\n", addr, fname);
  dylib = addr;

  add_dll(addr, fname);
  // immediately process it
  remote_stopped_at_debug_event();

  // check if we just found the address of dyld raw information
  if ( dyri.version != 1 && dylib_infos != BADADDR )
  {
    read_mem(dylib_infos, &dyri, sizeof(dyri));
    if ( dyri.version != 1 )
      error("Unexpected dyld link version (%d), can not continue.", dyri.version);
    // set a breakpoint for library loads/unloads
    msg("%a: setting bpt for library notifications\n", dyri.dyld_notify);
    uchar opcode = 0;
    read_mem(dyri.dyld_notify, &opcode, 1);
    if ( opcode != 0x55 ) // we expect push ebp
      error("Unexpected condition in the debugger server (init_dylib)");
    remote_add_bpt(BPT_SOFT, dyri.dyld_notify, 0);
//    msg("bpt is set!\n");
  }
}

//--------------------------------------------------------------------------
static image_info_t *get_image(ea_t addr)
{
  if ( dlls.empty() )
    return NULL;
  images_t::iterator p = dlls.lower_bound(addr);
  if ( p != dlls.end() && p->first == addr )
    return &p->second;
  if ( p == dlls.begin() )
    return NULL;
  --p;
  if ( p->first > addr )
    return NULL;
  image_info_t &im = p->second;
  if ( im.base+im.imagesize <= addr )
    return NULL;
  return &im;
}

//--------------------------------------------------------------------------
static const char *get_share_mode_name(unsigned char sm)
{
  switch ( sm )
  {
    case SM_COW:             return "COW";
    case SM_PRIVATE:         return "PRIVATE";
    case SM_EMPTY:           return "EMPTY";
    case SM_SHARED:          return "SHARED";
    case SM_TRUESHARED:      return "TRUESHARED";
    case SM_PRIVATE_ALIASED: return "PRIV_ALIAS";
    case SM_SHARED_ALIASED:  return "SHRD_ALIAS";
  }                               // 1234567890
  static char buf[80];
  qsnprintf(buf, sizeof(buf), "%x", sm);
  return buf;
}

//--------------------------------------------------------------------------
static int get_memory_info(memory_info_t **areas, int *qty, bool suspend)
{
  if ( suspend && !suspend_all_threads() )
    return -1;
  if ( exited() )
    return -1;

  qvector<memory_info_t> miv;
  vm_size_t size = 0;
  for ( vm_address_t addr = 0; ; addr += size )
  {
    mach_port_t object_name; // unused
    vm_region_top_info_data_t info;
    mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
    kern_return_t code = vm_region(task, &addr, &size, VM_REGION_TOP_INFO,
                        (vm_region_info_t)&info, &count, &object_name);

//    debdeb("task=%d addr=%a size=%x err=%x\n", task, addr, size, code);
    if ( code != KERN_SUCCESS )
      break;

    // ignore segments at address 0
    if ( addr == 0 )
      continue;

    // find dylib in the memory if not found yet
    char fname[QMAXPATH];
    if ( dylib == BADADDR && is_dylib_header(addr, fname, sizeof(fname)) )
      init_dylib(addr, fname);

    vm_address_t subaddr;
    vm_size_t subsize = 0;
    vm_address_t end = addr + size;
    for ( subaddr=addr; subaddr < end; subaddr += subsize )
    {
      natural_t depth = 1;
      vm_region_submap_info_data_64_t sinfo;
      mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
      kern_return_t code = vm_region_recurse(task, &subaddr, &subsize, &depth,
                                         (vm_region_info_t)&sinfo, &count);
      QASSERT(code == KERN_SUCCESS);

      memory_info_t mi;
      mi.startEA = subaddr;
      mi.endEA   = subaddr + subsize;
      // check if we have information about this memory chunk
      image_info_t *im = get_image(subaddr);
      if ( im == NULL )
      {
        // it is not a good idea to hide any addresses because
        // they will be used by the program and the user will be
        // left blind, not seeing the executed instructions.
#if 0
        if ( subaddr >= GLOBAL_SHARED_TEXT_SEGMENT
          && subaddr < GLOBAL_SHARED_TEXT_SEGMENT+SHARED_TEXT_REGION_SIZE )
        {
          // hide unloaded shared libraries???
          continue;
        }
#endif
        mi.name[0] = '\0';
      }
      else
      {
        qstrncpy(mi.name, im->name.c_str(), sizeof(mi.name));
      }
      mi.perm = 0;
      if ( sinfo.protection & 1 ) mi.perm |= SEGPERM_READ;
      if ( sinfo.protection & 2 ) mi.perm |= SEGPERM_WRITE;
      if ( sinfo.protection & 4 ) mi.perm |= SEGPERM_EXEC;
      miv.push_back(mi);
//      msg("%a..%a: share mode %s\n", subaddr, subaddr+subsize, get_share_mode_name(sinfo.share_mode));
    }
  }

  // add hidden dsmos data
  memory_info_t mi;
  mi.startEA = 0xFFFF0000;
  mi.endEA   = 0xFFFFF000;
  qstrncpy(mi.name, "COMMPAGE", sizeof(mi.name));
  mi.perm = 0;
  miv.push_back(mi);

  update_dylib();
  if ( suspend )
    resume_all_threads();
  *qty = miv.size();
  *areas = miv.extract();
  return 1;
}

//--------------------------------------------------------------------------
int idaapi remote_get_memory_info(memory_info_t **areas, int *qty)
{
  int code = get_memory_info(areas, qty, true);
  if ( code == 1 )
  {
    if ( same_as_oldmemcfg(*areas, *qty) )
    {
      qfree(*areas);
      code = -2;
    }
    else
    {
      save_oldmemcfg(*areas, *qty);
    }
  }
  return code;
}

//--------------------------------------------------------------------------
int idaapi remote_init(bool _debug_debugger)
{
  // remember if the input is a dll
  cleanup();
  cleanup_hwbpts();
  debug_debugger = _debug_debugger;
  return 3; // process_get_info, detach
}

//--------------------------------------------------------------------------
void idaapi remote_term(void)
{
  cleanup();
  cleanup_hwbpts();
  input_file_path[0] = '\0';
  /* ... */
}

//--------------------------------------------------------------------------
bool idaapi thread_get_fs_base(thid_t tid, int reg_idx, ea_t *pea)
{
  qnotused(tid);
  qnotused(reg_idx);
  qnotused(pea);
  return false;
}

//--------------------------------------------------------------------------
int idaapi remote_ioctl(int fn, const void *in, size_t insize, void **, ssize_t *)
{
  return -1;
}
