blob: ea7b1867e7f74a790f5b1ae1f6737792dd3e82f1 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
#if defined(_M_IX86)
#include "Language/i386/StackFrame.h"
typedef Js::X86StackFrame StackFrame;
#elif defined(_M_X64)
#include "Language/amd64/StackFrame.h"
#ifdef _WIN32 // xplat-todo
#include "Language/amd64/StackFrame.inl"
#endif
typedef Js::Amd64StackFrame StackFrame;
#elif defined(_M_ARM)
#include "Language/arm/StackFrame.h"
typedef Js::ArmStackFrame StackFrame;
#elif defined(_M_ARM64)
#include "Language/arm64/StackFrame.h"
typedef Js::Arm64StackFrame StackFrame;
#else
#error JavascriptStackWalker is not supported on this architecture.
#endif
namespace Js
{
struct ScriptEntryExitRecord;
enum InternalFrameType {
InternalFrameType_None,
InternalFrameType_EhFrame,
InternalFrameType_LoopBody
};
struct AsmJsCallStackLayout
{
Js::JavascriptFunction * functionObject;
Js::Var args[0];
};
class JavascriptCallStackLayout
{
public:
Js::JavascriptFunction * functionObject;
Js::CallInfo callInfo;
Js::Var args[0];
Js::ArgumentsObject * GetArgumentsObject() const;
Js::Var * GetArgumentsObjectLocation() const;
void SetArgumentsObject(Js::ArgumentsObject* obj);
Js::Var* GetArgv() const;
Js::Var GetOffset(int offset) const;
double GetDoubleAtOffset(int offset) const;
int32 GetInt32AtOffset(int offset) const;
SIMDValue GetSimdValueAtOffset(int offset) const;
char * GetValueChangeOffset(int offset) const;
ForInObjectEnumerator * GetForInObjectEnumeratorArrayAtOffset(int offset) const;
static JavascriptCallStackLayout *FromFramePointer(void *const framePointer);
static void* const ToFramePointer(JavascriptCallStackLayout* callstackLayout);
private:
JavascriptCallStackLayout() : callInfo(0) {};
};
#if ENABLE_NATIVE_CODEGEN
/*
* The InlinedFrameStackWalker knows how to walk an inlinee's virtual frames inside a
* physical frame. If the stack walker is in the inlineeFramesBeingWalked mode it
* defers pretty much all functionality to its helpers. The virtual stack frames themselves
* are laid out in the reverse order on the stack. So we do one pass to find out the count of
* frames and another to construct an array of pointers to frames in the correct order
* (top most first like a real stack). Each frame begins with a count. Since frames are laid
* out continuously in memory, this is all the stack walker needs to find the next frame.
* We don't maintain explicit prev, next pointers. We also clear the count field of the frame
* next to the top most frame to indicate that the top most frame is, well, the top most frame.
* Whenever an inlinee's code ends, the count field in its frame gets set to 0 indicating this
* frame isn't valid anymore. This keeps the fast case fast and offloads the heavy lifting
* to the stack walker.
*/
class InlinedFrameWalker
{
public:
InlinedFrameWalker()
: parentFunction(nullptr)
, frames(nullptr)
, currentIndex(-1)
, frameCount(0)
{
}
~InlinedFrameWalker()
{
Assert(!parentFunction);
Assert(!this->frames);
Assert(!frameCount);
Assert(currentIndex == -1);
}
static bool FromPhysicalFrame(InlinedFrameWalker& self, StackFrame& physicalFrame, Js::ScriptFunction *parent, bool fromBailout = false,
int loopNum = -1, const JavascriptStackWalker * const walker = nullptr, bool useInternalFrameInfo = false, bool noAlloc = false);
void Close();
bool Next(CallInfo& callInfo);
size_t GetArgc() const;
Js::Var *GetArgv(bool includeThis = true) const;
Js::JavascriptFunction *GetFunctionObject() const;
void SetFunctionObject(Js::JavascriptFunction * function);
Js::Var GetArgumentsObject() const;
void SetArgumentsObject(Js::Var arguments);
Js::Var GetThisObject() const;
bool IsCallerPhysicalFrame() const;
bool IsTopMostFrame() const;
int32 GetFrameIndex() const { Assert(currentIndex != -1); return currentIndex; }
uint32 GetCurrentInlineeOffset() const;
uint32 GetBottomMostInlineeOffset() const;
Js::JavascriptFunction *GetBottomMostFunctionObject() const;
void FinalizeStackValues(__in_ecount(argCount) Js::Var args[], size_t argCount) const;
private:
enum {
InlinedFrameArgIndex_This = 0,
InlinedFrameArgIndex_SecondScriptArg = 1
};
struct InlinedFrame : public InlinedFrameLayout
{
Js::Var argv[0]; // It's defined here as in C++ can't have 0-size array in the base class.
struct InlinedFrame *Next()
{
InlinedFrameLayout *next = __super::Next();
return (InlinedFrame*)next;
}
static InlinedFrame *FromPhysicalFrame(StackFrame& currentFrame, const JavascriptStackWalker * const stackWalker, void *entry, EntryPointInfo* entryPointInfo, bool useInternalFrameInfo);
};
void Initialize(int32 frameCount, __in_ecount(frameCount) InlinedFrame **frames, Js::ScriptFunction *parent);
void MoveNext();
InlinedFrame *const GetCurrentFrame() const;
InlinedFrame *const GetFrameAtIndex(signed index) const;
Js::ScriptFunction *parentFunction;
InlinedFrame **frames;
int32 currentIndex;
int32 frameCount;
};
class InternalFrameInfo
{
public:
void *codeAddress;
void *framePointer;
size_t stackCheckCodeHeight;
InternalFrameType frameType;
JavascriptFunction* function;
bool hasInlinedFramesOnStack;
bool previousInterpreterFrameIsFromBailout;
InternalFrameInfo() :
codeAddress(nullptr),
framePointer(nullptr),
stackCheckCodeHeight((uint)-1),
frameType(InternalFrameType_None),
function(nullptr),
hasInlinedFramesOnStack(false),
previousInterpreterFrameIsFromBailout(false)
{
}
void Clear();
void Set(void *codeAddress, void *framePointer, size_t stackCheckCodeHeight, InternalFrameType frameType, JavascriptFunction* function, bool hasInlinedFramesOnStack, bool previousInterpreterFrameIsFromBailout);
};
#endif
class JavascriptStackWalker
{
friend Js::ScriptContext;
public:
JavascriptStackWalker(ScriptContext * scriptContext, bool useEERContext = TRUE /* use leafinterpreterframe of entryexit record */, PVOID returnAddress = NULL, bool _forceFullWalk = false);
#if ENABLE_NATIVE_CODEGEN
~JavascriptStackWalker() { inlinedFrameWalker.Close(); }
#endif
BOOL Walk(bool includeInlineFrames = true);
BOOL GetCaller(JavascriptFunction ** ppFunc, bool includeInlineFrames = true);
BOOL GetCallerWithoutInlinedFrames(JavascriptFunction ** ppFunc);
BOOL GetNonLibraryCodeCaller(JavascriptFunction ** ppFunc);
BOOL WalkToTarget(JavascriptFunction * funcTarget);
BOOL WalkToArgumentsFrame(ArgumentsObject *argsObj);
uint32 GetByteCodeOffset() const;
BOOL IsCallerGlobalFunction() const;
BOOL IsEvalCaller() const;
bool IsJavascriptFrame() const { return inlinedFramesBeingWalked || isJavascriptFrame; }
bool IsInlineFrame() const { return inlinedFramesBeingWalked; }
bool IsBailedOutFromInlinee() const
{
Assert(this->IsJavascriptFrame() && this->interpreterFrame);
return IsInlineFrame();
}
bool IsBailedOutFromFunction() const
{
Assert(this->IsJavascriptFrame() && this->interpreterFrame);
return !!JavascriptFunction::IsNativeAddress(this->scriptContext, this->currentFrame.GetInstructionPointer());
}
Var GetPermanentArguments() const;
void *GetCurrentCodeAddr() const;
JavascriptFunction *GetCurrentFunction(bool includeInlinedFrames = true) const;
void SetCurrentFunction(JavascriptFunction * function);
CallInfo GetCallInfo(bool includeInlinedFrames = true) const;
CallInfo GetCallInfoFromPhysicalFrame() const;
bool GetThis(Var *pThis, int moduleId) const;
Js::Var * GetJavascriptArgs() const;
void **GetCurrentArgv() const;
ScriptContext* GetCurrentScriptContext() const;
InterpreterStackFrame* GetCurrentInterpreterFrame() const
{
Assert(this->IsJavascriptFrame());
return interpreterFrame;
}
bool GetSourcePosition(const WCHAR** sourceFileName, ULONG* line, LONG* column);
static bool TryIsTopJavaScriptFrameNative(ScriptContext* scriptContext, bool* istopFrameNative, bool ignoreLibraryCode = false);
#if ENABLE_NATIVE_CODEGEN
void ClearCachedInternalFrameInfo();
void SetCachedInternalFrameInfo(InternalFrameType frameType, JavascriptFunction* function, bool hasInlinedFramesOnStack, bool prevIntFrameIsFromBailout);
InternalFrameInfo GetCachedInternalFrameInfo() const { return this->lastInternalFrameInfo; }
#endif
bool IsCurrentPhysicalFrameForLoopBody() const;
// noinline, we want to use own stack frame.
static _NOINLINE BOOL GetCaller(JavascriptFunction** ppFunc, ScriptContext* scriptContext);
static _NOINLINE BOOL GetCaller(JavascriptFunction** ppFunc, uint32* byteCodeOffset, ScriptContext* scriptContext);
static _NOINLINE bool GetThis(Var* pThis, int moduleId, ScriptContext* scriptContext);
static _NOINLINE bool GetThis(Var* pThis, int moduleId, JavascriptFunction* func, ScriptContext* scriptContext);
static bool IsDisplayCaller(JavascriptFunction* func);
bool GetDisplayCaller(JavascriptFunction ** ppFunc);
PCWSTR GetCurrentNativeLibraryEntryName() const;
static bool IsLibraryStackFrameEnabled(Js::ScriptContext * scriptContext);
static bool IsWalkable(ScriptContext *scriptContext);
// Walk frames (until walkFrame returns true)
template <class WalkFrame>
ushort WalkUntil(ushort stackTraceLimit, WalkFrame walkFrame, bool onlyOnDebugMode = false, bool filterDiagnosticsOM = false)
{
ushort frameIndex = 0;
JavascriptFunction* jsFunction;
BOOL foundCaller = GetNonLibraryCodeCaller(&jsFunction);
while (foundCaller)
{
if (IsDisplayCaller(jsFunction))
{
bool needToPass = (!onlyOnDebugMode || jsFunction->GetScriptContext()->IsScriptContextInDebugMode())
&& (!filterDiagnosticsOM || !jsFunction->GetScriptContext()->IsDiagnosticsScriptContext());
if (needToPass)
{
if (walkFrame(jsFunction, frameIndex))
{
break;
}
frameIndex++;
}
}
foundCaller = frameIndex < stackTraceLimit && GetCaller(&jsFunction);
}
return frameIndex;
}
template <class WalkFrame>
ushort WalkUntil(WalkFrame walkFrame, bool onlyOnDebugMode = false, bool filterDiagnosticsOM = false)
{
return WalkUntil(USHORT_MAX, walkFrame, onlyOnDebugMode, filterDiagnosticsOM);
}
BYTE** GetCurrentAddresOfReturnAddress() const
{
return (BYTE**)this->currentFrame.GetAddressOfReturnAddress();
}
BYTE** GetCurrentAddressOfInstructionPointer() const
{
return (BYTE**)this->currentFrame.GetAddressOfInstructionPointer();
}
void* GetInstructionPointer() const
{
return this->currentFrame.GetInstructionPointer();
}
bool GetCurrentFrameFromBailout() const
{
return previousInterpreterFrameIsFromBailout;
}
#if DBG
static bool ValidateTopJitFrame(Js::ScriptContext* scriptContext);
#endif
private:
ScriptContext *scriptContext;
ScriptEntryExitRecord *entryExitRecord;
const NativeLibraryEntryRecord::Entry *nativeLibraryEntry;
const NativeLibraryEntryRecord::Entry *prevNativeLibraryEntry; // Saves previous nativeLibraryEntry when it moves to next
InterpreterStackFrame *interpreterFrame;
InterpreterStackFrame *tempInterpreterFrame;
#if ENABLE_NATIVE_CODEGEN
Js::InlinedFrameWalker inlinedFrameWalker;
#endif
CallInfo inlinedFrameCallInfo;
bool inlinedFramesBeingWalked : 1;
bool hasInlinedFramesOnStack : 1;
bool isJavascriptFrame : 1;
bool isNativeLibraryFrame : 1;
bool isInitialFrame : 1; // If we need to walk the initial frame
bool shouldDetectPartiallyInitializedInterpreterFrame : 1;
bool previousInterpreterFrameIsFromBailout : 1;
bool previousInterpreterFrameIsForLoopBody : 1;
bool forceFullWalk : 1; // ignoring hasCaller
Var GetThisFromFrame() const; // returns 'this' object from the physical frame
Var GetCurrentArgumentsObject() const; // returns arguments object from the current frame, which may be virtual (belonging to an inlinee)
void SetCurrentArgumentsObject(Var args); // sets arguments object for the current frame, which may be virtual (belonging to an inlinee)
Var GetCurrentNativeArgumentsObject() const; // returns arguments object from the physical native frame
void SetCurrentNativeArgumentsObject(Var args); // sets arguments object on the physical native frame
bool TryGetByteCodeOffsetFromInterpreterFrame(uint32& offset) const;
#if ENABLE_NATIVE_CODEGEN
bool TryGetByteCodeOffsetFromNativeFrame(uint32& offset) const;
bool TryGetByteCodeOffsetOfInlinee(Js::JavascriptFunction* function, uint loopNum, DWORD_PTR pCodeAddr, Js::FunctionBody** inlinee, uint32& offset, bool useInternalFrameInfo) const;
uint GetLoopNumber(bool& usedInternalFrameInfo) const;
bool InlinedFramesBeingWalked() const;
bool HasInlinedFramesOnStack() const;
bool PreviousInterpreterFrameIsFromBailout() const;
InternalFrameInfo lastInternalFrameInfo;
#endif
mutable StackFrame currentFrame;
Js::JavascriptFunction * UpdateFrame(bool includeInlineFrames);
bool CheckJavascriptFrame(bool includeInlineFrames);
JavascriptFunction *GetCurrentFunctionFromPhysicalFrame() const;
};
class AutoPushReturnAddressForStackWalker
{
private:
ScriptContext *m_scriptContext;
public:
AutoPushReturnAddressForStackWalker(ScriptContext *scriptContext, void* returnAddress) : m_scriptContext(scriptContext)
{
scriptContext->SetFirstInterpreterFrameReturnAddress(returnAddress);
}
~AutoPushReturnAddressForStackWalker()
{
m_scriptContext->SetFirstInterpreterFrameReturnAddress(NULL);
}
};
}