blob: 1e5b586192987f42d2520d86951859ea7abd9d1e [file] [log] [blame]
/* Copyright (c) 2007, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ---
* Author: Craig Silverstein
*/
#ifndef WIN32
# error You should only be including windows/port.cc in a windows environment!
#endif
#include "config.h"
#include <string.h> // for strlen(), memset(), memcmp()
#include <assert.h>
#include <stdarg.h> // for va_list, va_start, va_end
#include <windows.h>
#include <dbghelp.h> // Provided with Microsoft Debugging Tools for Windows
#include "port.h"
#include "base/logging.h"
#include "system-alloc.h"
// -----------------------------------------------------------------------
// Basic libraries
// These call the windows _vsnprintf, but always NUL-terminate.
int safe_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
if (size == 0) // not even room for a \0?
return -1; // not what C99 says to do, but what windows does
str[size-1] = '\0';
return _vsnprintf(str, size-1, format, ap);
}
int snprintf(char *str, size_t size, const char *format, ...) {
va_list ap;
va_start(ap, format);
const int r = vsnprintf(str, size, format, ap);
va_end(ap);
return r;
}
int getpagesize() {
static int pagesize = 0;
if (pagesize == 0) {
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
pagesize = system_info.dwPageSize;
}
return pagesize;
}
extern "C" PERFTOOLS_DLL_DECL void* __sbrk(ptrdiff_t increment) {
LOG(FATAL, "Windows doesn't implement sbrk!\n");
return NULL;
}
// -----------------------------------------------------------------------
// Threads code
bool CheckIfKernelSupportsTLS() {
// TODO(csilvers): return true (all win's since win95, at least, support this)
return false;
}
// Windows doesn't support pthread_key_create's destr_function, and in
// fact it's a bit tricky to get code to run when a thread exits. This
// is cargo-cult magic from http://www.codeproject.com/threads/tls.asp.
// This code is for VC++ 7.1 and later; VC++ 6.0 support is possible
// but more busy-work -- see the webpage for how to do it. If all
// this fails, we could use DllMain instead. The big problem with
// DllMain is it doesn't run if this code is statically linked into a
// binary (it also doesn't run if the thread is terminated via
// TerminateThread, which if we're lucky this routine does).
// This makes the linker create the TLS directory if it's not already
// there (that is, even if __declspec(thead) is not used).
#pragma comment(linker, "/INCLUDE:__tls_used")
// When destr_fn eventually runs, it's supposed to take as its
// argument the tls-value associated with key that pthread_key_create
// creates. (Yeah, it sounds confusing but it's really not.) We
// store the destr_fn/key pair in this data structure. Because we
// store this in a single var, this implies we can only have one
// destr_fn in a program! That's enough in practice. If asserts
// trigger because we end up needing more, we'll have to turn this
// into an array.
struct DestrFnClosure {
void (*destr_fn)(void*);
pthread_key_t key_for_destr_fn_arg;
};
static DestrFnClosure destr_fn_info; // initted to all NULL/0.
static int on_process_term(void) {
if (destr_fn_info.destr_fn) {
void *ptr = TlsGetValue(destr_fn_info.key_for_destr_fn_arg);
if (ptr) // pthread semantics say not to call if ptr is NULL
(*destr_fn_info.destr_fn)(ptr);
}
return 0;
}
static void NTAPI on_tls_callback(HINSTANCE h, DWORD dwReason, PVOID pv) {
if (dwReason == DLL_THREAD_DETACH) { // thread is being destroyed!
on_process_term();
}
}
// This tells the linker to run these functions
#pragma data_seg(push, old_seg)
#pragma data_seg(".CRT$XLB")
static void (NTAPI *p_thread_callback)(HINSTANCE h, DWORD dwReason, PVOID pv)
= on_tls_callback;
#pragma data_seg(".CRT$XTU")
static int (*p_process_term)(void) = on_process_term;
#pragma data_seg(pop, old_seg)
pthread_key_t PthreadKeyCreate(void (*destr_fn)(void*)) {
// Semantics are: we create a new key, and then promise to call
// destr_fn with TlsGetValue(key) when the thread is destroyed
// (as long as TlsGetValue(key) is not NULL).
pthread_key_t key = TlsAlloc();
if (destr_fn) { // register it
// If this assert fails, we'll need to support an array of destr_fn_infos
assert(destr_fn_info.destr_fn == NULL);
destr_fn_info.destr_fn = destr_fn;
destr_fn_info.key_for_destr_fn_arg = key;
}
return key;
}
// This replaces testutil.cc
struct FunctionAndId {
void (*ptr_to_function)(int);
int id;
};
// This helper function has the signature that pthread_create wants.
DWORD WINAPI RunFunctionInThread(LPVOID ptr_to_ptr_to_fn) {
(**static_cast<void (**)()>(ptr_to_ptr_to_fn))(); // runs fn
return NULL;
}
DWORD WINAPI RunFunctionInThreadWithId(LPVOID ptr_to_fnid) {
FunctionAndId* fn_and_id = static_cast<FunctionAndId*>(ptr_to_fnid);
(*fn_and_id->ptr_to_function)(fn_and_id->id); // runs fn
return NULL;
}
void RunManyInThread(void (*fn)(), int count) {
DWORD dummy;
HANDLE* hThread = new HANDLE[count];
for (int i = 0; i < count; i++) {
hThread[i] = CreateThread(NULL, 0, RunFunctionInThread, &fn, 0, &dummy);
if (hThread[i] == NULL) ExitProcess(i);
}
WaitForMultipleObjects(count, hThread, TRUE, INFINITE);
for (int i = 0; i < count; i++) {
CloseHandle(hThread[i]);
}
delete[] hThread;
}
void RunInThread(void (*fn)()) {
RunManyInThread(fn, 1);
}
void RunManyInThreadWithId(void (*fn)(int), int count, int stacksize) {
DWORD dummy;
HANDLE* hThread = new HANDLE[count];
FunctionAndId* fn_and_ids = new FunctionAndId[count];
for (int i = 0; i < count; i++) {
fn_and_ids[i].ptr_to_function = fn;
fn_and_ids[i].id = i;
hThread[i] = CreateThread(NULL, stacksize, RunFunctionInThreadWithId,
&fn_and_ids[i], 0, &dummy);
if (hThread[i] == NULL) ExitProcess(i);
}
WaitForMultipleObjects(count, hThread, TRUE, INFINITE);
for (int i = 0; i < count; i++) {
CloseHandle(hThread[i]);
}
delete[] fn_and_ids;
delete[] hThread;
}
// -----------------------------------------------------------------------
// These functions replace system-alloc.cc
static SpinLock alloc_lock(SpinLock::LINKER_INITIALIZED);
// This is mostly like MmapSysAllocator::Alloc, except it does these weird
// munmap's in the middle of the page, which is forbidden in windows.
extern void* TCMalloc_SystemAlloc(size_t size, size_t *actual_size,
size_t alignment) {
// Safest is to make actual_size same as input-size.
if (actual_size) {
*actual_size = size;
}
SpinLockHolder sh(&alloc_lock);
// Align on the pagesize boundary
const int pagesize = getpagesize();
if (alignment < pagesize) alignment = pagesize;
size = ((size + alignment - 1) / alignment) * alignment;
// Ask for extra memory if alignment > pagesize
size_t extra = 0;
if (alignment > pagesize) {
extra = alignment - pagesize;
}
void* result = VirtualAlloc(0, size + extra,
MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
if (result == NULL)
return NULL;
// Adjust the return memory so it is aligned
uintptr_t ptr = reinterpret_cast<uintptr_t>(result);
size_t adjust = 0;
if ((ptr & (alignment - 1)) != 0) {
adjust = alignment - (ptr & (alignment - 1));
}
ptr += adjust;
return reinterpret_cast<void*>(ptr);
}
void TCMalloc_SystemRelease(void* start, size_t length) {
// TODO(csilvers): should I be calling VirtualFree here?
}
bool RegisterSystemAllocator(SysAllocator *allocator, int priority) {
return false; // we don't allow registration on windows, right now
}
// -----------------------------------------------------------------------
// These functions rework existing functions of the same name in the
// Google codebase.
// A replacement for HeapProfiler::CleanupOldProfiles.
void DeleteMatchingFiles(const char* prefix, const char* full_glob) {
WIN32_FIND_DATAA found; // that final A is for Ansi (as opposed to Unicode)
HANDLE hFind = FindFirstFileA(full_glob, &found); // A is for Ansi
if (hFind != INVALID_HANDLE_VALUE) {
const int prefix_length = strlen(prefix);
do {
const char *fname = found.cFileName;
if ((strlen(fname) >= prefix_length) &&
(memcmp(fname, prefix, prefix_length) == 0)) {
RAW_VLOG(0, "Removing old heap profile %s\n", fname);
// TODO(csilvers): we really need to unlink dirname + fname
_unlink(fname);
}
} while (FindNextFileA(hFind, &found) != FALSE); // A is for Ansi
FindClose(hFind);
}
}
// -----------------------------------------------------------------------
// Stacktrace functionality
static SpinLock get_stack_trace_lock(SpinLock::LINKER_INITIALIZED);
// TODO(csilvers): This will need some loving care to get it working.
// It's also not super-fast.
// Here are some notes from mmentovai:
// ----
// Line 140: GetThreadContext(hThread, &context);
// Doesn't work. GetThreadContext only returns the saved thread
// context, which is only valid as a present-state snapshot for
// suspended threads. For running threads, it's just going to be the
// context from the last time the scheduler started the thread. You
// obviously can't suspend the current thread to grab its context.
//
// You can call RtlCaptureContext if you don't care about Win2k or
// earlier. If you do, you'll need to provide CPU-specific code
// (usually a little bit of _asm and a function call) to grab the
// values of important registers.
// ------------------------------------
// Line 144: frame.AddrPC.Offset = context.Eip;
// This (and other uses of context members, and
// IMAGE_FILE_MACHINE_I386) is x86(-32)-only. I see a comment about
// that below, but you should probably mention it more prominently.
//
// (I don't think there's anything nonportable about
// frame.AddrPC.Offset below.)
// ------------------------------------
// Line 148:
// You also need to set frame.AddrStack. Its offset field gets the
// value of context.Esp (on x86). The initial stack pointer can be
// crucial to a stackwalk in the FPO cases I mentioned.
int GetStackTrace(void** result, int max_depth, int skip_count) {
int n = 0;
#if 0 // TODO(csilvers): figure out how to get this to work
SpinLockHolder holder(&get_stack_trace_lock);
HANDLE hProc = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
CONTEXT context;
memset(&context, 0, sizeof(context));
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(hThread, &context);
STACKFRAME64 frame;
memset(&frame, 0, sizeof(frame));
frame.AddrPC.Offset = context.Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
while (StackWalk64(IMAGE_FILE_MACHINE_I386,
hProc,
hThread,
&frame,
&context,
0,
SymFunctionTableAccess64,
SymGetModuleBase64,
0)
&& n < max_depth) {
if (skip_count > 0) {
skip_count--;
} else {
result[n++] = (void*)frame.AddrPC.Offset; // Might break x64 portability
}
}
#endif
return n;
}