/* Mac OS X support for GDB, the GNU debugger.
   Copyright 1997, 1998, 1999, 2000, 2001, 2002
   Free Software Foundation, Inc.

   Contributed by Apple Computer, Inc.

   This file is part of GDB.

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

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

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.  */

/* This file is part of GDB.

GDB 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 1, or (at your option)
any later version.

GDB 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 GDB; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "defs.h"
#include "inferior.h"
#include "target.h"
#include "symfile.h"
#include "symtab.h"
#include "objfiles.h"
#include "gdbcmd.h"
#include "language.h"

#include "libaout.h"	 	/* FIXME Secret internal BFD stuff for a.out */
#include "aout/aout64.h"
#include "complaints.h"

#include "mach-o.h"
#include "objc-lang.h"

#include "macosx-tdep.h"

#if 0
struct deprecated_complaint unknown_macho_symtype_complaint =
  {"unknown Mach-O symbol type %s", 0, 0};

struct deprecated_complaint unknown_macho_section_complaint =
  {"unknown Mach-O section value %s (assuming DATA)", 0, 0};

struct deprecated_complaint unsupported_indirect_symtype_complaint =
  {"unsupported Mach-O symbol type %s (indirect)", 0, 0};
#endif

#define BFD_GETB16(addr) ((addr[0] << 8) | addr[1])
#define BFD_GETB32(addr) ((((((unsigned long) addr[0] << 8) | addr[1]) << 8) | addr[2]) << 8 | addr[3])
#define BFD_GETL16(addr) ((addr[1] << 8) | addr[0])
#define BFD_GETL32(addr) ((((((unsigned long) addr[3] << 8) | addr[2]) << 8) | addr[1]) << 8 | addr[0])

unsigned char macosx_symbol_types[256];

static unsigned char macosx_symbol_type_base (macho_type)
  unsigned char macho_type;
{
  unsigned char mtype = macho_type;
  unsigned char ntype = 0;
  
  if (macho_type & BFD_MACH_O_N_STAB) {
    return macho_type;
  }

  if (mtype & BFD_MACH_O_N_PEXT) {
    mtype &= ~BFD_MACH_O_N_PEXT;
    ntype |= N_EXT;
  }

  if (mtype & BFD_MACH_O_N_EXT) {
    mtype &= ~BFD_MACH_O_N_EXT;
    ntype |= N_EXT;
  }

  switch (mtype & BFD_MACH_O_N_TYPE) {
  case BFD_MACH_O_N_SECT:
    /* should add section here */
    break;

  case BFD_MACH_O_N_PBUD:
    ntype |= N_UNDF;
    break;

  case BFD_MACH_O_N_ABS:
    ntype |= N_ABS;
    break;

  case BFD_MACH_O_N_UNDF:
    ntype |= N_UNDF;
    break;

  case BFD_MACH_O_N_INDR:
    /* complain (&unsupported_indirect_symtype_complaint, local_hex_string (macho_type)); */
    return macho_type;

  default:
    /* complain (&unknown_macho_symtype_complaint, local_hex_string (macho_type)); */
    return macho_type;
  }
  mtype &= ~BFD_MACH_O_N_TYPE;

  CHECK_FATAL (mtype == 0);

  return ntype;
}

static void macosx_symbol_types_init ()
{
  unsigned int i;
  for (i = 0; i < 256; i++) {
    macosx_symbol_types[i] = macosx_symbol_type_base (i);
  }
}

static unsigned char macosx_symbol_type (macho_type, macho_other, abfd)
     unsigned char macho_type;
     unsigned char macho_other;
     bfd *abfd;
{
  unsigned char ntype = macosx_symbol_types[macho_type];

  if ((macho_type & BFD_MACH_O_N_TYPE) == BFD_MACH_O_N_SECT)
    {
      if (macho_other == 1)
	{
	  ntype |= N_TEXT;
	}
      else if (macho_other > 0 && macho_other <= abfd->tdata.mach_o_data->nsects)
	{
	  const char *segname = abfd->tdata.mach_o_data->sections[macho_other - 1]->segname;
	  const char *sectname = abfd->tdata.mach_o_data->sections[macho_other - 1]->sectname;
	
	  if ((segname != NULL) && (strcmp (segname, "__DATA") == 0))
	    {
	      if ((sectname != NULL) && (strcmp (sectname, "__bss") == 0))
		ntype |= N_BSS;
	      else
		ntype |= N_DATA;
	    }

	  if ((segname != NULL) && (strcmp (segname, "__TEXT") == 0))
	    ntype |= N_TEXT;
	}
      else
	{
	  /* complain (&unknown_macho_section_complaint, local_hex_string (macho_other)); */
	  ntype |= N_DATA;
	}
    }
  
  return ntype;
}

void macosx_internalize_symbol (in, ext, abfd)
     struct internal_nlist *in;
     struct external_nlist *ext;
     bfd *abfd;
{
  if (bfd_header_big_endian (abfd)) {
    in->n_strx = BFD_GETB32 (ext->e_strx);
    in->n_desc = BFD_GETB16 (ext->e_desc);
    in->n_value = BFD_GETB32 (ext->e_value);
  } else if (bfd_header_little_endian (abfd)) {
    in->n_strx = BFD_GETL32 (ext->e_strx);
    in->n_desc = BFD_GETL16 (ext->e_desc);
    in->n_value = BFD_GETL32 (ext->e_value);
  } else {
    error ("unable to internalize symbol (unknown endianness)");
  }

  in->n_type = macosx_symbol_type (ext->e_type[0], ext->e_other[0], abfd);
  in->n_other = 0;
}

CORE_ADDR dyld_symbol_stub_function_address (CORE_ADDR pc, const char **name)
{
  struct symbol *sym = NULL;
  struct minimal_symbol *msym = NULL;
  const char *lname = NULL;

  lname = dyld_symbol_stub_function_name (pc);
  if (name)
    *name = lname;

  if (lname == NULL)
    return 0;

  /* found a name, now find a symbol and address */

  sym = lookup_symbol (lname, 0, VAR_NAMESPACE, 0, 0);
  if ((sym == NULL) && (lname[0] == '_'))
    sym = lookup_symbol (lname + 1, 0, VAR_NAMESPACE, 0, 0);
  if (sym != NULL && SYMBOL_BLOCK_VALUE (sym) != NULL)
    return BLOCK_START (SYMBOL_BLOCK_VALUE (sym));

  msym = lookup_minimal_symbol (lname, NULL, NULL);
  if ((msym == 0) && (lname[0] == '_'))
    msym = lookup_minimal_symbol (lname + 1, NULL, NULL);
  if (msym != NULL)
    return SYMBOL_VALUE_ADDRESS (msym);

  return 0;
}

const char *dyld_symbol_stub_function_name (CORE_ADDR pc)
{
  struct minimal_symbol *msymbol = NULL;
  const char *DYLD_PREFIX = "dyld_stub_";

  msymbol = lookup_minimal_symbol_by_pc (pc);

  if (msymbol == NULL)
    return NULL;

  if (SYMBOL_VALUE_ADDRESS (msymbol) != pc)
    return NULL;

  if (strncmp (SYMBOL_NAME (msymbol), DYLD_PREFIX, strlen (DYLD_PREFIX)) != 0)
    return NULL;

  return SYMBOL_NAME (msymbol) + strlen (DYLD_PREFIX);
}

static void info_trampoline_command (char *exp, int from_tty)
{
  struct expression *expr;
  struct value *val;
  CORE_ADDR address;
  CORE_ADDR trampoline;
  CORE_ADDR objc;

  expr = parse_expression (exp);
  val = evaluate_expression (expr);
  if (TYPE_CODE (VALUE_TYPE (val)) == TYPE_CODE_REF)
    val = value_ind (val);
  if ((TYPE_CODE (VALUE_TYPE (val)) == TYPE_CODE_FUNC) && (VALUE_LVAL (val) == lval_memory))
    address = VALUE_ADDRESS (val);
  else
    address = value_as_address (val);

#if defined (TARGET_POWERPC)
  trampoline = ppc_macosx_skip_trampoline_code (address);
#elif defined (TARGET_I386)
  trampoline = i386_macosx_skip_trampoline_code (address);
#else
#error unknown architecture
#endif

  find_objc_msgcall (trampoline, &objc);

  fprintf_filtered 
    (gdb_stderr, "Function at 0x%lx becomes 0x%lx becomes 0x%lx\n",
     (unsigned long) address, (unsigned long) trampoline, (unsigned long) objc);
}

void
_initialize_macosx_tdep ()
{
  macosx_symbol_types_init ();

  add_info ("trampoline", info_trampoline_command,
	    "Resolve function for DYLD trampoline stub and/or Objective-C call");
}
