Home | History | Annotate | Download | only in crash_reporting
      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 }
    323