<reponame>cedricga91/framework
﻿// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
//-----------------------------------------------------------------------------
// Copyright 2000-2022 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
// See the top-level COPYRIGHT file for details.
// SPDX-License-Identifier: Apache-2.0
//-----------------------------------------------------------------------------
/*---------------------------------------------------------------------------*/
/* LibStackTraceService.cc                                     (C) 2000-2016 */
/*                                                                           */
/* Service de trace des appels de fonctions utilisant 'libunwind'.           */
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

#include "arcane/utils/ArcanePrecomp.h"

#include "arcane/utils/PlatformUtils.h"
#include "arcane/utils/IStackTraceService.h"
#include "arcane/utils/StackTrace.h"
#include "arcane/utils/StringBuilder.h"
#include "arcane/utils/Array.h"
#include "arcane/utils/Process.h"

#include "arcane/utils/ISymbolizerService.h"
#include "arcane/ServiceBuilder.h"
#include "arcane/Directory.h"

#include "arcane/ServiceFactory.h"
#include "arcane/AbstractService.h"

#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
#include <cxxabi.h>

#include <map>

#include <execinfo.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

ARCANE_BEGIN_NAMESPACE

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*!
 * \brief Service de trace des appels de fonctions utilisant la libunwind.
 */
class LibUnwindStackTraceService
: public AbstractService
, public IStackTraceService
{
 private:
  struct ProcInfo
  {
   public:
    ProcInfo() : m_file_name(nullptr), m_file_loaded_address(0){}
    ProcInfo(const String& aname) : m_file_name(nullptr), m_file_loaded_address(0)
    { m_name = aname; }
   public:
    const String& name() const { return m_name; }
    //! Nom du fichier contenant la procédure. Peut-être nul.
    const char* fileName() const { return m_file_name; }
   public:
    //! Nom (démanglé) de la procédure
    String m_name;
    //! Nom de la bibliothèque (.so ou .exe) dans laquelle se trouve la méthode
    const char* m_file_name;
    //! Adresse de chargement de la bibliothèque.
    //TODO: ne pas stocker cela pour chaque fonction.
    unw_word_t m_file_loaded_address;
    unw_word_t m_base_ip;
  };
 public:

  LibUnwindStackTraceService(const ServiceBuildInfo& sbi)
  : AbstractService(sbi), m_want_gdb_info(false), m_use_backtrace(false)
  {
    m_application = sbi.application();
  }

 public:

  void build() override
  {
    if (!platform::getEnvironmentVariable("ARCANE_GDB_STACK").null())
      m_want_gdb_info = true;
    if (!platform::getEnvironmentVariable("ARCANE_USE_BACKTRACE").null())
      m_use_backtrace = true;
  }

 public:

  //! Chaîne de caractère indiquant la pile d'appel
  StackTrace stackTrace(int first_function) override;
  StackTrace stackTraceFunction(int function_index) override;

 private:

  typedef std::map<unw_word_t,ProcInfo> ProcInfoMap;
  std::map<unw_word_t,ProcInfo> m_proc_name_map;
  bool m_want_gdb_info;
  bool m_use_backtrace;
  IApplication* m_application; //A SUPPRIMER
  ProcInfo _getFuncInfo(unw_word_t ip,unw_cursor_t* cursor);
  ProcInfo _getFuncInfo(void* ip);
  String _getGDBStack();
  StackTrace _backtraceStackTrace(const FixedStackFrameArray& stack_frames);
  String _generateFileAndOffset(const FixedStackFrameArray& stack_frames);
  FixedStackFrameArray _backtraceStackFrame(int first_function);
};

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

LibUnwindStackTraceService::ProcInfo LibUnwindStackTraceService::
_getFuncInfo(unw_word_t ip,unw_cursor_t* cursor)
{
  ProcInfoMap::const_iterator v = m_proc_name_map.find(ip);
  if (v!=m_proc_name_map.end())
    return v->second;

  unw_word_t offset;
  char func_name_buf[10000];
  char demangled_func_name_buf[10000];
  unw_get_proc_name(cursor,func_name_buf,10000,&offset);
  int dstatus = 0;
  size_t len = 10000;
  char* buf = abi::__cxa_demangle (func_name_buf,demangled_func_name_buf,&len,&dstatus);
  ProcInfo pi;
  pi.m_base_ip = offset;
  {
    Dl_info dl_info;
    void* addr = (void*)ip;
    int r2 = dladdr(addr,&dl_info);
    if (r2!=0){
      const char* dli_fname = dl_info.dli_fname;
      // Adresse de base de chargement du fichier.
      void* dli_fbase = dl_info.dli_fbase;
      pi.m_file_name = dli_fname;
      pi.m_file_loaded_address = (unw_word_t)dli_fbase;
    }
  }

  if (buf)
    pi.m_name = std::string_view(buf);
  else
    pi.m_name = std::string_view(func_name_buf);
  m_proc_name_map.insert(ProcInfoMap::value_type(ip,pi));

  return pi;
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*!
 * \brief Récupère le nom de la fonction via dladdr.
 * \todo: rendre thread-safe
 */
LibUnwindStackTraceService::ProcInfo LibUnwindStackTraceService::
_getFuncInfo(void* addr)
{
  ProcInfoMap::const_iterator v = m_proc_name_map.find((unw_word_t)addr);
  if (v!=m_proc_name_map.end()){
    return v->second;
  }
  const size_t buf_size = 10000;
  char demangled_func_name_buf[buf_size];
  Dl_info dl_info;
  int r = dladdr(addr,&dl_info);
  if (r==0){
    // Erreur dans dladdr.
    std::cout << "ERROR in dladdr\n";
    return ProcInfo("Unknown");
  }
  const char* dli_sname = dl_info.dli_sname;
  if (!dli_sname)
    dli_sname = "";
  int dstatus = 0;
  size_t len = buf_size;
  char* buf = abi::__cxa_demangle (dli_sname,demangled_func_name_buf,&len,&dstatus);
  //char* buf = (char*)dli_sname;
  ProcInfo pi;
  if (buf)
    pi.m_name = std::string_view(buf);
  else
    pi.m_name = std::string_view(dli_sname);
  m_proc_name_map.insert(ProcInfoMap::value_type((unw_word_t)addr,pi));
  return pi;
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

String LibUnwindStackTraceService::
_getGDBStack()
{
  void *array [256];
  char **names;
  const size_t cmd_size = 4096;
  char cmd [cmd_size+1];
  int i, size;

  fprintf (stderr, "\nNative stacktrace:\n\n");

  size = backtrace (array, 256);
  names = backtrace_symbols (array, size);
  for (i =0; i < size; ++i) {
    fprintf (stderr, "\t%s\n", names [i]);
  }

  fflush (stderr);

  //sprintf (cmd, "gdb --ex 'attach %ld' --ex 'info threads' --ex 'thread apply all bt'", (long)getpid ());
  //sprintf (cmd, "gdb --ex 'attach %ld' --ex 'info threads' --ex 'thread apply all bt' --batch", (long)getpid ());
  char filename[4096];
  long pid = (long)getpid();
  sprintf(filename,"errlog.%ld",pid);
  snprintf (cmd,cmd_size,"gdb --ex 'attach %ld' --ex 'info threads' --ex 'thread apply all bt full' --batch",pid);
  system(cmd);

  long unsigned int file_length = platform::getFileLength(filename);
  if (file_length==0)
    return String();

  std::ifstream ifile;
  ifile.open(filename,std::ios::binary);
  ByteUniqueArray bytes(arcaneCheckArraySize(file_length));
  ifile.read((char*)bytes.data(),file_length);
  return String(bytes);
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

//! Chaîne de caractère indiquant la pile d'appel
StackTrace LibUnwindStackTraceService::
stackTrace(int first_function)
{
  unw_cursor_t cursor;
  unw_context_t uc;

  String last_str;
  if (m_want_gdb_info)
    last_str = _getGDBStack();
  FixedStackFrameArray backtrace_stack_frames = _backtraceStackFrame(first_function);
  if (m_use_backtrace)
    return _backtraceStackTrace(backtrace_stack_frames);

  const size_t hexa_buf_size = 100;
  char hexa[hexa_buf_size+1];

  unw_getcontext(&uc);
  unw_init_local(&cursor, &uc);
  int current_func = 0;
  StringBuilder message;

  FixedStackFrameArray stack_frames;
  while (unw_step(&cursor) > 0) {
    unw_word_t ip;
    unw_get_reg(&cursor, UNW_REG_IP, &ip);
    if (current_func>=first_function){
      ProcInfo pi = _getFuncInfo(ip,&cursor);
      String func_name = pi.m_name;
      message += " ";
      snprintf(hexa,hexa_buf_size,"%14llx",(long long)ip);
      message += hexa;
      message += "  ";
      message += func_name;
      message += "\n";

      stack_frames.addFrame(StackFrame((intptr_t)ip));
    }
    ++current_func;
  }
  ISymbolizerService* ss = platform::getSymbolizerService();

  if (ss){
    // Il faut mieux lire la pile d'appel de _backtrace() car
    // la libunwind retourne l'adresse de retour pour chaque fonction
    // ce qui provoque un décalage dans les infos des lignes du code
    // source (on pointe vers la ligne après celle qu'on est en train d'exécuter).
    last_str = ss->stackTrace(backtrace_stack_frames.view());
  }
  else{
    message += "\nFileAndOffsetStack:{{\n";
    message += _generateFileAndOffset(backtrace_stack_frames);
    message += "}}\n";
  }
  message += last_str;
  return StackTrace(stack_frames,message);
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*!
 * \brief Retourne la pile à d'appel actuelle.
 *
 * Les \a function_index premières fonctions de la pile sont ignorées.
 */
StackTrace LibUnwindStackTraceService::
stackTraceFunction(int function_index)
{
  unw_cursor_t cursor;
  unw_context_t uc;
  unw_word_t ip;
  //unw_word_t offset;

  String last_str;
  if (m_want_gdb_info)
    last_str = _getGDBStack();

  unw_getcontext(&uc);
  unw_init_local(&cursor, &uc);
  int current_func = 0;
  StringBuilder message;

  while (unw_step(&cursor) > 0) {
    unw_get_reg(&cursor, UNW_REG_IP, &ip);
    if (current_func==function_index){
      ProcInfo pi = _getFuncInfo(ip,&cursor);
      String func_name = pi.m_name;
      message += func_name;
      break;
    }
    ++current_func;
  }
  return StackTrace(message.toString()+last_str);
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

//! Pile d'appel via la fonction backtrace.
FixedStackFrameArray LibUnwindStackTraceService::
_backtraceStackFrame(int first_function)
{
	void *ips [256];
	Integer size = backtrace (ips, 256);

  FixedStackFrameArray stack_frames;
  for( Integer i=first_function; i<size; ++i ){
    stack_frames.addFrame(StackFrame((intptr_t)ips[i]));
  }
  return stack_frames;
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

//! Pile d'appel via la fonction backtrace.
StackTrace LibUnwindStackTraceService::
_backtraceStackTrace(const FixedStackFrameArray& stack_frames)
{
  const size_t buf_size = 100;
  char hexa[buf_size+1];

  StringBuilder message;
  ConstArrayView<StackFrame> frames_view = stack_frames.view();
  for( StackFrame f : frames_view.range() ){
    intptr_t ip = f.address();
    ProcInfo pinfo = _getFuncInfo((void*)ip);
    String func_name = pinfo.name();
    message += "  ";
    snprintf(hexa,buf_size,"%10llx",(long long)ip);
    message += hexa;
    message += "  ";
    message += func_name;
    message += "\n";
  }
  return StackTrace(stack_frames,message);
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*!
 * \brief Génère liste des noms de fichier et offset d'une pile d'appel.
 *
 * Génère une chaîne de caractère contenant pour chaque adresse de
 * la pile d'appel \a stack_frames le nom du fichier contenant le symbol
 * et l'offset de cette adresse dans ce fichier.
 *
 * Le format de sortie permet la lecture par des outils tels que addr2line
 * ou llmv-symbolizer.
 */
String LibUnwindStackTraceService::
_generateFileAndOffset(const FixedStackFrameArray& stack_frames)
{
  const size_t buf_size = 100;
  char hexa[buf_size+1];

  StringBuilder message;
  ConstArrayView<StackFrame> frames_view = stack_frames.view();
  for( StackFrame f : frames_view.range() ){
    intptr_t ip = f.address();
    ProcInfo pinfo = _getFuncInfo((void*)ip);
    message += (pinfo.fileName() ? pinfo.fileName() : "()");
    message += "  ";
    intptr_t file_base_address = pinfo.m_file_loaded_address;
    // Sous Linux (CentOS 6 et 7), l'adresse 0x400000 correspond à celle
    // de chargement de  l'exécutable mais si on retranche cette adresse de celle
    // de la fonction alors le symboliser ne fonctionne pas (que ce soit
    // llvm-symbolize ou addr2line)
    if (file_base_address==0x400000)
      file_base_address = 0;
    intptr_t offset_ip = (ip - file_base_address);
    snprintf(hexa,buf_size,"%llx",(long long)offset_ip);
    message += "0x";
    message += hexa;
    message += "\n";
  }
  return message;
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

class LLVMSymbolizerService
: public AbstractService
, public ISymbolizerService
{
 public:
  LLVMSymbolizerService(const ServiceBuildInfo& sbi)
  : AbstractService(sbi), m_is_check_done(false), m_is_valid(false) {}
  virtual ~LLVMSymbolizerService() {}
 public:
  void build() override
  {
    m_llvm_symbolizer_path = platform::getEnvironmentVariable("ARCANE_LLVMSYMBOLIZER_PATH");
  }
 public:
  String stackTrace(ConstArrayView<StackFrame> frames) override;
 private:
  String m_llvm_symbolizer_path;
  bool m_is_check_done;
  bool m_is_valid;
  //! Vérifie que le chemin spécifié est valid
  void _checkValid()
  {
    if (m_is_check_done)
      return;
    // Avant appel à cette méthode, m_llvm_symbolizer_path doit contenir
    // le nom du répertoire dans lequel se trouve llvm-symbolizer.
    Directory dir(m_llvm_symbolizer_path);
    String fullpath = dir.file("llvm-symbolizer");
    Int64 length = platform::getFileLength(fullpath);
    m_llvm_symbolizer_path = fullpath;
    if (length>0)
      m_is_valid = true;
    m_is_check_done = true;
  }
};

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

String LLVMSymbolizerService::
stackTrace(ConstArrayView<StackFrame> frames)
{
  _checkValid();
  if (!m_is_valid)
    return String();
  std::stringstream ostr;
  // NOTE: le code ci dessous est similaire à
  // LibUnwindStackTraceService::_generateFileAndOffset(). Il faudrait
  // fusionner les deux.
  for( Integer i=0, n=frames.size(); i<n; ++i ){
    Dl_info dl_info;
    intptr_t addr = frames[i].address();
    int r2 = dladdr((void*)addr,&dl_info);
    const char* dli_fname = nullptr;
    intptr_t base_address = 0;
    if (r2!=0){
      dli_fname = dl_info.dli_fname;
      // Adresse de base de chargement du fichier.
      void* dli_fbase = dl_info.dli_fbase;
      intptr_t true_base = (intptr_t)dli_fbase;
      // Sous Linux (CentOS 6 et 7), l'adresse 0x400000 correspond à celle
      // de chargement de  l'exécutable mais si on retranche cette adresse de celle
      // de la fonction alors le symboliser ne fonctionne pas (que ce soit
      // llvm-symbolize ou addr2line)
      if (true_base==0x400000)
        true_base = 0;
      base_address = addr - true_base;
    }
    // TODO: écrire base address en hexa pour pouvoir le relire avec addr2line
    ostr << (dli_fname ? dli_fname : "??") << " " << base_address << '\n';
  }

  std::string input_str(ostr.str());
  String output_str;
  ProcessExecArgs args;
  args.setCommand(m_llvm_symbolizer_path);
  Integer input_size = arcaneCheckArraySize(input_str.length());
  ByteConstArrayView input_bytes(input_size,(const Byte*)input_str.c_str());
  args.setInputBytes(input_bytes);
  args.addArguments("--demangle");
  args.addArguments("--pretty-print");
  //args.addArguments("--print-source-context-lines=5");
  Process::execute(args);
  return String(args.outputBytes());
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

ARCANE_REGISTER_SERVICE(LibUnwindStackTraceService,
                        ServiceProperty("LibUnwind",ST_Application),
                        ARCANE_SERVICE_INTERFACE(IStackTraceService));

ARCANE_REGISTER_SERVICE(LLVMSymbolizerService,
                        ServiceProperty("LLVMSymbolizer",ST_Application),
                        ARCANE_SERVICE_INTERFACE(ISymbolizerService));

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

ARCANE_END_NAMESPACE

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
