2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/util/stack-trace.h"
28 #include <sys/types.h>
32 #include <folly/Conv.h>
33 #include <folly/Demangle.h>
34 #include <folly/FileUtil.h>
35 #include <folly/Format.h>
36 #include <folly/ScopeGuard.h>
37 #include <folly/String.h>
39 #include "hphp/util/assertions.h"
40 #include "hphp/util/compatibility.h"
41 #include "hphp/util/conv-10.h"
42 #include "hphp/util/hash-map-typedefs.h"
43 #include "hphp/util/hash.h"
44 #include "hphp/util/process.h"
45 #include "hphp/util/thread-local.h"
47 #if defined USE_FOLLY_SYMBOLIZER
49 #include <folly/experimental/symbolizer/Symbolizer.h>
51 #elif defined HAVE_LIBBFD
58 ////////////////////////////////////////////////////////////////////////////////
60 const char* const s_defaultBlacklist
[] = {
61 "_ZN4HPHP16StackTraceNoHeap",
62 "_ZN5folly10symbolizer17getStackTraceSafeEPmm",
65 bool StackTraceBase::Enabled
= true;
67 const char* const* StackTraceBase::FunctionBlacklist
= s_defaultBlacklist
;
68 unsigned StackTraceBase::FunctionBlacklistCount
= 2;
70 ////////////////////////////////////////////////////////////////////////////////
74 void printStr(int fd
, folly::StringPiece s
) {
75 write(fd
, s
.begin(), s
.size());
78 void printInt(int fd
, int64_t val
) {
80 printStr(fd
, conv_10(val
, buf
+ sizeof buf
));
83 void printFrameHdr(int fd
, int fr
) {
86 printStr(fd
, (fr
< 10 ? " " : " "));
89 void printPair(int fd
,
90 folly::StringPiece first
,
91 folly::StringPiece second
) {
98 void printPair(int fd
,
99 folly::StringPiece first
,
102 printPair(fd
, first
, conv_10(second
, buf
+ sizeof buf
));
106 * Writes a file path to a file while folding any consecutive forward slashes.
108 void write_path(int fd
, folly::StringPiece path
) {
109 while (!path
.empty()) {
110 auto const pos
= path
.find('/');
111 if (pos
== std::string::npos
) {
112 folly::writeNoInt(fd
, path
.data(), path
.size());
116 auto const left
= path
.subpiece(0, pos
+ 1);
117 folly::writeNoInt(fd
, left
.data(), left
.size());
119 auto right
= path
.subpiece(pos
);
120 while (!right
.empty() && right
[0] == '/') {
121 right
= right
.subpiece(1);
128 bool isBlacklisted(const char* funcname
) {
129 for (int i
= 0; i
< StackTraceBase::FunctionBlacklistCount
; i
++) {
130 auto ignoreFunc
= StackTraceBase::FunctionBlacklist
[i
];
131 if (strncmp(funcname
, ignoreFunc
, strlen(ignoreFunc
)) == 0) {
139 * Demangle a function name and return it as a string.
141 std::string
demangle(const char* mangled
) {
142 auto const skip_first
=
143 static_cast<int>(mangled
[0] == '.' || mangled
[0] == '$');
145 auto const result
= folly::demangle(mangled
+ skip_first
);
146 auto const ret
= std::string(result
.data(), result
.size());
147 if (mangled
[0] == '.') {
155 * Demangle a function name and write it to a file.
157 void demangle(int fd
, const char* mangled
) {
158 if (mangled
== nullptr || mangled
[0] == '\0') {
163 auto const skip_first
=
164 static_cast<int>(mangled
[0] == '.' || mangled
[0] == '$');
167 auto sz
= folly::demangle(mangled
+ skip_first
, buf
, sizeof buf
);
168 if (sz
> sizeof buf
) {
169 write(fd
, mangled
, strlen(mangled
));
173 if (mangled
[0] == '.') write(fd
, ".", 1);
177 std::mutex s_perfMapCacheMutex
;
178 StackTrace::PerfMap s_perfMapCache
;
179 std::set
<void*> s_perfMapNegCache
;
181 void translateFromPerfMap(StackFrameExtra
* frame
) {
182 std::lock_guard
<std::mutex
> lock(s_perfMapCacheMutex
);
184 if (s_perfMapCache
.translate(frame
)) {
185 if (s_perfMapNegCache
.count(frame
->addr
)) {
186 // A prior failed lookup of frame already triggered a rebuild.
189 // Rebuild the cache, then search again.
190 s_perfMapCache
.rebuild();
191 if (s_perfMapCache
.translate(frame
)) {
192 s_perfMapNegCache
.insert(frame
->addr
);
198 int ALWAYS_INLINE
get_backtrace(void** frame
, int max
) {
200 #if defined USE_FOLLY_SYMBOLIZER
202 ret
= folly::symbolizer::getStackTraceSafe((uintptr_t*)frame
, max
);
204 ret
= folly::symbolizer::getStackTrace((uintptr_t*)frame
, max
);
206 #elif defined __GLIBC__
207 ret
= backtrace(frame
, max
);
209 if (ret
< 0 || ret
> max
) {
217 struct StackTraceLog
{
218 hphp_string_map
<std::string
> data
;
220 static THREAD_LOCAL(StackTraceLog
, s_logData
);
222 THREAD_LOCAL(StackTraceLog
, StackTraceLog::s_logData
);
224 ////////////////////////////////////////////////////////////////////////////////
226 StackTraceBase::StackTraceBase() {
227 #if !defined USE_FOLLY_SYMBOLIZER && defined HAVE_LIBBFD
232 StackTrace::StackTrace(bool trace
) {
233 if (trace
&& Enabled
) {
234 m_frames
.resize(kMaxFrame
);
235 m_frames
.resize(get_backtrace
<false>(m_frames
.data(), kMaxFrame
));
239 StackTrace::StackTrace(StackTrace::Force
) {
240 m_frames
.resize(kMaxFrame
);
241 m_frames
.resize(get_backtrace
<false>(m_frames
.data(), kMaxFrame
));
244 StackTrace::StackTrace(void* const* ips
, size_t count
) {
245 m_frames
.resize(count
);
246 m_frames
.assign(ips
, ips
+ count
);
249 StackTrace::StackTrace(folly::StringPiece hexEncoded
) {
250 // Can't split into StringPieces, strtoll() expects a null terminated string.
251 std::vector
<std::string
> frames
;
252 folly::split(':', hexEncoded
, frames
);
253 for (auto const& frame
: frames
) {
254 m_frames
.push_back((void*)strtoll(frame
.c_str(), nullptr, 16));
258 void StackTrace::get(std::vector
<std::shared_ptr
<StackFrameExtra
>>&
261 for (auto const frame_ptr
: m_frames
) {
262 frames
.push_back(Translate(frame_ptr
));
266 std::string
StackTrace::hexEncode(int minLevel
/* = 0 */,
267 int maxLevel
/* = 999 */) const {
269 for (int i
= minLevel
; i
< (int)m_frames
.size() && i
< maxLevel
; i
++) {
270 if (i
> minLevel
) bts
+= ':';
272 snprintf(buf
, sizeof(buf
), "%" PRIx64
, (int64_t)m_frames
[i
]);
278 const std::string
& StackTrace::toString(int skip
, int limit
) const {
279 auto usable
= skip
== 0 && limit
== -1;
280 if (!usable
|| !m_trace_usable
) {
284 if (!m_trace
.empty()) return m_trace
;
287 for (auto const frame_ptr
: m_frames
) {
288 auto const framename
= Translate(frame_ptr
)->toString();
289 // Ignore frames in the StackTrace class.
290 if (framename
.find("StackTrace::") != std::string::npos
) {
293 if (skip
-- > 0) continue;
295 m_trace
+= folly::to
<std::string
>(frame
);
296 if (frame
< 10) m_trace
+= " ";
299 m_trace
+= framename
;
302 if ((int)frame
== limit
) break;
305 m_trace_usable
= usable
;
309 void StackTrace::PerfMap::rebuild() {
313 snprintf(filename
, sizeof(filename
), "/tmp/perf-%d.map", getpid());
315 std::ifstream
perf_map(filename
);
316 if (!perf_map
) return;
320 while (!perf_map
.bad() && !perf_map
.eof()) {
321 if (!std::getline(perf_map
, line
)) continue;
326 // We compare < 2 because some implementations of sscanf() increment the
327 // assignment count for %n against the specification.
328 if (sscanf(line
.c_str(), "%lx %x %n", &addr
, &size
, &name_pos
) < 2 ||
329 name_pos
< 4 /* not assigned, or not enough spaces */) {
332 if (name_pos
>= line
.size()) continue;
334 auto const past
= addr
+ size
;
335 auto const range
= PerfMap::Range
{ addr
, past
};
337 m_map
[range
] = line
.substr(name_pos
);
343 bool StackTrace::PerfMap::translate(StackFrameExtra
* frame
) const {
345 const_cast<StackTrace::PerfMap
*>(this)->rebuild();
348 // Use a key with non-zero span, because otherwise a key right at the base of
349 // a range will be treated as before the range (bad) rather than within it
351 PerfMap::Range key
{uintptr_t(frame
->addr
), uintptr_t(frame
->addr
)+1};
352 auto const& it
= m_map
.find(key
);
353 if (it
== m_map
.end()) {
355 frame
->filename
= "?";
356 frame
->funcname
= "TC?"; // Note HHProf::HandlePProfSymbol() dependency.
361 auto const& filefunc
= it
->second
;
362 auto const colon_pos
= filefunc
.find("::");
363 if (colon_pos
== std::string::npos
) {
364 frame
->funcname
= filefunc
;
368 auto const prefix
= std::string(filefunc
, 0, colon_pos
);
369 if (prefix
== "HHVM") {
370 // HHVM unique stubs are simply "HHVM::stubName".
371 frame
->funcname
= filefunc
+ "()";
375 if (prefix
!= "PHP") {
376 // We don't recognize the prefix, so don't do any munging.
377 frame
->funcname
= filefunc
;
381 // Jitted PHP functions have the format "PHP::file.php::Full::funcName".
382 auto const file_pos
= colon_pos
+ 2;
383 auto const file_end_pos
= filefunc
.find("::", file_pos
);
384 if (file_end_pos
== std::string::npos
) {
385 // Bad PHP function descriptor.
386 frame
->funcname
= filefunc
;
389 frame
->filename
= std::string(filefunc
, file_pos
, file_end_pos
- file_pos
);
390 frame
->funcname
= "PHP::" + std::string(filefunc
, file_end_pos
+ 2) + "()";
394 ////////////////////////////////////////////////////////////////////////////////
396 std::string
StackFrameExtra::toString() const {
397 constexpr folly::StringPiece qq
{"??"};
398 return folly::sformat(
400 funcname
.empty() ? qq
: folly::StringPiece
{funcname
},
401 filename
.empty() ? qq
: folly::StringPiece
{filename
},
406 ////////////////////////////////////////////////////////////////////////////////
408 StackTraceNoHeap::StackTraceNoHeap(bool trace
) {
409 if (trace
&& Enabled
) {
410 m_frame_count
= get_backtrace
<true>(m_frames
, kMaxFrame
);
414 void StackTraceNoHeap::AddExtraLogging(const char* name
,
415 const std::string
& value
) {
416 assertx(name
!= nullptr && name
[0] != '\0');
417 StackTraceLog::s_logData
->data
[name
] = value
;
420 void StackTraceNoHeap::ClearAllExtraLogging() {
421 StackTraceLog::s_logData
->data
.clear();
424 void StackTraceNoHeap::log(const char* errorType
, int fd
, const char* buildId
,
425 int debuggerCount
) const {
428 printPair(fd
, "Host", Process::GetHostName().c_str());
429 printPair(fd
, "ProcessID", (int64_t)getpid());
430 printPair(fd
, "ThreadID", (int64_t)Process::GetThreadId());
431 printPair(fd
, "ThreadPID", Process::GetThreadPid());
432 printPair(fd
, "Name", Process::GetAppName().c_str());
433 printPair(fd
, "Type", errorType
? errorType
: "(unknown error)");
434 printPair(fd
, "Runtime", "hhvm");
435 printPair(fd
, "Version", buildId
);
436 printPair(fd
, "DebuggerCount", debuggerCount
);
439 for (auto const& pair
: StackTraceLog::s_logData
->data
) {
440 printPair(fd
, pair
.first
.c_str(), pair
.second
.c_str());
447 ////////////////////////////////////////////////////////////////////////////////
449 #if defined USE_FOLLY_SYMBOLIZER
451 void StackTraceNoHeap::printStackTrace(int fd
) const {
452 folly::symbolizer::Symbolizer symbolizer
;
453 folly::symbolizer::SymbolizedFrame frames
[kMaxFrame
];
454 symbolizer
.symbolize((uintptr_t*)m_frames
, frames
, m_frame_count
);
455 for (int i
= 0, fr
= 0; i
< m_frame_count
; i
++) {
456 auto const& frame
= frames
[i
];
459 isBlacklisted(frame
.name
)) {
462 printFrameHdr(fd
, fr
);
463 demangle(fd
, frame
.name
);
464 if (frame
.location
.hasFileAndLine
) {
465 char fileBuf
[PATH_MAX
];
467 frame
.location
.file
.toBuffer(fileBuf
, sizeof(fileBuf
));
468 printStr(fd
, " at ");
469 write_path(fd
, fileBuf
);
471 printInt(fd
, frame
.location
.line
);
478 std::shared_ptr
<StackFrameExtra
> StackTrace::Translate(void* frame_addr
,
480 folly::symbolizer::Symbolizer symbolizer
;
481 folly::symbolizer::SymbolizedFrame sf
;
482 symbolizer
.symbolize((uintptr_t*)&frame_addr
, &sf
, 1);
484 auto frame
= std::make_shared
<StackFrameExtra
>(frame_addr
);
488 // Lookup failed, so this is probably a PHP symbol. Let's
489 // check the perf map.
491 pm
->translate(frame
.get());
493 translateFromPerfMap(frame
.get());
498 if (sf
.location
.hasFileAndLine
) {
499 frame
->filename
= sf
.location
.file
.toString();
500 frame
->lineno
= sf
.location
.line
;
503 frame
->funcname
= sf
.name
? demangle(sf
.name
) : "";
508 #elif defined HAVE_LIBBFD
511 ////////////////////////////////////////////////////////////////////////////////
513 constexpr int MaxKey
= 100;
517 asymbol
** syms
{nullptr};
520 if (abfd
== nullptr) return;
521 bfd_cache_close(abfd
);
522 bfd_free_cached_info(abfd
);
523 bfd_close_all_done(abfd
);
532 using NamedBfdRange
= folly::Range
<NamedBfd
*>;
534 struct Addr2lineData
{
537 const char* filename
{nullptr};
538 const char* functionname
{nullptr};
540 bfd_boolean found
{FALSE
};
543 using BfdMap
= hphp_hash_map
<
545 std::shared_ptr
<BfdCache
>,
550 * We cache opened bfd file pointers that in turn cache frame pointer lookup
553 std::mutex s_bfdMutex
;
556 ////////////////////////////////////////////////////////////////////////////////
558 /* Copied and re-factored from addr2line. */
560 void find_address_in_section(bfd
* abfd
, asection
* section
, void* data
) {
561 auto adata
= reinterpret_cast<Addr2lineData
*>(data
);
566 if ((bfd_get_section_flags(abfd
, section
) & SEC_ALLOC
) == 0) {
570 auto const vma
= bfd_get_section_vma(abfd
, section
);
571 if (adata
->pc
< vma
) {
575 auto const size
= bfd_get_section_size(section
);
576 if (adata
->pc
>= vma
+ size
) {
580 // libdwarf allocates its own unaligned memory so it doesn't play well with
583 adata
->found
= bfd_find_nearest_line(
584 abfd
, section
, adata
->syms
, adata
->pc
- vma
, &adata
->filename
,
585 &adata
->functionname
, &adata
->line
590 auto file
= adata
->filename
;
591 auto line
= adata
->line
;
592 bfd_boolean found
= TRUE
;
594 found
= bfd_find_inliner_info(abfd
, &file
, &adata
->functionname
, &line
);
599 bool slurp_symtab(asymbol
*** syms
, bfd
* abfd
) {
602 auto symcount
= bfd_read_minisymbols(abfd
, FALSE
, (void**)syms
, &size
);
604 symcount
= bfd_read_minisymbols(
605 abfd
, TRUE
/* dynamic */, (void**)syms
, &size
608 return symcount
>= 0;
611 bool translate_addresses(bfd
* abfd
, const char* addr
, Addr2lineData
* adata
) {
612 if (abfd
== nullptr) return false;
613 adata
->pc
= bfd_scan_vma(addr
, nullptr, 16);
615 adata
->found
= FALSE
;
616 bfd_map_over_sections(abfd
, find_address_in_section
, adata
);
618 if (!adata
->found
|| !adata
->functionname
|| !*adata
->functionname
) {
624 bool fill_bfd_cache(folly::StringPiece filename
, BfdCache
& p
) {
625 // Hard to avoid heap here!
626 auto abfd
= bfd_openr(filename
.begin(), nullptr);
627 if (abfd
== nullptr) return true;
629 // Some systems don't have the BFD_DECOMPRESS flag.
630 #ifdef BFD_DECOMPRESS
631 abfd
->flags
|= BFD_DECOMPRESS
;
637 if (bfd_check_format(abfd
, bfd_archive
) ||
638 !bfd_check_format_matches(abfd
, bfd_object
, &match
) ||
639 !slurp_symtab(&p
.syms
, abfd
)) {
647 std::shared_ptr
<BfdCache
> get_bfd_cache(folly::StringPiece filename
) {
648 // Heterogeneous lookup is in C++14. Otherwise we'll end up making a
650 auto iter
= std::find_if(
653 [&] (const BfdMap::value_type
& pair
) { return pair
.first
== filename
; }
656 if (iter
!= s_bfds
.end()) {
660 auto p
= std::make_shared
<BfdCache
>();
661 if (fill_bfd_cache(filename
, *p
)) {
664 s_bfds
[filename
.str()] = p
;
668 BfdCache
* get_bfd_cache(folly::StringPiece filename
, NamedBfdRange bfds
) {
669 auto probe
= hash_string_cs(filename
.begin(), filename
.size()) % bfds
.size();
671 // Match on the end of filename instead of the beginning, if necessary.
672 if (filename
.size() >= MaxKey
) {
673 filename
= filename
.subpiece(filename
.size() - MaxKey
+ 1);
676 while (bfds
[probe
].key
[0] && strcmp(filename
.begin(), bfds
[probe
].key
) != 0) {
677 probe
= probe
? probe
-1 : bfds
.size()-1;
680 auto p
= &bfds
[probe
].bc
;
681 if (bfds
[probe
].key
[0]) return p
;
683 assert(filename
.size() < MaxKey
);
684 assert(filename
.begin()[filename
.size()] == 0);
685 // Accept the rare collision on keys (requires probe collision too).
686 memcpy(bfds
[probe
].key
, filename
.begin(), filename
.size() + 1);
687 fill_bfd_cache(filename
, *p
);
692 * Run addr2line to translate a function pointer into function name and line
695 bool addr2line(folly::StringPiece filename
, StackFrame
* frame
,
696 Addr2lineData
* data
, NamedBfdRange bfds
) {
698 snprintf(address
, sizeof(address
), "%p", frame
->addr
);
700 std::lock_guard
<std::mutex
> lock(s_bfdMutex
);
701 data
->filename
= nullptr;
702 data
->functionname
= nullptr;
707 auto p
= get_bfd_cache(filename
);
708 data
->syms
= p
->syms
;
709 ret
= translate_addresses(p
->abfd
, address
, data
);
711 // Don't let shared_ptr malloc behind the scenes in this case.
712 auto q
= get_bfd_cache(filename
, bfds
);
713 data
->syms
= q
->syms
;
714 ret
= translate_addresses(q
->abfd
, address
, data
);
718 frame
->lineno
= data
->line
;
724 * Translate a frame pointer to file name and line number pair.
726 bool translate(StackFrame
* frame
, Dl_info
& dlInfo
, Addr2lineData
* data
,
727 NamedBfdRange bfds
= NamedBfdRange()) {
728 if (!dladdr(frame
->addr
, &dlInfo
)) {
732 // Frame pointer offset in previous frame.
733 frame
->offset
= (char*)frame
->addr
- (char*)dlInfo
.dli_saddr
;
735 if (dlInfo
.dli_fname
) {
736 // First attempt without offsetting base address.
737 if (!addr2line(dlInfo
.dli_fname
, frame
, data
, bfds
) &&
738 dlInfo
.dli_fname
&& strstr(dlInfo
.dli_fname
, ".so")) {
739 // Offset shared lib's base address.
741 frame
->addr
= (char*)frame
->addr
- (size_t)dlInfo
.dli_fbase
;
743 // Use addr2line to get line number info.
744 addr2line(dlInfo
.dli_fname
, frame
, data
, bfds
);
750 ///////////////////////////////////////////////////////////////////////////////
753 * Variant of translate() used by StackTraceNoHeap.
755 bool translate(int fd
, void* frame_addr
, int frame_num
, NamedBfdRange bfds
) {
756 // Frame pointer offset in previous frame.
759 StackFrame
frame(frame_addr
);
760 if (!translate(&frame
, dlInfo
, &adata
, bfds
)) {
764 auto filename
= adata
.filename
? adata
.filename
: dlInfo
.dli_fname
;
765 if (filename
== nullptr) filename
= "??";
766 auto funcname
= adata
.functionname
? adata
.functionname
: dlInfo
.dli_sname
;
767 if (funcname
== nullptr) funcname
= "??";
769 // Ignore some frames that are always present.
770 if (isBlacklisted(funcname
)) return false;
772 printFrameHdr(fd
, frame_num
);
773 demangle(fd
, funcname
);
774 printStr(fd
, " at ");
775 write_path(fd
, filename
);
777 printInt(fd
, frame
.lineno
);
783 ////////////////////////////////////////////////////////////////////////////////
786 std::shared_ptr
<StackFrameExtra
> StackTrace::Translate(void* frame_addr
,
791 auto frame
= std::make_shared
<StackFrameExtra
>(frame_addr
);
792 if (!translate(frame
.get(), dlInfo
, &adata
)) {
793 // Lookup using dladdr() failed, so this is probably a PHP symbol. Let's
794 // check the perf map.
796 pm
->translate(frame
.get());
798 translateFromPerfMap(frame
.get());
803 if (adata
.filename
) {
804 frame
->filename
= adata
.filename
;
806 if (adata
.functionname
) {
807 frame
->funcname
= demangle(adata
.functionname
);
809 if (frame
->filename
.empty() && dlInfo
.dli_fname
) {
810 frame
->filename
= dlInfo
.dli_fname
;
812 if (frame
->funcname
.empty() && dlInfo
.dli_sname
) {
813 frame
->funcname
= demangle(dlInfo
.dli_sname
);
819 void StackTraceNoHeap::printStackTrace(int fd
) const {
820 // m_frame_count must be an upper bound on the number of filenames then *2 for
821 // tolerable hash table behavior.
822 auto const size
= m_frame_count
* 2;
824 // Using the heap in "NoHeap" is bad but we do it anyway.
825 const std::unique_ptr
<NamedBfd
[]> bfds(new NamedBfd
[size
]);
826 for (unsigned i
= 0; i
< size
; i
++) {
827 bfds
.get()[i
].key
[0] = '\0';
831 for (unsigned i
= 0; i
< m_frame_count
; i
++) {
832 auto range
= NamedBfdRange(bfds
.get(), size
);
833 if (translate(fd
, m_frames
[i
], frame
, range
)) {
837 // ~bfds[i].bc here (unlike the heap case).
840 ////////////////////////////////////////////////////////////////////////////////
842 #else // No libbfd or folly::Symbolizer
846 void printHex(int fd
, uint64_t val
) {
848 auto ptr
= buf
+ sizeof buf
;
852 *--ptr
= ch
+ (ch
>= 10 ? 'a' - 10 : '0');
856 printStr(fd
, folly::StringPiece(buf
, sizeof buf
));
861 std::shared_ptr
<StackFrameExtra
> StackTrace::Translate(void* bt
, PerfMap
* pm
) {
862 return std::make_shared
<StackFrameExtra
>(bt
);
865 void StackTraceNoHeap::printStackTrace(int fd
) const {
866 for (int i
= 0; i
< m_frame_count
; i
++) {
869 printStr(fd
, (i
< 10 ? " " : " "));
870 printHex(fd
, (uintptr_t)m_frames
[i
]);
877 ////////////////////////////////////////////////////////////////////////////////