From 56bd5b59b49766c76e846d698def41b2f10233c4 Mon Sep 17 00:00:00 2001 From: janwas Date: Sun, 23 Sep 2007 10:15:28 +0000 Subject: [PATCH] sysdep cleanup: - factor cursor and clipboard out of sysdep.h - remove empty sysdep.cpp - wcpu: remove profiler code (it's in wprofiler.cpp if we ever need it) - robustify clipboard code - wsysdep: move code to determine app window into wutil; it's now used by the clipboard code This was SVN commit r5364. --- source/gui/CInput.cpp | 2 +- source/lib/res/graphics/cursor.cpp | 2 +- source/lib/sysdep/clipboard.h | 13 ++ source/lib/sysdep/cursor.h | 26 +++ source/lib/sysdep/sysdep.cpp | 14 -- source/lib/sysdep/sysdep.h | 51 ------ source/lib/sysdep/win/wclipboard.cpp | 98 +++++++++++ source/lib/sysdep/win/wcpu.cpp | 240 -------------------------- source/lib/sysdep/win/wcursor.cpp | 107 ++++++++++++ source/lib/sysdep/win/wprofiler.cpp | 220 ++++++++++++++++++++++++ source/lib/sysdep/win/wsysdep.cpp | 246 +-------------------------- source/lib/sysdep/win/wutil.cpp | 39 +++++ source/lib/sysdep/win/wutil.h | 10 ++ source/ps/CConsole.cpp | 2 +- 14 files changed, 522 insertions(+), 548 deletions(-) create mode 100644 source/lib/sysdep/clipboard.h create mode 100644 source/lib/sysdep/cursor.h delete mode 100644 source/lib/sysdep/sysdep.cpp create mode 100644 source/lib/sysdep/win/wclipboard.cpp create mode 100644 source/lib/sysdep/win/wcursor.cpp create mode 100644 source/lib/sysdep/win/wprofiler.cpp diff --git a/source/gui/CInput.cpp b/source/gui/CInput.cpp index 719183a78d..a1e18bb09b 100644 --- a/source/gui/CInput.cpp +++ b/source/gui/CInput.cpp @@ -10,7 +10,7 @@ CInput #include "lib/ogl.h" #include "lib/res/graphics/unifont.h" -#include "lib/sysdep/sysdep.h" +#include "lib/sysdep/clipboard.h" #include "ps/Hotkey.h" #include "ps/CLogger.h" diff --git a/source/lib/res/graphics/cursor.cpp b/source/lib/res/graphics/cursor.cpp index 08d304704d..a9c52ef923 100644 --- a/source/lib/res/graphics/cursor.cpp +++ b/source/lib/res/graphics/cursor.cpp @@ -24,7 +24,7 @@ #endif #include "lib/ogl.h" -#include "lib/sysdep/sysdep.h" // sys_cursor_* +#include "lib/sysdep/cursor.h" #include "lib/res/res.h" #include "ogl_tex.h" diff --git a/source/lib/sysdep/clipboard.h b/source/lib/sysdep/clipboard.h new file mode 100644 index 0000000000..b7cd1d0ddf --- /dev/null +++ b/source/lib/sysdep/clipboard.h @@ -0,0 +1,13 @@ + +// "copy" text into the clipboard. replaces previous contents. +extern LibError sys_clipboard_set(const wchar_t* text); + +// allow "pasting" from clipboard. returns the current contents if they +// can be represented as text, otherwise 0. +// when it is no longer needed, the returned pointer must be freed via +// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) +extern wchar_t* sys_clipboard_get(void); + +// frees memory used by , which must have been returned by +// sys_clipboard_get. see note above. +extern LibError sys_clipboard_free(wchar_t* copy); diff --git a/source/lib/sysdep/cursor.h b/source/lib/sysdep/cursor.h new file mode 100644 index 0000000000..48c17931d0 --- /dev/null +++ b/source/lib/sysdep/cursor.h @@ -0,0 +1,26 @@ + +// note: these do not warn on error; that is left to the caller. + +// creates a cursor from the given image. +// w, h specify image dimensions [pixels]. limit is implementation- +// dependent; 32x32 is typical and safe. +// bgra_img is the cursor image (BGRA format, bottom-up). +// it is no longer needed and can be freed after this call returns. +// hotspot (hx,hy) is the offset from its upper-left corner to the +// position where mouse clicks are registered. +// cursor is only valid when INFO::OK is returned; in that case, it must be +// sys_cursor_free-ed when no longer needed. +extern LibError sys_cursor_create(uint w, uint h, void* bgra_img, + uint hx, uint hy, void** cursor); + +// create a fully transparent cursor (i.e. one that when passed to set hides +// the system cursor) +extern LibError sys_cursor_create_empty(void **cursor); + +// replaces the current system cursor with the one indicated. need only be +// called once per cursor; pass 0 to restore the default. +extern LibError sys_cursor_set(void* cursor); + +// destroys the indicated cursor and frees its resources. if it is +// currently the system cursor, the default cursor is restored first. +extern LibError sys_cursor_free(void* cursor); diff --git a/source/lib/sysdep/sysdep.cpp b/source/lib/sysdep/sysdep.cpp deleted file mode 100644 index 334769e170..0000000000 --- a/source/lib/sysdep/sysdep.cpp +++ /dev/null @@ -1,14 +0,0 @@ -/** - * ========================================================================= - * File : sysdep.cpp - * Project : 0 A.D. - * Description : various system-specific function implementations - * ========================================================================= - */ - -// license: GPL; see lib/license.txt - -#include "precompiled.h" -#include "sysdep.h" - - diff --git a/source/lib/sysdep/sysdep.h b/source/lib/sysdep/sysdep.h index c0a259190e..167b2c6bbf 100644 --- a/source/lib/sysdep/sysdep.h +++ b/source/lib/sysdep/sysdep.h @@ -32,55 +32,6 @@ extern void sys_display_msgw(const wchar_t* caption, const wchar_t* msg); extern ErrorReaction sys_display_error(const wchar_t* text, uint flags); -// -// clipboard -// - -// "copy" text into the clipboard. replaces previous contents. -extern LibError sys_clipboard_set(const wchar_t* text); - -// allow "pasting" from clipboard. returns the current contents if they -// can be represented as text, otherwise 0. -// when it is no longer needed, the returned pointer must be freed via -// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) -extern wchar_t* sys_clipboard_get(void); - -// frees memory used by , which must have been returned by -// sys_clipboard_get. see note above. -extern LibError sys_clipboard_free(wchar_t* copy); - - -// -// mouse cursor -// - -// note: these do not warn on error; that is left to the caller. - -// creates a cursor from the given image. -// w, h specify image dimensions [pixels]. limit is implementation- -// dependent; 32x32 is typical and safe. -// bgra_img is the cursor image (BGRA format, bottom-up). -// it is no longer needed and can be freed after this call returns. -// hotspot (hx,hy) is the offset from its upper-left corner to the -// position where mouse clicks are registered. -// cursor is only valid when INFO::OK is returned; in that case, it must be -// sys_cursor_free-ed when no longer needed. -extern LibError sys_cursor_create(uint w, uint h, void* bgra_img, - uint hx, uint hy, void** cursor); - -// create a fully transparent cursor (i.e. one that when passed to set hides -// the system cursor) -extern LibError sys_cursor_create_empty(void **cursor); - -// replaces the current system cursor with the one indicated. need only be -// called once per cursor; pass 0 to restore the default. -extern LibError sys_cursor_set(void* cursor); - -// destroys the indicated cursor and frees its resources. if it is -// currently the system cursor, the default cursor is restored first. -extern LibError sys_cursor_free(void* cursor); - - // // misc // @@ -118,8 +69,6 @@ extern LibError sys_get_executable_name(char* n_path, size_t buf_size); extern LibError sys_pick_directory(char* n_path, size_t buf_size); - - // return the largest sector size [bytes] of any storage medium // (HD, optical, etc.) in the system. // diff --git a/source/lib/sysdep/win/wclipboard.cpp b/source/lib/sysdep/win/wclipboard.cpp new file mode 100644 index 0000000000..1237ed53ad --- /dev/null +++ b/source/lib/sysdep/win/wclipboard.cpp @@ -0,0 +1,98 @@ +#include "precompiled.h" +#include "../clipboard.h" + +#include "win.h" +#include "wutil.h" + +// caller is responsible for freeing *hMem. +static LibError SetClipboardText(const wchar_t* text, HGLOBAL* hMem) +{ + const size_t len = wcslen(text); + *hMem = GlobalAlloc(GMEM_MOVEABLE, (len+1) * sizeof(wchar_t)); + if(!*hMem) + WARN_RETURN(ERR::NO_MEM); + + wchar_t* lockedMemory = (wchar_t*)GlobalLock(*hMem); + if(!lockedMemory) + WARN_RETURN(ERR::NO_MEM); + SAFE_WCSCPY(lockedMemory, text); + GlobalUnlock(*hMem); + + HANDLE hData = SetClipboardData(CF_UNICODETEXT, *hMem); + if(!hData) // failed + WARN_RETURN(ERR::FAIL); + + return INFO::OK; +} + + +// "copy" text into the clipboard. replaces previous contents. +LibError sys_clipboard_set(const wchar_t* text) +{ + // note: MSDN claims that the window handle must not be 0; + // that does actually work on WinXP, but we'll play it safe. + if(!OpenClipboard(wutil_AppWindow())) + WARN_RETURN(ERR::FAIL); + EmptyClipboard(); + + HGLOBAL hMem; + LibError ret = SetClipboardText(text, &hMem); + + CloseClipboard(); + + // note: SetClipboardData says hMem must not be freed until after + // CloseClipboard. however, GlobalFree still fails after the successful + // completion of both. to avoid memory leaks when one of the calls fails, + // we'll leave it in and just ignore the return value. + (void)GlobalFree(hMem); + + return ret; +} + + +// allow "pasting" from clipboard. returns the current contents if they +// can be represented as text, otherwise 0. +// when it is no longer needed, the returned pointer must be freed via +// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) +wchar_t* sys_clipboard_get() +{ + wchar_t* ret = 0; + + const HWND newOwner = 0; + // MSDN: passing 0 requests the current task be granted ownership; + // there's no need to pass our window handle. + if(!OpenClipboard(newOwner)) + return 0; + + // Windows NT/2000+ auto convert UNICODETEXT <-> TEXT + HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT); + if(hMem != 0) + { + wchar_t* text = (wchar_t*)GlobalLock(hMem); + if(text) + { + SIZE_T size = GlobalSize(hMem); + wchar_t* copy = (wchar_t*)malloc(size); // unavoidable + if(copy) + { + wcscpy(copy, text); + ret = copy; + } + + GlobalUnlock(hMem); + } + } + + CloseClipboard(); + + return ret; +} + + +// frees memory used by , which must have been returned by +// sys_clipboard_get. see note above. +LibError sys_clipboard_free(wchar_t* copy) +{ + free(copy); + return INFO::OK; +} diff --git a/source/lib/sysdep/win/wcpu.cpp b/source/lib/sysdep/win/wcpu.cpp index f7a6e791e8..405d1285e0 100644 --- a/source/lib/sysdep/win/wcpu.cpp +++ b/source/lib/sysdep/win/wcpu.cpp @@ -158,243 +158,3 @@ static LibError wcpu_Init() return INFO::OK; } - - - - - - - - - - - - - - - - -////////////////////////////////////////////////////////////////////////////// -// -// -// -////////////////////////////////////////////////////////////////////////////// - -#if 0 - -// we need a means of measuring performance, since it is hard to predict and -// depends on many factors. to cover a wider range of configurations, this -// must also be possible on end-user systems lacking specialized developer -// tools. therefore, we must ship our own implementation; this complements -// Intel VTune et al. -// -// there are 3 approaches to the problem: -// - single-step analysis logs every executed instruction. very thorough, but -// intolerably slow (~1000x) and not suitable for performance measurement. -// - intrusive measuring tracks execution time of explicitly marked -// functions or 'zones'. more complex, requires adding code, and -// inaccurate when thread switches are frequent. -// - IP sampling records the current instruction pointer at regular -// intervals; slow sections of code will over time appear more often. -// not exact, but simple and low-overhead. -// -// we implement IP sampling due to its simplicity. an intrusive approach -// might also be added later to account for performance per-module -// (helps spot the culprit in case hotspots are called from multiple sites). - - -// on Windows, we retrieve the current IP with GetThreadContext. dox require -// this to happen from another thread, and for the target to be suspended -// (now enforced by XP SP2). this leads to all sorts of problems: -// - if the suspended thread was dispatching an exception in the kernel, -// register state may be a mix between the correct values and -// those captured from the exception. -// - if running on Win9x with real-mode drivers, interrupts may interfere -// with GetThreadContext. however, it's not supported anyway due to other -// deficiencies (e.g. lack of proper mmap support). -// - the suspended thread may be holding locks; we need to be extremely -// careful to avoid deadlock! many win api functions acquire locks in -// non-obvious ways. - -static HANDLE prof_target_thread; - -static pthread_t prof_thread; - -// delay [ms] between samples. OS sleep timers usually provide only -// ms resolution. increasing interval reduces overhead and accuracy. -static const int PROFILE_INTERVAL_MS = 1; - - -static uintptr_t get_target_pc() -{ - DWORD ret; - HANDLE hThread = prof_target_thread; // convenience - - ret = SuspendThread(hThread); - if(ret == (DWORD)-1) - { - debug_warn("get_target_pc: SuspendThread failed"); - return 0; - } - // note: we don't need to call more than once: this increments a DWORD - // 'suspend count'; target is guaranteed to be suspended unless - // the function failed. - - ///////////////////////////////////////////// - - // be VERY CAREFUL to avoid anything that may acquire a lock until - // after ResumeThread! this includes locks taken by the OS, - // e.g. malloc -> heap or GetProcAddress -> loader. - // reason is, if the target thread was holding a lock we try to - // acquire here, a classic deadlock results. - - uintptr_t pc = 0; // => will return 0 if GetThreadContext fails - - CONTEXT context; - context.ContextFlags = CONTEXT_CONTROL; - if(GetThreadContext(hThread, &context)) - pc = context.PC_; - - ///////////////////////////////////////////// - - ret = ResumeThread(hThread); - debug_assert(ret != 0); - // don't fail (we have a valid PC), but warn - - return pc; -} - - -static pthread_t thread; -static sem_t exit_flag; - -static void* prof_thread_func(void* UNUSED(data)) -{ - debug_set_thread_name("eip_sampler"); - - const long _1e6 = 1000000; - const long _1e9 = 1000000000; - - for(;;) - { - // calculate absolute timeout for sem_timedwait - struct timespec abs_timeout; - clock_gettime(CLOCK_REALTIME, &abs_timeout); - abs_timeout.tv_nsec += PROFILE_INTERVAL_MS * _1e6; - // .. handle nanosecond wraparound (must not be > 1000m) - if(abs_timeout.tv_nsec >= _1e9) - { - abs_timeout.tv_nsec -= _1e9; - abs_timeout.tv_sec++; - } - - errno = 0; - // if we acquire the semaphore, exit was requested. - if(sem_timedwait(&exit_flag, &abs_timeout) == 0) - break; - // actual error: warn - if(errno != ETIMEDOUT) - debug_warn("wpcu prof_thread_func: sem_timedwait failed"); - - uintptr_t pc = get_target_pc(); - UNUSED2(pc); - - // ADD TO LIST - } - - return 0; -} - - - -// call from thread that is to be profiled -LibError prof_start() -{ - // we need a real HANDLE to the target thread for use with - // Suspend|ResumeThread and GetThreadContext. - // alternative: DuplicateHandle on the current thread pseudo-HANDLE. - // this way is a bit more obvious/simple. - const DWORD access = THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME; - HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId()); - if(hThread == INVALID_HANDLE_VALUE) - WARN_RETURN(ERR::FAIL); - - prof_target_thread = hThread; - - sem_init(&exit_flag, 0, 0); - pthread_create(&thread, 0, prof_thread_func, 0); - return INFO::OK; -} - -LibError prof_shutdown() -{ - WARN_IF_FALSE(CloseHandle(prof_target_thread)); - return INFO::OK; -} - - - -/* -open question: how to store the EIP values returned? some background: -the mechanism above churns out an EIP value (may be in our process, but might -also be bogus); we need to store it somehow pending analysis. - -when done with the current run, we'd want to resolve EIP -> function name, -source file etc. (rather slow, so don't do it at runtime). - -so, how to store it in the meantime? 2 possibilities: -- simple array/vector of addresses (of course optimized to reduce allocs) -- fixed size array of 'bins' (range of addresses; may be as fine as 1 byte); - each bin has a counter which is incremented when the bin's corresponding - address has been hit. - -it's a size tradeoff here; for simple runs of < 1 min (60,000 ms), #1 -would use 240kb of mem. #2 requires sizeof_whole_program * bytes_per_counter -up front, and has problems measuring DLLs (we'd have to explicitly map -the DLL address range into a bin - ugh). however, if we ever want to -test for say an hour (improves accuracy of profiling due to larger sample size), -#1 would guzzle 15mb of memory. - -hm, another idea would be to write out #1's list of addresses periodically. -to make sure the disk I/O doesn't come at a bad time, we could have the main -thread call into the profiler and request it write out at that time. -this would require extreme caution to avoid the deadlock problem, but looks -doable. - --------- [2] ---------- - -realistic profiler runs will take up to an hour. - -writing out to disk would work: could have main thread call back. -that and adding EIP to list would be atomic (locked). -BUT: large amount of data, that's bad (loading at 30mb/s => 500ms load time alone) - -problem with enumerating all symbols at startup: how do we enum all DLLs? - -hybrid idea: std::map of EIPs. we don't build the map at startup, -but add when first seen and subsequently increment counter stored there. - problem: uses more memory/slower access than list. - would have to make sure EIPs are reused. - to help that, could quantize down to 4 byte (or so) bins. - accessing debug information at runtime to determine function length is too slow. - - maybe some weird data structure: one bucket controls say 256 bytes of code - bucket is found by stripping off lower 8 bits. then, store only - the hit count for that byte. where's the savings over normal count? - -TODO: what if the thread is sleeping at the time we query EIP? -can't detect that - suspend count is only set by SuspendThread -do we want to report that point (it's good to know), or try to access other threads? - -TODO split off target thread / get PC into sysdep; profiler thread is portable! - - -at exit: resolve list to hotspots -probably hard; a start would be just the function in which the address is, then hit count - - -========================================== - - -*/ -#endif diff --git a/source/lib/sysdep/win/wcursor.cpp b/source/lib/sysdep/win/wcursor.cpp new file mode 100644 index 0000000000..834bb81b85 --- /dev/null +++ b/source/lib/sysdep/win/wcursor.cpp @@ -0,0 +1,107 @@ +#include "precompiled.h" +#include "../cursor.h" + +#include "win.h" +#include "wutil.h" + + +static void* ptr_from_HICON(HICON hIcon) +{ + return (void*)(uintptr_t)hIcon; +} + +static void* ptr_from_HCURSOR(HCURSOR hCursor) +{ + return (void*)(uintptr_t)hCursor; +} + +static HICON HICON_from_ptr(void* p) +{ + return (HICON)(uintptr_t)p; +} + +static HCURSOR HCURSOR_from_ptr(void* p) +{ + return (HCURSOR)(uintptr_t)p; +} + + +// creates a cursor from the given image. +// w, h specify image dimensions [pixels]. limit is implementation- +// dependent; 32x32 is typical and safe. +// bgra_img is the cursor image (BGRA format, bottom-up). +// it is no longer needed and can be freed after this call returns. +// hotspot (hx,hy) is the offset from its upper-left corner to the +// position where mouse clicks are registered. +// cursor is only valid when INFO::OK is returned; in that case, it must be +// sys_cursor_free-ed when no longer needed. +LibError sys_cursor_create(uint w, uint h, void* bgra_img, uint hx, uint hy, void** cursor) +{ + // MSDN says selecting this HBITMAP into a DC is slower since we use + // CreateBitmap; bpp/format must be checked against those of the DC. + // this is the simplest way and we don't care about slight performance + // differences because this is typically only called once. + HBITMAP hbmColour = CreateBitmap(w, h, 1, 32, bgra_img); + + // CreateIconIndirect doesn't access this; we just need to pass + // an empty bitmap. + HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, 0); + + // create the cursor (really an icon; they differ only in + // fIcon and the hotspot definitions). + ICONINFO ii; + ii.fIcon = FALSE; // cursor + ii.xHotspot = hx; + ii.yHotspot = hy; + ii.hbmMask = hbmMask; + ii.hbmColor = hbmColour; + HICON hIcon = CreateIconIndirect(&ii); + + // CreateIconIndirect makes copies, so we no longer need these. + DeleteObject(hbmMask); + DeleteObject(hbmColour); + + if(!hIcon) // not INVALID_HANDLE_VALUE + WARN_RETURN(ERR::FAIL); + + *cursor = ptr_from_HICON(hIcon); + return INFO::OK; +} + +LibError sys_cursor_create_empty(void **cursor) +{ + u8 bgra_img[] = {0, 0, 0, 0}; + return sys_cursor_create(1, 1, bgra_img, 0, 0, cursor); +} + +// replaces the current system cursor with the one indicated. need only be +// called once per cursor; pass 0 to restore the default. +LibError sys_cursor_set(void* cursor) +{ + // restore default cursor. + if(!cursor) + cursor = ptr_from_HCURSOR(LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW))); + + (void)SetCursor(HCURSOR_from_ptr(cursor)); + // return value (previous cursor) is useless. + + return INFO::OK; +} + + +// destroys the indicated cursor and frees its resources. if it is +// currently the system cursor, the default cursor is restored first. +LibError sys_cursor_free(void* cursor) +{ + // bail now to prevent potential confusion below; there's nothing to do. + if(!cursor) + return INFO::OK; + + // if the cursor being freed is active, restore the default arrow + // (just for safety). + if(ptr_from_HCURSOR(GetCursor()) == cursor) + WARN_ERR(sys_cursor_set(0)); + + BOOL ok = DestroyIcon(HICON_from_ptr(cursor)); + return LibError_from_win32(ok); +} diff --git a/source/lib/sysdep/win/wprofiler.cpp b/source/lib/sysdep/win/wprofiler.cpp new file mode 100644 index 0000000000..79243ca5ee --- /dev/null +++ b/source/lib/sysdep/win/wprofiler.cpp @@ -0,0 +1,220 @@ +#include "precompiled.h" + +#if 0 + +// we need a means of measuring performance, since it is hard to predict and +// depends on many factors. to cover a wider range of configurations, this +// must also be possible on end-user systems lacking specialized developer +// tools. therefore, we must ship our own implementation; this complements +// Intel VTune et al. +// +// there are 3 approaches to the problem: +// - single-step analysis logs every executed instruction. very thorough, but +// intolerably slow (~1000x) and not suitable for performance measurement. +// - intrusive measuring tracks execution time of explicitly marked +// functions or 'zones'. more complex, requires adding code, and +// inaccurate when thread switches are frequent. +// - IP sampling records the current instruction pointer at regular +// intervals; slow sections of code will over time appear more often. +// not exact, but simple and low-overhead. +// +// we implement IP sampling due to its simplicity. an intrusive approach +// might also be added later to account for performance per-module +// (helps spot the culprit in case hotspots are called from multiple sites). + + +// on Windows, we retrieve the current IP with GetThreadContext. dox require +// this to happen from another thread, and for the target to be suspended +// (now enforced by XP SP2). this leads to all sorts of problems: +// - if the suspended thread was dispatching an exception in the kernel, +// register state may be a mix between the correct values and +// those captured from the exception. +// - if running on Win9x with real-mode drivers, interrupts may interfere +// with GetThreadContext. however, it's not supported anyway due to other +// deficiencies (e.g. lack of proper mmap support). +// - the suspended thread may be holding locks; we need to be extremely +// careful to avoid deadlock! many win api functions acquire locks in +// non-obvious ways. + +static HANDLE prof_target_thread; + +static pthread_t prof_thread; + +// delay [ms] between samples. OS sleep timers usually provide only +// ms resolution. increasing interval reduces overhead and accuracy. +static const int PROFILE_INTERVAL_MS = 1; + + +static uintptr_t get_target_pc() +{ + DWORD ret; + HANDLE hThread = prof_target_thread; // convenience + + ret = SuspendThread(hThread); + if(ret == (DWORD)-1) + { + debug_warn("get_target_pc: SuspendThread failed"); + return 0; + } + // note: we don't need to call more than once: this increments a DWORD + // 'suspend count'; target is guaranteed to be suspended unless + // the function failed. + + ///////////////////////////////////////////// + + // be VERY CAREFUL to avoid anything that may acquire a lock until + // after ResumeThread! this includes locks taken by the OS, + // e.g. malloc -> heap or GetProcAddress -> loader. + // reason is, if the target thread was holding a lock we try to + // acquire here, a classic deadlock results. + + uintptr_t pc = 0; // => will return 0 if GetThreadContext fails + + CONTEXT context; + context.ContextFlags = CONTEXT_CONTROL; + if(GetThreadContext(hThread, &context)) + pc = context.PC_; + + ///////////////////////////////////////////// + + ret = ResumeThread(hThread); + debug_assert(ret != 0); + // don't fail (we have a valid PC), but warn + + return pc; +} + + +static pthread_t thread; +static sem_t exit_flag; + +static void* prof_thread_func(void* UNUSED(data)) +{ + debug_set_thread_name("eip_sampler"); + + const long _1e6 = 1000000; + const long _1e9 = 1000000000; + + for(;;) + { + // calculate absolute timeout for sem_timedwait + struct timespec abs_timeout; + clock_gettime(CLOCK_REALTIME, &abs_timeout); + abs_timeout.tv_nsec += PROFILE_INTERVAL_MS * _1e6; + // .. handle nanosecond wraparound (must not be > 1000m) + if(abs_timeout.tv_nsec >= _1e9) + { + abs_timeout.tv_nsec -= _1e9; + abs_timeout.tv_sec++; + } + + errno = 0; + // if we acquire the semaphore, exit was requested. + if(sem_timedwait(&exit_flag, &abs_timeout) == 0) + break; + // actual error: warn + if(errno != ETIMEDOUT) + debug_warn("wpcu prof_thread_func: sem_timedwait failed"); + + uintptr_t pc = get_target_pc(); + UNUSED2(pc); + + // ADD TO LIST + } + + return 0; +} + + + +// call from thread that is to be profiled +LibError prof_start() +{ + // we need a real HANDLE to the target thread for use with + // Suspend|ResumeThread and GetThreadContext. + // alternative: DuplicateHandle on the current thread pseudo-HANDLE. + // this way is a bit more obvious/simple. + const DWORD access = THREAD_GET_CONTEXT|THREAD_SUSPEND_RESUME; + HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId()); + if(hThread == INVALID_HANDLE_VALUE) + WARN_RETURN(ERR::FAIL); + + prof_target_thread = hThread; + + sem_init(&exit_flag, 0, 0); + pthread_create(&thread, 0, prof_thread_func, 0); + return INFO::OK; +} + +LibError prof_shutdown() +{ + WARN_IF_FALSE(CloseHandle(prof_target_thread)); + return INFO::OK; +} + + + +/* +open question: how to store the EIP values returned? some background: +the mechanism above churns out an EIP value (may be in our process, but might +also be bogus); we need to store it somehow pending analysis. + +when done with the current run, we'd want to resolve EIP -> function name, +source file etc. (rather slow, so don't do it at runtime). + +so, how to store it in the meantime? 2 possibilities: +- simple array/vector of addresses (of course optimized to reduce allocs) +- fixed size array of 'bins' (range of addresses; may be as fine as 1 byte); +each bin has a counter which is incremented when the bin's corresponding +address has been hit. + +it's a size tradeoff here; for simple runs of < 1 min (60,000 ms), #1 +would use 240kb of mem. #2 requires sizeof_whole_program * bytes_per_counter +up front, and has problems measuring DLLs (we'd have to explicitly map +the DLL address range into a bin - ugh). however, if we ever want to +test for say an hour (improves accuracy of profiling due to larger sample size), +#1 would guzzle 15mb of memory. + +hm, another idea would be to write out #1's list of addresses periodically. +to make sure the disk I/O doesn't come at a bad time, we could have the main +thread call into the profiler and request it write out at that time. +this would require extreme caution to avoid the deadlock problem, but looks +doable. + +-------- [2] ---------- + +realistic profiler runs will take up to an hour. + +writing out to disk would work: could have main thread call back. +that and adding EIP to list would be atomic (locked). +BUT: large amount of data, that's bad (loading at 30mb/s => 500ms load time alone) + +problem with enumerating all symbols at startup: how do we enum all DLLs? + +hybrid idea: std::map of EIPs. we don't build the map at startup, +but add when first seen and subsequently increment counter stored there. +problem: uses more memory/slower access than list. +would have to make sure EIPs are reused. +to help that, could quantize down to 4 byte (or so) bins. +accessing debug information at runtime to determine function length is too slow. + +maybe some weird data structure: one bucket controls say 256 bytes of code +bucket is found by stripping off lower 8 bits. then, store only +the hit count for that byte. where's the savings over normal count? + +TODO: what if the thread is sleeping at the time we query EIP? +can't detect that - suspend count is only set by SuspendThread +do we want to report that point (it's good to know), or try to access other threads? + +TODO split off target thread / get PC into sysdep; profiler thread is portable! + + +at exit: resolve list to hotspots +probably hard; a start would be just the function in which the address is, then hit count + + +========================================== + + +*/ +#endif diff --git a/source/lib/sysdep/win/wsysdep.cpp b/source/lib/sysdep/win/wsysdep.cpp index c55ed6f0a4..47459b39b7 100644 --- a/source/lib/sysdep/win/wsysdep.cpp +++ b/source/lib/sysdep/win/wsysdep.cpp @@ -14,10 +14,12 @@ #include "win.h" // includes windows.h; must come before shlobj #include // pick_dir +#include "lib/sysdep/clipboard.h" #include "error_dialog.h" #include "wutil.h" + #if MSC_VERSION #pragma comment(lib, "shell32.lib") // for sys_pick_directory SH* calls #endif @@ -38,41 +40,6 @@ void sys_display_msgw(const wchar_t* caption, const wchar_t* msg) // "program error" dialog (triggered by debug_assert and exception) //----------------------------------------------------------------------------- -// we need to know the app's main window for the error dialog, so that -// it is modal and actually stops the app. if it keeps running while -// we're reporting an error, it'll probably crash and take down the -// error window before it is seen (since we're in the same process). - -static BOOL CALLBACK is_this_our_window(HWND hWnd, LPARAM lParam) -{ - DWORD pid; - DWORD tid = GetWindowThreadProcessId(hWnd, &pid); - UNUSED2(tid); // the function can't fail - - if(pid == GetCurrentProcessId()) - { - *(HWND*)lParam = hWnd; - return FALSE; // done - } - - return TRUE; // keep calling -} - -// try to determine the app's main window by enumerating all -// top-level windows and comparing their PIDs. -// returns 0 if not found, e.g. if the app doesn't have one yet. -static HWND get_app_main_window() -{ - HWND our_window = 0; - DWORD ret = EnumWindows(is_this_our_window, (LPARAM)&our_window); - UNUSED2(ret); - // the callback returns FALSE when it has found the window - // (so as not to waste time); EnumWindows then returns 0. - // therefore, we can't check this; just return our_window. - return our_window; -} - - // support for resizing the dialog / its controls // (have to do this manually - grr) @@ -289,9 +256,10 @@ ErrorReaction sys_display_error(const wchar_t* text, uint flags) LPCSTR lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG1); const DialogParams params = { text, flags }; // get the enclosing app's window handle. we can't just pass 0 or - // the desktop window because the dialog must be modal (the app - // must not crash/continue to run before it has been displayed). - const HWND hWndParent = get_app_main_window(); + // the desktop window because the dialog must be modal (if the app + // continues running, it may crash and take down the process before + // we've managed to show the dialog). + const HWND hWndParent = wutil_AppWindow(); INT_PTR ret = DialogBoxParam(hInstance, lpTemplateName, hWndParent, error_dialog_proc, (LPARAM)¶ms); @@ -308,204 +276,6 @@ ErrorReaction sys_display_error(const wchar_t* text, uint flags) } -//----------------------------------------------------------------------------- -// clipboard -//----------------------------------------------------------------------------- - -// "copy" text into the clipboard. replaces previous contents. -LibError sys_clipboard_set(const wchar_t* text) -{ - const HWND new_owner = 0; - // MSDN: passing 0 requests the current task be granted ownership; - // there's no need to pass our window handle. - if(!OpenClipboard(new_owner)) - WARN_RETURN(ERR::FAIL); - EmptyClipboard(); - - LibError err = ERR::FAIL; - - { - const size_t len = wcslen(text); - HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, (len+1) * sizeof(wchar_t)); - if(!hMem) - { - err = ERR::NO_MEM; - goto fail; - } - - wchar_t* copy = (wchar_t*)GlobalLock(hMem); - if(copy) - { - wcscpy(copy, text); - - GlobalUnlock(hMem); - - if(SetClipboardData(CF_UNICODETEXT, hMem) != 0) - err = INFO::OK; - } - } - -fail: - CloseClipboard(); - return err; -} - - -// allow "pasting" from clipboard. returns the current contents if they -// can be represented as text, otherwise 0. -// when it is no longer needed, the returned pointer must be freed via -// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) -wchar_t* sys_clipboard_get() -{ - wchar_t* ret = 0; - - const HWND new_owner = 0; - // MSDN: passing 0 requests the current task be granted ownership; - // there's no need to pass our window handle. - if(!OpenClipboard(new_owner)) - return 0; - - // Windows NT/2000+ auto convert UNICODETEXT <-> TEXT - HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT); - if(hMem != 0) - { - wchar_t* text = (wchar_t*)GlobalLock(hMem); - if(text) - { - SIZE_T size = GlobalSize(hMem); - wchar_t* copy = (wchar_t*)malloc(size); // unavoidable - if(copy) - { - wcscpy(copy, text); - ret = copy; - } - - GlobalUnlock(hMem); - } - } - - CloseClipboard(); - - return ret; -} - - -// frees memory used by , which must have been returned by -// sys_clipboard_get. see note above. -LibError sys_clipboard_free(wchar_t* copy) -{ - free(copy); - return INFO::OK; -} - - -//----------------------------------------------------------------------------- -// mouse cursor -//----------------------------------------------------------------------------- - -static void* ptr_from_HICON(HICON hIcon) -{ - return (void*)(uintptr_t)hIcon; -} - -static void* ptr_from_HCURSOR(HCURSOR hCursor) -{ - return (void*)(uintptr_t)hCursor; -} - -static HICON HICON_from_ptr(void* p) -{ - return (HICON)(uintptr_t)p; -} - -static HCURSOR HCURSOR_from_ptr(void* p) -{ - return (HCURSOR)(uintptr_t)p; -} - - -// creates a cursor from the given image. -// w, h specify image dimensions [pixels]. limit is implementation- -// dependent; 32x32 is typical and safe. -// bgra_img is the cursor image (BGRA format, bottom-up). -// it is no longer needed and can be freed after this call returns. -// hotspot (hx,hy) is the offset from its upper-left corner to the -// position where mouse clicks are registered. -// cursor is only valid when INFO::OK is returned; in that case, it must be -// sys_cursor_free-ed when no longer needed. -LibError sys_cursor_create(uint w, uint h, void* bgra_img, - uint hx, uint hy, void** cursor) -{ - // MSDN says selecting this HBITMAP into a DC is slower since we use - // CreateBitmap; bpp/format must be checked against those of the DC. - // this is the simplest way and we don't care about slight performance - // differences because this is typically only called once. - HBITMAP hbmColour = CreateBitmap(w, h, 1, 32, bgra_img); - - // CreateIconIndirect doesn't access this; we just need to pass - // an empty bitmap. - HBITMAP hbmMask = CreateBitmap(w, h, 1, 1, 0); - - // create the cursor (really an icon; they differ only in - // fIcon and the hotspot definitions). - ICONINFO ii; - ii.fIcon = FALSE; // cursor - ii.xHotspot = hx; - ii.yHotspot = hy; - ii.hbmMask = hbmMask; - ii.hbmColor = hbmColour; - HICON hIcon = CreateIconIndirect(&ii); - - // CreateIconIndirect makes copies, so we no longer need these. - DeleteObject(hbmMask); - DeleteObject(hbmColour); - - if(!hIcon) // not INVALID_HANDLE_VALUE - WARN_RETURN(ERR::FAIL); - - *cursor = ptr_from_HICON(hIcon); - return INFO::OK; -} - -LibError sys_cursor_create_empty(void **cursor) -{ - u8 bgra_img[] = {0, 0, 0, 0}; - return sys_cursor_create(1, 1, bgra_img, 0, 0, cursor); -} - -// replaces the current system cursor with the one indicated. need only be -// called once per cursor; pass 0 to restore the default. -LibError sys_cursor_set(void* cursor) -{ - // restore default cursor. - if(!cursor) - cursor = ptr_from_HCURSOR(LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW))); - - (void)SetCursor(HCURSOR_from_ptr(cursor)); - // return value (previous cursor) is useless. - - return INFO::OK; -} - - -// destroys the indicated cursor and frees its resources. if it is -// currently the system cursor, the default cursor is restored first. -LibError sys_cursor_free(void* cursor) -{ - // bail now to prevent potential confusion below; there's nothing to do. - if(!cursor) - return INFO::OK; - - // if the cursor being freed is active, restore the default arrow - // (just for safety). - if(ptr_from_HCURSOR(GetCursor()) == cursor) - WARN_ERR(sys_cursor_set(0)); - - BOOL ok = DestroyIcon(HICON_from_ptr(cursor)); - return LibError_from_win32(ok); -} - - //----------------------------------------------------------------------------- // misc //----------------------------------------------------------------------------- @@ -605,7 +375,3 @@ LibError sys_pick_directory(char* path, size_t buf_size) return LibError_from_win32(ok); } - - - - diff --git a/source/lib/sysdep/win/wutil.cpp b/source/lib/sysdep/win/wutil.cpp index cc73be6a31..abe8a0cf21 100644 --- a/source/lib/sysdep/win/wutil.cpp +++ b/source/lib/sysdep/win/wutil.cpp @@ -424,6 +424,45 @@ void wutil_RevertWow64Redirection(void* wasRedirectionEnabled) } +//----------------------------------------------------------------------------- +// find main window + +// this is required by the error dialog and clipboard code. +// note that calling from wutil_Init won't work, because the app will not +// have created its window by then. + +static HWND hAppWindow; + +static BOOL CALLBACK FindAppWindowByPid(HWND hWnd, LPARAM UNUSED(lParam)) +{ + DWORD pid; + const DWORD tid = GetWindowThreadProcessId(hWnd, &pid); + UNUSED2(tid); // the function can't fail + + if(pid == GetCurrentProcessId()) + { + hAppWindow = hWnd; + return FALSE; // done + } + + return TRUE; // keep calling +} + +HWND wutil_AppWindow() +{ + if(!hAppWindow) + { + const DWORD ret = EnumWindows(FindAppWindowByPid, 0); + // the callback returns FALSE when it has found the window + // (so as not to waste time); EnumWindows then returns 0. + // we therefore cannot check for errors. + UNUSED2(ret); + } + + return hAppWindow; +} + + //----------------------------------------------------------------------------- static LibError wutil_Init() diff --git a/source/lib/sysdep/win/wutil.h b/source/lib/sysdep/win/wutil.h index d197e12e15..290e05fdbd 100644 --- a/source/lib/sysdep/win/wutil.h +++ b/source/lib/sysdep/win/wutil.h @@ -133,4 +133,14 @@ extern void wutil_DisableWow64Redirection(void*& wasRedirectionEnabled); extern void wutil_RevertWow64Redirection(void* wasRedirectionEnabled); +/** + * @return handle to the first window owned by the current process, or + * 0 if none exist (e.g. it hasn't yet created one). + * + * enumerates all top-level windows and stops if PID matches. + * once this function returns a non-NULL handle, it will always + * return that cached value. + **/ +extern HWND wutil_AppWindow(); + #endif // #ifndef INCLUDED_WUTIL diff --git a/source/ps/CConsole.cpp b/source/ps/CConsole.cpp index fca9744e32..6f44ff969e 100644 --- a/source/ps/CConsole.cpp +++ b/source/ps/CConsole.cpp @@ -14,7 +14,7 @@ #include "lib/ogl.h" #include "lib/res/file/vfs.h" #include "lib/res/graphics/unifont.h" -#include "lib/sysdep/sysdep.h" +#include "lib/sysdep/clipboard.h" #include "maths/MathUtil.h" #include "network/Client.h" #include "network/Server.h"