/*
   (c) Copyright 2001-2008  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

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

   This library 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
   Lesser General Public License for more details.

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

#include <config.h>

#include <direct/filesystem.h>
#include <direct/list.h>
#include <direct/log.h>
#include <direct/mem.h>
#include <direct/memcpy.h>
#include <direct/messages.h>
#include <direct/print.h>
#include <direct/system.h>
#include <direct/thread.h>
#include <direct/trace.h>
#include <direct/util.h>


#if DIRECT_BUILD_TRACE

#if DIRECT_BUILD_DYNLOAD
#include <dlfcn.h>
#endif

#define MAX_BUFFERS 200
#define MAX_LEVEL   200

#define NAME_LEN    92


typedef enum {
     TF_NONE   = 0x00000000,

     TF_DEBUG  = 0x00000001
} TraceFlags;

typedef struct {
     void       *addr;
     TraceFlags  flags;
} Trace;

struct __D_DirectTraceBuffer {
     pid_t tid;
     char *name;
     int   level;
     bool  in_trace;
     Trace trace[MAX_LEVEL];
};

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

static DirectTraceBuffer *buffers[MAX_BUFFERS];
static int                buffers_num  = 0;
static DirectMutex        buffers_lock = DIRECT_RECURSIVE_MUTEX_INITIALIZER(buffers_lock);

DIRECT_TLS_DATA( trace_key );

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

__attribute__((no_instrument_function))
static void
buffer_destroy( void *arg )
{
     int                i;
     DirectTraceBuffer *buffer = arg;

     direct_mutex_lock( &buffers_lock );

     /* Remove from list. */
     for (i=0; i<buffers_num; i++) {
          if (buffers[i] == buffer)
               break;
     }

     for (; i<buffers_num-1; i++)
          buffers[i] = buffers[i+1];

     buffers_num--;

     /* Deallocate the buffer. */
     direct_trace_free_buffer( buffer );

     direct_mutex_unlock( &buffers_lock );
}

__attribute__((no_instrument_function))
static inline DirectTraceBuffer *
get_trace_buffer( void )
{
     DirectTraceBuffer *buffer;

     buffer = direct_tls_get( trace_key );
     if (!buffer) {
          const char *name = direct_thread_self_name();

          direct_mutex_lock( &buffers_lock );

          if (!buffers_num)
               direct_tls_register( &trace_key, buffer_destroy );
          else if (buffers_num == MAX_BUFFERS) {
               D_ERROR( "Direct/Trace: Maximum number of threads (%d) reached!\n", MAX_BUFFERS );
               direct_mutex_unlock( &buffers_lock );
               return NULL;
          }

          direct_tls_set( trace_key, buffer = direct_calloc( 1, sizeof(DirectTraceBuffer) ) );

          buffer->tid  = direct_gettid();
          buffer->name = name ? direct_strdup( name ) : NULL;

          buffers[buffers_num++] = buffer;

          direct_mutex_unlock( &buffers_lock );
     }

     return buffer;
}

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

typedef struct {
     long offset;
     char name[NAME_LEN];
} Symbol;

typedef struct {
     DirectLink  link;

     char       *filename;
     Symbol     *symbols;
     int         capacity;
     int         num_symbols;
} SymbolTable;

static DirectLink  *tables      = NULL;
static DirectMutex  tables_lock = DIRECT_RECURSIVE_MUTEX_INITIALIZER(tables_lock);


__attribute__((no_instrument_function))
static void
add_symbol( SymbolTable *table, long offset, const char *name )
{
     Symbol *symbol;

     if (table->num_symbols == table->capacity) {
          Symbol *symbols;
          int     capacity = table->capacity * 2;

          if (!capacity)
               capacity = 256;

          symbols = direct_malloc( capacity * sizeof(Symbol) );
          if (!symbols) {
               D_WARN( "out of memory" );
               return;
          }

          direct_memcpy( symbols, table->symbols, table->num_symbols * sizeof(Symbol) );

          direct_free( table->symbols );

          table->symbols  = symbols;
          table->capacity = capacity;
     }

     symbol = &table->symbols[ table->num_symbols++ ];

     symbol->offset = offset;

     direct_snputs( symbol->name, name, NAME_LEN );
}

__attribute__((no_instrument_function))
static SymbolTable *
load_symbols( const char *filename )
{
     DirectResult ret;
     SymbolTable *table;
     DirectFile   fp;
     bool         is_pipe = false;
     char         file[1024];
     char         line[1024];
     int          command_len;
     const char  *full_path = filename;
     char        *tmp;

     if (filename) {
          ret = direct_access( filename, R_OK );
          if (ret && ret == DR_FILENOTFOUND) {
               size_t len = 0;

               ret = direct_readlink( "/proc/self/exe", file, sizeof(file) - 1, &len );
               if (ret) {
                    D_DERROR( ret, "Direct/Trace: direct_readlink( \"/proc/self/exe\" ) failed!\n" );
                    return NULL;
               }

               // Terminator!
               file[len] = 0;


               tmp = strrchr( file, '/' );
               if (!tmp)
                    return NULL;

               if (direct_strcmp( filename, tmp + 1 ))
                    return NULL;

               full_path = file;
          }
     }
     else {
          size_t len = 0;

          ret = direct_readlink( "/proc/self/exe", file, sizeof(file) - 1, &len );
          if (ret) {
               D_DERROR( ret, "Direct/Trace: readlink( \"/proc/self/exe\" ) failed!\n" );
               return NULL;
          }

          // Terminator 2
          file[len] = 0;

          full_path = file;
     }

     command_len = direct_strlen( full_path ) + 32;

     {
          char command[command_len+1];

          /* First check if there's an "nm-n" file. */
          tmp = strrchr( full_path, '/' );
          if (!tmp)
               return NULL;

          *tmp = 0;
          direct_snprintf( command, command_len, "%s/nm-n.%s", full_path, tmp + 1 );
          *tmp = '/';

          ret = direct_access( command, R_OK );
          if (ret == DR_OK) {
               ret = direct_file_open( &fp, command, O_RDONLY, 0 );
               if (ret)
                    D_DERROR( ret, "Direct/Trace: direct_file_open( \"%s\", \"r\" ) failed!\n", command );
          }
          else {
               direct_snprintf( command, command_len, "%s.nm", full_path );

               ret = direct_access( command, R_OK );
               if (ret == DR_OK) {
                    ret = direct_file_open( &fp, command, O_RDONLY, 0 );
                    if (ret)
                         D_DERROR( ret, "Direct/Trace: direct_file_open( \"%s\", \"r\" ) failed!\n", command );
               }
          }

          /* Fallback to live mode. */
          if (ret) {
               direct_snprintf( command, command_len, "nm -nC %s", full_path );

               ret = direct_popen( &fp, command, O_RDONLY );
               if (ret) {
                    D_DERROR( ret, "Direct/Trace: direct_popen( \"%s\", \"r\" ) failed!\n", command );
                    return NULL;
               }

               is_pipe = true;
          }
     }

     table = direct_calloc( 1, sizeof(SymbolTable) );
     if (!table) {
          D_OOM();
          goto out;
     }

     if (filename)
          table->filename = direct_strdup( filename );

     while (direct_fgets( &fp, line, sizeof(line) ) == DR_OK) {
          int  n;
          int  digits = sizeof(long) * 2;
          long offset = 0;
          int  length = direct_strlen(line);

          if (line[0] == ' ' || length < (digits + 5) || line[length-1] != '\n')
               continue;

          if (line[digits + 1] != 't' && line[digits + 1] != 'T' && line[digits + 1] != 'W')
               continue;

          if (line[digits] != ' ' || line[digits + 2] != ' ' || line[digits + 3] == '.')
               continue;

          for (n=0; n<digits; n++) {
               char c = line[n];

               offset <<= 4;

               if (c >= '0' && c <= '9')
                    offset |= c - '0';
               else
                    offset |= c - 'a' + 10;
          }

          line[length-1] = 0;

          add_symbol( table, offset, line + digits + 3 );
     }

out:
     if (is_pipe)
          direct_pclose( &fp );
     else
          direct_file_close( &fp );

     return table;
}

__attribute__((no_instrument_function))
static int
compare_symbols(const void *x, const void *y)
{
     return  *((const long*) x)  -  *((const long*) y);
}

__attribute__((no_instrument_function))
static SymbolTable *
find_table( const char *filename )
{
     SymbolTable *table;

     if (filename) {
          direct_list_foreach (table, tables) {
               if (table->filename && !direct_strcmp( filename, table->filename ))
                    return table;
          }
     }
     else {
          direct_list_foreach (table, tables) {
               if (!table->filename)
                    return table;
          }
     }

     return NULL;
}

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

__attribute__((no_instrument_function))
const char *
direct_trace_lookup_symbol( const char *filename, long offset )
{
     Symbol      *symbol;
     SymbolTable *table;

     direct_mutex_lock( &tables_lock );

     table = find_table( filename );
     if (!table) {
          table = load_symbols( filename );
          if (!table) {
               direct_mutex_unlock( &tables_lock );
               return false;
          }

          direct_list_prepend( &tables, &table->link );
     }

     direct_mutex_unlock( &tables_lock );

     symbol = direct_bsearch( &offset, table->symbols, table->num_symbols,
                              sizeof(Symbol), compare_symbols );

     return symbol ? symbol->name : NULL;
}

__attribute__((no_instrument_function))
const char *
direct_trace_lookup_file( void *address, void **ret_base )
{
#if DIRECT_BUILD_DYNLOAD
     Dl_info info;

     if (dladdr( address, &info )) {
          if (ret_base)
               *ret_base = info.dli_fbase;

          return info.dli_fname;
     }
     else
#endif
     {
          if (ret_base)
               *ret_base = NULL;
     }

     return NULL;
}

__attribute__((no_instrument_function))
void
direct_trace_print_stack( DirectTraceBuffer *buffer )
{
#if DIRECT_BUILD_DYNLOAD
     Dl_info info;
#endif
     int     i;
     int     level;

     if (!direct_config->trace)
          return;

     if (!buffer)
          buffer = get_trace_buffer();

     if (buffer->in_trace)
          return;

     buffer->in_trace = true;


     level = buffer->level;
     if (level > MAX_LEVEL) {
          D_WARN( "only showing %d of %d items", MAX_LEVEL, level );
          level = MAX_LEVEL;
     }
     else if (level == 0) {
          buffer->in_trace = false;
          return;
     }

     direct_log_lock( NULL );

     if (buffer->name)
          direct_log_printf( NULL, "(-) [%5d: -STACK- '%s']\n", buffer->tid, buffer->name );
     else
          direct_log_printf( NULL, "(-) [%5d: -STACK- ]\n", buffer->tid );

     for (i=level-1; i>=0; i--) {
          void *fn = buffer->trace[i].addr;

#if DIRECT_BUILD_DYNLOAD
          if (dladdr( fn, &info )) {
               if (info.dli_fname) {
                    const char *symbol = NULL;//info.dli_sname;

                    if (!symbol) {
                         symbol = direct_trace_lookup_symbol(info.dli_fname, (long)(fn - info.dli_fbase));
                         if (!symbol) {
                              symbol = direct_trace_lookup_symbol(info.dli_fname, (long)(fn));
                              if (!symbol) {
                                   if (info.dli_sname)
                                        symbol = info.dli_sname;
                                   else
                                        symbol = "??";
                              }
                         }
                    }

                    direct_log_printf( NULL, "  #%-2d 0x%08lx in %s () from %s [%p]\n",
                                       level - i - 1, (unsigned long) fn, symbol, info.dli_fname, info.dli_fbase );
               }
               else if (info.dli_sname) {
                    direct_log_printf( NULL, "  #%-2d 0x%08lx in %s ()\n",
                                       level - i - 1, (unsigned long) fn, info.dli_sname );
               }
               else
                    direct_log_printf( NULL, "  #%-2d 0x%08lx in ?? ()\n",
                                       level - i - 1, (unsigned long) fn );
          }
          else
#endif
          {
               const char *symbol = direct_trace_lookup_symbol(NULL, (long)(fn));
               direct_log_printf( NULL, "  #%-2d 0x%08lx in %s ()\n",
                                  level - i - 1, (unsigned long) fn, symbol ? symbol : "??" );
          }
     }

     direct_log_printf( NULL, "\n" );
     direct_log_unlock( NULL );

     buffer->in_trace = false;
}

__attribute__((no_instrument_function))
void
direct_trace_print_stacks( void )
{
     int                i;
     DirectTraceBuffer *buffer = get_trace_buffer();

     if (buffer->level)
          direct_trace_print_stack( buffer );

     direct_mutex_lock( &buffers_lock );

     for (i=0; i<buffers_num; i++) {
          if (buffers[i] != buffer && buffers[i]->level)
               direct_trace_print_stack( buffers[i] );
     }

     direct_mutex_unlock( &buffers_lock );
}

__attribute__((no_instrument_function))
int
direct_trace_debug_indent( void )
{
     int                in;
     DirectTraceBuffer *buffer = get_trace_buffer();
     int                level  = buffer->level - 1;

     if (level < 0)
          return 0;

     buffer->trace[level--].flags |= TF_DEBUG;

     for (in=0; level>=0; level--) {
          if (buffer->trace[level].flags & TF_DEBUG)
               in++;
     }

     return in;
}

__attribute__((no_instrument_function))
DirectTraceBuffer *
direct_trace_copy_buffer( DirectTraceBuffer *buffer )
{
     int                level;
     DirectTraceBuffer *copy;

     if (!buffer)
          buffer = get_trace_buffer();

     level = buffer->level;
     if (level > MAX_LEVEL) {
          D_WARN( "only copying %d of %d items", MAX_LEVEL, level );
          level = MAX_LEVEL;
     }

     copy = direct_calloc( 1, sizeof(DirectTraceBuffer) - sizeof(Trace) * (MAX_LEVEL - level) );
     if (!copy)
          return NULL;

     if (buffer->name)
          copy->name = direct_strdup( buffer->name );

     copy->tid   = buffer->tid;
     copy->level = buffer->level;

     direct_memcpy( &copy->trace[0], &buffer->trace[0], level * sizeof(Trace) );

     return copy;
}

__attribute__((no_instrument_function))
void
direct_trace_free_buffer( DirectTraceBuffer *buffer )
{
     if (buffer->name)
          direct_free( buffer->name );

     direct_free( buffer );
}

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

__attribute__((no_instrument_function)) void __cyg_profile_func_enter( void *this_fn, void *call_site );
__attribute__((no_instrument_function)) void __cyg_profile_func_exit ( void *this_fn, void *call_site );

void
__cyg_profile_func_enter( void *this_fn,
                          void *call_site )
{
     if (direct_config->trace) {
          DirectTraceBuffer *buffer = get_trace_buffer();
          int                level  = buffer->level++;
          Trace             *trace  = &buffer->trace[level];

          if (level < MAX_LEVEL) {
               trace->addr  = this_fn;
               trace->flags = TF_NONE;
          }
     }
}

void
__cyg_profile_func_exit( void *this_fn,
                         void *call_site )
{
     if (direct_config->trace) {
          DirectTraceBuffer *buffer = get_trace_buffer();

          if (buffer->level > 0)
               buffer->level--;
     }
}

#else

const char *
direct_trace_lookup_symbol( const char *filename, long offset )
{
     return NULL;
}

const char *
direct_trace_lookup_file( void *address, void **ret_base )
{
     if (ret_base)
          *ret_base = NULL;

     return NULL;
}

void
direct_trace_print_stack( DirectTraceBuffer *buffer )
{
}

void
direct_trace_print_stacks( void )
{
}

int
direct_trace_debug_indent( void )
{
     return 0;
}

DirectTraceBuffer *
direct_trace_copy_buffer( DirectTraceBuffer *buffer )
{
     return NULL;
}

void
direct_trace_free_buffer( DirectTraceBuffer *buffer )
{
}

#endif


const char *
direct_trace_lookup_symbol_at( void *address )
{
     void       *base;
     const char *filename;

     filename = direct_trace_lookup_file( address, &base );

     return direct_trace_lookup_symbol( filename, (unsigned long) address - (unsigned long) base );
}

