HRESULT SourceEventListener::onRemoveChild(_In_ IDebugApplicationNode* prddpChild) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); ATLENSURE_RETURN_HR(prddpChild != nullptr, E_INVALIDARG); return m_spSourceController->OnRemoveChild(m_ownerId, prddpChild); }
HRESULT SourceEventListener::Initialize(_In_ DWORD dispatchThreadId, _In_ SourceController* pSourceController, _In_ ULONG ownerId, _In_ IDebugApplicationNode* pDebugApplicationNode, _In_opt_ IDebugDocumentText* pDebugDocumentText) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(dispatchThreadId), E_UNEXPECTED); ATLENSURE_RETURN_HR(pSourceController != nullptr, E_INVALIDARG); ATLENSURE_RETURN_HR(pDebugApplicationNode != nullptr, E_INVALIDARG); m_dispatchThreadId = dispatchThreadId; m_spSourceController = pSourceController; m_ownerId = ownerId; m_spDebugApplicationNode = pDebugApplicationNode; m_spDebugDocumentText = pDebugDocumentText; // Connect node event sink HRESULT hr = ATL::AtlAdvise(m_spDebugApplicationNode, GetUnknown(), IID_IDebugApplicationNodeEvents, &m_dwNodeCookie); m_advisedNodeEvents = (hr == S_OK); // Not all nodes have the IDebugDocumentText interface, depending on the host that is managing it. // We only want to sink text events if this interface exists, otherwise we can just safely ignore it. if (m_spDebugDocumentText.p != nullptr) { hr = ATL::AtlAdvise(m_spDebugDocumentText, GetUnknown(), IID_IDebugDocumentTextEvents, &m_dwTextCookie); m_advisedTextEvents = (hr == S_OK); } return S_OK; }
HRESULT ThreadController::BeginBreakNotification() { // This needs to be called from the debugger dispatch due to an issue in the IE pause notification. // Since the IE pause UI is drawn with no timeout, it will flash on/off everytime we hit a break that gets resumed straight away // For example during a conditional breakpoint with a false condition. // To prevent the flash we call this asynchronously from the debugger dispatch instead of synchronously from our OnPDMBreak event // This does mean that there is a possibility of a deadlock occuring when we are docked and the IE threads are still tied to together after hitting a break. // The current version has this issue, but we require a change in IE (to separate the pause UI from the thread decoupling) to allow us to call this // synchronously with our ODM break event. ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Should really be synchronous with PDM thread (see comment above) // This gets called on the pdm thread, but it makes a synchronous call to the IE thread to notify it of the break. // When IE has finished processing the break, m_hBreakNotificationComplete gets signalled. // During this wait, we need to process very specific messages to allow docked debugging to work correctly. HRESULT hr = S_OK; // Reset the event used by the notification ::ResetEvent(m_hBreakNotificationComplete); // We need to use send message to make sure that the pipe handler has sent the message to the BHO // before we continue and start waiting on the break event // This prevents a freeze where we haven't managed to tell IE about the break before we start waiting for it on this thread. ::SendMessageW(m_hwndDebugPipeHandler, WM_BREAKMODECHANGED, /*IsAtBreak=*/ TRUE, NULL); if (m_isDocked) { // Get the IRemoteDebugApplication using our helper function that keeps it thread safe CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive; hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive); if (hr == S_OK) { // While docked we also need to pump focus messages on the IE UI thread so that the IE Frame has chance to detach from the tab's input queue CComQIPtr<IDebugApplication110> spDebugApp110 = spDebugAppKeepAlive; ATLENSURE_RETURN_HR(spDebugApp110 != nullptr, E_NOINTERFACE); // We should only pump messages if we have stopped on the UI thread. If we attempt to call the PDM thread switch onto the UI thread // when we are not broken on the UI thread, we will cause a deadlock since all other (non-broken) threads are halted. if (m_mainIEScriptThreadId == m_currentPDMThreadId && m_mainIEScriptThreadId != 0) { // Create a callback that we can use to switch threads in the PDM CComObject<PDMThreadCallback>* pCall; hr = CComObject<PDMThreadCallback>::CreateInstance(&pCall); BPT_FAIL_IF_NOT_S_OK(hr); CComObjPtr<PDMThreadCallback> spCall(pCall); // We need to use the synchronous call because PDM processes ALL async calls before ANY sync calls (opposite of NT). // If we were to use the async mechanism, we could get into a state where synch calls never get a chance to run on the UI thread. hr = spDebugApp110->SynchronousCallInMainThread(spCall, (DWORD_PTR)PDMThreadCallbackMethod::WaitForBreak, (DWORD_PTR)this, (DWORD_PTR)nullptr); BPT_FAIL_IF_NOT_S_OK(hr); } } } return hr; }
HRESULT ThreadController::PopulateCallStack() { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); ATLENSURE_RETURN_HR(this->IsAtBreak(), E_NOT_VALID_STATE); // Lock the access to the callstack so that we are thread safe CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); CComPtr<IEnumDebugStackFrames> pStackFrameEnum; HRESULT hr = m_spCurrentBrokenThread->EnumStackFrames(&pStackFrameEnum); if (hr == S_OK) { if ((hr = pStackFrameEnum->Reset()) != S_OK) { return hr; } shared_ptr<DebugStackFrameDescriptor> spFrameDescriptor(new DebugStackFrameDescriptor()); ULONG nFetched; for (ULONG ulIndex = 0; (pStackFrameEnum->Next(1, spFrameDescriptor.get(), &nFetched) == S_OK) && (nFetched == 1); ulIndex++) { // Get a new identifier for this source node ULONG newFrameId = ThreadController::CreateUniqueFrameId(); ULONG newPropertyId = ThreadController::CreateUniquePropertyId(); // Create object that represents the source file CComObject<CallFrame>* pFrame; hr = CComObject<CallFrame>::CreateInstance(&pFrame); BPT_FAIL_IF_NOT_S_OK(hr); hr = pFrame->Initialize(m_dispatchThreadId, newFrameId, spFrameDescriptor, m_spSourceController); BPT_FAIL_IF_NOT_S_OK(hr); // Add to our maps m_callFrameMap[newFrameId] = pFrame; m_callFrames.push_back(newFrameId); // Get the information about this call frame shared_ptr<CallFrameInfo> spFrameInfo; CComPtr<IDebugStackFrame> spStackFrame; CComPtr<IDebugProperty> spLocalsDebugProperty; hr = pFrame->GetCallFrameInfo(spFrameInfo, spStackFrame, spLocalsDebugProperty); BPT_FAIL_IF_NOT_S_OK(hr); // Store that info in our maps m_callFramesInfoMap[newFrameId] = spFrameInfo; m_debugFrameMap[newFrameId] = spStackFrame; m_propertyMap[newPropertyId] = spLocalsDebugProperty; // Link the locals to the frame id for later lookup m_localsMap[newFrameId] = newPropertyId; } } return S_OK; }
LRESULT IEDiagnosticsAdapter::OnMessageFromIE(UINT nMsg, WPARAM wParam, LPARAM lParam, _Inout_ BOOL& /*bHandled*/) { CString message; // Scope for the copied data { // Take ownership of the copydata struct memory unique_ptr<COPYDATASTRUCT, void(*)(COPYDATASTRUCT*)> spParams(reinterpret_cast<PCOPYDATASTRUCT>(lParam), ::FreeCopyDataStructCopy); // Get the string message from the structure CopyDataPayload_StringMessage_Data* pMessage = reinterpret_cast<CopyDataPayload_StringMessage_Data*>(spParams->lpData); LPCWSTR lpString = reinterpret_cast<LPCWSTR>(reinterpret_cast<BYTE*>(pMessage)+pMessage->uMessageOffset); message = lpString; } HWND proxyHwnd = reinterpret_cast<HWND>(wParam); if (m_proxyConnections.find(proxyHwnd) != m_proxyConnections.end()) { string utf8; int length = message.GetLength(); LPWSTR buffer = message.GetBuffer(); // Convert the message into valid UTF-8 text int utf8Length = ::WideCharToMultiByte(CP_UTF8, 0, buffer, length, nullptr, 0, nullptr, nullptr); if (utf8Length == 0) { message.ReleaseBuffer(); ATLENSURE_RETURN_HR(false, ::GetLastError()); } utf8.resize(utf8Length); utf8Length = ::WideCharToMultiByte(CP_UTF8, 0, buffer, length, &utf8[0], static_cast<int>(utf8.length()), nullptr, nullptr); message.ReleaseBuffer(); ATLENSURE_RETURN_HR(utf8Length > 0, ::GetLastError()); // Forward the message to the websocket try { m_server.send(m_proxyConnections[proxyHwnd], utf8, websocketpp::frame::opcode::text); } catch (const std::exception & e) { std::cout << "Execption during send: " << e.what() << std::endl; } catch (websocketpp::lib::error_code e) { std::cout << "Error during send: " << e.message() << std::endl; } catch (...) { std::cout << "Unknown exception during send" << std::endl; } } return 0; }
HRESULT ThreadController::PopulatePropertyInfo(_Inout_ DebugPropertyInfo& propInfo, _Out_ shared_ptr<PropertyInfo>& spPropertyInfo) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); ATLENSURE_RETURN_HR(propInfo.m_pDebugProp != nullptr, E_INVALIDARG); // Store this property for later enumeration ULONG newPropertyId = ThreadController::CreateUniquePropertyId(); m_propertyMap[newPropertyId] = propInfo.m_pDebugProp; // Create the info and assign to the out param spPropertyInfo.reset(new PropertyInfo()); spPropertyInfo->propertyId = newPropertyId; // Use attach to ensure we free the actual BSTR from the PDM when we are finished spPropertyInfo->type.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_TYPE) == DBGPROP_INFO_TYPE) ? propInfo.m_bstrType : ::SysAllocString(L"")); spPropertyInfo->value.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_VALUE) == DBGPROP_INFO_VALUE) ? propInfo.m_bstrValue : ::SysAllocString(L"")); spPropertyInfo->isExpandable = ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE) == DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE); spPropertyInfo->isReadOnly = ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_READONLY) == DBGPROP_ATTRIB_VALUE_READONLY); spPropertyInfo->isFake = ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_FAKE) == DBGPROP_ATTRIB_VALUE_IS_FAKE); spPropertyInfo->isInvalid = ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_INVALID) == DBGPROP_ATTRIB_VALUE_IS_INVALID); spPropertyInfo->isReturnValue = ((propInfo.m_dwAttrib & DBGPROP_ATTRIB_VALUE_IS_RETURN_VALUE) == DBGPROP_ATTRIB_VALUE_IS_RETURN_VALUE); // Calculate the name if it has one spPropertyInfo->name.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_NAME) == DBGPROP_INFO_NAME) ? propInfo.m_bstrName : ::SysAllocString(L"")); // Remove /*F12Eval Q:<id>*/ from name, if present CString name(spPropertyInfo->name); if (name.Find(ThreadController::s_f12EvalIdentifierPrefix) == 0 && name.GetLength() >= ThreadController::s_f12EvalIdentifierPrefix.GetLength() + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength() + 1) { name = name.Mid(name.Find(ThreadController::s_f12EvalIdentifierPrefixEnd) + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength()); spPropertyInfo->name.Attach(name.AllocSysString()); } // Calculate the full name if it has one spPropertyInfo->fullName.Attach(((propInfo.m_dwValidFields & DBGPROP_INFO_FULLNAME) == DBGPROP_INFO_FULLNAME) ? propInfo.m_bstrFullName : ::SysAllocString(L"")); // Remove /*F12Eval Q:<id>*/ from fullname, if present CString fullName(spPropertyInfo->fullName); if (fullName.Find(ThreadController::s_f12EvalIdentifierPrefix) == 0 && fullName.GetLength() >= ThreadController::s_f12EvalIdentifierPrefix.GetLength() + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength() + 1) { fullName = fullName.Mid(fullName.Find(ThreadController::s_f12EvalIdentifierPrefixEnd) + ThreadController::s_f12EvalIdentifierPrefixEnd.GetLength()); spPropertyInfo->fullName.Attach(fullName.AllocSysString()); } return S_OK; }
HRESULT ThreadController::SetPropertyValueAsString(_In_ ULONG propertyId, _In_ BSTR* pValue, _In_ UINT radix) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } HRESULT hr = S_OK; auto it = m_propertyMap.find(propertyId); if (it != m_propertyMap.end()) { hr = it->second->SetValueAsString(*pValue, radix); } else { hr = E_NOT_FOUND; } return hr; }
HRESULT ThreadController::CallDebuggerThread(_In_ PDMThreadCallbackMethod method, _In_opt_ DWORD_PTR pController, _In_opt_ DWORD_PTR pArgs) { // Get the IRemoteDebugApplication using our helper function that keeps it thread safe CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive; HRESULT hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive); // We need to check if spDebugAppKeepAlive is null, since the debugger may have been activated // while debugging was disabled (e.g.: after the profiler started). // If that happened, then m_spDebugApplication would be null when we tried to AddRef it. if (hr == S_OK && spDebugAppKeepAlive.p != nullptr) { // Get the IDebugApplication interface used to make the actual thread switch call CComQIPtr<IDebugApplication> spDebugApp(spDebugAppKeepAlive); ATLENSURE_RETURN_HR(spDebugApp.p != nullptr, E_NOINTERFACE); // Create an object for thread switching on to the debugger thread CComObject<PDMThreadCallback>* pCall; hr = CComObject<PDMThreadCallback>::CreateInstance(&pCall); BPT_FAIL_IF_NOT_S_OK(hr); if (pController == NULL) { // Use the IDebugApplication by default pController = (DWORD_PTR)spDebugApp.p; } // Begin the call into the debugger thread using the supplied parameters CComObjPtr<PDMThreadCallback> spCall(pCall); hr = spDebugApp->SynchronousCallInDebuggerThread(spCall, (DWORD)method, (DWORD_PTR)pController, (DWORD_PTR)pArgs); ATLASSERT(hr == S_OK); } return hr; }
HRESULT ThreadController::Initialize(_In_ HWND hwndDebugPipeHandler, _In_ HANDLE hBreakNotificationComplete, _In_ bool isDocked, _In_ PDMEventMessageQueue* pMessageQueue, _In_ SourceController* pSourceController) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); ATLENSURE_RETURN_HR(hBreakNotificationComplete != nullptr, E_INVALIDARG); ATLENSURE_RETURN_HR(hwndDebugPipeHandler != nullptr, E_INVALIDARG); ATLENSURE_RETURN_HR(pMessageQueue != nullptr, E_INVALIDARG); ATLENSURE_RETURN_HR(pSourceController != nullptr, E_INVALIDARG); m_hwndDebugPipeHandler = hwndDebugPipeHandler; m_hBreakNotificationComplete = hBreakNotificationComplete; m_isDocked = isDocked; m_spMessageQueue = pMessageQueue; m_spSourceController = pSourceController; return S_OK; }
HRESULT ThreadController::SetEnabledState(_In_ bool isEnabled) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Set the enabled state of the source controller m_spSourceController->SetEnabledState(isEnabled); return S_OK; }
HRESULT ThreadController::SetDockState(_In_ bool isDocked) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); m_isDocked = isDocked; return S_OK; }
HRESULT SourceEventListener::onReplaceText(_In_ ULONG /* cCharacterPosition */, _In_ ULONG /* cNumToReplace */) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); // Even if cNumToReplace is 0, we still need to forward the event to update breakpoint bindings. // Deduplication of successive update document events occurs in DebugProvider. return this->OnSourceTextChange(); }
HRESULT ThreadController::OnPDMClose() { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); // Inform the DebuggerDispatch about the event m_spMessageQueue->Push(PDMEventType::PDMClosed); return S_OK; }
HRESULT ThreadController::Disconnect() { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); if (!this->IsConnected()) { return S_OK; } HRESULT hr = S_OK; // Get the IRemoteDebugApplication using our helper function that keeps it thread safe CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive; hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive); BPT_FAIL_IF_NOT_S_OK(hr); // We need to check if spDebugAppKeepAlive is null, since the debugger may have been activated // while debugging was disabled (e.g.: after the profiler started). // If that happened, then m_spDebugApplication would be null when we tried to AddRef it. if (spDebugAppKeepAlive.p != nullptr) { // Unbind all the the breakboints and remove them from the engine so we don't hit any once we close (if debugging IE is enabled via checkbox) m_spSourceController->UnbindAllCodeBreakpoints(/*removeFromEngine=*/ true); m_spSourceController->UnbindAllEventBreakpoints(/*removeListeners=*/ true); // Disconnect the debugger first to prevent re-entrancy into handle breakpoint if (m_spThreadEventListener.p != nullptr) // If we don't have a listener then we only connected for sources, not with debugging { hr = spDebugAppKeepAlive->DisconnectDebugger(); ATLASSERT(hr == S_OK); // Failure to disconnect, should still clean up the rest of the debugger } if (this->IsAtBreak()) { hr = this->Resume(BREAKRESUMEACTION_CONTINUE); BPT_FAIL_IF_NOT_S_OK(hr); } } // Release the listener that handles the pdm callbacks if (m_spThreadEventListener.p != nullptr) { m_spThreadEventListener.Release(); } // Clear all source nodes but don't release the Controller since we will re-use it on next connect m_spSourceController->Clear(); // Do Release the IDebugApplication pointer, since we get a new one when we next connect m_spDebugApplication.Release(); // Set our connected flag m_isConnected = false; return hr; }
HRESULT ThreadController::SetNextStatement(_In_ ULONG docId, _In_ ULONG start) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } HRESULT hr = S_OK; // We use the current call frame, which should be the first one in the m_callFrames list ULONG firstFrameId = (m_callFrames.empty() ? 0 : m_callFrames.front()); auto it = m_debugFrameMap.find(firstFrameId); if (it != m_debugFrameMap.end()) { // Get the code context for the passed in map // If it is not found, we fail gracefully (no assert) to the caller CComPtr<IDebugCodeContext> spDebugCodeContext; hr = m_spSourceController->GetCodeContext(docId, start, spDebugCodeContext); if (hr == S_OK) // This could fail due to requiring a refresh (the caller will handle the hresult) { CComQIPtr<IDebugStackFrame> spStackFrame(it->second); ATLENSURE_RETURN_HR(spStackFrame.p != nullptr, E_NOINTERFACE); CComQIPtr<ISetNextStatement> spSetter(spStackFrame); ATLENSURE_RETURN_HR(spSetter.p != nullptr, E_NOINTERFACE); hr = spSetter->SetNextStatement(spStackFrame, spDebugCodeContext); } } else { return E_NOT_FOUND; } return hr; }
HRESULT ThreadController::SetNextEvent(_In_ const CString& eventBreakpointType) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); // Store the event break type for the next break (it will be an empty string for a regular async break) m_nextBreakEventType = eventBreakpointType; return S_OK; }
HRESULT ThreadController::SetBreakOnFirstChanceExceptions(_In_ bool breakOnFirstChance) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // This function is safe to call while we are not at a breakpoint, // So we don't need to make a call to IsAtBreak() if (!this->IsConnected()) { return E_FAIL; } // Get the IRemoteDebugApplication using our helper function that keeps it thread safe CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive; HRESULT hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive); BPT_FAIL_IF_NOT_S_OK(hr); CComQIPtr<IRemoteDebugApplication110> spDebugApp110 = spDebugAppKeepAlive; ATLENSURE_RETURN_HR(spDebugApp110.p != nullptr, E_NOINTERFACE); hr = spDebugApp110->SetDebuggerOptions(SDO_ENABLE_FIRST_CHANCE_EXCEPTIONS, breakOnFirstChance ? SDO_ENABLE_FIRST_CHANCE_EXCEPTIONS : SDO_NONE); return hr; }
/** * Return the path of the file containing the public key. * * The path is set via SetKeyPaths(). */ HRESULT OnPublicKeyFileRequest(BSTR *pbstrPublicKeyFile) { ATLENSURE_RETURN_HR(pbstrPublicKeyFile, E_POINTER); *pbstrPublicKeyFile = NULL; try { *pbstrPublicKeyFile = comet::bstr_t::detach( m_publicKey.file_string()); } WINAPI_COM_CATCH_AUTO_INTERFACE(); return S_OK; }
HRESULT ThreadController::SetRemoteDebugApplication(_In_ IRemoteDebugApplication* pDebugApplication) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); ATLENSURE_RETURN_HR(pDebugApplication != nullptr, E_INVALIDARG); CComCritSecLock<CComAutoCriticalSection> lock(m_csThreadControllerLock); if (m_spDebugApplication == pDebugApplication) { // Already connected to this, so do nothing return S_OK; } m_isConnected = false; m_spDebugApplication = pDebugApplication; m_mainIEScriptThreadId = 0; // IRemoteDebugApplication110 is needed to grab the id for the UI thread CComQIPtr<IRemoteDebugApplication110> spRemoteDebugApp110(pDebugApplication); ATLENSURE_RETURN_HR(spRemoteDebugApp110.p != nullptr, E_NOINTERFACE); // Get the thread id for the main IE UI thread which is used to know if we need to pump messages at a break CComPtr<IRemoteDebugApplicationThread> spThread; HRESULT hr = spRemoteDebugApp110->GetMainThread(&spThread); if (hr == S_OK) { hr = spThread->GetSystemThreadId(&m_mainIEScriptThreadId); if (hr != S_OK) { // Default back to 0 (invalid id) if the call failed for some reason m_mainIEScriptThreadId = 0; } } return S_OK; }
// Break Mode Actions HRESULT ThreadController::GetCurrentThreadDescription(_Out_ CComBSTR& threadDescription) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } CComBSTR state; return m_spCurrentBrokenThread->GetDescription(&threadDescription, &state); }
HRESULT ThreadController::SetBreakOnNewWorker(_In_ bool enable) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // This function is safe to call while we are not at a breakpoint, // So we don't need to make a call to IsAtBreak() if (!this->IsConnected()) { return E_FAIL; } m_spSourceController->SetBreakOnNewWorker(enable); return S_OK; }
HRESULT ThreadController::GetBreakEventInfo(_Out_ shared_ptr<BreakEventInfo>& spBreakInfo) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } spBreakInfo = m_spCurrentBreakInfo; return S_OK; }
HRESULT ThreadController::Pause() { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); if (!this->IsConnected() || this->IsAtBreak()) { return S_OK; } // We need to call pause on the debugger thread so that we get the internal pdm locks in the correct order: // m_csCrossThreadCallLock then m_csBreakPoint // If we call CauseBreak directly, we will end up getting the locks in the reverse order which can cause a deadlock. HRESULT hr = this->CallDebuggerThread(PDMThreadCallbackMethod::Pause, (DWORD_PTR)nullptr, (DWORD_PTR)nullptr); // This could fail if we already disconnected, so don't assert, but do return the hr return hr; }
HRESULT ThreadController::WaitForBreakNotification() { // Process messages on the IE thread so that F12 doesn't get blocked waiting for a synchronous call to finish. ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); // Since we are sending the break notification directly from our debugger backend here (instead of via the frame) // we don't want to process our messages until the 'break complete' event has been triggered. HRESULT hr = S_OK; while (hr == S_OK) { // Since the IE tab and frame had their input queues synchronized before the break, there may be pending messages // on the frame's queue that are blocking us from working while docked. // We need to dispose of these messages so that the tab and frame's threads can be detached by the BHO. DWORD dwWaitResult = ::MsgWaitForMultipleObjects(1, &m_hBreakNotificationComplete, FALSE, INFINITE, QS_SENDMESSAGE); if (dwWaitResult != WAIT_FAILED) { if (dwWaitResult == WAIT_OBJECT_0) { break; } else { // There was a message put into our queue, so we need to dispose of it MSG msg; while (::PeekMessage(&msg, NULL, WM_KILLFOCUS, WM_KILLFOCUS, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); // Check if the event was triggered before pumping more messages if (::WaitForSingleObject(m_hBreakNotificationComplete, 0) == WAIT_OBJECT_0) { break; } } } } else { hr = ::AtlHresultFromLastError(); } } return hr; }
HRESULT IEDiagnosticsAdapter::ConnectToInstance(_In_ IEInstance& instance) { if (instance.isConnected) { return E_NOT_VALID_STATE; } CComPtr<IHTMLDocument2> spDocument; HRESULT hr = Helpers::GetDocumentFromHwnd(instance.hwnd, spDocument); if (hr == S_OK) { CString path(m_rootPath); path.Append(L"Proxy.dll"); CComPtr<IOleWindow> spSite; hr = Helpers::StartDiagnosticsMode(spDocument, __uuidof(ProxySite), path, __uuidof(spSite), reinterpret_cast<void**>(&spSite.p)); if (hr == S_OK) { HWND hwnd; hr = spSite->GetWindow(&hwnd); FAIL_IF_NOT_S_OK(hr); // Send our hwnd to the proxy so it can connect back BOOL succeeded = ::PostMessage(hwnd, Get_WM_SET_CONNECTION_HWND(), reinterpret_cast<WPARAM>(m_hWnd), NULL); ATLENSURE_RETURN_HR(succeeded, E_FAIL); // Inject script onto the browser thread hr = this->InjectScript(L"browser", L"Common.js", IDR_COMMON_SCRIPT, hwnd); hr = this->InjectScript(L"browser", L"browserMain.js", IDR_BROWSER_SCRIPT, hwnd); hr = this->InjectScript(L"browser", L"DOM.js", IDR_DOM_SCRIPT, hwnd); // Inject script onto the debugger thread hr = this->InjectScript(L"debugger", L"Common.js", IDR_COMMON_SCRIPT, hwnd); hr = this->InjectScript(L"debugger", L"debuggerMain.js", IDR_DEBUGGER_SCRIPT, hwnd); // Connected instance.isConnected = true; instance.spSite = spSite; instance.connectionHwnd = hwnd; } } return hr; }
HRESULT ThreadController::GetThreadDescriptions(_Inout_ vector<CComBSTR>& spThreadDescriptions) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // This function is safe to call while we are not at a breakpoint, // So we don't need to make a call to IsAtBreak() if (!this->IsConnected()) { return E_FAIL; } // Get the IRemoteDebugApplication using our helper function that keeps it thread safe CComPtr<IRemoteDebugApplication> spDebugAppKeepAlive; HRESULT hr = this->GetRemoteDebugApplication(spDebugAppKeepAlive); BPT_FAIL_IF_NOT_S_OK(hr); CComPtr<IEnumRemoteDebugApplicationThreads> spEnumThreads; hr = spDebugAppKeepAlive->EnumThreads(&spEnumThreads); BPT_FAIL_IF_NOT_S_OK(hr); // Enumerate each thread and get its description ULONG numFetched = 0; do { CComPtr<IRemoteDebugApplicationThread> spRDAThread; if (spEnumThreads->Next(1, &spRDAThread.p, &numFetched) != S_OK || spRDAThread.p == nullptr) { break; } // Get the description and add it to the array that we will return CComBSTR bstrDescription; CComBSTR bstrState; hr = spRDAThread->GetDescription(&bstrDescription, &bstrState); if (hr == S_OK) { spThreadDescriptions.push_back(bstrDescription); } } while (numFetched != 0); return hr; }
HRESULT ThreadController::GetCallFrames(_Inout_ vector<shared_ptr<CallFrameInfo>>& spFrames) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } for (auto& frameId : m_callFrames) { auto it = m_callFramesInfoMap.find(frameId); if (it != m_callFramesInfoMap.end()) { spFrames.push_back(it->second); } } return S_OK; }
HRESULT ThreadController::GetLocals(_In_ ULONG frameId, _Out_ ULONG& propertyId) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } auto it = m_localsMap.find(frameId); if (it != m_localsMap.end()) { propertyId = it->second; } else { return E_NOT_FOUND; } return S_OK; }
HRESULT ThreadController::Eval(_In_ ULONG frameId, _In_ const CComBSTR& evalString, _In_ ULONG radix, _Out_ shared_ptr<PropertyInfo>& spPropertyInfo) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnDispatchThread(m_dispatchThreadId), E_UNEXPECTED); // Lock the callframe access CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); if (!this->IsConnected() || !this->IsAtBreak()) { return E_NOT_VALID_STATE; } HRESULT hr = S_OK; if (m_evalCallbacks.size() >= ThreadController::s_maxConcurrentEvalCount) { // Since evaluating an eval call is relatively quick, having > max running concurrently, // probably means that there was a failure in one causing the others to queue up behind it. // If we get into this state we should abort the latest eval and we cannot process this new one. // This will free up the waiting message pump to see if it can unwind the stack further. m_evalCallbacks.back().first->Abort(); m_evalCallbacks.back().second->Abort(); return E_ABORT; } // To help diagnose chakra recursion issues (such as the above queued up evals) we append // an identifying string to the eval so that it will appear in debugging information. CString evalWithId; evalWithId.Format(ThreadController::s_f12EvalIdentifier, m_evalCallbacks.size()); evalWithId.Append(evalString); auto it = m_debugFrameMap.find(frameId); if (it != m_debugFrameMap.end()) { CComQIPtr<IDebugExpressionContext> spEvalContext(it->second); ATLENSURE_RETURN_HR(spEvalContext.p != nullptr, E_NOINTERFACE); CComPtr<IDebugExpression> spDebugExpression; hr = spEvalContext->ParseLanguageText(evalWithId, radix, L"" /* not actually used, but required */, DEBUG_TEXT_RETURNVALUE | DEBUG_TEXT_ISEXPRESSION, &spDebugExpression); BPT_FAIL_IF_NOT_S_OK(hr); // Free the lock as we no longer use the eval context (spEvalContext) past this point, // This will allow the PDM thread to update the callstacks on its thread. lock.Unlock(); // Local scope for COM objects. { // Keep our reference alive until all evals have finished (or been aborted) CComObjPtr<ThreadController> spKeepAlive(this); // Create expression callback CComObject<EvalCallback>* pDebugCallback; CComObject<EvalCallback>::CreateInstance(&pDebugCallback); ATLENSURE_RETURN_HR(pDebugCallback != nullptr, E_NOINTERFACE); CComObjPtr<EvalCallback> spDebugCallback(pDebugCallback); hr = spDebugCallback->Initialize(); BPT_FAIL_IF_NOT_S_OK(hr); // Add this to our list so we can abandon it if we resume or close before it completes m_evalCallbacks.push_back(::make_pair(spDebugExpression, spDebugCallback)); // Evaluate the expression hr = spDebugExpression->Start(spDebugCallback); BPT_FAIL_IF_NOT_S_OK(hr); hr = spDebugCallback->WaitForCompletion(); // Remove from our list ATLENSURE_RETURN_HR(m_evalCallbacks.back().first == spDebugExpression, E_UNEXPECTED); m_evalCallbacks.pop_back(); // Check if we were closed down before this evaluation completed, if so just return abort failure if (hr == E_ABORT) { return hr; } } HRESULT returnResult; CComPtr<IDebugProperty> spDebugProperty; // Determine if the expression evaluation was successful hr = spDebugExpression->GetResultAsDebugProperty(&returnResult, &spDebugProperty); BPT_FAIL_IF_NOT_S_OK(hr); // The get result function should always succeed, but the actual result from the eval could fail if // chakra was unable to process the request. If that is the case we should return the failure out // to the caller, which will result in sending null to the JavaScript which is handled correctly. if (returnResult != S_OK) { return returnResult; } DebugPropertyInfo propInfo; hr = spDebugProperty->GetPropertyInfo(PROP_INFO_ALL, radix, &propInfo); if (hr == S_OK) { hr = this->PopulatePropertyInfo(propInfo, spPropertyInfo); } else { return E_NOT_FOUND; } } else { return E_NOT_FOUND; } return hr; }
// PDM Event Notifications HRESULT ThreadController::OnPDMBreak(_In_ IRemoteDebugApplicationThread* pRDAThread, _In_ BREAKREASON br, _In_ const CComBSTR& description, _In_ WORD errorId, _In_ bool isFirstChance, _In_ bool isUserUnhandled) { ATLENSURE_RETURN_HR(ThreadHelpers::IsOnPDMThread(m_dispatchThreadId), E_UNEXPECTED); CComCritSecLock<CComAutoCriticalSection> lock(m_csCallFramesLock); // Get the new call stack information m_spCurrentBrokenThread = pRDAThread; this->PopulateCallStack(); // If the callframe list is empty, we were not given any information about this break, so default to invalid id (0) ULONG firstFrameId = (m_callFrames.empty() ? 0 : m_callFrames.front()); // Set our new break state m_spCurrentBreakInfo.reset(new BreakEventInfo()); m_spCurrentBreakInfo->firstFrameId = firstFrameId; m_spCurrentBreakInfo->errorId = errorId; m_spCurrentBreakInfo->breakReason = br; m_spCurrentBreakInfo->description = description; m_spCurrentBreakInfo->isFirstChanceException = isFirstChance; m_spCurrentBreakInfo->isUserUnhandled = isUserUnhandled; // Get the PDM thread id of the one that was actually broken HRESULT hr = m_spCurrentBrokenThread->GetSystemThreadId(&m_currentPDMThreadId); if (hr != S_OK) { m_currentPDMThreadId = 0; } m_spCurrentBreakInfo->systemThreadId = m_currentPDMThreadId; // If this is caused by a breakpoint, we need to gather further information. // Any condition needs to be evaluated on the other thread since we cannot continue or evaluate // synchronously during the onHandleBreakpoint function. // Doing so causes a deadlock in the PDM. if (br == BREAKREASON_BREAKPOINT) { // Find the code breakpoint auto it = m_callFramesInfoMap.find(firstFrameId); if (it != m_callFramesInfoMap.end()) { shared_ptr<BreakpointInfo> spBreakpointInfo; const SourceLocationInfo& sourceLocation = it->second->sourceLocation; hr = m_spSourceController->GetBreakpointInfoForBreak(sourceLocation.docId, sourceLocation.charPosition, spBreakpointInfo); if (hr == S_OK) { m_spCurrentBreakInfo->breakpoints.push_back(spBreakpointInfo); } } } else if (!m_nextBreakEventType.IsEmpty()) { if (br == BREAKREASON_DEBUGGER_HALT) { // Find the event breakpoints for this type vector<shared_ptr<BreakpointInfo>> breakpointList; hr = m_spSourceController->GetBreakpointInfosForEventBreak(m_nextBreakEventType, breakpointList); if (hr == S_OK) { m_spCurrentBreakInfo->breakpoints = std::move(breakpointList); } m_spCurrentBreakInfo->breakReason = BREAKREASON_BREAKPOINT; } m_spCurrentBreakInfo->breakEventType = m_nextBreakEventType; // If this breakpoint was created with the Create XHR Breakpoint button, we need to tell the frontend that an XHR breakpoint was hit in order to display appropriate strings if (m_nextBreakEventType == L"readystatechange") { for (auto it = m_spCurrentBreakInfo->breakpoints.begin(); it != m_spCurrentBreakInfo->breakpoints.end(); ++it) { if (std::find((**it).spEventTypes->begin(), (**it).spEventTypes->end(), s_xhrBreakpointFlag) != (**it).spEventTypes->end()) { m_spCurrentBreakInfo->breakEventType = s_xhrBreakpointFlag; } } } } // Inform the DebuggerDispatch about the event m_spMessageQueue->Push(PDMEventType::BreakpointHit); return S_OK; }