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/runtime/base/backtrace.h"
18 #include "hphp/runtime/base/array-init.h"
19 #include "hphp/runtime/base/execution-context.h"
20 #include "hphp/runtime/base/rds-header.h"
21 #include "hphp/runtime/base/runtime-option.h"
22 #include "hphp/runtime/ext/asio/ext_async-generator-wait-handle.h"
23 #include "hphp/runtime/vm/act-rec.h"
24 #include "hphp/runtime/vm/bytecode.h"
25 #include "hphp/runtime/vm/func.h"
26 #include "hphp/runtime/vm/runtime.h"
27 #include "hphp/runtime/vm/vm-regs.h"
28 #include "hphp/runtime/vm/jit/tc.h"
29 #include "hphp/runtime/vm/jit/unique-stubs.h"
30 #include "hphp/runtime/vm/treadmill.h"
31 #include "hphp/util/concurrent-scalable-cache.h"
32 #include "hphp/util/struct-log.h"
34 #include <folly/small_vector.h>
41 s_function("function"),
48 s_metadata("metadata"),
49 s_86metadata("86metadata"),
53 static c_WaitableWaitHandle
* getParentWH(
54 c_WaitableWaitHandle
* wh
,
55 context_idx_t contextIdx
,
56 folly::small_vector
<c_WaitableWaitHandle
*, 64>& visitedWHs
) {
57 assertx(!wh
->isFinished());
58 auto p
= wh
->getParentChain().firstInContext(contextIdx
);
60 UNLIKELY(std::find(visitedWHs
.begin(), visitedWHs
.end(), p
)
61 != visitedWHs
.end())) {
62 // If the parent exists in our backtrace, it means we have detected a
63 // cycle. We well fall back to savedFP in that case.
66 visitedWHs
.push_back(p
);
70 // walks up the wait handle dependency chain, until it finds activation record
71 static ActRec
* getActRecFromWaitHandle(
72 c_WaitableWaitHandle
* currentWaitHandle
,
73 context_idx_t contextIdx
,
75 folly::small_vector
<c_WaitableWaitHandle
*, 64>& visitedWHs
) {
76 while (currentWaitHandle
!= nullptr) {
77 assertx(!currentWaitHandle
->isFinished());
78 if (currentWaitHandle
->getKind() == c_WaitHandle::Kind::AsyncFunction
) {
79 auto resumable
= currentWaitHandle
->asAsyncFunction()->resumable();
80 *prevPc
= resumable
->resumeOffset();
81 return resumable
->actRec();
83 if (currentWaitHandle
->getKind() == c_WaitHandle::Kind::AsyncGenerator
) {
84 auto resumable
= currentWaitHandle
->asAsyncGenerator()->resumable();
85 *prevPc
= resumable
->resumeOffset();
86 return resumable
->actRec();
89 currentWaitHandle
= getParentWH(currentWaitHandle
, contextIdx
, visitedWHs
);
92 return AsioSession::Get()->getContext(contextIdx
)->getSavedFP();
95 static ActRec
* getPrevActRec(
96 const ActRec
* fp
, Offset
* prevPc
,
97 folly::small_vector
<c_WaitableWaitHandle
*, 64>& visitedWHs
) {
98 c_WaitableWaitHandle
* currentWaitHandle
= nullptr;
100 if (fp
&& fp
->func() && fp
->resumed()) {
101 if (fp
->func()->isAsyncFunction()) {
102 currentWaitHandle
= frame_afwh(fp
);
103 } else if (fp
->func()->isAsyncGenerator()) {
104 auto const gen
= frame_async_generator(fp
);
105 if (gen
->isRunning() && !gen
->isEagerlyExecuted()) {
106 currentWaitHandle
= gen
->getWaitHandle();
111 if (currentWaitHandle
!= nullptr) {
112 if (currentWaitHandle
->isFinished()) {
114 * It's possible in very rare cases (it will return a truncated stack):
115 * 1) async function which WaitHandle is not referenced by anything
117 * 2) its return value is an object with destructor
118 * 3) this destructor gets called as part of destruction of the
119 * WaitHandleobject, which happens right before FP is adjusted
124 auto const contextIdx
= currentWaitHandle
->getContextIdx();
126 currentWaitHandle
= getParentWH(currentWaitHandle
, contextIdx
, visitedWHs
);
127 return getActRecFromWaitHandle(
128 currentWaitHandle
, contextIdx
, prevPc
, visitedWHs
);
131 return g_context
->getPrevVMState(fp
, prevPc
);
134 // wrapper around getActRecFromWaitHandle, which does some extra validation
135 static ActRec
* getActRecFromWaitHandleWrapper(
136 c_WaitableWaitHandle
* currentWaitHandle
, Offset
* prevPc
,
137 folly::small_vector
<c_WaitableWaitHandle
*, 64>& visitedWHs
) {
138 if (currentWaitHandle
->isFinished()) {
142 auto const contextIdx
= currentWaitHandle
->getContextIdx();
143 if (contextIdx
<= 0) {
147 return getActRecFromWaitHandle(
148 currentWaitHandle
, contextIdx
, prevPc
, visitedWHs
);
151 Array
createBacktrace(const BacktraceArgs
& btArgs
) {
152 if (btArgs
.isCompact()) {
153 return createCompactBacktrace()->extract();
156 auto bt
= Array::Create();
157 folly::small_vector
<c_WaitableWaitHandle
*, 64> visitedWHs
;
159 // If there is a parser frame, put it at the beginning of the backtrace.
160 if (btArgs
.m_parserFrame
) {
163 s_file
, btArgs
.m_parserFrame
->filename
,
164 s_line
, btArgs
.m_parserFrame
->lineNumber
170 ActRec
* fp
= nullptr;
173 if (btArgs
.m_fromWaitHandle
) {
174 fp
= getActRecFromWaitHandleWrapper(
175 btArgs
.m_fromWaitHandle
, &pc
, visitedWHs
);
176 // no frames found, we are done
180 // If there are no VM frames, we're done.
181 if (!rds::header() || !vmfp()) return bt
;
183 // Get the fp and pc of the top frame (possibly skipping one frame).
185 if (btArgs
.m_skipTop
) {
186 fp
= getPrevActRec(vmfp(), &pc
, visitedWHs
);
187 // We skipped over the only VM frame, we're done.
191 auto const unit
= fp
->func()->unit();
193 pc
= unit
->offsetOf(vmpc());
196 if (btArgs
.m_skipInlined
&& RuntimeOption::EvalJit
) {
197 while (fp
&& (jit::TCA
)fp
->m_savedRip
== jit::tc::ustubs().retInlHelper
) {
198 fp
= getPrevActRec(fp
, &pc
, visitedWHs
);
204 // Handle the top frame.
205 if (btArgs
.m_withSelf
) {
206 // Builtins don't have a file and line number, so find the first user frame
209 while (curFp
&& curFp
->func()->isBuiltin()) {
210 curFp
= g_context
->getPrevVMState(curFp
, &curPc
);
213 auto const unit
= curFp
->func()->unit();
215 auto const filename
= curFp
->func()->filename();
217 ArrayInit
frame(btArgs
.m_parserFrame
? 4 : 2, ArrayInit::Map
{});
218 frame
.set(s_file
, Variant
{const_cast<StringData
*>(filename
)});
219 frame
.set(s_line
, unit
->getLineNumber(curPc
));
220 if (btArgs
.m_parserFrame
) {
221 frame
.set(s_function
, s_include
);
222 frame
.set(s_args
, Array::Create(btArgs
.m_parserFrame
->filename
));
224 bt
.append(frame
.toVariant());
229 // Handle the subsequent VM frames.
231 for (auto prevFp
= getPrevActRec(fp
, &prevPc
, visitedWHs
);
232 fp
!= nullptr && (btArgs
.m_limit
== 0 || depth
< btArgs
.m_limit
);
233 fp
= prevFp
, pc
= prevPc
,
234 prevFp
= getPrevActRec(fp
, &prevPc
, visitedWHs
)) {
235 // Do not capture frame for HPHP only functions.
236 if (fp
->func()->isNoInjection()) continue;
238 ArrayInit
frame(8, ArrayInit::Map
{});
240 auto const curUnit
= fp
->func()->unit();
241 auto const curOp
= curUnit
->getOp(pc
);
242 auto const isReturning
=
243 curOp
== Op::RetC
|| curOp
== Op::RetV
|| curOp
== Op::RetM
||
244 curOp
== Op::CreateCont
|| curOp
== Op::Await
||
247 // Builtins and generators don't have a file and line number
248 if (prevFp
&& !prevFp
->func()->isBuiltin()) {
249 auto const prevUnit
= prevFp
->func()->unit();
250 auto prevFile
= prevUnit
->filepath();
251 if (prevFp
->func()->originalFilename()) {
252 prevFile
= prevFp
->func()->originalFilename();
255 frame
.set(s_file
, Variant
{const_cast<StringData
*>(prevFile
)});
257 // In the normal method case, the "saved pc" for line number printing is
258 // pointing at the cell conversion (Unbox/Pop) instruction, not the call
259 // itself. For multi-line calls, this instruction is associated with the
260 // subsequent line which results in an off-by-n. We're subtracting one
261 // in order to look up the line associated with the FCall/FCallArray
262 // instruction. Exception handling and the other opcodes (ex. BoxR)
263 // already do the right thing. The emitter associates object access with
264 // the subsequent expression and this would be difficult to modify.
265 auto const opAtPrevPc
= prevUnit
->getOp(prevPc
);
267 if (opAtPrevPc
== Op::PopR
||
268 opAtPrevPc
== Op::UnboxR
||
269 opAtPrevPc
== Op::UnboxRNop
) {
273 prevFp
->func()->unit()->getLineNumber(prevPc
- pcAdjust
));
276 // Check for include.
277 String funcname
{const_cast<StringData
*>(fp
->func()->displayName())};
278 if (fp
->func()->isClosureBody()) {
279 // Strip the file hash from the closure name.
280 String fullName
{const_cast<StringData
*>(fp
->func()->baseCls()->name())};
281 funcname
= fullName
.substr(0, fullName
.find(';'));
284 // Check for pseudomain.
285 if (funcname
.empty()) {
286 if (!prevFp
&& !btArgs
.m_withPseudoMain
) continue;
287 else if (!prevFp
) funcname
= s_main
;
288 else funcname
= s_include
;
291 frame
.set(s_function
, funcname
);
293 if (!funcname
.same(s_include
)) {
294 // Closures have an m_this but they aren't in object context.
295 auto ctx
= arGetContextClass(fp
);
296 if (ctx
!= nullptr && !fp
->func()->isClosureBody()) {
297 frame
.set(s_class
, Variant
{const_cast<StringData
*>(ctx
->name())});
298 if (!isReturning
&& fp
->hasThis()) {
299 if (btArgs
.m_withThis
) {
300 frame
.set(s_object
, Object(fp
->getThis()));
302 frame
.set(s_type
, s_arrow
);
304 frame
.set(s_type
, s_double_colon
);
309 bool const mayUseVV
= fp
->func()->attrs() & AttrMayUseVV
;
311 auto const withNames
= btArgs
.m_withArgNames
;
312 auto const withValues
= btArgs
.m_withArgValues
;
313 if ((!btArgs
.m_withArgNames
&& !btArgs
.m_withArgValues
) ||
314 !RuntimeOption::EnableArgsInBacktraces
) {
316 } else if (funcname
.same(s_include
)) {
317 auto filepath
= const_cast<StringData
*>(curUnit
->filepath());
318 frame
.set(s_args
, make_packed_array(filepath
));
319 } else if (isReturning
) {
320 frame
.set(s_args
, empty_array());
322 auto args
= Array::Create();
323 auto const nparams
= fp
->func()->numNonVariadicParams();
324 auto const nargs
= fp
->numArgs();
325 auto const nformals
= std::min
<int>(nparams
, nargs
);
327 if (UNLIKELY(mayUseVV
) &&
328 UNLIKELY(fp
->hasVarEnv() && fp
->getVarEnv()->getFP() != fp
)) {
329 // VarEnv is attached to eval or debugger frame, other than the current
330 // frame. Access locals thru VarEnv.
331 auto varEnv
= fp
->getVarEnv();
332 auto func
= fp
->func();
333 for (int i
= 0; i
< nformals
; i
++) {
334 auto const argname
= func
->localVarName(i
);
335 auto const tv
= varEnv
->lookup(argname
);
337 auto val
= init_null();
338 if (tv
!= nullptr) { // the variable hasn't been unset
339 val
= withValues
? tvAsVariant(tv
) : "";
343 args
.set(String(const_cast<StringData
*>(argname
)), val
);
349 for (int i
= 0; i
< nformals
; i
++) {
350 Variant val
= withValues
? tvAsVariant(frame_local(fp
, i
)) : "";
353 auto const argname
= fp
->func()->localVarName(i
);
354 args
.set(String(const_cast<StringData
*>(argname
)), val
);
361 // Builtin extra args are not stored in varenv.
362 if (UNLIKELY(mayUseVV
) && nargs
> nparams
&& fp
->hasExtraArgs()) {
363 for (int i
= nparams
; i
< nargs
; i
++) {
364 auto arg
= fp
->getExtraArg(i
- nparams
);
365 if (arg
->m_type
== KindOfUninit
) {
366 args
.append(init_null());
368 args
.append(tvAsVariant(arg
));
372 frame
.set(s_args
, args
);
375 if (btArgs
.m_withMetadata
&& !isReturning
) {
376 if (UNLIKELY(mayUseVV
) && UNLIKELY(fp
->hasVarEnv())) {
377 auto tv
= fp
->getVarEnv()->lookup(s_86metadata
.get());
378 if (tv
!= nullptr && tv
->m_type
!= KindOfUninit
) {
379 frame
.set(s_metadata
, tvAsVariant(tv
));
382 auto local
= fp
->func()->lookupVarId(s_86metadata
.get());
383 if (local
!= kInvalidId
) {
384 auto tv
= frame_local(fp
, local
);
385 if (tv
->m_type
!= KindOfUninit
) {
386 frame
.set(s_metadata
, tvAsVariant(tv
));
392 bt
.append(frame
.toVariant());
399 void addBacktraceToStructLog(const Array
& bt
, StructuredLogEntry
& cols
) {
400 std::set
<std::string
> strings
;
401 std::vector
<folly::StringPiece
> files
;
402 std::vector
<folly::StringPiece
> functions
;
403 std::vector
<folly::StringPiece
> lines
;
404 auto addString
= [&] (std::string
&& s
) -> folly::StringPiece
{
405 return *strings
.insert(std::move(s
)).first
;
407 auto addVariant
= [&] (const Variant
& v
) -> folly::StringPiece
{
408 if (v
.isString()) return v
.toCStrRef().slice();
409 return addString(v
.toString().toCppString());
411 for (ArrayIter
it(bt
.get()); it
; ++it
) {
412 Array frame
= it
.second().toArray();
413 files
.emplace_back(addVariant(frame
[s_file
]));
414 if (frame
.exists(s_class
)) {
415 functions
.emplace_back(
416 addString(folly::sformat("{}{}{}",
417 frame
[s_class
].toString().data(),
418 frame
[s_type
].toString().data(),
419 frame
[s_function
].toString().data()
423 functions
.emplace_back(addVariant(frame
[s_function
]));
425 lines
.emplace_back(addVariant(frame
[s_line
]));
427 cols
.setVec("php_files", files
);
428 cols
.setVec("php_functions", functions
);
429 cols
.setVec("php_lines", lines
);
433 static void walkStack(L func
) {
435 folly::small_vector
<c_WaitableWaitHandle
*, 64> visitedWHs
;
438 // If there are no VM frames, we're done.
439 if (!fp
|| !rds::header()) return;
441 // Handle the subsequent VM frames.
443 for (; fp
!= nullptr; fp
= getPrevActRec(fp
, &prevPc
, visitedWHs
)) {
445 // Do not capture frame for HPHP only functions.
446 if (fp
->func()->isNoInjection()) continue;
452 int64_t createBacktraceHash() {
453 // Settings constants before looping
454 int64_t hash
= 0x9e3779b9;
455 Unit
* prev_unit
= nullptr;
457 walkStack([&] (ActRec
* fp
, Offset
) {
458 auto const curFunc
= fp
->func();
459 auto const curUnit
= curFunc
->unit();
461 // Only do a filehash if the file changed. It is very common
462 // to see sequences of calls within the same file
463 // File paths are already hashed, and the hash bits are random enough
464 // That allows us to do a faster combination of hashes using a known
465 // implementation (boost::hash_combine)
466 if (prev_unit
!= curUnit
) {
468 auto filehash
= curUnit
->filepath()->hash();
469 hash
^= filehash
+ 0x9e3779b9 + (hash
<< 6) + (hash
>> 2);
472 // Function names are already hashed, and the hash bits are random enough
473 // That allows us to do a faster combination of hashes using a known
474 // implementation (boost::hash_combine)
475 auto funchash
= curFunc
->fullName()->hash();
476 hash
^= funchash
+ 0x9e3779b9 + (hash
<< 6) + (hash
>> 2);
482 req::ptr
<CompactTrace
> createCompactBacktrace() {
483 auto ret
= req::make
<CompactTrace
>();
484 walkStack([&] (ActRec
* fp
, Offset prevPc
) { ret
->insert(fp
, prevPc
); });
490 struct CTKHasher final
{
491 uint64_t hash(const CompactTrace::Key
& k
) const { return k
.m_hash
; }
492 bool equal(const CompactTrace::Key
& k1
, const CompactTrace::Key
& k2
) const;
495 struct CacheDeleter final
{
496 void operator()(ArrayData
* ad
) const {
497 if (!ad
->isUncounted()) return;
498 Treadmill::enqueue([ad
] {
499 PackedArray::ReleaseUncounted(ad
);
504 using CachedArray
= std::shared_ptr
<ArrayData
>;
505 using Cache
= ConcurrentScalableCache
<CompactTrace::Key
,CachedArray
,CTKHasher
>;
508 bool CTKHasher::equal(
509 const CompactTrace::Key
& k1
,
510 const CompactTrace::Key
& k2
512 if (k1
.m_hash
!= k2
.m_hash
|| k1
.m_frames
.size() != k2
.m_frames
.size()) {
515 for (int i
= 0; i
< k1
.m_frames
.size(); ++i
) {
516 auto& a
= k1
.m_frames
[i
];
517 auto& b
= k2
.m_frames
[i
];
518 if (a
.func
!= b
.func
|| a
.prevPcAndHasThis
!= b
.prevPcAndHasThis
) {
526 IMPLEMENT_RESOURCE_ALLOCATION(CompactTrace
)
528 void CompactTrace::Key::insert(const ActRec
* fp
, int32_t prevPc
) {
529 auto const funcHash
= use_lowptr
530 ? (uintptr_t)fp
->func() << 32
531 : (uintptr_t)fp
->func();
532 m_hash
^= funcHash
+ 0x9e3779b9 + (m_hash
<< 6) + (m_hash
>> 2);
533 m_hash
^= prevPc
+ 0x9e3779b9 + (m_hash
<< 6) + (m_hash
>> 2);
535 auto const curUnit
= fp
->func()->unit();
536 auto const curOp
= curUnit
->getOp(prevPc
);
537 auto const isReturning
=
538 curOp
== Op::RetC
|| curOp
== Op::RetV
|| curOp
== Op::RetM
||
539 curOp
== Op::CreateCont
|| curOp
== Op::Await
||
541 m_frames
.push_back(Frame
{
544 !isReturning
&& arGetContextClass(fp
) && fp
->hasThis()
548 Array
CompactTrace::Key::extract() const {
549 PackedArrayInit
aInit(m_frames
.size(), CheckAllocation
{});
550 for (int idx
= 0; idx
< m_frames
.size(); ++idx
) {
551 auto const prev
= idx
< m_frames
.size() - 1 ? &m_frames
[idx
+ 1] : nullptr;
552 ArrayInit
frame(6, ArrayInit::Map
{});
553 if (prev
&& !prev
->func
->isBuiltin()) {
554 auto const prevUnit
= prev
->func
->unit();
555 auto prevFile
= prevUnit
->filepath();
556 if (prev
->func
->originalFilename()) {
557 prevFile
= prev
->func
->originalFilename();
560 auto const prevPc
= prev
->prevPc
;
561 auto const opAtPrevPc
= prevUnit
->getOp(prevPc
);
563 if (opAtPrevPc
== Op::PopR
||
564 opAtPrevPc
== Op::UnboxR
||
565 opAtPrevPc
== Op::UnboxRNop
) {
568 frame
.set(s_file
, StrNR(prevFile
).asString());
569 frame
.set(s_line
, prevUnit
->getLineNumber(prevPc
- pcAdjust
));
572 auto const f
= m_frames
[idx
].func
;
574 // Check for include.
575 String funcname
{const_cast<StringData
*>(f
->displayName())};
576 if (f
->isClosureBody()) {
577 // Strip the file hash from the closure name.
578 String fullName
{const_cast<StringData
*>(f
->baseCls()->name())};
579 funcname
= fullName
.substr(0, fullName
.find(';'));
582 // Check for pseudomain.
583 if (funcname
.empty()) {
585 else funcname
= s_include
;
588 frame
.set(s_function
, funcname
);
590 if (!funcname
.same(s_include
)) {
591 // Closures have an m_this but they aren't in object context.
592 auto ctx
= m_frames
[idx
].func
->cls();
593 if (ctx
!= nullptr && !f
->isClosureBody()) {
594 frame
.set(s_class
, Variant
{const_cast<StringData
*>(ctx
->name())});
595 if (m_frames
[idx
].hasThis
) {
596 frame
.set(s_type
, s_arrow
);
598 frame
.set(s_type
, s_double_colon
);
602 auto filepath
= const_cast<StringData
*>(f
->unit()->filepath());
603 frame
.set(s_args
, make_packed_array(filepath
));
606 aInit
.append(frame
.toVariant());
609 return aInit
.toArray();
612 Array
CompactTrace::extract() const {
613 if (m_key
.m_frames
.size() == 1) return empty_array();
615 Cache::ConstAccessor acc
;
616 if (s_cache
.find(acc
, m_key
)) {
617 return Array(acc
.get()->get());
620 auto arr
= m_key
.extract();
621 auto ins
= CachedArray(
624 : PackedArray::MakeUncounted(arr
.get()),
627 if (!s_cache
.insert(m_key
, ins
)) {
630 return Array(ins
.get());