text: /*
     +----------------------------------------------------------------------+
     | HipHop for PHP                                                       |
     +----------------------------------------------------------------------+
     | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com)     |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
  */
  #include "hphp/util/stack-trace.h"
  
  #include <execinfo.h>
  #include <bfd.h>
  #include <signal.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  
  #include "folly/String.h"
  #include "folly/ScopeGuard.h"
  #include "folly/Conv.h"
  
  #include "hphp/util/process.h"
  #include "hphp/util/lock.h"
  #include "hphp/util/logger.h"
  #include "hphp/util/light-process.h"
  #include "hphp/util/compatibility.h"
  #include "hphp/util/hash.h"
  
  namespace HPHP {
  
  ///////////////////////////////////////////////////////////////////////////////
  
  std::string StackTrace::Frame::toString() const {
    std::string out;
    out = funcname.empty() ? "??" : funcname;
    out += " at ";
    out += filename.empty() ? "??" : filename;
    out += ":";
    out += folly::to<std::string>(lineno);
    return out;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // signal handler
  
  ///////////////////////////////////////////////////////////////////////////////
  // Types
  struct bfd_cache {
    bfd_cache() : abfd(nullptr) {}
    bfd *abfd;
    asymbol **syms;
  
    ~bfd_cache() {
      if (abfd) {
        bfd_cache_close(abfd);
        bfd_free_cached_info(abfd);
        bfd_close_all_done(abfd);
      }
    }
  };
  
  static const int MaxKey = 100;
  struct NamedBfd {
    bfd_cache bc;
    char key [MaxKey] ;
  };
  
  ///////////////////////////////////////////////////////////////////////////////
  // statics
  
  bool StackTraceBase::Enabled = true;
  
  ///////////////////////////////////////////////////////////////////////////////
  // constructor and destructor
  
  StackTraceBase::StackTraceBase() {
    bfd_init();
  }
  
  StackTrace::StackTrace(const StackTrace &bt) {
    assert(this != &bt);
  
    m_bt_pointers = bt.m_bt_pointers;
    m_bt = bt.m_bt;
  }
  
  StackTrace::StackTrace(bool trace) {
    if (trace && Enabled) {
      create();
    }
  }
  
  StackTraceNoHeap::StackTraceNoHeap(bool trace) {
    if (trace && Enabled) {
      create();
    }
  }
  
  void StackTrace::initFromHex(const char *hexEncoded) {
    std::vector<std::string> frames;
    folly::split(':', hexEncoded, frames);
    for (unsigned int i = 0; i < frames.size(); i++) {
      m_bt_pointers.push_back((void*)strtoll(frames[i].c_str(), nullptr, 16));
    }
  }
  
  StackTrace::StackTrace(const std::string &hexEncoded) {
    initFromHex(hexEncoded.c_str());
  }
  
  StackTrace::StackTrace(const char *hexEncoded) {
    initFromHex(hexEncoded);
  }
  
  void StackTrace::create() {
    void *btpointers[MAXFRAME];
    int framecount = 0;
    framecount = backtrace(btpointers, MAXFRAME);
    if (framecount <= 0 || framecount > (signed) MAXFRAME) {
      m_bt_pointers.clear();
      return;
    }
    m_bt_pointers.resize(framecount);
    for (int i = 0; i < framecount; i++) {
      m_bt_pointers[i] = btpointers[i];
    }
  }
  
  void StackTraceNoHeap::create() {
    int unsigned framecount = 0;
    framecount = backtrace(m_btpointers, MAXFRAME);
    if (framecount <= 0 || framecount > MAXFRAME) {
      m_btpointers_cnt = 0;
      return;
    }
    m_btpointers_cnt = framecount;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // reporting functions
  
  const std::string &StackTrace::toString(int skip, int limit) const {
    if (skip != 0 || limit != -1) m_bt.clear();
    if (m_bt.empty()) {
      size_t frame = 0;
      for (auto btpi = m_bt_pointers.begin();
           btpi != m_bt_pointers.end(); ++btpi) {
        std::string framename = Translate(*btpi)->toString();
        if (framename.find("StackTrace::") != std::string::npos) {
          continue; // ignore frames in the StackTrace class
        }
        if (skip-- > 0) continue;
        m_bt += "# ";
        m_bt += folly::to<std::string>(frame);
        if (frame < 10) m_bt += " ";
  
        m_bt += " ";
        m_bt += framename;
        m_bt += "\n";
        ++frame;
        if ((int)frame == limit) break;
      }
    }
    return m_bt;
  }
  
  void StackTraceNoHeap::printStackTrace(int fd) const {
  
    int frame = 0;
    // m_btpointers_cnt must be an upper bound on the number of filenames
    // then *2 for tolerable hash table behavior
    unsigned int bfds_size = m_btpointers_cnt * 2;
    const std::unique_ptr<NamedBfd[]> bfds (new NamedBfd[bfds_size]);
    for (unsigned int i = 0; i < bfds_size; i++) bfds.get()[i].key[0]='\0';
    for (unsigned int i = 0; i < m_btpointers_cnt; i++) {
      if (Translate(fd, m_btpointers[i], frame, bfds.get(), bfds_size)) {
        frame++;
      }
    }
    // ~bfds[i].bc here (unlike the heap case)
  }
  
  void StackTrace::get(std::vector<std::shared_ptr<Frame>> &frames) const {
    frames.clear();
    for (auto btpi = m_bt_pointers.begin();
         btpi != m_bt_pointers.end(); ++btpi) {
      frames.push_back(Translate(*btpi));
    }
  }
  
  std::string StackTrace::hexEncode(int minLevel /* = 0 */,
                                    int maxLevel /* = 999 */) const {
    std::string bts;
    for (int i = minLevel; i < (int)m_bt_pointers.size() && i < maxLevel; i++) {
      if (i > minLevel) bts += ':';
      char buf[20];
      snprintf(buf, sizeof(buf), "%" PRIx64, (int64_t)m_bt_pointers[i]);
      bts.append(buf);
    }
    return bts;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // crash log
  
  class StackTraceLog {
  public:
    hphp_string_map<std::string> data;
  
    static DECLARE_THREAD_LOCAL(StackTraceLog, s_logData);
  };
  IMPLEMENT_THREAD_LOCAL(StackTraceLog, StackTraceLog::s_logData);
  
  void StackTraceNoHeap::AddExtraLogging(const char *name,
                                         const std::string &value) {
    assert(name && *name);
    StackTraceLog::s_logData->data[name] = value;
  }
  
  void StackTraceNoHeap::ClearAllExtraLogging() {
    StackTraceLog::s_logData->data.clear();
  }
  
  void StackTraceNoHeap::log(const char *errorType, const char *tracefn,
                             const char *pid, const char *buildId,
                             int debuggerCount) const {
    int fd = ::open(tracefn, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR);
    if (fd < 0) return;
  
    dprintf(fd, "Host: %s\n",Process::GetHostName().c_str());
    dprintf(fd, "ProcessID: %s\n", pid);
    dprintf(fd, "ThreadID: %" PRIx64"\n", (int64_t)Process::GetThreadId());
    dprintf(fd, "ThreadPID: %u\n", Process::GetThreadPid());
    dprintf(fd, "Name: %s\n", Process::GetAppName().c_str());
    dprintf(fd, "Type: %s\n", errorType ? errorType : "(unknown error)");
    dprintf(fd, "Runtime: hhvm\n");
    dprintf(fd, "Version: %s\n", buildId);
    dprintf(fd, "DebuggerCount: %d\n", debuggerCount);
    dprintf(fd, "\n");
  
    for (auto const& pair : StackTraceLog::s_logData->data) {
      dprintf(fd, "%s: %s\n", pair.first.c_str(), pair.second.c_str());
    }
    dprintf(fd, "\n");
  
    printStackTrace(fd);
  
    ::close(fd);
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // helpers
  
  struct addr2line_data {
    asymbol **syms;
    bfd_vma pc;
    const char *filename;
    const char *functionname;
    unsigned int line;
    bfd_boolean found;
  };
  
  
  bool StackTraceBase::Translate(void *frame, StackTraceBase::Frame * f,
                                 Dl_info &dlInfo, void* data,
                                 void *bfds, unsigned bfds_size) {
    char sframe[32];
    snprintf(sframe, sizeof(sframe), "%p", frame);
  
    if (!dladdr(frame, &dlInfo)) {
      return false;
    }
  
    // frame pointer offset in previous frame
    f->offset = (char*)frame - (char*)dlInfo.dli_saddr;
  
    if (dlInfo.dli_fname) {
  
      // 1st attempt without offsetting base address
      if (!Addr2line(dlInfo.dli_fname, sframe, f, data, bfds, bfds_size) &&
          dlInfo.dli_fname && strstr(dlInfo.dli_fname,".so")) {
        // offset shared lib's base address
        frame = (char*)frame - (size_t)dlInfo.dli_fbase;
        snprintf(sframe, sizeof(sframe), "%p", frame);
  
        // Use addr2line to get line number info.
        Addr2line(dlInfo.dli_fname, sframe, f, data, bfds, bfds_size);
      }
    }
    return true;
  }
  
  std::shared_ptr<StackTrace::Frame> StackTrace::Translate(void *frame) {
    Dl_info dlInfo;
    addr2line_data adata;
  
    Frame * f1 = new Frame(frame);
    std::shared_ptr<Frame> f(f1);
    if (!StackTraceBase::Translate(frame, f1, dlInfo, &adata)) {
      // Lookup using dladdr() failed, so this is probably a PHP symbol.
      // Let's check the perf map.
      StackTrace::TranslateFromPerfMap(frame, f1);
      return f;
    }
  
    if (adata.filename) {
      f->filename = adata.filename;
    }
    if (adata.functionname) {
      f->funcname = Demangle(adata.functionname);
    }
    if (f->filename.empty() && dlInfo.dli_fname) {
      f->filename = dlInfo.dli_fname;
    }
    if (f->funcname.empty() && dlInfo.dli_sname) {
      f->funcname = Demangle(dlInfo.dli_sname);
    }
  
    return f;
  }
  
  void StackTrace::TranslateFromPerfMap(void* bt, Frame* f) {
    // For now just read the whole map file every time.  If this is
    // egregiously slow I'll build a map and cache it.
    char perfMapName[64];
    snprintf(perfMapName,
             sizeof(perfMapName),
             "/tmp/perf-%d.map", getpid());
    FILE* perfMap = fopen(perfMapName, "r");
    if (!perfMap) {
      f->filename = "?";
      f->funcname = "TC?";
      return;
    }
    SCOPE_EXIT { fclose(perfMap); };
    uintptr_t begin;
    uint32_t size;
    char name[256];
    while (fscanf(perfMap, "%lx %x %255s", &begin, &size, name) == 3) {
      uintptr_t end = begin + size;
      if (bt >= (void*)begin && bt <= (void*)end) {
        break;
      }
    }
    std::string filefunc = std::string(name);
    size_t endPhp = filefunc.find("::");
    if (endPhp == std::string::npos) {
      f->funcname = std::string(filefunc);
      return;
    }
    endPhp += 2;   // Skip the ::
    size_t endFile = filefunc.find("::", endPhp);
    f->filename = std::string(filefunc, endPhp, endFile - endPhp);
    f->funcname = "PHP::" + std::string(filefunc, endFile + 2) + "()";
  }
  
  bool StackTraceNoHeap::Translate(int fd, void *frame, int frame_num,
                                   void *bfds, unsigned bfds_size) {
    // frame pointer offset in previous frame
    Dl_info dlInfo;
    addr2line_data adata;
    Frame f(frame);
    if (!StackTraceBase::Translate(frame, &f, dlInfo, &adata, bfds,
                                   bfds_size))  {
      return false;
    }
  
    const char *filename = adata.filename ? adata.filename : dlInfo.dli_fname;
    if (!filename) filename = "??";
    const char *funcname = adata.functionname ? adata.functionname
                                              : dlInfo.dli_sname;
    if (!funcname) funcname = "??";
  
    // ignore frames in the StackTrace class
    if (strstr(funcname, "StackTraceNoHeap")) return false ;
  
    dprintf(fd, "# %d%s ", frame_num, frame_num < 10 ? " " : "");
    Demangle(fd, funcname);
    dprintf(fd, " at %s:%u\n", filename, f.lineno);
  
    return true;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // copied and re-factored from addr2line
  
  static void find_address_in_section(bfd *abfd, asection *section, void *data) {
    addr2line_data *adata = reinterpret_cast<addr2line_data*>(data);
  
    bfd_vma vma;
    bfd_size_type size;
  
    if (adata->found) {
      return;
    }
  
    if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0) {
      return;
    }
  
    vma = bfd_get_section_vma(abfd, section);
    if (adata->pc < vma) {
      return;
    }
  
    size = bfd_get_section_size(section);
    if (adata->pc >= vma + size) {
      return;
    }
  
    // libdwarf allocates its own unaligned memory so it doesn't play well with
    // valgrind
  #ifndef VALGRIND
    adata->found = bfd_find_nearest_line(abfd, section, adata->syms,
                                         adata->pc - vma, &adata->filename,
                                         &adata->functionname, &adata->line);
  #endif
  
    if (adata->found) {
      const char *file = adata->filename;
      unsigned int line = adata->line;
      bfd_boolean found = TRUE;
      while (found) {
        found = bfd_find_inliner_info(abfd, &file, &adata->functionname, &line);
      }
    }
  }
  
  static bool slurp_symtab(asymbol ***syms, bfd *abfd) {
    long symcount;
    unsigned int size;
  
    symcount = bfd_read_minisymbols(abfd, FALSE, (void **)syms, &size);
    if (symcount == 0) {
      symcount = bfd_read_minisymbols(abfd, TRUE /* dynamic */, (void **)syms,
                                      &size);
    }
    return symcount >= 0;
  }
  
  static bool translate_addresses(bfd *abfd, const char *addr,
                                  addr2line_data *adata) {
    if (!abfd) return false;
    adata->pc = bfd_scan_vma(addr, nullptr, 16);
  
    adata->found = FALSE;
    bfd_map_over_sections(abfd, find_address_in_section, adata);
  
    if (!adata->found || !adata->functionname || !*adata->functionname) {
      return false;
    }
    return true;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // We cache opened bfd file pointers that in turn cached frame pointer lookup
  // tables.
  
  
  typedef std::shared_ptr<bfd_cache> bfd_cache_ptr;
  typedef hphp_hash_map<std::string, bfd_cache_ptr, string_hash> bfdMap;
  static Mutex s_bfdMutex;
  static bfdMap s_bfds;
  
  static bool fill_bfd_cache(const char *filename, bfd_cache *p) {
    bfd *abfd = bfd_openr(filename, nullptr); // hard to avoid heap here!
    if (!abfd) return true;
  // Some systems don't have the BFD_DECOMPRESS flag
  #ifdef BFD_DECOMPRESS
    abfd->flags |= BFD_DECOMPRESS;
  #endif
    p->abfd = abfd;
    p->syms = nullptr;
    char **match;
    if (bfd_check_format(abfd, bfd_archive) ||
        !bfd_check_format_matches(abfd, bfd_object, &match) ||
        !slurp_symtab(&p->syms, abfd)) {
      bfd_close(abfd);
      return true;
    }
    return false;
  }
  
  static bfd_cache_ptr get_bfd_cache(const char *filename) {
    bfdMap::const_iterator iter = s_bfds.find(filename);
    if (iter != s_bfds.end()) {
      return iter->second;
    }
    bfd_cache_ptr p(new bfd_cache());
    if (fill_bfd_cache(filename, p.get())) {
      p.reset();
    }
    s_bfds[filename] = p;
    return p;
  }
  
  static bfd_cache * get_bfd_cache(const char *filename, NamedBfd* bfds,
                                     int bfds_size) {
    int probe = hash_string(filename) % bfds_size;
    // match on the end of filename instead of the beginning, if necessary
    int tooLong = strlen(filename) - MaxKey;
    if (tooLong > 0) filename += tooLong;
    while (bfds[probe].key[0]
           && strncmp(filename, bfds[probe].key, MaxKey) != 0) {
       probe = probe ? probe-1 : bfds_size-1;
    }
    bfd_cache *p = &bfds[probe].bc;
    if (bfds[probe].key[0]) return p;
    // accept the rare collision on keys (requires probe collision too)
    strncpy(bfds[probe].key, filename, MaxKey);
    fill_bfd_cache(filename, p);
    return p;
  }
  
  bool StackTraceBase::Addr2line(const char *filename, const char *address,
                             Frame *frame, void *adata,
                             void *bfds, unsigned bfds_size) {
    Lock lock(s_bfdMutex);
    addr2line_data *data = reinterpret_cast<addr2line_data*>(adata);
    data->filename = nullptr;
    data->functionname = nullptr;
    data->line = 0;
    bool ret;
  
    if (!bfds) {
      bfd_cache_ptr p = get_bfd_cache(filename);
      if (!p) return false;
      data->syms = p->syms;
      ret = translate_addresses(p->abfd, address, data);
    } else {
      // don't let bfd_cache_ptr malloc behind the scenes in this case
      bfd_cache *q = get_bfd_cache(filename, (NamedBfd*)bfds, bfds_size);
      if (!q) return false;
      data->syms = q->syms;
      ret = translate_addresses(q->abfd, address, data);
    }
  
    if (ret) {
      frame->lineno = data->line;
    }
    return ret;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // copied and re-factored from demangle/c++filt
  
  #define DMGL_PARAMS   (1 << 0)  /* Include function args */
  #define DMGL_ANSI     (1 << 1)  /* Include const, volatile, etc */
  #define DMGL_VERBOSE  (1 << 3)  /* Include implementation details. */
  
  extern "C" {
    extern char *cplus_demangle (const char *mangled, int options);
  }
  
  std::string StackTrace::Demangle(const char *mangled) {
    assert(mangled);
    if (!mangled || !*mangled) {
      return "";
    }
  
    size_t skip_first = 0;
    if (mangled[0] == '.' || mangled[0] == '$') ++skip_first;
    //if (mangled[skip_first] == '_') ++skip_first;
  
    char *result = cplus_demangle(mangled + skip_first, DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE);
    if (result == nullptr) return mangled;
  
    std::string ret;
    if (mangled[0] == '.') ret += '.';
    ret += result;
    free (result);
    return ret;
  }
  
  void StackTraceNoHeap::Demangle(int fd, const char *mangled) {
    assert(mangled);
    if (!mangled || !*mangled) {
      dprintf(fd, "??");
      return ;
    }
  
    size_t skip_first = 0;
    if (mangled[0] == '.' || mangled[0] == '$') ++skip_first;
    //if (mangled[skip_first] == '_') ++skip_first;
  
    char *result = cplus_demangle(mangled + skip_first,
                                  DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE);
    if (result == nullptr) {
      dprintf(fd, "%s", mangled);
      return;
    }
    dprintf(fd, "%s%s", mangled[0]=='.' ? "." : "", result);
    return ;
  }
  
  
  ///////////////////////////////////////////////////////////////////////////////
  }
  