Skip to content

Instantly share code, notes, and snippets.

@kennyyu
Forked from lewissbaker/StackTrace.cpp
Created April 29, 2021 18:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kennyyu/a1cf3d9b09d1761e5fd004cca9f2b381 to your computer and use it in GitHub Desktop.
Save kennyyu/a1cf3d9b09d1761e5fd004cca9f2b381 to your computer and use it in GitHub Desktop.
namespace {
// Helper struct for manually walking the stack using stack frame pointers
struct StackFrame {
StackFrame* parentFrame;
void* returnAddress;
};
size_t walkNormalStack(
uintptr_t* addresses,
size_t maxAddresses,
StackFrame* normalStackFrame,
StackFrame* normalStackFrameStop) {
size_t numFrames = 0;
while (numFrames < maxAddresses && normalStackFrame != nullptr) {
auto* normalStackFrameNext = normalStackFrame->parentFrame;
if (normalStackFrameStop != nullptr &&
normalStackFrameNext == normalStackFrameStop) {
// Reached end of normal stack, need to transition to the async stack.
// Do not include the return address in the stack trace that points
// to the frame that registered the AsyncStackRoot.
// Use the return address from the AsyncStackFrame as the current frame's
// return address rather than the return address from the normal
// stack frame, which would be the address of the executor function
// that invoked the callback.
break;
}
addresses[numFrames++] =
reinterpret_cast<std::uintptr_t>(normalStackFrame->returnAddress);
normalStackFrame = normalStackFrameNext;
}
return numFrames;
}
struct WalkAsyncStackResult {
// Number of frames added in this walk
size_t numFrames{0};
// The next async stack root to use in the next normal async stack walk
const AsyncStackRoot* asyncStackRoot{nullptr};
// Normal stack frame to start the next normal stack walk
StackFrame* normalStackFrame{nullptr};
StackFrame* normalStackFrameStop{nullptr};
// Async stack frame to start the next normal async stack walk
AsyncStackFrame* asyncStackFrame{nullptr};
};
WalkAsyncStackResult walkAsyncStack(
uintptr_t* addresses,
size_t maxAddresses,
AsyncStackFrame* asyncStackFrame) {
WalkAsyncStackResult result;
while (result.numFrames < maxAddresses && asyncStackFrame != nullptr) {
addresses[result.numFrames++] =
reinterpret_cast<std::uintptr_t>(asyncStackFrame->getReturnAddress());
auto* asyncStackFrameNext = asyncStackFrame->getParentFrame();
if (asyncStackFrameNext == nullptr) {
// Reached end of async-stack.
// Check if there is an AsyncStackRoot and if so, whether there
// is an associated stack frame that indicates the normal stack
// frame we should continue walking at.
result.asyncStackRoot = asyncStackFrame->getStackRoot();
if (result.asyncStackRoot == nullptr) {
// This is a detached async stack. We are done
break;
}
// Get the normal stack frame holding this async root.
result.normalStackFrame =
(StackFrame*)result.asyncStackRoot->getStackFramePointer();
if (result.normalStackFrame == nullptr) {
// No associated normal stack frame for this async stack root.
// This means we should treat this as a top-level/detached
// stack and not try to walk any further.
break;
}
// Skip to the parent stack-frame pointer
result.normalStackFrame = result.normalStackFrame->parentFrame;
// Check if there is a higher-level AsyncStackRoot that defines
// the stop point we should stop walking normal stack frames at.
// If there is no higher stack root then we will walk to the
// top of the normal stack (normalStackFrameStop == nullptr).
// Otherwise we record the frame pointer that we should stop
// at and walk normal stack frames until we hit that frame.
// Also get the async stack frame where the next async stack walk should
// begin after the next normal stack walk finishes.
result.asyncStackRoot = result.asyncStackRoot->getNextRoot();
if (result.asyncStackRoot != nullptr) {
result.normalStackFrameStop =
(StackFrame*)result.asyncStackRoot->getStackFramePointer();
result.asyncStackFrame = result.asyncStackRoot->getTopFrame();
}
}
asyncStackFrame = asyncStackFrameNext;
}
return result;
}
} // namespace
ssize_t getAsyncStackTraceSafe(uintptr_t* addresses, size_t maxAddresses) {
size_t numFrames = 0;
const auto* asyncStackRoot = tryGetCurrentAsyncStackRoot();
if (asyncStackRoot == nullptr) {
// No async operation in progress. Return empty stack
return numFrames;
}
// Start by walking the normal stack until we get to the frame right before
// the frame that holds the async root.
auto* normalStackFrame = (StackFrame*)FOLLY_ASYNC_STACK_FRAME_POINTER();
auto* normalStackFrameStop =
(StackFrame*)asyncStackRoot->getStackFramePointer();
if (numFrames < maxAddresses) {
addresses[numFrames++] = (uintptr_t)FOLLY_ASYNC_STACK_RETURN_ADDRESS();
}
auto* asyncStackFrame = asyncStackRoot->getTopFrame();
while (numFrames < maxAddresses &&
(normalStackFrame != nullptr || asyncStackFrame != nullptr)) {
numFrames += walkNormalStack(
addresses + numFrames,
maxAddresses - numFrames,
normalStackFrame,
normalStackFrameStop);
auto walkAsyncStackResult = walkAsyncStack(
addresses + numFrames, maxAddresses - numFrames, asyncStackFrame);
numFrames += walkAsyncStackResult.numFrames;
asyncStackRoot = walkAsyncStackResult.asyncStackRoot;
normalStackFrame = walkAsyncStackResult.normalStackFrame;
normalStackFrameStop = walkAsyncStackResult.normalStackFrameStop;
asyncStackFrame = walkAsyncStackResult.asyncStackFrame;
}
return numFrames;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment