/* go-caller.c -- look up function/file/line/entry info

   Copyright 2009 The Go Authors. All rights reserved.
   Use of this source code is governed by a BSD-style
   license that can be found in the LICENSE file.  */

/* Implement runtime.Caller.  */

#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "backtrace.h"

#include "runtime.h"

/* Get the function name, file name, and line number for a PC value.
   We use the backtrace library to get this.  */

/* Data structure to gather file/line information.  */

struct caller
{
  String fn;
  String file;
  intgo line;
  intgo index;
};

/* Collect file/line information for a PC value.  If this is called
   more than once, due to inlined functions, we use the last call, as
   that is usually the most useful one.  */

static int
callback (void *data, uintptr_t pc __attribute__ ((unused)),
	  const char *filename, int lineno, const char *function)
{
  struct caller *c = (struct caller *) data;

  /* The libbacktrace library says that these strings might disappear,
     but with the current implementation they won't.  We can't easily
     allocate memory here, so for now assume that we can save a
     pointer to the strings.  */
  c->fn = runtime_gostringnocopy ((const byte *) function);
  c->file = runtime_gostringnocopy ((const byte *) filename);
  c->line = lineno;

  if (c->index == 0)
    return 1;

  if (c->index > 0)
    --c->index;

  return 0;
}

/* The error callback for backtrace_pcinfo and backtrace_syminfo.  */

static void
error_callback (void *data __attribute__ ((unused)),
		const char *msg, int errnum)
{
  if (errnum == -1)
    return;
  if (errnum > 0)
    runtime_printf ("%s errno %d\n", msg, errnum);
  runtime_throw (msg);
}

/* The backtrace library state.  */

static void *back_state;

/* A lock to control creating back_state.  */

static Lock back_state_lock;

/* The program arguments.  */

extern Slice runtime_get_args(void);

/* Fetch back_state, creating it if necessary.  */

struct backtrace_state *
__go_get_backtrace_state ()
{
  runtime_lock (&back_state_lock);
  if (back_state == NULL)
    {
      Slice args;
      const char *filename;
      struct stat s;

      args = runtime_get_args();
      filename = NULL;
      if (args.__count > 0)
	filename = (const char*)((String*)args.__values)[0].str;

      /* If there is no '/' in FILENAME, it was found on PATH, and
	 might not be the same as the file with the same name in the
	 current directory.  */
      if (filename != NULL && __builtin_strchr (filename, '/') == NULL)
	filename = NULL;

      /* If the file is small, then it's not the real executable.
	 This is specifically to deal with Docker, which uses a bogus
	 argv[0] (http://gcc.gnu.org/PR61895).  It would be nice to
	 have a better check for whether this file is the real
	 executable.  */
      if (stat (filename, &s) < 0 || s.st_size < 1024)
	filename = NULL;

      back_state = backtrace_create_state (filename, 1, error_callback, NULL);
    }
  runtime_unlock (&back_state_lock);
  return back_state;
}

/* Return function/file/line information for PC.  The index parameter
   is the entry on the stack of inlined functions; -1 means the last
   one.  */

_Bool
__go_file_line (uintptr pc, int index, String *fn, String *file, intgo *line)
{
  struct caller c;

  runtime_memclr (&c, sizeof c);
  c.index = index;
  backtrace_pcinfo (__go_get_backtrace_state (), pc, callback,
		    error_callback, &c);
  *fn = c.fn;
  *file = c.file;
  *line = c.line;
  return c.file.len > 0;
}

/* Collect symbol information.  */

static void
syminfo_callback (void *data, uintptr_t pc __attribute__ ((unused)),
		  const char *symname __attribute__ ((unused)),
		  uintptr_t address, uintptr_t size __attribute__ ((unused)))
{
  uintptr_t *pval = (uintptr_t *) data;

  *pval = address;
}

/* Set *VAL to the value of the symbol for PC.  */

static _Bool
__go_symbol_value (uintptr_t pc, uintptr_t *val)
{
  *val = 0;
  backtrace_syminfo (__go_get_backtrace_state (), pc, syminfo_callback,
		     error_callback, val);
  return *val != 0;
}

/* The values returned by runtime.Caller.  */

struct caller_ret
{
  uintptr_t pc;
  String file;
  intgo line;
  _Bool ok;
};

struct caller_ret Caller (int n) __asm__ (GOSYM_PREFIX "runtime.Caller");

/* Implement runtime.Caller.  */

struct caller_ret
Caller (int skip)
{
  struct caller_ret ret;
  Location loc;
  int32 n;

  runtime_memclr (&ret, sizeof ret);
  n = runtime_callers (skip + 1, &loc, 1, false);
  if (n < 1 || loc.pc == 0)
    return ret;
  ret.pc = loc.pc;
  ret.file = loc.filename;
  ret.line = loc.lineno;
  ret.ok = 1;
  return ret;
}

/* Look up the function name, file name, and line number for a PC.  */

struct funcfileline_return
{
  String retfn;
  String retfile;
  intgo retline;
};

struct funcfileline_return
runtime_funcfileline (uintptr targetpc, int32 index)
  __asm__ (GOSYM_PREFIX "runtime.funcfileline");

struct funcfileline_return
runtime_funcfileline (uintptr targetpc, int32 index)
{
  struct funcfileline_return ret;

  if (!__go_file_line (targetpc, index, &ret.retfn, &ret.retfile,
		       &ret.retline))
    runtime_memclr (&ret, sizeof ret);
  return ret;
}

/* Return the entry point of a function.  */
uintptr runtime_funcentry(uintptr)
  __asm__ (GOSYM_PREFIX "runtime.funcentry");

uintptr
runtime_funcentry (uintptr pc)
{
  uintptr val;

  if (!__go_symbol_value (pc, &val))
    return 0;
  return val;
}
