diff options
author | siggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-11 18:01:55 +0000 |
---|---|---|
committer | siggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-11 18:01:55 +0000 |
commit | b438c4a50990c32e11e8b9a4d806c8a7bc2b8746 (patch) | |
tree | 593ef9d3b446c624b9e6d2dc5de74d8050087aa1 /chrome_frame | |
parent | 66eabe1f06ce4bc35eb619aa2f2e4695255137fa (diff) | |
download | chromium_src-b438c4a50990c32e11e8b9a4d806c8a7bc2b8746.zip chromium_src-b438c4a50990c32e11e8b9a4d806c8a7bc2b8746.tar.gz chromium_src-b438c4a50990c32e11e8b9a4d806c8a7bc2b8746.tar.bz2 |
Utility functions to interact with the NT loader's data structures and associated tests.
This is in preparation for squelching false positive crash reports during DLL load.
BUG=31980
TEST=Unittests in this change.
Review URL: http://codereview.chromium.org/882001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@41289 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-rw-r--r-- | chrome_frame/crash_reporting/crash_dll.cc | 39 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/crash_dll.h | 13 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/crash_reporting.gyp | 49 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/nt_loader.cc | 29 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/nt_loader.h | 175 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/nt_loader_unittest.cc | 282 |
6 files changed, 587 insertions, 0 deletions
diff --git a/chrome_frame/crash_reporting/crash_dll.cc b/chrome_frame/crash_reporting/crash_dll.cc new file mode 100644 index 0000000..97f41c9 --- /dev/null +++ b/chrome_frame/crash_reporting/crash_dll.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Main entry point for a DLL that can be instructed to crash on +// load or unload by setting an environment variable appropriately. +// +// Note: This code has no CRT to lean on, because some versions of the CRT +// have a bug whereby they leave dangling state after taking an exception +// during DLL_PROCESS_ATTACH. This in turn causes the loading process to +// crash on exit. To work around this, this DLL has its entrypoint set +// to the DllMain routine and does not link with the CRT. +#include <windows.h> +#include "crash_dll.h" + +void Crash() { + char* null_pointer = reinterpret_cast<char*>(kCrashAddress); + + *null_pointer = '\0'; +} + +void CrashConditionally(const wchar_t* name) { + wchar_t value[1024]; + DWORD ret = ::GetEnvironmentVariable(name, value, ARRAYSIZE(value)); + if (ret != 0 || ERROR_ENVVAR_NOT_FOUND != ::GetLastError()) + Crash(); +} + +extern "C" BOOL WINAPI DllMain(HINSTANCE instance, + DWORD reason, + LPVOID reserved) { + if (reason == DLL_PROCESS_ATTACH) { + CrashConditionally(kCrashOnLoadMode); + } else if (reason == DLL_PROCESS_DETACH) { + CrashConditionally(kCrashOnUnloadMode); + } + + return 1; +} diff --git a/chrome_frame/crash_reporting/crash_dll.h b/chrome_frame/crash_reporting/crash_dll.h new file mode 100644 index 0000000..ee4fafd --- /dev/null +++ b/chrome_frame/crash_reporting/crash_dll.h @@ -0,0 +1,13 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_CRASH_REPORT_CRASH_DLL_H_ +#define CHROME_FRAME_CRASH_REPORT_CRASH_DLL_H_ + +// Set either of these environment variables to +// crash at load or unload time, respectively. +static const wchar_t* kCrashOnLoadMode = L"CRASH_DLL_CRASH_ON_LOAD"; +static const wchar_t* kCrashOnUnloadMode = L"CRASH_DLL_CRASH_ON_UNLOAD"; +static const DWORD kCrashAddress = 0x42; + +#endif // CHROME_FRAME_CRASH_REPORT_CRASH_DLL_H_ diff --git a/chrome_frame/crash_reporting/crash_reporting.gyp b/chrome_frame/crash_reporting/crash_reporting.gyp index f8644d4..54473fc 100644 --- a/chrome_frame/crash_reporting/crash_reporting.gyp +++ b/chrome_frame/crash_reporting/crash_reporting.gyp @@ -19,6 +19,8 @@ 'sources': [ 'crash_report.cc', 'crash_report.h', + 'nt_loader.cc', + 'nt_loader.h', 'vectored_handler-impl.h', 'vectored_handler.h', ], @@ -31,14 +33,61 @@ ], }, { + 'target_name': 'crash_dll', + 'type': 'loadable_module', + 'sources': [ + 'crash_dll.cc', + 'crash_dll.h', + ], + 'msvs_settings': { + # To work around a bug in some versions of the CRT, which cause + # crashes on program exit if a DLL crashes at process attach time, + # we cut out the CRT entirely, and set our DLL main routine as the + # entry point for the DLL. + 'VCLinkerTool': { + 'EntryPointSymbol': 'DllMain', + 'IgnoreAllDefaultLibraries': 1, + }, + # Turn off buffer security checks, since we don't have CRT + # support for them, given that we don't link the CRT. + 'VCCLCompilerTool': { + 'BufferSecurityCheck': 'false', + }, + }, + 'configurations': { + 'Debug': { + 'msvs_settings': { + # Turn off basic CRT checks, since we don't have CRT support. + # We have to do this per configuration, as base.gypi specifies + # this per-config, which binds tighter than the defaults above. + 'VCCLCompilerTool': { + 'BasicRuntimeChecks': '0', + }, + }, + }, + 'Debug_x64': { + 'msvs_settings': { + # Turn off basic CRT checks, since we don't have CRT support. + # We have to do this per configuration, as base.gypi specifies + # this per-config, which binds tighter than the defaults above. + 'VCCLCompilerTool': { + 'BasicRuntimeChecks': '0', + }, + }, + }, + }, + }, + { 'target_name': 'vectored_handler_tests', 'type': 'executable', 'sources': [ + 'nt_loader_unittest.cc', 'vectored_handler_unittest.cc', 'veh_test.cc', 'veh_test.h', ], 'dependencies': [ + 'crash_dll', 'crash_report', '../../base/base.gyp:base', '../../testing/gmock.gyp:gmock', diff --git a/chrome_frame/crash_reporting/nt_loader.cc b/chrome_frame/crash_reporting/nt_loader.cc new file mode 100644 index 0000000..3e5a99a --- /dev/null +++ b/chrome_frame/crash_reporting/nt_loader.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "chrome_frame/crash_reporting/nt_loader.h" + +#include <winnt.h> +#include <winternl.h> +#include "base/logging.h" + +namespace nt_loader { + +LDR_DATA_TABLE_ENTRY* GetLoaderEntry(HMODULE module) { + // Make sure we own the loader's lock on entry here. + DCHECK(OwnsCriticalSection(GetLoaderLock())); + PEB* peb = GetCurrentPeb(); + + LIST_ENTRY* head = &peb->Ldr->InLoadOrderModuleList; + for (LIST_ENTRY* entry = head->Flink; entry != head; entry = entry->Flink) { + LDR_DATA_TABLE_ENTRY* ldr_entry = + CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); + + if (reinterpret_cast<HMODULE>(ldr_entry->DllBase) == module) + return ldr_entry; + } + + return NULL; +} + +} // namespace nt_loader diff --git a/chrome_frame/crash_reporting/nt_loader.h b/chrome_frame/crash_reporting/nt_loader.h new file mode 100644 index 0000000..fc4efb1 --- /dev/null +++ b/chrome_frame/crash_reporting/nt_loader.h @@ -0,0 +1,175 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef CHROME_FRAME_NT_LOADER_H_ +#define CHROME_FRAME_NT_LOADER_H_ + +#include <windows.h> +#include <winnt.h> +#include <winternl.h> + +namespace nt_loader { + +// These structures are gleaned from public symbol information. +struct _PEB; +struct _PEB_LDR_DATA; +struct _RTL_USER_PROCESS_PARAMETERS; +struct _PEB_FREE_BLOCK; + + +typedef struct _NT_TIB { + _EXCEPTION_REGISTRATION_RECORD* ExceptionList; // 0x000 + void *StackBase; // 0x004 + void* StackLimit; // 0x008 + void* SubSystemTib; // 0x00c + union { + void *FiberData; // 0x010 + DWORD Version; // 0x010 + }; + void* ArbitraryUserPointer; // 0x014 + _NT_TIB* Self; // 0x018 +} _NT_TIB, NT_TIB; + +typedef struct _CLIENT_ID { + void* UniqueProcess; // 0x000 + void* UniqueThread; // 0x004 +} _CLIENT_ID, CLIENT_ID; + +typedef struct _TEB { + _NT_TIB NtTib; // 0x000 + void* EnvironmentPointer; // 0x01c + _CLIENT_ID ClientId; // 0x020 + void* ActiveRpcHandle; // 0x028 + void* ThreadLocalStoragePointer; // 0x02c + _PEB* ProcessEnvironmentBlock; // 0x030 + // There is more in a TEB, but this is all we need. +} _TEB, TEB; + +typedef struct _PEB { + BYTE InheritedAddressSpace; // 0x000 + BYTE ReadImageFileExecOptions; // 0x001 + BYTE BeingDebugged; // 0x002 + BYTE SpareBool; // 0x003 + void* Mutant; // 0x004 + void* ImageBaseAddress; // 0x008 + _PEB_LDR_DATA* Ldr; // 0x00c + _RTL_USER_PROCESS_PARAMETERS* ProcessParameters; // 0x010 + void* SubSystemData; // 0x014 + void* ProcessHeap; // 0x018 + _RTL_CRITICAL_SECTION* FastPebLock; // 0x01c + void* FastPebLockRoutine; // 0x020 + void* FastPebUnlockRoutine; // 0x024 + ULONG EnvironmentUpdateCount; // 0x028 + void* KernelCallbackTable; // 0x02c + ULONG SystemReserved[1]; // 0x030 + ULONG AtlThunkSListPtr32; // 0x034 + _PEB_FREE_BLOCK* FreeList; // 0x038 + ULONG TlsExpansionCounter; // 0x03c + void* TlsBitmap; // 0x040 + ULONG TlsBitmapBits[2]; // 0x044 + void* ReadOnlySharedMemoryBase; // 0x04c + void* ReadOnlySharedMemoryHeap; // 0x050 + void** ReadOnlyStaticServerData; // 0x054 + void* AnsiCodePageData; // 0x058 + void* OemCodePageData; // 0x05c + void* UnicodeCaseTableData; // 0x060 + ULONG NumberOfProcessors; // 0x064 + ULONG NtGlobalFlag; // 0x068 + _LARGE_INTEGER CriticalSectionTimeout; // 0x070 + ULONG HeapSegmentReserve; // 0x078 + ULONG HeapSegmentCommit; // 0x07c + ULONG HeapDeCommitTotalFreeThreshold; // 0x080 + ULONG HeapDeCommitFreeBlockThreshold; // 0x084 + ULONG NumberOfHeaps; // 0x088 + ULONG MaximumNumberOfHeaps; // 0x08c + void** ProcessHeaps; // 0x090 + void* GdiSharedHandleTable; // 0x094 + void* ProcessStarterHelper; // 0x098 + ULONG GdiDCAttributeList; // 0x09c + RTL_CRITICAL_SECTION* LoaderLock; // 0x0a0 + // There is more in a PEB, but this is all we need. +} _PEB, PEB; + +struct _PEB_LDR_DATA { + ULONG Length; // 0x000 + BYTE Initialized; // 0x004 + void* SsHandle; // 0x008 + LIST_ENTRY InLoadOrderModuleList; // 0x00c + LIST_ENTRY InMemoryOrderModuleList; // 0x014 + LIST_ENTRY InInitializationOrderModuleList; // 0x01c + // There is more data in this structure, but this is all we need. +}; + +// These flags are gleaned from the !dlls Windbg extension. +#define LDRP_STATIC_LINK 0x00000002 +#define LDRP_IMAGE_DLL 0x00000004 +#define LDRP_LOAD_IN_PROGRESS 0x00001000 +#define LDRP_UNLOAD_IN_PROGRESS 0x00002000 +#define LDRP_ENTRY_PROCESSED 0x00004000 +#define LDRP_DONT_CALL_FOR_THREADS 0x00040000 +#define LDRP_PROCESS_ATTACH_CALLED 0x00080000 +#define LDRP_COR_IMAGE 0x00400000 +#define LDRP_COR_OWNS_UNMAP 0x00800000 +#define LDRP_COR_IL_ONLY 0x01000000 +#define LDRP_REDIRECTED 0x10000000 + +typedef struct _LDR_DATA_TABLE_ENTRY { + LIST_ENTRY InLoadOrderLinks; // 0x000 + LIST_ENTRY InMemoryOrderLinks; // 0x008 + LIST_ENTRY InInitializationOrderLinks; // 0x010 + void* DllBase; // 0x018 + void* EntryPoint; // 0x01c + ULONG SizeOfImage; // 0x020 + UNICODE_STRING FullDllName; // 0x024 + UNICODE_STRING BaseDllName; // 0x02c + ULONG Flags; // 0x034 + USHORT LoadCount; // 0x038 + USHORT TlsIndex; // 0x03a + union { + LIST_ENTRY HashLinks; // 0x03c + struct { + void* SectionPointer; // 0x03c + ULONG CheckSum; // 0x040 + }; + }; + union { + ULONG TimeDateStamp; // 0x044 + void* LoadedImports; // 0x044 + }; + void *EntryPointActivationContext; // 0x048 + void* PatchInformation; // 0x04c +} _LDR_DATA_TABLE_ENTRY, LDR_DATA_TABLE_ENTRY; + +// Retrieves the current thread's TEB. +inline TEB* GetCurrentTeb() { + return reinterpret_cast<TEB*>(NtCurrentTeb()); +} + +// Retrieves the current process' PEB. +inline PEB* GetCurrentPeb() { + return GetCurrentTeb()->ProcessEnvironmentBlock; +} + +// Returns true iff the current thread owns critsec. +inline bool OwnsCriticalSection(CRITICAL_SECTION* critsec) { + return reinterpret_cast<DWORD>(critsec->OwningThread) == + GetCurrentThreadId(); +} + +// Finds a loader table entry for module. +// Note: must hold the loader's lock on entry. +LDR_DATA_TABLE_ENTRY* GetLoaderEntry(HMODULE module); + +// Returns the loader's lock. +inline CRITICAL_SECTION* GetLoaderLock() { + return GetCurrentPeb()->LoaderLock; +} + +// Returns true iff the current thread owns the loader's lock on call. +inline bool OwnsLoaderLock() { + return OwnsCriticalSection(GetLoaderLock()); +} + +} // namespace nt_loader + +#endif // CHROME_FRAME_NT_LOADER_H_ diff --git a/chrome_frame/crash_reporting/nt_loader_unittest.cc b/chrome_frame/crash_reporting/nt_loader_unittest.cc new file mode 100644 index 0000000..67392bd --- /dev/null +++ b/chrome_frame/crash_reporting/nt_loader_unittest.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "chrome_frame/crash_reporting/nt_loader.h" + +#include <tlhelp32.h> +#include <winnt.h> +#include <base/at_exit.h> +#include <base/scoped_handle.h> +#include <base/sys_info.h> +#include <base/thread.h> +#include "chrome_frame/crash_reporting/crash_dll.h" +#include "gtest/gtest.h" + +namespace { +void AssertIsCriticalSection(CRITICAL_SECTION* critsec) { + // Assert on some of the internals of the debug info if it has one. + RTL_CRITICAL_SECTION_DEBUG* debug = critsec->DebugInfo; + if (debug) { + ASSERT_EQ(RTL_CRITSECT_TYPE, debug->Type); + ASSERT_EQ(critsec, debug->CriticalSection); + } + + // TODO(siggi): assert on the semaphore handle & object type? +} + +class ScopedEnterCriticalSection { + public: + ScopedEnterCriticalSection(CRITICAL_SECTION* critsec) : critsec_(critsec) { + ::EnterCriticalSection(critsec_); + } + + ~ScopedEnterCriticalSection() { + ::LeaveCriticalSection(critsec_); + } + + private: + CRITICAL_SECTION* critsec_; +}; + +std::wstring FromUnicodeString(const UNICODE_STRING& str) { + return std::wstring(str.Buffer, str.Length / sizeof(str.Buffer[0])); +} + +} // namespace + +using namespace nt_loader; + +TEST(NtLoader, OwnsCriticalSection) { + // Use of Thread requires an atexit manager. + base::AtExitManager at_exit; + + CRITICAL_SECTION cs = {}; + ::InitializeCriticalSection(&cs); + EXPECT_FALSE(OwnsCriticalSection(&cs)); + + // Enter the critsec and assert we own it. + { + ScopedEnterCriticalSection lock1(&cs); + + EXPECT_TRUE(OwnsCriticalSection(&cs)); + + // Re-enter the critsec and assert we own it. + ScopedEnterCriticalSection lock2(&cs); + + EXPECT_TRUE(OwnsCriticalSection(&cs)); + } + + // Should no longer own it. + EXPECT_FALSE(OwnsCriticalSection(&cs)); + + // Make another thread grab it. + base::Thread other("Other threads"); + ASSERT_TRUE(other.Start()); + other.message_loop()->PostTask( + FROM_HERE, NewRunnableFunction(::EnterCriticalSection, &cs)); + + ScopedHandle event(::CreateEvent(NULL, FALSE, FALSE, NULL)); + other.message_loop()->PostTask( + FROM_HERE, NewRunnableFunction(::SetEvent, event.Get())); + + ASSERT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE)); + + // We still shouldn't own it - the other thread does. + EXPECT_FALSE(OwnsCriticalSection(&cs)); + // And we shouldn't be able to enter it. + EXPECT_EQ(0, ::TryEnterCriticalSection(&cs)); + + // Make the other thread release it. + other.message_loop()->PostTask( + FROM_HERE, NewRunnableFunction(::LeaveCriticalSection, &cs)); + + other.Stop(); + + ::DeleteCriticalSection(&cs); +} + +TEST(NtLoader, GetLoaderLock) { + CRITICAL_SECTION* loader_lock = GetLoaderLock(); + + AssertIsCriticalSection(loader_lock); + + // We should be able to enter and leave the loader's lock without trouble. + EnterCriticalSection(loader_lock); + LeaveCriticalSection(loader_lock); +} + +TEST(NtLoader, OwnsLoaderLock) { + CRITICAL_SECTION* loader_lock = GetLoaderLock(); + + EXPECT_FALSE(OwnsLoaderLock()); + EnterCriticalSection(loader_lock); + EXPECT_TRUE(OwnsLoaderLock()); + LeaveCriticalSection(loader_lock); + EXPECT_FALSE(OwnsLoaderLock()); +} + +TEST(NtLoader, GetLoaderEntry) { + ScopedEnterCriticalSection lock(GetLoaderLock()); + + // Get all modules in the current process. + ScopedHandle snap(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, + ::GetCurrentProcessId())); + EXPECT_TRUE(snap.Get() != NULL); + + // Walk them, while checking we get an entry for each, and that it + // contains sane information. + MODULEENTRY32 module = { sizeof(module) }; + ASSERT_TRUE(::Module32First(snap.Get(), &module)); + do { + nt_loader::LDR_DATA_TABLE_ENTRY* entry = + nt_loader::GetLoaderEntry(module.hModule); + ASSERT_TRUE(entry != NULL); + ASSERT_EQ(module.hModule, reinterpret_cast<HMODULE>(entry->DllBase)); + ASSERT_STREQ(module.szModule, + FromUnicodeString(entry->BaseDllName).c_str()); + ASSERT_STREQ(module.szExePath, + FromUnicodeString(entry->FullDllName).c_str()); + } while(::Module32Next(snap.Get(), &module)); +} + +namespace { + +typedef void (*ExceptionFunction)(EXCEPTION_POINTERS* ex_ptrs); + +class NtLoaderTest: public testing::Test { + public: + NtLoaderTest() : veh_id_(NULL), exception_function_(NULL) { + EXPECT_EQ(NULL, current_); + current_ = this; + } + + ~NtLoaderTest() { + EXPECT_TRUE(this == current_); + current_ = NULL; + } + + void SetUp() { + veh_id_ = ::AddVectoredExceptionHandler(FALSE, &ExceptionHandler); + EXPECT_TRUE(veh_id_ != NULL); + + // Clear the crash DLL environment. + ::SetEnvironmentVariable(kCrashOnLoadMode, NULL); + ::SetEnvironmentVariable(kCrashOnUnloadMode, NULL); + } + + void TearDown() { + if (veh_id_ != NULL) + EXPECT_NE(0, ::RemoveVectoredExceptionHandler(veh_id_)); + + // Clear the crash DLL environment. + ::SetEnvironmentVariable(kCrashOnLoadMode, NULL); + ::SetEnvironmentVariable(kCrashOnUnloadMode, NULL); + } + + void set_exception_function(ExceptionFunction func) { + exception_function_ = func; + } + + private: + static LONG NTAPI ExceptionHandler(EXCEPTION_POINTERS* ex_ptrs){ + // Dispatch the exception to any exception function, + // but only on the main thread. + if (main_thread_ == ::GetCurrentThreadId() && + current_ != NULL && + current_->exception_function_ != NULL) + current_->exception_function_(ex_ptrs); + + return ExceptionContinueSearch; + } + + void* veh_id_; + ExceptionFunction exception_function_; + + static NtLoaderTest* current_; + static DWORD main_thread_; +}; + +NtLoaderTest* NtLoaderTest::current_ = NULL; +DWORD NtLoaderTest::main_thread_ = ::GetCurrentThreadId(); + +const wchar_t kCrashDllName[] = L"crash_dll.dll"; + +} // namespace + +static int exceptions_handled = 0; +static void OnCrashDuringLoadLibrary(EXCEPTION_POINTERS* ex_ptrs) { + ASSERT_EQ(STATUS_ACCESS_VIOLATION, ex_ptrs->ExceptionRecord->ExceptionCode); + ASSERT_EQ(2, ex_ptrs->ExceptionRecord->NumberParameters); + ASSERT_EQ(EXCEPTION_WRITE_FAULT, + ex_ptrs->ExceptionRecord->ExceptionInformation[0]); + ASSERT_EQ(kCrashAddress, + ex_ptrs->ExceptionRecord->ExceptionInformation[1]); + + // Bump the exceptions count. + exceptions_handled++; + + EXPECT_TRUE(OwnsLoaderLock()); + + HMODULE crash_dll = ::GetModuleHandle(kCrashDllName); + ASSERT_TRUE(crash_dll != NULL); + + nt_loader::LDR_DATA_TABLE_ENTRY* entry = GetLoaderEntry(crash_dll); + ASSERT_TRUE(entry != NULL); + ASSERT_EQ(0, entry->Flags & LDRP_PROCESS_ATTACH_CALLED); +} + +TEST_F(NtLoaderTest, CrashOnLoadLibrary) { + exceptions_handled = 0; + set_exception_function(OnCrashDuringLoadLibrary); + + // Setup to crash on load. + ::SetEnvironmentVariable(kCrashOnLoadMode, L"1"); + + // And load it. + HMODULE module = ::LoadLibrary(kCrashDllName); + DWORD err = ::GetLastError(); + EXPECT_EQ(NULL, module); + EXPECT_EQ(ERROR_NOACCESS, err); + EXPECT_EQ(1, exceptions_handled); + + if (module != NULL) + ::FreeLibrary(module); +} + +static void OnCrashDuringUnloadLibrary(EXCEPTION_POINTERS* ex_ptrs) { + ASSERT_EQ(STATUS_ACCESS_VIOLATION, ex_ptrs->ExceptionRecord->ExceptionCode); + ASSERT_EQ(2, ex_ptrs->ExceptionRecord->NumberParameters); + ASSERT_EQ(EXCEPTION_WRITE_FAULT, + ex_ptrs->ExceptionRecord->ExceptionInformation[0]); + ASSERT_EQ(kCrashAddress, + ex_ptrs->ExceptionRecord->ExceptionInformation[1]); + + // Bump the exceptions count. + exceptions_handled++; + + EXPECT_TRUE(OwnsLoaderLock()); + + HMODULE crash_dll = ::GetModuleHandle(kCrashDllName); + ASSERT_TRUE(crash_dll == NULL); + + nt_loader::LDR_DATA_TABLE_ENTRY* entry = GetLoaderEntry(crash_dll); + ASSERT_TRUE(entry == NULL); +} + +TEST_F(NtLoaderTest, CrashOnUnloadLibrary) { + // Setup to crash on unload. + ::SetEnvironmentVariable(kCrashOnUnloadMode, L"1"); + + // And load it. + HMODULE module = ::LoadLibrary(kCrashDllName); + EXPECT_TRUE(module != NULL); + + exceptions_handled = 0; + set_exception_function(OnCrashDuringUnloadLibrary); + + // We should crash during unload. + if (module != NULL) + ::FreeLibrary(module); + + EXPECT_EQ(1, exceptions_handled); +} |