DiagProbe.cpp 15 KB

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