OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome_frame/crash_reporting/nt_loader.h" | |
6 | |
7 #include <tlhelp32.h> | |
8 #include <winnt.h> | |
9 | |
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/message_loop.h" | |
16 #include "base/strings/string_util.h" | |
17 #include "base/strings/utf_string_conversions.h" | |
18 #include "base/sys_info.h" | |
19 #include "base/threading/thread.h" | |
20 #include "base/win/scoped_handle.h" | |
21 #include "chrome_frame/crash_reporting/crash_dll.h" | |
22 #include "gtest/gtest.h" | |
23 | |
24 namespace { | |
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; | |
28 if (debug) { | |
29 ASSERT_EQ(RTL_CRITSECT_TYPE, debug->Type); | |
30 ASSERT_EQ(critsec, debug->CriticalSection); | |
31 } | |
32 | |
33 // TODO(siggi): assert on the semaphore handle & object type? | |
34 } | |
35 | |
36 class ScopedEnterCriticalSection { | |
37 public: | |
38 explicit ScopedEnterCriticalSection(CRITICAL_SECTION* critsec) | |
39 : critsec_(critsec) { | |
40 ::EnterCriticalSection(critsec_); | |
41 } | |
42 | |
43 ~ScopedEnterCriticalSection() { | |
44 ::LeaveCriticalSection(critsec_); | |
45 } | |
46 | |
47 private: | |
48 CRITICAL_SECTION* critsec_; | |
49 }; | |
50 | |
51 std::wstring FromUnicodeString(const UNICODE_STRING& str) { | |
52 return std::wstring(str.Buffer, str.Length / sizeof(str.Buffer[0])); | |
53 } | |
54 | |
55 } // namespace | |
56 | |
57 using namespace nt_loader; | |
58 | |
59 TEST(NtLoader, OwnsCriticalSection) { | |
60 // Use of Thread requires an atexit manager. | |
61 base::AtExitManager at_exit; | |
62 | |
63 CRITICAL_SECTION cs = {}; | |
64 ::InitializeCriticalSection(&cs); | |
65 EXPECT_FALSE(OwnsCriticalSection(&cs)); | |
66 | |
67 // Enter the critsec and assert we own it. | |
68 { | |
69 ScopedEnterCriticalSection lock1(&cs); | |
70 | |
71 EXPECT_TRUE(OwnsCriticalSection(&cs)); | |
72 | |
73 // Re-enter the critsec and assert we own it. | |
74 ScopedEnterCriticalSection lock2(&cs); | |
75 | |
76 EXPECT_TRUE(OwnsCriticalSection(&cs)); | |
77 } | |
78 | |
79 // Should no longer own it. | |
80 EXPECT_FALSE(OwnsCriticalSection(&cs)); | |
81 | |
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)); | |
87 | |
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())); | |
91 | |
92 ASSERT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE)); | |
93 | |
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)); | |
98 | |
99 // Make the other thread release it. | |
100 other.message_loop()->PostTask( | |
101 FROM_HERE, base::Bind(::LeaveCriticalSection, &cs)); | |
102 | |
103 other.Stop(); | |
104 | |
105 ::DeleteCriticalSection(&cs); | |
106 } | |
107 | |
108 TEST(NtLoader, GetLoaderLock) { | |
109 CRITICAL_SECTION* loader_lock = GetLoaderLock(); | |
110 | |
111 AssertIsCriticalSection(loader_lock); | |
112 | |
113 // We should be able to enter and leave the loader's lock without trouble. | |
114 EnterCriticalSection(loader_lock); | |
115 LeaveCriticalSection(loader_lock); | |
116 } | |
117 | |
118 TEST(NtLoader, OwnsLoaderLock) { | |
119 CRITICAL_SECTION* loader_lock = GetLoaderLock(); | |
120 | |
121 EXPECT_FALSE(OwnsLoaderLock()); | |
122 EnterCriticalSection(loader_lock); | |
123 EXPECT_TRUE(OwnsLoaderLock()); | |
124 LeaveCriticalSection(loader_lock); | |
125 EXPECT_FALSE(OwnsLoaderLock()); | |
126 } | |
127 | |
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); | |
133 | |
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)); | |
138 do { | |
139 ScopedEnterCriticalSection lock(GetLoaderLock()); | |
140 | |
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()); | |
149 | |
150 ULONG flags = entry->Flags; | |
151 | |
152 // All entries should have this flag set. | |
153 EXPECT_TRUE(flags & LDRP_ENTRY_PROCESSED); | |
154 | |
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)); | |
161 } else { | |
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); | |
167 } else { | |
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 | |
170 // for details. | |
171 bool is_shimeng = LowerCaseEqualsASCII( | |
172 FromUnicodeString(entry->BaseDllName), "shimeng.dll"); | |
173 | |
174 EXPECT_TRUE(is_shimeng || (flags & LDRP_PROCESS_ATTACH_CALLED)); | |
175 } | |
176 } | |
177 } while (::Module32Next(snap.Get(), &module)); | |
178 } | |
179 | |
180 namespace { | |
181 | |
182 typedef void (*ExceptionFunction)(EXCEPTION_POINTERS* ex_ptrs); | |
183 | |
184 class NtLoaderTest: public testing::Test { | |
185 public: | |
186 NtLoaderTest() : veh_id_(NULL), exception_function_(NULL) { | |
187 EXPECT_EQ(NULL, current_); | |
188 current_ = this; | |
189 } | |
190 | |
191 ~NtLoaderTest() { | |
192 EXPECT_TRUE(this == current_); | |
193 current_ = NULL; | |
194 } | |
195 | |
196 void SetUp() { | |
197 veh_id_ = ::AddVectoredExceptionHandler(FALSE, &ExceptionHandler); | |
198 EXPECT_TRUE(veh_id_ != NULL); | |
199 | |
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()); | |
204 } | |
205 | |
206 void TearDown() { | |
207 if (veh_id_ != NULL) | |
208 EXPECT_NE(0, ::RemoveVectoredExceptionHandler(veh_id_)); | |
209 | |
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()); | |
214 } | |
215 | |
216 void set_exception_function(ExceptionFunction func) { | |
217 exception_function_ = func; | |
218 } | |
219 | |
220 private: | |
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() && | |
225 current_ != NULL && | |
226 current_->exception_function_ != NULL) | |
227 current_->exception_function_(ex_ptrs); | |
228 | |
229 return ExceptionContinueSearch; | |
230 } | |
231 | |
232 void* veh_id_; | |
233 ExceptionFunction exception_function_; | |
234 | |
235 static NtLoaderTest* current_; | |
236 static DWORD main_thread_; | |
237 }; | |
238 | |
239 NtLoaderTest* NtLoaderTest::current_ = NULL; | |
240 DWORD NtLoaderTest::main_thread_ = ::GetCurrentThreadId(); | |
241 | |
242 } // namespace | |
243 | |
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]); | |
252 | |
253 // Bump the exceptions count. | |
254 exceptions_handled++; | |
255 | |
256 EXPECT_TRUE(OwnsLoaderLock()); | |
257 | |
258 HMODULE crash_dll = ::GetModuleHandle(kCrashDllName); | |
259 ASSERT_TRUE(crash_dll != NULL); | |
260 | |
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); | |
264 } | |
265 | |
266 TEST_F(NtLoaderTest, CrashOnLoadLibrary) { | |
267 exceptions_handled = 0; | |
268 set_exception_function(OnCrashDuringLoadLibrary); | |
269 | |
270 // Setup to crash on load. | |
271 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
272 env->SetVar(WideToASCII(kCrashOnLoadMode).c_str(), "1"); | |
273 | |
274 // And load it. | |
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); | |
280 | |
281 if (module != NULL) | |
282 ::FreeLibrary(module); | |
283 } | |
284 | |
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]); | |
292 | |
293 // Bump the exceptions count. | |
294 exceptions_handled++; | |
295 | |
296 EXPECT_TRUE(OwnsLoaderLock()); | |
297 | |
298 HMODULE crash_dll = ::GetModuleHandle(kCrashDllName); | |
299 ASSERT_TRUE(crash_dll == NULL); | |
300 | |
301 nt_loader::LDR_DATA_TABLE_ENTRY* entry = GetLoaderEntry(crash_dll); | |
302 ASSERT_TRUE(entry == NULL); | |
303 } | |
304 | |
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"); | |
309 | |
310 // And load it. | |
311 HMODULE module = ::LoadLibrary(kCrashDllName); | |
312 EXPECT_TRUE(module != NULL); | |
313 | |
314 exceptions_handled = 0; | |
315 set_exception_function(OnCrashDuringUnloadLibrary); | |
316 | |
317 // We should crash during unload. | |
318 if (module != NULL) | |
319 ::FreeLibrary(module); | |
320 | |
321 EXPECT_EQ(1, exceptions_handled); | |
322 } | |
OLD | NEW |