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
54int 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
65extern "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.
74extern "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
86bool 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.
125struct DestrFnClosure {
126  void (*destr_fn)(void*);
127  pthread_key_t key_for_destr_fn_arg;
128};
129
130static DestrFnClosure destr_fn_info;   // initted to all NULL/0.
131
132static 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
145static 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.
155extern "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.
171BOOL 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
181extern "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...
196extern "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.
225extern 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
259size_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
284void 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
306void 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
330bool RegisterSystemAllocator(SysAllocator *allocator, int priority) {
331  return false;   // we don't allow registration on windows, right now
332}
333
334void DumpSystemAllocatorStats(TCMalloc_Printer* printer) {
335  // We don't dump stats on windows, right now
336}
337
338// The current system allocator
339SysAllocator* 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.
347void 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