DiagProbe.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeDebugPch.h"
  6. #include "Language/InterpreterStackFrame.h"
  7. #define InvalidScriptId 0xFFFFFFFF
  8. namespace Js
  9. {
  10. InterpreterHaltState::InterpreterHaltState(StopType _stopType, const FunctionBody* _executingFunction, MutationBreakpoint* _activeMutationBP/*= nullptr*/) :
  11. stopType(_stopType),
  12. executingFunction(_executingFunction),
  13. topFrame(nullptr),
  14. framePointers(nullptr),
  15. referencedDiagnosticArena(nullptr),
  16. exceptionObject(nullptr),
  17. stringBuilder(nullptr),
  18. activeMutationBP(_activeMutationBP)
  19. {
  20. Assert(executingFunction || (stopType == STOP_EXCEPTIONTHROW || stopType == STOP_MUTATIONBREAKPOINT || stopType == STOP_DOMMUTATIONBREAKPOINT));
  21. }
  22. FunctionBody* InterpreterHaltState::GetFunction()
  23. {
  24. Assert(IsValid());
  25. return this->topFrame->GetFunction();
  26. }
  27. int InterpreterHaltState::GetCurrentOffset()
  28. {
  29. Assert(IsValid());
  30. return this->topFrame->GetByteCodeOffset();
  31. }
  32. void InterpreterHaltState::SetCurrentOffset(int offset)
  33. {
  34. Assert(IsValid());
  35. if (this->topFrame->IsInterpreterFrame())
  36. {
  37. // For interpreter frames, actual scenarios we need changed offset are: set next in topmost frame, ignore exception.
  38. // For throw exception we don't need it, but it doesn't hurt because interpreter will ignore the offset
  39. // and rather just throw the exception.
  40. this->topFrame->AsInterpreterFrame()->GetReader()->SetCurrentOffset(offset);
  41. }
  42. else
  43. {
  44. // For native frames, the only scenario we need to record changed offset is when we ignore exception.
  45. if (this->exceptionObject && this->exceptionObject->IsDebuggerSkip())
  46. {
  47. this->exceptionObject->SetByteCodeOffsetAfterDebuggerSkip(offset);
  48. }
  49. }
  50. }
  51. bool InterpreterHaltState::IsValid() const
  52. {
  53. // "executingFunction == nullptr" when dispatching exception or mutation bp.
  54. return topFrame && (topFrame->GetFunction() == executingFunction || executingFunction == nullptr);
  55. }
  56. StepController::StepController()
  57. : stepType(STEP_NONE),
  58. byteOffset(0),
  59. statementMap(NULL),
  60. frameCountWhenSet(0),
  61. frameAddrWhenSet((size_t)-1),
  62. stepCompleteOnInlineBreakpoint(false),
  63. pActivatedContext(NULL),
  64. scriptIdWhenSet(InvalidScriptId),
  65. returnedValueRecordingDepth(0),
  66. returnedValueList(nullptr)
  67. {
  68. }
  69. bool StepController::IsActive()
  70. {
  71. return stepType != STEP_NONE;
  72. }
  73. void StepController::Activate(StepType stepType, InterpreterHaltState* haltState)
  74. {
  75. this->stepType = stepType;
  76. this->byteOffset = haltState->GetCurrentOffset();
  77. this->pActivatedContext = haltState->framePointers->Peek()->GetScriptContext();
  78. Assert(this->pActivatedContext);
  79. Js::FunctionBody* functionBody = haltState->GetFunction();
  80. this->body.Root(functionBody, this->pActivatedContext->GetRecycler());
  81. this->statementMap = body->GetMatchingStatementMapFromByteCode(byteOffset, false);
  82. this->frameCountWhenSet = haltState->framePointers->Count();
  83. if (stepType != STEP_DOCUMENT)
  84. {
  85. this->frameAddrWhenSet = (size_t)haltState->framePointers->Peek(0)->GetStackAddress();
  86. }
  87. else
  88. {
  89. // for doc mode, do not bail out automatically on frame changes
  90. this->frameAddrWhenSet = (size_t)-1;
  91. }
  92. this->scriptIdWhenSet = GetScriptId(functionBody);
  93. if (this->returnedValueList == nullptr)
  94. {
  95. this->returnedValueList = JsUtil::List<ReturnedValue*>::New(this->pActivatedContext->GetRecycler());
  96. this->pActivatedContext->GetThreadContext()->SetReturnedValueList(this->returnedValueList);
  97. }
  98. }
  99. void StepController::AddToReturnedValueContainer(Js::Var returnValue, Js::JavascriptFunction * function, bool isValueOfReturnStatement)
  100. {
  101. if (this->pActivatedContext != nullptr) // This will be null when we execute scripts when on break.
  102. {
  103. ReturnedValue *valuePair = RecyclerNew(pActivatedContext->GetRecycler(), ReturnedValue, returnValue, function, isValueOfReturnStatement);
  104. this->returnedValueList->Add(valuePair);
  105. }
  106. }
  107. void StepController::AddReturnToReturnedValueContainer()
  108. {
  109. AddToReturnedValueContainer(nullptr/*returnValue*/, nullptr/*function*/, true/*isValueOfReturnStatement*/);
  110. }
  111. void StepController::StartRecordingCall()
  112. {
  113. returnedValueRecordingDepth++;
  114. }
  115. void StepController::EndRecordingCall(Js::Var returnValue, Js::JavascriptFunction * function)
  116. {
  117. if (IsActive() && this->pActivatedContext != nullptr && returnValue != nullptr)
  118. {
  119. if (this->pActivatedContext->GetThreadContext()->GetDebugManager()->IsAtDispatchHalt())
  120. {
  121. // OS bug 3050302 - Keeping this FatalError for finding other issues where we can record when we are at break
  122. Js::Throw::FatalInternalError();
  123. }
  124. bool isStepOut = stepType == STEP_OUT || stepType == STEP_DOCUMENT;
  125. // Record when :
  126. // If step-out/document : we need to record calls only which are already on the stack, that means the recording-depth is zero or negative.
  127. // if not step-out (step-in and step-over). only for those, which are called from the current call-site or the ones as if we step-out
  128. if ((!isStepOut && returnedValueRecordingDepth <= 1) || (isStepOut && returnedValueRecordingDepth <= 0))
  129. {
  130. // if we are step_document, we should be removing whatever we have collected so-far,
  131. // since they belong to the current document which is a library code
  132. if (stepType == STEP_DOCUMENT)
  133. {
  134. this->returnedValueList->ClearAndZero();
  135. }
  136. AddToReturnedValueContainer(returnValue, function, false/*isValueOfReturnStatement*/);
  137. }
  138. }
  139. returnedValueRecordingDepth--;
  140. }
  141. void StepController::ResetReturnedValueList()
  142. {
  143. returnedValueRecordingDepth = 0;
  144. if (this->returnedValueList != nullptr)
  145. {
  146. this->returnedValueList->ClearAndZero();
  147. }
  148. }
  149. void StepController::HandleResumeAction(Js::InterpreterHaltState* haltState, BREAKRESUMEACTION resumeAction)
  150. {
  151. ResetReturnedValueList();
  152. switch (resumeAction)
  153. {
  154. case BREAKRESUMEACTION_STEP_INTO:
  155. Activate(Js::STEP_IN, haltState);
  156. break;
  157. case BREAKRESUMEACTION_STEP_OVER:
  158. Activate(Js::STEP_OVER, haltState);
  159. break;
  160. case BREAKRESUMEACTION_STEP_OUT:
  161. Activate(Js::STEP_OUT, haltState);
  162. break;
  163. case BREAKRESUMEACTION_STEP_DOCUMENT:
  164. Activate(Js::STEP_DOCUMENT, haltState);
  165. break;
  166. }
  167. }
  168. void StepController::Deactivate(InterpreterHaltState* haltState /*=nullptr*/)
  169. {
  170. // If we are deactivating the step controller during ProbeContainer close or attach/detach we should clear return value list
  171. // If we break other than step -> clear the list.
  172. // If we step in and we land on different function (we are in recording phase the current function) -> clear the list
  173. if ((haltState == nullptr) || (haltState->stopType != Js::STOP_STEPCOMPLETE || (this->stepType == STEP_IN && this->returnedValueRecordingDepth > 0)))
  174. {
  175. ResetReturnedValueList();
  176. }
  177. if (this->body)
  178. {
  179. Assert(this->pActivatedContext);
  180. this->body.Unroot(this->pActivatedContext->GetRecycler());
  181. }
  182. this->pActivatedContext = NULL;
  183. stepType = STEP_NONE;
  184. byteOffset = Js::Constants::NoByteCodeOffset;
  185. statementMap = NULL;
  186. frameCountWhenSet = 0;
  187. scriptIdWhenSet = InvalidScriptId;
  188. frameAddrWhenSet = (size_t)-1;
  189. }
  190. bool StepController::IsStepComplete_AllowingFalsePositives(InterpreterStackFrame * stackFrame)
  191. {
  192. Assert(stackFrame);
  193. if (stepType == STEP_IN)
  194. {
  195. return true;
  196. }
  197. else if (stepType == STEP_DOCUMENT)
  198. {
  199. Assert(stackFrame->GetFunctionBody());
  200. return GetScriptId(stackFrame->GetFunctionBody()) != this->scriptIdWhenSet;
  201. }
  202. // A STEP_OUT or a STEP_OVER has not completed if we are currently deeper on the callstack.
  203. return this->frameAddrWhenSet <= stackFrame->GetStackAddress();
  204. }
  205. bool StepController::IsStepComplete(InterpreterHaltState* haltState, HaltCallback * haltCallback, OpCode originalOpcode)
  206. {
  207. int currentFrameCount = haltState->framePointers->Count();
  208. AssertMsg(currentFrameCount > 0, "In IsStepComplete we must have at least one frame.");
  209. FunctionBody* body = haltState->framePointers->Peek()->GetJavascriptFunction()->GetFunctionBody();
  210. bool canPossiblyHalt = haltCallback->CanHalt(haltState);
  211. OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(): stepType = %d "), stepType);
  212. uint scriptId = GetScriptId(body);
  213. AssertMsg(scriptId != InvalidScriptId, "scriptId cannot be 'invalid-reserved'");
  214. int byteOffset = haltState->GetCurrentOffset();
  215. bool fCanHalt = false;
  216. if (this->frameCountWhenSet > currentFrameCount && STEP_DOCUMENT != stepType)
  217. {
  218. // all steps match once the frame they started on has popped.
  219. fCanHalt = canPossiblyHalt;
  220. }
  221. else if (STEP_DOCUMENT == stepType)
  222. {
  223. OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(): docId when set=%d, currentDocId = %d, can Halt = %d, will halt = %d "), this->scriptIdWhenSet, scriptId, canPossiblyHalt, fCanHalt);
  224. fCanHalt = (scriptId != this->scriptIdWhenSet) && canPossiblyHalt;
  225. }
  226. else if (STEP_IN != stepType && this->frameCountWhenSet < currentFrameCount)
  227. {
  228. // Only step into allows the stack to be deeper
  229. OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(stepType = %d) returning false "), stepType);
  230. return false;
  231. }
  232. else if (STEP_OUT == stepType)
  233. {
  234. fCanHalt = this->frameCountWhenSet > currentFrameCount && canPossiblyHalt;
  235. }
  236. else if (nullptr != this->statementMap && this->statementMap->isSubexpression && STEP_IN != stepType)
  237. {
  238. // Only step into started from subexpression is allowed to stop on another subexpression
  239. Js::FunctionBody* pCurrentFuncBody = haltState->GetFunction();
  240. Js::FunctionBody::StatementMap* map = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(byteOffset, false);
  241. if (nullptr != map && map->isSubexpression) // Execute remaining Subexpressions
  242. {
  243. fCanHalt = false;
  244. }
  245. else
  246. {
  247. Js::FunctionBody::StatementMap* outerMap = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(this->statementMap->byteCodeSpan.begin, true);
  248. if (nullptr != outerMap && map == outerMap) // Execute the rest of current regular statement
  249. {
  250. fCanHalt = false;
  251. }
  252. else
  253. {
  254. fCanHalt = canPossiblyHalt;
  255. }
  256. }
  257. }
  258. else
  259. {
  260. // Match if we are no longer on the original statement. Stepping means move off current statement.
  261. if (body != this->body || NULL == this->statementMap ||
  262. !this->statementMap->byteCodeSpan.Includes(byteOffset))
  263. {
  264. fCanHalt = canPossiblyHalt;
  265. }
  266. }
  267. // At this point we are verifying of global return opcode.
  268. // The global returns are alway added as a zero range begin with zero.
  269. if (fCanHalt && originalOpcode == OpCode::Ret)
  270. {
  271. Js::FunctionBody* pCurrentFuncBody = haltState->GetFunction();
  272. Js::FunctionBody::StatementMap* map = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(byteOffset, true);
  273. fCanHalt = !FunctionBody::IsDummyGlobalRetStatement(&map->sourceSpan);
  274. if (fCanHalt)
  275. {
  276. // We are breaking at last line of function, imagine '}'
  277. AddReturnToReturnedValueContainer();
  278. }
  279. }
  280. OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(stepType = %d) returning %d "), stepType, fCanHalt);
  281. return fCanHalt;
  282. }
  283. bool StepController::ContinueFromInlineBreakpoint()
  284. {
  285. bool ret = stepCompleteOnInlineBreakpoint;
  286. stepCompleteOnInlineBreakpoint = false;
  287. return ret;
  288. }
  289. uint StepController::GetScriptId(_In_ FunctionBody* body)
  290. {
  291. // safe value
  292. uint retValue = BuiltInFunctionsScriptId;
  293. if (body != nullptr)
  294. {
  295. // FYI - Different script blocks within a HTML page will have different source Info ids even though they have the same backing file.
  296. // It might imply we notify the debugger a bit more than needed - thus can be TODO for performance improvements of the Just-My-Code
  297. // or step to next document boundary mode.
  298. AssertMsg(body->GetUtf8SourceInfo() != nullptr, "body->GetUtf8SourceInfo() == nullptr");
  299. retValue = body->GetUtf8SourceInfo()->GetSourceInfoId();
  300. }
  301. return retValue;
  302. }
  303. AsyncBreakController::AsyncBreakController()
  304. : haltCallback(NULL)
  305. {
  306. }
  307. void AsyncBreakController::Activate(HaltCallback* haltCallback)
  308. {
  309. InterlockedExchangePointer((PVOID*)&this->haltCallback, haltCallback);
  310. }
  311. void AsyncBreakController::Deactivate()
  312. {
  313. InterlockedExchangePointer((PVOID*)&this->haltCallback, NULL);
  314. }
  315. bool AsyncBreakController::IsBreak()
  316. {
  317. return haltCallback != NULL;
  318. }
  319. bool AsyncBreakController::IsAtStoppingLocation(InterpreterHaltState* haltState)
  320. {
  321. HaltCallback* callback = this->haltCallback;
  322. if (callback)
  323. {
  324. return callback->CanHalt(haltState);
  325. }
  326. return false;
  327. }
  328. void AsyncBreakController::DispatchAndReset(InterpreterHaltState* haltState)
  329. {
  330. HaltCallback* callback = this->haltCallback;
  331. Deactivate();
  332. if (callback)
  333. {
  334. callback->DispatchHalt(haltState);
  335. }
  336. }
  337. }