// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/tracing/public/cpp/stack_sampling/loader_lock_sampler_win.h"
#include <ostream>
#include "base/check.h"
#include "base/native_library.h"
#include "base/win/windows_types.h"
#include "services/tracing/public/cpp/buildflags.h"
namespace tracing {
namespace {
// Function signatures are derived from
// https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrapi/lockloaderlock.htm
// The signature there only works on 32-bit - use of ULONG_PTR for the cookie
// is from https://www.winehq.org/pipermail/wine-cvs/2014-June/102116.html.
//
// This signature should work for 32-bit and 64-bit builds but it is only
// enabled for 64-bit for now because 32-bit is not well tested.
// (TracingSampleProfilerTest.SampleLoaderLockAlwaysHeld fails for an unknown
// reason.)
//
// TODO(crbug.com/40124365): Read the critical section directly, as crashpad
// does in third_party/crashpad/crashpad/util/win/loader_lock.cc. This should
// work on all versions since it's stable enough to ship with crashpad.
#if !BUILDFLAG(ENABLE_LOADER_LOCK_SAMPLING)
static_assert(false,
"Loader lock sampling should only be compiled in 64-bit builds.");
#endif
using LockLoaderLockFunc = NTSTATUS(NTAPI*)(ULONG flags,
ULONG* state,
ULONG_PTR* cookie);
using UnlockLoaderLockFunc = NTSTATUS(NTAPI*)(ULONG flags, ULONG_PTR cookie);
LockLoaderLockFunc g_lock_loader_lock = nullptr;
UnlockLoaderLockFunc g_unlock_loader_lock = nullptr;
// Ensures the mechanism that samples the loader lock is initialized. This may
// take the loader lock itself so it must be called before sampling starts.
bool InitializeLoaderLockSampling() {
// The handle to ntdll is intentionally leaked to ensure that the function
// pointers below remain valid for the lifetime of the process. (Note: ntdll
// is always loaded in the process in practice, so this will be the case
// regardless.)
base::NativeLibrary ntdll = base::LoadSystemLibrary(L"ntdll.dll");
DPCHECK(ntdll);
g_lock_loader_lock = reinterpret_cast<LockLoaderLockFunc>(
base::GetFunctionPointerFromNativeLibrary(ntdll, "LdrLockLoaderLock"));
g_unlock_loader_lock = reinterpret_cast<UnlockLoaderLockFunc>(
base::GetFunctionPointerFromNativeLibrary(ntdll, "LdrUnlockLoaderLock"));
return true;
}
} // namespace
ProbingLoaderLockSampler::ProbingLoaderLockSampler() {
static bool is_initialized = InitializeLoaderLockSampling();
DCHECK(is_initialized);
// IsLoaderLockHeld should always be called from the same thread but it
// doesn't need to be this thread.
DETACH_FROM_THREAD(thread_checker_);
}
ProbingLoaderLockSampler::~ProbingLoaderLockSampler() = default;
bool ProbingLoaderLockSampler::IsLoaderLockHeld() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(g_lock_loader_lock);
DCHECK(g_unlock_loader_lock);
// All constants are derived from
// https://www.geoffchappell.com/studies/windows/win32/ntdll/api/ldrapi/lockloaderlock.htm.
constexpr ULONG kDoNotWaitFlag = 0x2;
constexpr ULONG kEnteredLockState = 0x01;
ULONG state = 0;
ULONG_PTR cookie = 0;
NTSTATUS status = (*g_lock_loader_lock)(kDoNotWaitFlag, &state, &cookie);
if (status < 0) {
// Loader lock state unknown; default to false.
return false;
}
if (state == kEnteredLockState) {
// Keeping the loader lock would be very bad, so raise an exception if that
// happens.
constexpr ULONG kRaiseExceptionOnErrorFlag = 0x1;
(*g_unlock_loader_lock)(kRaiseExceptionOnErrorFlag, cookie);
// Since this thread was able to take the loader lock, no other thread held
// it during the sample.
return false;
}
// Since this thread was not able to take the loader lock, another thread
// held it during the sample.
return true;
}
} // namespace tracing