Example #1
0
namespace HPHP {

///////////////////////////////////////////////////////////////////////////////

std::string StackTrace::Frame::toString() const {
  string out;
  out = funcname.empty() ? "??" : funcname;
  out += " at ";
  out += filename.empty() ? "??" : filename;
  out += ":";
  out += lexical_cast<string>(lineno);
  return out;
}

///////////////////////////////////////////////////////////////////////////////
// signal handler

bool SegFaulting = false;

static void bt_handler(int sig) {
  // In case we crash again in the signal hander or something
  signal(sig, SIG_DFL);

  // Generating a stack dumps significant time, try to stop threads
  // from flushing bad data or generating more faults meanwhile
  if (sig==SIGQUIT || sig==SIGILL || sig==SIGSEGV || sig==SIGBUS) {
    SegFaulting=true;
    // leave running for SIGTERM SIGFPE SIGABRT
  }

  // Turn on stack traces for coredumps
  StackTrace::Enabled = true;
  StackTraceNoHeap st;

  char pid[sizeof(Process::GetProcessId())*3+2]; // '-' and \0
  sprintf(pid,"%u",Process::GetProcessId());
  char tracefn [strlen("/tmp/stacktrace..log" + strlen(pid) +1 )];
  sprintf(tracefn,"/tmp/stacktrace.%s.log" , pid);

  st.log(strsignal(sig), tracefn, pid);

  //cerr << logmessage << endl;
  if (!StackTrace::ReportEmail.empty()) {
    //cerr << "Emailing trace to " << StackTrace::ReportEmail << endl;
    char format [] = "cat %s | mail -s \"Stack Trace from %s\"%s";
    char cmdline[strlen(format)+strlen(tracefn)
                 +strlen(Process::GetAppName().c_str())
                 +strlen(StackTrace::ReportEmail.c_str())+1];
    sprintf(cmdline, format, tracefn, Process::GetAppName().c_str(),
            StackTrace::ReportEmail.c_str());
    Util::ssystem(cmdline);
  }

  // Calling all of these library functions in a signal handler
  // is completely undefined behavior, but we seem to get away with it.
  // Do it last just in case

  Logger::Error("Core dumped: %s", strsignal(sig));

  // re-raise the signal and pass it to the default handler
  // to terminate the process.
  raise(sig);
}
///////////////////////////////////////////////////////////////////////////////
// statics

bool StackTraceBase::Enabled = true;
string StackTraceBase::ReportEmail;

void StackTraceBase::InstallReportOnSignal(int sig) {
  signal(sig, bt_handler);
}

void StackTraceBase::InstallReportOnErrors() {
  static bool already_set = false;
  if (already_set) return;
  already_set = true;

  // Turn on bt-on-sig for a good default set of error signals
  signal(SIGQUIT, bt_handler);
  signal(SIGTERM, bt_handler);
  signal(SIGILL,  bt_handler);
  signal(SIGFPE,  bt_handler);
  signal(SIGSEGV, bt_handler);
  signal(SIGBUS,  bt_handler);
  signal(SIGABRT, bt_handler);
}

///////////////////////////////////////////////////////////////////////////////
// constructor and destructor

StackTraceBase::StackTraceBase() {
#ifndef MAC_OS_X
  bfd_init();
#endif
}

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) {
  vector<string> frames;
  Util::split(':', hexEncoded, frames);
  for (unsigned int i = 0; i < frames.size(); i++) {
    m_bt_pointers.push_back((void*)strtoll(frames[i].c_str(), NULL, 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;
#ifndef MAC_OS_X
  framecount = backtrace(btpointers, MAXFRAME);
#endif
  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;
#ifndef MAC_OS_X
  framecount = backtrace(m_btpointers, MAXFRAME);
#endif
  if (framecount <= 0 || framecount > MAXFRAME) {
    m_btpointers_cnt = 0;
    return;
  }
  m_btpointers_cnt = framecount;
}

///////////////////////////////////////////////////////////////////////////////
// reporting functions

const std::string &StackTrace::toString() const {
  if (m_bt.empty()) {
    size_t frame = 0;
    for (vector<void*>::const_iterator btpi = m_bt_pointers.begin();
         btpi != m_bt_pointers.end(); ++btpi) {
      string framename = Translate(*btpi)->toString();
      if (framename.find("StackTrace::") != string::npos) {
        continue; // ignore frames in the StackTrace class
      }
      m_bt += "# ";
      m_bt += lexical_cast<string>(frame);
      if (frame < 10) m_bt += " ";

      m_bt += " ";
      m_bt += framename;
      m_bt += "\n";
      ++frame;
    }
  }
  return m_bt;
}

void StackTraceNoHeap::print(FILE *f) const {
  int frame=0;
  for (unsigned int i=0; i<m_btpointers_cnt; i++) {
    if (!Translate(f, m_btpointers[i], frame)) continue;;
    frame++;
  }
}

void StackTrace::get(FramePtrVec &frames) const {
  frames.clear();
  for (vector<void*>::const_iterator 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 {
  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), "%llx", (int64)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 char *value) {
  ASSERT(name && *name);
  ASSERT(value);

  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 {
  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: %llx\n", (int64)Process::GetThreadId());
  dprintf(fd, "Name: %s\n", Process::GetAppName().c_str());
  dprintf(fd, "Type: %s\n", errorType ? errorType : "(unknown error)");
  dprintf(fd, "\n");

  hphp_string_map<std::string> &extra = StackTraceLog::s_logData->data;
  for (hphp_string_map<std::string>::const_iterator iter = extra.begin();
       iter != extra.end(); ++iter) {
    dprintf(fd, "%s: %s\n", iter->first.c_str(), iter->second.c_str());
  }
  dprintf(fd, "\n");

  ::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) {
  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) &&
        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);
    }
  }
  return true;
}

StackTrace::FramePtr StackTrace::Translate(void *frame) {
  Dl_info dlInfo;
  addr2line_data adata;

  Frame * f1 = new Frame(frame);
  FramePtr f(f1);
  if (!StackTraceBase::Translate(frame, f1, dlInfo, &adata)) 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;
}

bool StackTraceNoHeap::Translate(FILE *fp, void *frame, int frame_num) {
  // frame pointer offset in previous frame
  Dl_info dlInfo;
  addr2line_data adata;
  Frame f(frame);
  if (!StackTraceBase::Translate(frame, &f, dlInfo, &adata))  {
    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 ;

  fprintf(fp, "# %d%s ", frame_num, frame_num < 10 ? " " : "");
  Demangle(fp, funcname);
  fprintf(fp, " at %s:%u\n", filename, f.lineno);

  return true;
}

///////////////////////////////////////////////////////////////////////////////
// copied and re-factored from addr2line

#ifndef MAC_OS_X

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;
  }

  adata->found = bfd_find_nearest_line(abfd, section, adata->syms,
                                       adata->pc - vma, &adata->filename,
                                       &adata->functionname, &adata->line);
  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) {
  adata->pc = bfd_scan_vma(addr, NULL, 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.

struct bfd_cache {
  bfd *abfd;
  asymbol **syms;

  ~bfd_cache() {
    if (abfd) {
      bfd_cache_close(abfd);
      bfd_free_cached_info(abfd);
      bfd_close_all_done(abfd);
    }
  }
};
typedef boost::shared_ptr<bfd_cache> bfd_cache_ptr;
typedef __gnu_cxx::hash_map<std::string, bfd_cache_ptr, string_hash> bfdMap;
static Mutex s_bfdMutex;
static bfdMap s_bfds;

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());
  bfd *abfd = bfd_openr(filename, NULL);
  if (abfd) {
    p->abfd = abfd;
    p->syms = NULL;
    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);
      p.reset();
    }
  } else {
    p.reset();
  }
  s_bfds[filename] = p;
  return p;
}

#endif

bool StackTraceBase::Addr2line(const char *filename, const char *address,
                           Frame *frame, void *adata) {
#ifndef MAC_OS_X
  Lock lock(s_bfdMutex);
  addr2line_data *data = reinterpret_cast<addr2line_data*>(adata);
  bfd_cache_ptr p = get_bfd_cache(filename);
  if (!p) return false;

  data->filename = NULL;
  data->functionname = NULL;
  data->line = 0;
  data->syms = p->syms;
  bool ret = translate_addresses(p->abfd, address, data);
  if (ret) {
    frame->lineno = data->line;
  }
  return ret;
#else
  return false;
#endif
}

///////////////////////////////////////////////////////////////////////////////
// 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. */

#ifndef MAC_OS_X

extern "C" {
  extern char *cplus_demangle (const char *mangled, int options);
}

#endif

std::string StackTrace::Demangle(const char *mangled) {
  assert(mangled);
  if (!mangled || !*mangled) {
    return "";
  }

#ifndef MAC_OS_X
  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 == NULL) return mangled;

  string ret;
  if (mangled[0] == '.') ret += '.';
  ret += result;
  free (result);
  return ret;
#else
  return mangled;
#endif
}

void StackTraceNoHeap::Demangle(FILE *f, const char *mangled) {
  assert(mangled);
  if (!mangled || !*mangled) {
    fprintf(f, "??");
    return ;
  }

#ifndef MAC_OS_X
  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 == NULL) {
    fprintf (f, "%s", mangled);
    return;
  }
  fprintf (f, "%s%s", mangled[0]=='.' ? "." : "", result);
  return ;
#else
  fprintf (f, "%s", mangled);
  return ;
#endif
}


///////////////////////////////////////////////////////////////////////////////
}
Example #2
0
namespace HPHP {

///////////////////////////////////////////////////////////////////////////////

std::string StackTrace::Frame::toString() const {
  string out;
  out = funcname.empty() ? "??" : funcname;
  out += " at ";
  out += filename.empty() ? "??" : filename;
  out += ":";
  out += lexical_cast<string>(lineno);
  return out;
}

///////////////////////////////////////////////////////////////////////////////
// signal handler

///////////////////////////////////////////////////////////////////////////////
// Types
struct bfd_cache {
  bfd_cache() : abfd(NULL) {}
  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) {
  vector<string> frames;
  Util::split(':', hexEncoded, frames);
  for (unsigned int i = 0; i < frames.size(); i++) {
    m_bt_pointers.push_back((void*)strtoll(frames[i].c_str(), NULL, 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 (vector<void*>::const_iterator btpi = m_bt_pointers.begin();
         btpi != m_bt_pointers.end(); ++btpi) {
      string framename = Translate(*btpi)->toString();
      if (framename.find("StackTrace::") != string::npos) {
        continue; // ignore frames in the StackTrace class
      }
      if (skip-- > 0) continue;
      m_bt += "# ";
      m_bt += lexical_cast<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;
  NamedBfd bfds[bfds_size];
  for (unsigned int i = 0; i < bfds_size; i++) bfds[i].key[0]='\0';
  for (unsigned int i = 0; i < m_btpointers_cnt; i++) {
    if (Translate(fd, m_btpointers[i], frame, bfds, bfds_size)) {
      frame++;
    }
  }
  // ~bfds[i].bc here (unlike the heap case)
}

void StackTrace::get(FramePtrVec &frames) const {
  frames.clear();
  for (vector<void*>::const_iterator 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 {
  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)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 char *value) {
  assert(name && *name);
  assert(value);

  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) 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)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: %s\n", hhvm ? "hhvm" : "hphp");
  dprintf(fd, "Version: %s\n", buildId);
  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;
}

StackTrace::FramePtr StackTrace::Translate(void *frame) {
  Dl_info dlInfo;
  addr2line_data adata;

  Frame * f1 = new Frame(frame);
  FramePtr f(f1);
  if (!StackTraceBase::Translate(frame, f1, dlInfo, &adata)) 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;
}

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;
  }

  adata->found = bfd_find_nearest_line(abfd, section, adata->syms,
                                       adata->pc - vma, &adata->filename,
                                       &adata->functionname, &adata->line);
  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, NULL, 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 boost::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, NULL); // hard to avoid heap here!
  if (!abfd) return true;
  p->abfd = abfd;
  p->syms = NULL;
  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 = NULL;
  data->functionname = NULL;
  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 == NULL) return mangled;

  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 == NULL) {
    dprintf(fd, "%s", mangled);
    return;
  }
  dprintf(fd, "%s%s", mangled[0]=='.' ? "." : "", result);
  return ;
}


///////////////////////////////////////////////////////////////////////////////
}
Example #3
0
namespace HPHP {

///////////////////////////////////////////////////////////////////////////////

std::string StackTrace::Frame::toString() const {
  string out;
  out = funcname.empty() ? "??" : funcname;
  out += " at ";
  out += filename.empty() ? "??" : filename;
  out += ":";
  out += lexical_cast<string>(lineno);
  return out;
}

///////////////////////////////////////////////////////////////////////////////
// signal handler

bool SegFaulting = false;

static void bt_handler(int sig) {
  // In case we crash again in the signal hander or something
  signal(sig, SIG_DFL);

  // Generating a stack dumps significant time, try to stop threads
  // from flushing bad data or generating more faults meanwhile
  if (sig==SIGQUIT || sig==SIGILL || sig==SIGSEGV || sig==SIGBUS) {
    SegFaulting=true;
    LightProcess::Close();
    // leave running for SIGTERM SIGFPE SIGABRT
  }

  // Turn on stack traces for coredumps
  StackTrace::Enabled = true;
  StackTraceNoHeap st;

  char pid[sizeof(Process::GetProcessId())*3+2]; // '-' and \0
  sprintf(pid,"%u",Process::GetProcessId());
  char tracefn[StackTraceBase::ReportDirectory.length()
               + strlen("/stacktrace..log") + strlen(pid) + 1];
  sprintf(tracefn, "%s/stacktrace.%s.log",
          StackTraceBase::ReportDirectory.c_str(), pid);

  st.log(strsignal(sig), tracefn, pid);

  int fd = ::open(tracefn, O_APPEND|O_WRONLY, S_IRUSR|S_IWUSR);
  if (fd >= 0) {
    if (!g_context.isNull()) {
      dprintf(fd, "\nPHP Stacktrace:\n\n%s",
              debug_string_backtrace(false).data());
    }
    ::close(fd);
  }

  if (!StackTrace::ReportEmail.empty()) {
    char format [] = "cat %s | mail -s \"Stack Trace from %s\" '%s'";
    char cmdline[strlen(format)+strlen(tracefn)
                 +strlen(Process::GetAppName().c_str())
                 +strlen(StackTrace::ReportEmail.c_str())+1];
    sprintf(cmdline, format, tracefn, Process::GetAppName().c_str(),
            StackTrace::ReportEmail.c_str());
    Util::ssystem(cmdline);
  }

  // Calling all of these library functions in a signal handler
  // is completely undefined behavior, but we seem to get away with it.
  // Do it last just in case

  Logger::Error("Core dumped: %s", strsignal(sig));

  if (hhvm && !g_context.isNull()) {
    // sync up gdb Dwarf info so that gdb can do a full backtrace
    // from the core file. Do this at the very end as syncing needs
    // to allocate memory for the ELF file.
    g_vmContext->syncGdbState();
  }

  // re-raise the signal and pass it to the default handler
  // to terminate the process.
  raise(sig);
}

///////////////////////////////////////////////////////////////////////////////
// Types
struct bfd_cache {
  bfd_cache() : abfd(NULL) {}
  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;
string StackTraceBase::ReportEmail;
#if defined(HPHP_OSS)
string StackTraceBase::ReportDirectory("/tmp");
#else
string StackTraceBase::ReportDirectory("/var/tmp/cores");
#endif

void StackTraceBase::InstallReportOnSignal(int sig) {
  signal(sig, bt_handler);
}

void StackTraceBase::InstallReportOnErrors() {
  static bool already_set = false;
  if (already_set) return;
  already_set = true;

  // Turn on bt-on-sig for a good default set of error signals
  signal(SIGQUIT, bt_handler);
  signal(SIGILL,  bt_handler);
  signal(SIGFPE,  bt_handler);
  signal(SIGSEGV, bt_handler);
  signal(SIGBUS,  bt_handler);
  signal(SIGABRT, bt_handler);
}

///////////////////////////////////////////////////////////////////////////////
// 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) {
  vector<string> frames;
  Util::split(':', hexEncoded, frames);
  for (unsigned int i = 0; i < frames.size(); i++) {
    m_bt_pointers.push_back((void*)strtoll(frames[i].c_str(), NULL, 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 (vector<void*>::const_iterator btpi = m_bt_pointers.begin();
         btpi != m_bt_pointers.end(); ++btpi) {
      string framename = Translate(*btpi)->toString();
      if (framename.find("StackTrace::") != string::npos) {
        continue; // ignore frames in the StackTrace class
      }
      if (skip-- > 0) continue;
      m_bt += "# ";
      m_bt += lexical_cast<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;
  NamedBfd bfds[bfds_size];
  for (unsigned int i = 0; i < bfds_size; i++) bfds[i].key[0]='\0';
  for (unsigned int i = 0; i < m_btpointers_cnt; i++) {
    if (Translate(fd, m_btpointers[i], frame, bfds, bfds_size)) {
      frame++;
    }
  }
  // ~bfds[i].bc here (unlike the heap case)
}

void StackTrace::get(FramePtrVec &frames) const {
  frames.clear();
  for (vector<void*>::const_iterator 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 {
  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), "%llx", (int64)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 char *value) {
  ASSERT(name && *name);
  ASSERT(value);

  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 {
  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: %llx\n", (int64)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: %s\n", hhvm ? "hhvm" : "hphp");
  dprintf(fd, "Version: %s\n", COMPILER_ID);
  dprintf(fd, "\n");

  hphp_string_map<std::string> &extra = StackTraceLog::s_logData->data;
  for (hphp_string_map<std::string>::const_iterator iter = extra.begin();
       iter != extra.end(); ++iter) {
    dprintf(fd, "%s: %s\n", iter->first.c_str(), iter->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;
}

StackTrace::FramePtr StackTrace::Translate(void *frame) {
  Dl_info dlInfo;
  addr2line_data adata;

  Frame * f1 = new Frame(frame);
  FramePtr f(f1);
  if (!StackTraceBase::Translate(frame, f1, dlInfo, &adata)) 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;
}

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;
  }

  adata->found = bfd_find_nearest_line(abfd, section, adata->syms,
                                       adata->pc - vma, &adata->filename,
                                       &adata->functionname, &adata->line);
  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, NULL, 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 boost::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, NULL); // hard to avoid heap here!
  if (!abfd) return true;
  p->abfd = abfd;
  p->syms = NULL;
  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 = NULL;
  data->functionname = NULL;
  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 == NULL) return mangled;

  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 == NULL) {
    dprintf(fd, "%s", mangled);
    return;
  }
  dprintf(fd, "%s%s", mangled[0]=='.' ? "." : "", result);
  return ;
}


///////////////////////////////////////////////////////////////////////////////
}