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