mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
072d3d69de
On 64-bit Windows (x86_64, aarch64), stack walking relies on RtlLookupFunctionEntry to navigate from one frame to the next. This function acquires up to two ntdll internal locks when it is called. The profiler and the background hang monitor both need to walk the stacks of suspended threads. This can lead to deadlock situations, which so far we have avoided with stack walk suppressions. We guard some critical paths to mark them as suppressing stack walk, and we forbid stack walking when any thread is currently on such path. While stack walk suppression has helped remove most deadlock situations, some can remain because it is hard to detect and manually annotate all the paths that could lead to a deadlock situation. Another drawback is that stack walk suppression disables stack walking for much larger portions of code than required. For example, we disable stack walking for LdrLoadDll, so we cannot collect stacks while we are loading a DLL. Yet, the lock that could lead to a deadlock situation is only held during a very small portion of the whole time spent in LdrLoadDll. This patch addresses these two issues by implementing a finer-grained strategy to avoid deadlock situations. We acquire the pointers to the internel ntdll locks through a single-stepped execution of RtlLookupFunctionEntry. This allows us to try to acquire the locks non-blockingly so that we can guarantee safe stack walking with no deadlock. If we fail to collect pointers to the locks, we fall back to using stack walk suppressions like before. This way we get the best of both worlds: if we are confident that the situation is under control, we will use the new strategy and get better profiler accuracy and no deadlock; in case of doubt, we can still use the profiler thanks to stack walk suppressions. Differential Revision: https://phabricator.services.mozilla.com/D223498
82 lines
2.7 KiB
C++
82 lines
2.7 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/WindowsDiagnostics.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/Types.h"
|
|
|
|
#include <windows.h>
|
|
#include <winternl.h>
|
|
|
|
#if defined(_M_AMD64)
|
|
|
|
namespace mozilla {
|
|
|
|
static OnSingleStepCallback sOnSingleStepCallback{};
|
|
static void* sOnSingleStepCallbackState = nullptr;
|
|
static bool sIsSingleStepping = false;
|
|
|
|
MFBT_API AutoOnSingleStepCallback::AutoOnSingleStepCallback(
|
|
OnSingleStepCallback aOnSingleStepCallback, void* aState) {
|
|
MOZ_RELEASE_ASSERT(!sIsSingleStepping && !sOnSingleStepCallback &&
|
|
!sOnSingleStepCallbackState,
|
|
"Single-stepping is already active");
|
|
|
|
sOnSingleStepCallback = std::move(aOnSingleStepCallback);
|
|
sOnSingleStepCallbackState = aState;
|
|
sIsSingleStepping = true;
|
|
}
|
|
|
|
MFBT_API AutoOnSingleStepCallback::~AutoOnSingleStepCallback() {
|
|
sOnSingleStepCallback = OnSingleStepCallback();
|
|
sOnSingleStepCallbackState = nullptr;
|
|
sIsSingleStepping = false;
|
|
}
|
|
|
|
// Going though this assembly code turns on the trap flag, which will trigger
|
|
// a first single-step exception. It is then up to the exception handler to
|
|
// keep the trap flag enabled so that a new single step exception gets
|
|
// triggered with the following instruction.
|
|
MFBT_API MOZ_NEVER_INLINE MOZ_NAKED void EnableTrapFlag() {
|
|
asm volatile(
|
|
"pushfq;"
|
|
"orw $0x100,(%rsp);"
|
|
"popfq;"
|
|
"retq;");
|
|
}
|
|
|
|
// This function does not do anything special, but when we reach its address
|
|
// while single-stepping the exception handler will know that it is now time to
|
|
// leave the trap flag turned off.
|
|
MFBT_API MOZ_NEVER_INLINE MOZ_NAKED void DisableTrapFlag() {
|
|
asm volatile("retq;");
|
|
}
|
|
|
|
MFBT_API LONG SingleStepExceptionHandler(_EXCEPTION_POINTERS* aExceptionInfo) {
|
|
if (sIsSingleStepping && sOnSingleStepCallback &&
|
|
aExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
|
|
auto instructionPointer = aExceptionInfo->ContextRecord->Rip;
|
|
bool keepOnSingleStepping = false;
|
|
if (instructionPointer != reinterpret_cast<uintptr_t>(&DisableTrapFlag)) {
|
|
keepOnSingleStepping = sOnSingleStepCallback(
|
|
sOnSingleStepCallbackState, aExceptionInfo->ContextRecord);
|
|
}
|
|
if (keepOnSingleStepping) {
|
|
aExceptionInfo->ContextRecord->EFlags |= 0x100;
|
|
} else {
|
|
sIsSingleStepping = false;
|
|
}
|
|
return EXCEPTION_CONTINUE_EXECUTION;
|
|
}
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // _M_AMD64
|