Home | History | Annotate | Download | only in windows
      1 /* Copyright (c) 2007, Google Inc.
      2  * All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  *
     30  * ---
     31  * Author: Craig Silverstein
     32  */
     33 
     34 #ifndef _WIN32
     35 # error You should only be including windows/port.cc in a windows environment!
     36 #endif
     37 
     38 #define NOMINMAX       // so std::max, below, compiles correctly
     39 #include <config.h>
     40 #include <string.h>    // for strlen(), memset(), memcmp()
     41 #include <assert.h>
     42 #include <stdarg.h>    // for va_list, va_start, va_end
     43 #include <windows.h>
     44 #include <algorithm>
     45 #include "port.h"
     46 #include "base/logging.h"
     47 #include "base/spinlock.h"
     48 #include "internal_logging.h"
     49 #include "system-alloc.h"
     50 
     51 // -----------------------------------------------------------------------
     52 // Basic libraries
     53 
     54 int getpagesize() {
     55   static int pagesize = 0;
     56   if (pagesize == 0) {
     57     SYSTEM_INFO system_info;
     58     GetSystemInfo(&system_info);
     59     pagesize = std::max(system_info.dwPageSize,
     60                         system_info.dwAllocationGranularity);
     61   }
     62   return pagesize;
     63 }
     64 
     65 extern "C" PERFTOOLS_DLL_DECL void* __sbrk(ptrdiff_t increment) {
     66   LOG(FATAL, "Windows doesn't implement sbrk!\n");
     67   return NULL;
     68 }
     69 
     70 // We need to write to 'stderr' without having windows allocate memory.
     71 // The safest way is via a low-level call like WriteConsoleA().  But
     72 // even then we need to be sure to print in small bursts so as to not
     73 // require memory allocation.
     74 extern "C" PERFTOOLS_DLL_DECL void WriteToStderr(const char* buf, int len) {
     75   // Looks like windows allocates for writes of >80 bytes
     76   for (int i = 0; i < len; i += 80) {
     77     write(STDERR_FILENO, buf + i, std::min(80, len - i));
     78   }
     79 }
     80 
     81 
     82 // -----------------------------------------------------------------------
     83 // Threads code
     84 
     85 // Declared (not extern "C") in thread_cache.h
     86 bool CheckIfKernelSupportsTLS() {
     87   // TODO(csilvers): return true (all win's since win95, at least, support this)
     88   return false;
     89 }
     90 
     91 // Windows doesn't support pthread_key_create's destr_function, and in
     92 // fact it's a bit tricky to get code to run when a thread exits.  This
     93 // is cargo-cult magic from http://www.codeproject.com/threads/tls.asp.
     94 // This code is for VC++ 7.1 and later; VC++ 6.0 support is possible
     95 // but more busy-work -- see the webpage for how to do it.  If all
     96 // this fails, we could use DllMain instead.  The big problem with
     97 // DllMain is it doesn't run if this code is statically linked into a
     98 // binary (it also doesn't run if the thread is terminated via
     99 // TerminateThread, which if we're lucky this routine does).
    100 
    101 // Force a reference to _tls_used to make the linker create the TLS directory
    102 // if it's not already there (that is, even if __declspec(thread) is not used).
    103 // Force a reference to p_thread_callback_tcmalloc and p_process_term_tcmalloc
    104 // to prevent whole program optimization from discarding the variables.
    105 #ifdef _MSC_VER
    106 #if defined(_M_IX86)
    107 #pragma comment(linker, "/INCLUDE:__tls_used")
    108 #pragma comment(linker, "/INCLUDE:_p_thread_callback_tcmalloc")
    109 #pragma comment(linker, "/INCLUDE:_p_process_term_tcmalloc")
    110 #elif defined(_M_X64)
    111 #pragma comment(linker, "/INCLUDE:_tls_used")
    112 #pragma comment(linker, "/INCLUDE:p_thread_callback_tcmalloc")
    113 #pragma comment(linker, "/INCLUDE:p_process_term_tcmalloc")
    114 #endif
    115 #endif
    116 
    117 // When destr_fn eventually runs, it's supposed to take as its
    118 // argument the tls-value associated with key that pthread_key_create
    119 // creates.  (Yeah, it sounds confusing but it's really not.)  We
    120 // store the destr_fn/key pair in this data structure.  Because we
    121 // store this in a single var, this implies we can only have one
    122 // destr_fn in a program!  That's enough in practice.  If asserts
    123 // trigger because we end up needing more, we'll have to turn this
    124 // into an array.
    125 struct DestrFnClosure {
    126   void (*destr_fn)(void*);
    127   pthread_key_t key_for_destr_fn_arg;
    128 };
    129 
    130 static DestrFnClosure destr_fn_info;   // initted to all NULL/0.
    131 
    132 static int on_process_term(void) {
    133   if (destr_fn_info.destr_fn) {
    134     void *ptr = TlsGetValue(destr_fn_info.key_for_destr_fn_arg);
    135     // This shouldn't be necessary, but in Release mode, Windows
    136     // sometimes trashes the pointer in the TLS slot, so we need to
    137     // remove the pointer from the TLS slot before the thread dies.
    138     TlsSetValue(destr_fn_info.key_for_destr_fn_arg, NULL);
    139     if (ptr)  // pthread semantics say not to call if ptr is NULL
    140       (*destr_fn_info.destr_fn)(ptr);
    141   }
    142   return 0;
    143 }
    144 
    145 static void NTAPI on_tls_callback(HINSTANCE h, DWORD dwReason, PVOID pv) {
    146   if (dwReason == DLL_THREAD_DETACH) {   // thread is being destroyed!
    147     on_process_term();
    148   }
    149 }
    150 
    151 #ifdef _MSC_VER
    152 
    153 // extern "C" suppresses C++ name mangling so we know the symbol names
    154 // for the linker /INCLUDE:symbol pragmas above.
    155 extern "C" {
    156 // This tells the linker to run these functions.
    157 // We use CRT$XLY instead of CRT$XLB to ensure we're called LATER in sequence.
    158 #pragma section(".CRT$XLY", read)
    159 _declspec(allocate(".CRT$XLY")) \
    160   void (NTAPI *p_thread_callback_tcmalloc)(
    161     HINSTANCE h, DWORD dwReason, PVOID pv) = on_tls_callback;
    162 #pragma section(".CRT$XTU", read)
    163 _declspec(allocate(".CRT$XTU")) \
    164   int (*p_process_term_tcmalloc)(void) = on_process_term;
    165 }  // extern "C"
    166 
    167 #else  // #ifdef _MSC_VER  [probably msys/mingw]
    168 
    169 // We have to try the DllMain solution here, because we can't use the
    170 // msvc-specific pragmas.
    171 BOOL WINAPI DllMain(HINSTANCE h, DWORD dwReason, PVOID pv) {
    172   if (dwReason == DLL_THREAD_DETACH)
    173     on_tls_callback(h, dwReason, pv);
    174   else if (dwReason == DLL_PROCESS_DETACH)
    175     on_process_term();
    176   return TRUE;
    177 }
    178 
    179 #endif  // #ifdef _MSC_VER
    180 
    181 extern "C" pthread_key_t PthreadKeyCreate(void (*destr_fn)(void*)) {
    182   // Semantics are: we create a new key, and then promise to call
    183   // destr_fn with TlsGetValue(key) when the thread is destroyed
    184   // (as long as TlsGetValue(key) is not NULL).
    185   pthread_key_t key = TlsAlloc();
    186   if (destr_fn) {   // register it
    187     // If this assert fails, we'll need to support an array of destr_fn_infos
    188     assert(destr_fn_info.destr_fn == NULL);
    189     destr_fn_info.destr_fn = destr_fn;
    190     destr_fn_info.key_for_destr_fn_arg = key;
    191   }
    192   return key;
    193 }
    194 
    195 // NOTE: this is Win2K and later.  For Win98 we could use a CRITICAL_SECTION...
    196 extern "C" int perftools_pthread_once(pthread_once_t *once_control,
    197                                       void (*init_routine)(void)) {
    198   // Try for a fast path first. Note: this should be an acquire semantics read.
    199   // It is on x86 and x64, where Windows runs.
    200   if (*once_control != 1) {
    201     while (true) {
    202       switch (InterlockedCompareExchange(once_control, 2, 0)) {
    203         case 0:
    204           init_routine();
    205           InterlockedExchange(once_control, 1);
    206           return 0;
    207         case 1:
    208           // The initializer has already been executed
    209           return 0;
    210         default:
    211           // The initializer is being processed by another thread
    212           SwitchToThread();
    213       }
    214     }
    215   }
    216   return 0;
    217 }
    218 
    219 
    220 // -----------------------------------------------------------------------
    221 // These functions replace system-alloc.cc
    222 
    223 // This is mostly like MmapSysAllocator::Alloc, except it does these weird
    224 // munmap's in the middle of the page, which is forbidden in windows.
    225 extern void* TCMalloc_SystemAlloc(size_t size, size_t *actual_size,
    226                                   size_t alignment) {
    227   // Align on the pagesize boundary
    228   const int pagesize = getpagesize();
    229   if (alignment < pagesize) alignment = pagesize;
    230   size = ((size + alignment - 1) / alignment) * alignment;
    231 
    232   // Report the total number of bytes the OS actually delivered.  This might be
    233   // greater than |size| because of alignment concerns.  The full size is
    234   // necessary so that adjacent spans can be coalesced.
    235   // TODO(antonm): proper processing of alignments
    236   // in actual_size and decommitting.
    237   if (actual_size) {
    238     *actual_size = size;
    239   }
    240 
    241   // We currently do not support alignments larger than the pagesize or
    242   // alignments that are not multiples of the pagesize after being floored.
    243   // If this ability is needed it can be done by the caller (assuming it knows
    244   // the page size).
    245   assert(alignment <= pagesize);
    246 
    247   void* result = VirtualAlloc(0, size,
    248                               MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
    249   if (result == NULL)
    250     return NULL;
    251 
    252   // If the result is not aligned memory fragmentation will result which can
    253   // lead to pathological memory use.
    254   assert((reinterpret_cast<uintptr_t>(result) & (alignment - 1)) == 0);
    255 
    256   return result;
    257 }
    258 
    259 size_t TCMalloc_SystemAddGuard(void* start, size_t size) {
    260   static size_t pagesize = 0;
    261   if (pagesize == 0) {
    262     SYSTEM_INFO system_info;
    263     GetSystemInfo(&system_info);
    264     pagesize = system_info.dwPageSize;
    265   }
    266 
    267   // We know that TCMalloc_SystemAlloc will give us a correct page alignment
    268   // regardless, so we can just assert to detect erroneous callers.
    269   assert(reinterpret_cast<size_t>(start) % pagesize == 0);
    270 
    271   // Add a guard page to catch metadata corruption. We're using the
    272   // PAGE_GUARD flag rather than NO_ACCESS because we want the unique
    273   // exception in crash reports.
    274   DWORD permissions = 0;
    275   if (size > pagesize &&
    276       VirtualProtect(start, pagesize, PAGE_READONLY | PAGE_GUARD,
    277                      &permissions)) {
    278     return pagesize;
    279   }
    280 
    281   return 0;
    282 }
    283 
    284 void TCMalloc_SystemRelease(void* start, size_t length) {
    285   if (VirtualFree(start, length, MEM_DECOMMIT))
    286     return;
    287 
    288   // The decommit may fail if the memory region consists of allocations
    289   // from more than one call to VirtualAlloc.  In this case, fall back to
    290   // using VirtualQuery to retrieve the allocation boundaries and decommit
    291   // them each individually.
    292 
    293   char* ptr = static_cast<char*>(start);
    294   char* end = ptr + length;
    295   MEMORY_BASIC_INFORMATION info;
    296   while (ptr < end) {
    297     size_t resultSize = VirtualQuery(ptr, &info, sizeof(info));
    298     assert(resultSize == sizeof(info));
    299     size_t decommitSize = std::min<size_t>(info.RegionSize, end - ptr);
    300     BOOL success = VirtualFree(ptr, decommitSize, MEM_DECOMMIT);
    301     assert(success == TRUE);
    302     ptr += decommitSize;
    303   }
    304 }
    305 
    306 void TCMalloc_SystemCommit(void* start, size_t length) {
    307   if (VirtualAlloc(start, length, MEM_COMMIT, PAGE_READWRITE) == start)
    308     return;
    309 
    310   // The commit may fail if the memory region consists of allocations
    311   // from more than one call to VirtualAlloc.  In this case, fall back to
    312   // using VirtualQuery to retrieve the allocation boundaries and commit them
    313   // each individually.
    314 
    315   char* ptr = static_cast<char*>(start);
    316   char* end = ptr + length;
    317   MEMORY_BASIC_INFORMATION info;
    318   while (ptr < end) {
    319     size_t resultSize = VirtualQuery(ptr, &info, sizeof(info));
    320     assert(resultSize == sizeof(info));
    321 
    322     size_t commitSize = std::min<size_t>(info.RegionSize, end - ptr);
    323     void* newAddress = VirtualAlloc(ptr, commitSize, MEM_COMMIT,
    324                                     PAGE_READWRITE);
    325     assert(newAddress == ptr);
    326     ptr += commitSize;
    327   }
    328 }
    329 
    330 bool RegisterSystemAllocator(SysAllocator *allocator, int priority) {
    331   return false;   // we don't allow registration on windows, right now
    332 }
    333 
    334 void DumpSystemAllocatorStats(TCMalloc_Printer* printer) {
    335   // We don't dump stats on windows, right now
    336 }
    337 
    338 // The current system allocator
    339 SysAllocator* sys_alloc = NULL;
    340 
    341 
    342 // -----------------------------------------------------------------------
    343 // These functions rework existing functions of the same name in the
    344 // Google codebase.
    345 
    346 // A replacement for HeapProfiler::CleanupOldProfiles.
    347 void DeleteMatchingFiles(const char* prefix, const char* full_glob) {
    348   WIN32_FIND_DATAA found;  // that final A is for Ansi (as opposed to Unicode)
    349   HANDLE hFind = FindFirstFileA(full_glob, &found);   // A is for Ansi
    350   if (hFind != INVALID_HANDLE_VALUE) {
    351     const int prefix_length = strlen(prefix);
    352     do {
    353       const char *fname = found.cFileName;
    354       if ((strlen(fname) >= prefix_length) &&
    355           (memcmp(fname, prefix, prefix_length) == 0)) {
    356         RAW_VLOG(0, "Removing old heap profile %s\n", fname);
    357         // TODO(csilvers): we really need to unlink dirname + fname
    358         _unlink(fname);
    359       }
    360     } while (FindNextFileA(hFind, &found) != FALSE);  // A is for Ansi
    361     FindClose(hFind);
    362   }
    363 }
    364