1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome_frame/crash_reporting/nt_loader.h"
10 #include "base/at_exit.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/environment.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop.h"
16 #include "base/string_util.h"
17 #include "base/sys_info.h"
18 #include "base/threading/thread.h"
19 #include "base/utf_string_conversions.h"
20 #include "base/win/scoped_handle.h"
21 #include "chrome_frame/crash_reporting/crash_dll.h"
22 #include "gtest/gtest.h"
25 void AssertIsCriticalSection(CRITICAL_SECTION
* critsec
) {
26 // Assert on some of the internals of the debug info if it has one.
27 RTL_CRITICAL_SECTION_DEBUG
* debug
= critsec
->DebugInfo
;
29 ASSERT_EQ(RTL_CRITSECT_TYPE
, debug
->Type
);
30 ASSERT_EQ(critsec
, debug
->CriticalSection
);
33 // TODO(siggi): assert on the semaphore handle & object type?
36 class ScopedEnterCriticalSection
{
38 explicit ScopedEnterCriticalSection(CRITICAL_SECTION
* critsec
)
40 ::EnterCriticalSection(critsec_
);
43 ~ScopedEnterCriticalSection() {
44 ::LeaveCriticalSection(critsec_
);
48 CRITICAL_SECTION
* critsec_
;
51 std::wstring
FromUnicodeString(const UNICODE_STRING
& str
) {
52 return std::wstring(str
.Buffer
, str
.Length
/ sizeof(str
.Buffer
[0]));
57 using namespace nt_loader
;
59 TEST(NtLoader
, OwnsCriticalSection
) {
60 // Use of Thread requires an atexit manager.
61 base::AtExitManager at_exit
;
63 CRITICAL_SECTION cs
= {};
64 ::InitializeCriticalSection(&cs
);
65 EXPECT_FALSE(OwnsCriticalSection(&cs
));
67 // Enter the critsec and assert we own it.
69 ScopedEnterCriticalSection
lock1(&cs
);
71 EXPECT_TRUE(OwnsCriticalSection(&cs
));
73 // Re-enter the critsec and assert we own it.
74 ScopedEnterCriticalSection
lock2(&cs
);
76 EXPECT_TRUE(OwnsCriticalSection(&cs
));
79 // Should no longer own it.
80 EXPECT_FALSE(OwnsCriticalSection(&cs
));
82 // Make another thread grab it.
83 base::Thread
other("Other threads");
84 ASSERT_TRUE(other
.Start());
85 other
.message_loop()->PostTask(
86 FROM_HERE
, base::Bind(::EnterCriticalSection
, &cs
));
88 base::win::ScopedHandle
event(::CreateEvent(NULL
, FALSE
, FALSE
, NULL
));
89 other
.message_loop()->PostTask(
90 FROM_HERE
, base::Bind(base::IgnoreResult(::SetEvent
), event
.Get()));
92 ASSERT_EQ(WAIT_OBJECT_0
, ::WaitForSingleObject(event
.Get(), INFINITE
));
94 // We still shouldn't own it - the other thread does.
95 EXPECT_FALSE(OwnsCriticalSection(&cs
));
96 // And we shouldn't be able to enter it.
97 EXPECT_EQ(0, ::TryEnterCriticalSection(&cs
));
99 // Make the other thread release it.
100 other
.message_loop()->PostTask(
101 FROM_HERE
, base::Bind(::LeaveCriticalSection
, &cs
));
105 ::DeleteCriticalSection(&cs
);
108 TEST(NtLoader
, GetLoaderLock
) {
109 CRITICAL_SECTION
* loader_lock
= GetLoaderLock();
111 AssertIsCriticalSection(loader_lock
);
113 // We should be able to enter and leave the loader's lock without trouble.
114 EnterCriticalSection(loader_lock
);
115 LeaveCriticalSection(loader_lock
);
118 TEST(NtLoader
, OwnsLoaderLock
) {
119 CRITICAL_SECTION
* loader_lock
= GetLoaderLock();
121 EXPECT_FALSE(OwnsLoaderLock());
122 EnterCriticalSection(loader_lock
);
123 EXPECT_TRUE(OwnsLoaderLock());
124 LeaveCriticalSection(loader_lock
);
125 EXPECT_FALSE(OwnsLoaderLock());
128 TEST(NtLoader
, GetLoaderEntry
) {
129 // Get all modules in the current process.
130 base::win::ScopedHandle
snap(
131 ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE
, ::GetCurrentProcessId()));
132 EXPECT_TRUE(snap
.Get() != NULL
);
134 // Walk them, while checking we get an entry for each, and that it
135 // contains sane information.
136 MODULEENTRY32 module
= { sizeof(module
) };
137 ASSERT_TRUE(::Module32First(snap
.Get(), &module
));
139 ScopedEnterCriticalSection
lock(GetLoaderLock());
141 nt_loader::LDR_DATA_TABLE_ENTRY
* entry
=
142 nt_loader::GetLoaderEntry(module
.hModule
);
143 ASSERT_TRUE(entry
!= NULL
);
144 EXPECT_EQ(module
.hModule
, reinterpret_cast<HMODULE
>(entry
->DllBase
));
145 EXPECT_STREQ(module
.szModule
,
146 FromUnicodeString(entry
->BaseDllName
).c_str());
147 EXPECT_STREQ(module
.szExePath
,
148 FromUnicodeString(entry
->FullDllName
).c_str());
150 ULONG flags
= entry
->Flags
;
152 // All entries should have this flag set.
153 EXPECT_TRUE(flags
& LDRP_ENTRY_PROCESSED
);
155 if (0 == (flags
& LDRP_IMAGE_DLL
)) {
156 // TODO(siggi): write a test to assert this holds true for loading
157 // non-DLL, e.g. exe image files.
158 // Dlls have the LDRP_IMAGE_DLL flag set, any module that doesn't
159 // have that flag has to be the main executable.
160 EXPECT_TRUE(module
.hModule
== ::GetModuleHandle(NULL
));
162 // Since we're not currently loading any modules, all loaded
163 // modules should either have the LDRP_PROCESS_ATTACH_CALLED,
164 // or a NULL entrypoint.
165 if (entry
->EntryPoint
== NULL
) {
166 EXPECT_FALSE(flags
& LDRP_PROCESS_ATTACH_CALLED
);
168 // Shimeng.dll is an exception to the above, it's loaded
169 // in a special way, see e.g. http://www.alex-ionescu.com/?p=41
171 bool is_shimeng
= LowerCaseEqualsASCII(
172 FromUnicodeString(entry
->BaseDllName
), "shimeng.dll");
174 EXPECT_TRUE(is_shimeng
|| (flags
& LDRP_PROCESS_ATTACH_CALLED
));
177 } while (::Module32Next(snap
.Get(), &module
));
182 typedef void (*ExceptionFunction
)(EXCEPTION_POINTERS
* ex_ptrs
);
184 class NtLoaderTest
: public testing::Test
{
186 NtLoaderTest() : veh_id_(NULL
), exception_function_(NULL
) {
187 EXPECT_EQ(NULL
, current_
);
192 EXPECT_TRUE(this == current_
);
197 veh_id_
= ::AddVectoredExceptionHandler(FALSE
, &ExceptionHandler
);
198 EXPECT_TRUE(veh_id_
!= NULL
);
200 // Clear the crash DLL environment.
201 scoped_ptr
<base::Environment
> env(base::Environment::Create());
202 env
->UnSetVar(WideToASCII(kCrashOnLoadMode
).c_str());
203 env
->UnSetVar(WideToASCII(kCrashOnUnloadMode
).c_str());
208 EXPECT_NE(0, ::RemoveVectoredExceptionHandler(veh_id_
));
210 // Clear the crash DLL environment.
211 scoped_ptr
<base::Environment
> env(base::Environment::Create());
212 env
->UnSetVar(WideToASCII(kCrashOnLoadMode
).c_str());
213 env
->UnSetVar(WideToASCII(kCrashOnUnloadMode
).c_str());
216 void set_exception_function(ExceptionFunction func
) {
217 exception_function_
= func
;
221 static LONG NTAPI
ExceptionHandler(EXCEPTION_POINTERS
* ex_ptrs
){
222 // Dispatch the exception to any exception function,
223 // but only on the main thread.
224 if (main_thread_
== ::GetCurrentThreadId() &&
226 current_
->exception_function_
!= NULL
)
227 current_
->exception_function_(ex_ptrs
);
229 return ExceptionContinueSearch
;
233 ExceptionFunction exception_function_
;
235 static NtLoaderTest
* current_
;
236 static DWORD main_thread_
;
239 NtLoaderTest
* NtLoaderTest::current_
= NULL
;
240 DWORD
NtLoaderTest::main_thread_
= ::GetCurrentThreadId();
244 static int exceptions_handled
= 0;
245 static void OnCrashDuringLoadLibrary(EXCEPTION_POINTERS
* ex_ptrs
) {
246 ASSERT_EQ(STATUS_ACCESS_VIOLATION
, ex_ptrs
->ExceptionRecord
->ExceptionCode
);
247 ASSERT_EQ(2, ex_ptrs
->ExceptionRecord
->NumberParameters
);
248 ASSERT_EQ(EXCEPTION_WRITE_FAULT
,
249 ex_ptrs
->ExceptionRecord
->ExceptionInformation
[0]);
250 ASSERT_EQ(kCrashAddress
,
251 ex_ptrs
->ExceptionRecord
->ExceptionInformation
[1]);
253 // Bump the exceptions count.
254 exceptions_handled
++;
256 EXPECT_TRUE(OwnsLoaderLock());
258 HMODULE crash_dll
= ::GetModuleHandle(kCrashDllName
);
259 ASSERT_TRUE(crash_dll
!= NULL
);
261 nt_loader::LDR_DATA_TABLE_ENTRY
* entry
= GetLoaderEntry(crash_dll
);
262 ASSERT_TRUE(entry
!= NULL
);
263 ASSERT_EQ(0, entry
->Flags
& LDRP_PROCESS_ATTACH_CALLED
);
266 TEST_F(NtLoaderTest
, CrashOnLoadLibrary
) {
267 exceptions_handled
= 0;
268 set_exception_function(OnCrashDuringLoadLibrary
);
270 // Setup to crash on load.
271 scoped_ptr
<base::Environment
> env(base::Environment::Create());
272 env
->SetVar(WideToASCII(kCrashOnLoadMode
).c_str(), "1");
275 HMODULE module
= ::LoadLibrary(kCrashDllName
);
276 DWORD err
= ::GetLastError();
277 EXPECT_EQ(NULL
, module
);
278 EXPECT_EQ(ERROR_NOACCESS
, err
);
279 EXPECT_EQ(1, exceptions_handled
);
282 ::FreeLibrary(module
);
285 static void OnCrashDuringUnloadLibrary(EXCEPTION_POINTERS
* ex_ptrs
) {
286 ASSERT_EQ(STATUS_ACCESS_VIOLATION
, ex_ptrs
->ExceptionRecord
->ExceptionCode
);
287 ASSERT_EQ(2, ex_ptrs
->ExceptionRecord
->NumberParameters
);
288 ASSERT_EQ(EXCEPTION_WRITE_FAULT
,
289 ex_ptrs
->ExceptionRecord
->ExceptionInformation
[0]);
290 ASSERT_EQ(kCrashAddress
,
291 ex_ptrs
->ExceptionRecord
->ExceptionInformation
[1]);
293 // Bump the exceptions count.
294 exceptions_handled
++;
296 EXPECT_TRUE(OwnsLoaderLock());
298 HMODULE crash_dll
= ::GetModuleHandle(kCrashDllName
);
299 ASSERT_TRUE(crash_dll
== NULL
);
301 nt_loader::LDR_DATA_TABLE_ENTRY
* entry
= GetLoaderEntry(crash_dll
);
302 ASSERT_TRUE(entry
== NULL
);
305 TEST_F(NtLoaderTest
, CrashOnUnloadLibrary
) {
306 // Setup to crash on unload.
307 scoped_ptr
<base::Environment
> env(base::Environment::Create());
308 env
->SetVar(WideToASCII(kCrashOnUnloadMode
).c_str(), "1");
311 HMODULE module
= ::LoadLibrary(kCrashDllName
);
312 EXPECT_TRUE(module
!= NULL
);
314 exceptions_handled
= 0;
315 set_exception_function(OnCrashDuringUnloadLibrary
);
317 // We should crash during unload.
319 ::FreeLibrary(module
);
321 EXPECT_EQ(1, exceptions_handled
);