// -*-C++-*- 

/*  src/Backtrace.cpp  */

/*
 * Author: Philogelos A. <Philogelos@yahoo.com>
 * Maintainer: Philogelos A.
 * Keywords: C++, library, containers
 *
 * Copyright (C) 1999 Philogelos A.
 *
 * This file is part of Quercus Robusta.
 *
 * Quercus Robusta is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This software 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 Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this software; see the file COPYING.LIB.  If not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */



/* $Id$ */
#if !defined(_INLINE)
static char cvsid[] = "@(#)$Id$";
static char debugFileId[] = __FILE__;
#endif


#include "Debug.hpp"
#include "OGuard.hpp"
#include "Backtrace.hpp"
#include "StringBuffer.hpp"
#include "LinkManager.hpp"
#include "DataWrapper.hpp"
#include "platform/Platform.hpp"
#include "containers/lists/List.hpp"
#include "containers/dicts/DumbDictionary.hpp"

#if defined( __GNUC__ )
/* BFD uses boolean typedef (well, quercus too) */
#define boolean bfd_boolean_kludge
#define TRUE_FALSE_ALREADY_DEFINED
#include <bfd.h>
#include <stdlib.h> /* for qsort of sorts */
#undef boolean
#endif

#include "defines.h"

Backtrace::Backtrace()
{
  frames = new List();
  LinkManager::reg( this, frames );
  truncated = false;
  tableLoaded = false;
  table = new DumbDictionary();
  LinkManager::reg( this, table );
}

Backtrace::~Backtrace()
{
  LinkManager::free( this, frames );
  LinkManager::free( this, table );
}

void Backtrace::fill()
{
  void *fp;
  Index frame = 0;
    
  do
	{
	  fp = getAddressAtFrame( frame );
	  if( fp == NULL )
		{
		  truncated = true;
		  return;
		}
	  else
		{
		  frames -> append( new DataWrapper( fp ) );
		  ++frame;
		}
	}
  while( ! isInitialFrame( fp ) ); /*!( ( ( ( void * ) mainStartsHere ) <= fp ) && 
									 ( fp <= ( ( void * ) mainEndsHere ) ) ) ); */
}

void *Backtrace::getFrame( Index aFrame ) const
{
  preC_( isValidFrame( aFrame ) );
  
  return TCAST( getFrames() -> getAt( aFrame ), DataWrapper ) -> getValue();
}

boolean Backtrace::isTruncated() const
{
  return truncated;
}

Index Backtrace::getDepth() const
{
  return getFrames() -> length();
}

void Backtrace::setAddress( Index aFrame, void *anAddress )
{
  preC_( isValidFrame( aFrame ) );
  
  TCAST( getFrames() -> getAt( aFrame ), DataWrapper ) -> setValue( anAddress );
}

boolean Backtrace::isValidFrame( Index aFrame ) const
{
  return getFrames() -> isValidIndex( aFrame );
}
  
List *Backtrace::getFrames() const
{
  return frames;
}

boolean Backtrace::isInitialFrame( void *anAddress )
{
  preC_( initialised );
  return( anAddress == crt0Address );
}

void Backtrace::initBacktrace()
{
  /* __builtin_return_address( 0 ) == &initBacktrace()
	 __builtin_return_address( 1 ) == &main()
	 __builtin_return_address( 2 ) == somewhere in crt0 or whatever... */
  crt0Address = getAddressAtFrame( 2 );
  initialised = true;
}
  
String  Backtrace::toString() const
{
  Index depth;
  StringBuffer result( StringBuffer::defaultLength,
							 StringBuffer::defaultDelta );
  
  result.add( Object::toString().getUNIChars() );
  result.add( getFrames() -> toString().getUNIChars() );
  depth = 0;
  for( getFrames() -> toFirst() ; ! getFrames() -> atEnd() ; getFrames() -> next() )
	{
	  DataWrapper *frame;
	  
	  frame = DCAST( getFrames() -> getCurrentValue(), DataWrapper );
	  OGuard _frame( frame, this );
	  
	  result.add( String( "[ %li ]: 0x%x (%s)\n" ).
				  sprintf( 1, depth++, frame -> getValue(),
						   getNameFor( frame -> getValue() ).getChars() ).
				  getUNIChars() );
	}
  return result.asString();
}

String  Backtrace::getString() const
{
  return Object::getString() + 
	getFrames() -> getString();
}

String  Backtrace::getClassName() const
{
  return "Backtrace";
}

#define RA( i ) __builtin_return_address( i )

void *Backtrace::getAddressAtFrame( Index aFrame )
{
  preC_( aFrame >= 0 );

#if defined( __GNUC__ )
  switch( aFrame )
	{
	case 0: return RA( 0 );
	case 1: return RA( 1 );
	case 2: return RA( 2 );
	case 3: return RA( 3 );
	case 4: return RA( 4 );
	case 5: return RA( 5 );
	case 6: return RA( 6 );
	case 7: return RA( 7 );
	case 8: return RA( 8 );
	case 9: return RA( 9 );
	case 10: return RA( 10 );
	case 11: return RA( 11 );
	case 12: return RA( 12 );
	case 13: return RA( 13 );
	case 14: return RA( 14 );
	case 15: return RA( 15 );
	case 16: return RA( 16 );
	case 17: return RA( 17 );
	case 18: return RA( 18 );
	case 19: return RA( 19 );
	case 20: return RA( 20 );
	case 21: return RA( 21 );
	case 22: return RA( 22 );
	case 23: return RA( 23 );
	case 24: return RA( 24 );
	case 25: return RA( 25 );
	case 26: return RA( 26 );
	case 27: return RA( 27 );
	case 28: return RA( 28 );
	case 29: return RA( 29 );
	case 30: return RA( 30 );
	default: return ( void * ) NULL;
	}
#else
  /* XXX is there any (semi-)portable way to walk
	 through stack frames? */
  return ( void * ) NULL;
#endif
}

#undef RA

#if defined( __GNUC__ )
static int compare_by_address( const void *symb1, const void *symb2 )
{
  return( bfd_asymbol_value( *( asymbol ** ) symb1 ) - 
		  bfd_asymbol_value( *( asymbol ** ) symb2 ) );
}
#endif

boolean Backtrace::loadSymTableFor( const char *anImage ) const
{
#if !defined( __GNUC__ )
  return false;
#else
  preC_( anImage != NULL );
  
  bfd *abfd;
  long storage_needed = 0;
  static asymbol **symbol_table = NULL;
  long number_of_symbols;

  if( tableLoaded )
	{
	  return true;
	}

  
  bfd_init();
  if( bfd_get_error() != bfd_error_no_error )
	{
	  bfd_perror( "bfd_init()" );
	  return false;
	}

  abfd = bfd_openr( anImage, "default" );
  if( bfd_get_error() != bfd_error_no_error )
	{
	  bfd_perror( "bfd_fdopenr()" );
	  return false;
	}
  if( ! bfd_check_format( abfd, bfd_object ) )
	{
	  bfd_perror( "bfd_check_format()" );
	  return false;
	}

  storage_needed = bfd_get_symtab_upper_bound( abfd );
     
  if( storage_needed < 0 )
	{
	  bfd_perror( "bfd_get_symtab_upper_bound(): storage_needed < 0" );
	  return false;
	}
  if( storage_needed == 0 ) 
	{
	  Debug::getLogger() -> log( "no symbols\n" );
	  return false;
	}
  symbol_table = new asymbol *[ storage_needed ];

  number_of_symbols = bfd_canonicalize_symtab( abfd, symbol_table );
  if( number_of_symbols < 0 )
	{
	  bfd_perror( "bfd_canonicalize_symtab()" );
	  delete []symbol_table;
	  return false;
	}
     
  qsort( symbol_table, number_of_symbols, sizeof symbol_table[ 0 ],
		 compare_by_address );

  for( Index i = 0 ; i < number_of_symbols ; ++i )
	{
	  /** to defeat strange entries on SPARC Solaris */
	  if( bfd_asymbol_bfd( symbol_table[ i ] ) == NULL )
		{
		  continue;
		}
	  table -> addKey
		( new DataWrapper( ( void * ) bfd_asymbol_value( symbol_table[ i ] ) ),
		  new String( symbol_table[ i ] -> name ) );
	}
  
  if( ! bfd_close( abfd ) )
	{
	  bfd_perror( "bfd_close()" );
	  delete []symbol_table;
	  LinkManager::free( this, table );
	  MUTATE_MEMBER( table, Backtrace ) = new DumbDictionary();
	  return false;
	}
  
  MUTATE_MEMBER( tableLoaded, Backtrace ) = true;
  
  return true;
/* defined( __GNUC__ ) */
#endif
}

String Backtrace::getNameFor( void *anAddress ) const
{
  Enumeration *en;
  DataWrapper *current;
    
  if( !loadSymTableFor( Platform::getInstance() -> getImageName() ) )
	{
	  return "no symbol table";
	}
  test_( table != ( DumbDictionary * ) NULL );
  
  en = table -> getValues();
  current = ( DataWrapper * ) NULL;
  while( en -> hasMoreElements() )
	{
	  DataWrapper *symbol;
	  	  
	  symbol = TCAST( en -> getNextElement(), DataWrapper );

	  if( ( current <= anAddress ) &&
		  ( anAddress < symbol -> getValue() ) )
		{
		  if( current == NULL )
			{
			  return "unexistent";
			}
		  else
			{
			  return *TCAST( table -> getValueAtKey( current ), String );
			}
		}
	  else
		{
		  current = symbol;
		}
	}
  if( current != ( DataWrapper * ) NULL )
	{
	  return *TCAST( table -> getValueAtKey( current ), String );
	}
  else
	{
	  return "not found";
	}
}


void *Backtrace::crt0Address = ( void * ) NULL;
boolean Backtrace::initialised = false;

#if defined( TESTING )
boolean Backtrace::tester( int aParam ) const
{
  {
	Backtrace *trace;
	
	trace = new Backtrace();
	OGuard _trace( trace, this );
	
	trace -> fill();
	Debug::getLogger() -> logObject( "trace: %s", trace );
  }
  return true;
}
#endif

#if defined(_INLINE)
#include "../src/Debug.ipp"
#endif


/* $Log$ */