wineproc: a new wine utility

Eric Pouech pouech-eric at wanadoo.fr
Wed Sep 3 14:53:19 CDT 2003


this tool allows to do a few basic operations on a Wine session:
- list all running processes
- kill a given process
- list all threads from a process
- suspend/resume a thread
- list all (loaded) modules from a process
- list all debug channel from a process
- change any debug channel from a (running process)

it comes in two flavors:
- command line (run wineproc help to get an idea of the options)
- visual (requires a graphical driver in wine, like X11), with (I hope) 
an understandable UI

help is required to:
- get some decent icons for toolbar (I don't feel like a graphic designer)
- <put here what you want>

downside: the get_symbol function in system.c is really ugly. I wouldn't 
be surprised if Alexandre doesn't like it (I don't like it myself). A 
cleaner solution would be to implement dbghelp.dll for this kind of 
behavior (easier said than done).

any comment welcomed!!

(thanks to Dimi for his quick fixies on the listview control BTW)

A+
-- 
Eric Pouech
-------------- next part --------------
Name:          wp
ChangeLog:     Creation of wineproc - a tool for browsing wine processes
License:       X11
GenDate:       2003/09/03 19:44:49 UTC
ModifiedFiles: configure.ac programs/Makefile.in
AddedFiles:    programs/wineproc/Makefile.in programs/wineproc/splitter.c programs/wineproc/splitter.h programs/wineproc/system.c programs/wineproc/visual.c programs/wineproc/wineproc.c programs/wineproc/wineproc_En.rc programs/wineproc/wineproc.h programs/wineproc/wineproc.rc programs/wineproc/wineprocres.h
===================================================================
RCS file: /home/cvs/cvsroot/wine/wine/configure.ac,v
retrieving revision 1.176
diff -u -u -r1.176 configure.ac
--- configure.ac	3 Sep 2003 00:26:08 -0000	1.176
+++ configure.ac	3 Sep 2003 17:00:54 -0000
@@ -1571,9 +1572,10 @@
 programs/winemenubuilder/Makefile
 programs/winemine/Makefile
 programs/winepath/Makefile
+programs/wineproc/Makefile
 programs/winevdm/Makefile
 programs/winhelp/Makefile
 programs/winver/Makefile
 server/Makefile
 tools/Makefile
 tools/widl/Makefile
Index: programs/Makefile.in
===================================================================
RCS file: /home/cvs/cvsroot/wine/wine/programs/Makefile.in,v
retrieving revision 1.39
diff -u -u -r1.39 Makefile.in
--- programs/Makefile.in	23 Jun 2003 19:51:21 -0000	1.39
+++ programs/Makefile.in	24 Jun 2003 17:19:31 -0000
@@ -31,9 +31,10 @@
 	winemenubuilder \
 	winemine \
 	winepath \
+	wineproc \
 	winevdm \
 	winhelp \
 	winver
 
 # Sub-directories to run make install into
 INSTALLSUBDIRS = \
@@ -58,6 +63,7 @@
 	winemenubuilder \
 	winemine \
 	winepath \
+	wineproc \
 	winevdm \
 	winhelp \
 	winver
@@ -78,6 +84,7 @@
 	winefile \
 	winemine \
 	winepath \
+	wineproc \
 	winhelp
 
 # Symlinks to apps that we want to run from inside the source tree
@@ -87,6 +94,7 @@
 	wineconsole.exe \
 	winedbg.exe \
 	winemenubuilder.exe \
+	wineproc.exe \
 	winevdm.exe \
 	winhelp.exe
 
@@ -148,6 +156,9 @@
 winemenubuilder.exe$(DLLEXT): winemenubuilder/winemenubuilder.exe$(DLLEXT)
 	$(RM) $@ && $(LN_S) winemenubuilder/winemenubuilder.exe$(DLLEXT) $@
 
+wineproc.exe$(DLLEXT): wineproc/wineproc.exe$(DLLEXT)
+	$(RM) $@ && $(LN_S) wineproc/wineproc.exe$(DLLEXT) $@
+
 winevdm.exe$(DLLEXT): winevdm/winevdm.exe$(DLLEXT)
 	$(RM) $@ && $(LN_S) winevdm/winevdm.exe$(DLLEXT) $@
 
@@ -158,6 +169,7 @@
 wineconsole/wineconsole.exe$(DLLEXT): wineconsole
 winedbg/winedbg.exe$(DLLEXT): winedbg
 winemenubuilder/winemenubuilder.exe$(DLLEXT): winemenubuilder
+wineproc/wineproc.exe$(DLLEXT): wineproc
 winevdm/winevdm.exe$(DLLEXT): winevdm
 winhelp/winhelp.exe$(DLLEXT): winhelp
 
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/Makefile.in	2003-08-30 09:34:12.000000000 +0200
@@ -0,0 +1,19 @@
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+MODULE    = wineproc.exe
+APPMODE   = cui
+IMPORTS   = advapi32 kernel32 psapi gdi32 user32 comctl32
+
+C_SRCS = \
+	splitter.c \
+	system.c \
+	visual.c \
+	wineproc.c
+
+RC_SRCS = wineproc.rc
+
+ at MAKE_PROG_RULES@
+
+### Dependencies:
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/splitter.c	2003-08-30 09:45:42.000000000 +0200
@@ -0,0 +1,108 @@
+#include <windows.h>
+#include <commctrl.h>
+#include "splitter.h"
+
+/******************************************************************
+ *		splitter_move
+ *
+ *
+ */
+static void     splitter_move(HWND hWnd, LONG delta)
+{
+    NMHDR       nmh;
+    RECT        rect;
+
+    GetClientRect(hWnd, &rect);
+    MapWindowPoints(hWnd, GetParent(hWnd), (POINT*)&rect, 2);
+    if (GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL)
+    {
+        rect.top += delta;
+        rect.bottom += delta;
+    }
+    else
+    {
+        rect.left += delta;
+        rect.right += delta;
+    }
+    MoveWindow(hWnd, rect.left, rect.top, 
+               rect.right - rect.left, rect.bottom - rect.top, FALSE);
+
+    nmh.hwndFrom = hWnd;
+    nmh.idFrom = GetDlgCtrlID(hWnd);
+    nmh.code = NM_CLICK;
+    SendMessage(GetParent(hWnd), WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh);
+
+    UpdateWindow(hWnd);
+}
+
+/******************************************************************
+ *		SplitterWndProc
+ *
+ *
+ */
+static LRESULT CALLBACK SplitterWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+    RECT        rect;
+    POINT       pt;
+
+    switch (uMsg)
+    {
+    case SPM_GETPOS:
+        GetClientRect(hWnd, &rect);
+        MapWindowPoints(hWnd, GetParent(hWnd), (POINT*)&rect, 2);
+        *((LONG*)lParam) = (GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL) ? rect.top : rect.left;
+        break;
+    case WM_MOUSEMOVE:
+        SetCursor(LoadCursor(NULL, (GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL) ? IDC_SIZENS : IDC_SIZEWE));
+        if ((wParam == MK_LBUTTON) && GetCapture() == hWnd)
+        {
+            pt.x = (short)LOWORD(lParam);
+            pt.y = (short)HIWORD(lParam);
+            MapWindowPoints(hWnd, GetParent(hWnd), &pt, 1);
+            splitter_move(hWnd, ((GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL) ? pt.y : pt.x) - GetWindowLong(hWnd, 0));
+            SetWindowLong(hWnd, 0, (GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL) ? pt.y : pt.x);
+        }
+        return 0;
+    case WM_LBUTTONDOWN:
+        SetCapture(hWnd);
+        pt.x = (short)LOWORD(lParam);
+        pt.y = (short)HIWORD(lParam);
+        MapWindowPoints(hWnd, GetParent(hWnd), &pt, 1);
+        SetWindowLong(hWnd, 0, (GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL) ? pt.y : pt.x);
+        return 0;
+    case WM_LBUTTONUP:
+        ReleaseCapture();
+        if ((wParam == MK_LBUTTON) && GetCapture() == hWnd)
+        {
+            pt.x = (short)LOWORD(lParam);
+            pt.y = (short)HIWORD(lParam);
+            MapWindowPoints(hWnd, GetParent(hWnd), &pt, 1);
+            splitter_move(hWnd, ((GetWindowLong(hWnd, GWL_STYLE) & SPS_HORIZONTAL) ? pt.y : pt.x) - GetWindowLong(hWnd, 0));
+        }
+        return 0;
+    }
+    return DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+/******************************************************************
+ *		init_splitter
+ *
+ *
+ */
+BOOL init_splitter(void)
+{
+    WNDCLASS	                wndclass;
+
+    wndclass.style		= 0;
+    wndclass.lpfnWndProc	= SplitterWndProc;
+    wndclass.cbClsExtra	        = 0;
+    wndclass.cbWndExtra	        = sizeof(LONG);
+    wndclass.hInstance	        = GetModuleHandle(NULL);
+    wndclass.hIcon		= LoadIcon(NULL, IDI_INFORMATION);
+    wndclass.hCursor	        = NULL;
+    wndclass.hbrBackground	= (HBRUSH)GetStockObject(/*LTGRAY*/BLACK_BRUSH);
+    wndclass.lpszMenuName	= NULL;
+    wndclass.lpszClassName	= "MySplitter";
+
+    return RegisterClass(&wndclass);
+}
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/splitter.h	2003-08-30 09:33:31.000000000 +0200
@@ -0,0 +1,9 @@
+BOOL    init_splitter(void);
+
+/* splitter messages */
+#define SPM_GETPOS      (WM_USER+0)
+
+/* splitter window styles */
+#define SPS_VERTICAL    0x0000
+#define SPS_HORIZONTAL  0x0001
+#define SPS_VISUALMOVE  0x0002
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/system.c	2003-08-31 11:28:27.000000000 +0200
@@ -0,0 +1,347 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <psapi.h>
+#include <tlhelp32.h>
+#include "wineproc.h"
+
+/******************************************************************
+ *		open_process
+ *
+ * Returns a handle to a running process from its pid, with a given
+ * set of access rights
+ */
+HANDLE  open_process(DWORD pid, DWORD access)
+{
+    return (pid) ? OpenProcess(access, FALSE, pid) : NULL;
+}
+
+/******************************************************************
+ *		get_process_name
+ *
+ *
+ */
+void	get_process_name(HANDLE hProcess, char* buffer, unsigned size)
+{
+    HMODULE     hMod[1024];
+    DWORD	len;
+
+    if (!EnumProcessModules(hProcess, hMod, sizeof(hMod), &len) ||
+	!GetModuleBaseNameA(hProcess, hMod[0], buffer, size))
+        snprintf(buffer, size, "--Unknown-- (%lu)", GetLastError());
+}
+
+/******************************************************************
+ *		enum_processes
+ *
+ * list all known processes in the system
+ */
+void    enum_processes(EnumProcessesCB epcb, DWORD access, void* user)
+{
+    DWORD       pids[1024];
+    DWORD       cb;
+    int         i, ret = 1;
+    HANDLE      hProcess;
+
+    /* FIXME: this shouldn't fail if buffer is too small, but we
+     * should grow internal buffer instead
+     */
+    if (!EnumProcesses(pids, sizeof(pids), &cb)) return;
+
+    for (i = cb / sizeof(pids[0]) - 1; i >= 0 && ret; i--)
+    {
+        hProcess = open_process(pids[i], access);
+	ret = epcb(hProcess, pids[i], user);
+        CloseHandle(hProcess);
+    }
+}
+
+/******************************************************************
+ *		enum_modules
+ *
+ *
+ */
+void    enum_modules(HANDLE hProcess, EnumModulesCB em, void* user)
+{
+    HMODULE             hMod[1024];
+    MODULEINFO          mi;
+    DWORD               cb;
+    int                 i, ret = 1;
+    struct module_info  smi;
+
+    /* FIXME: if this fails, we should grow the hMod size */
+    if (EnumProcessModules(hProcess, hMod, sizeof(hMod), &cb))
+    {
+        for (i = 0; i < cb / sizeof(hMod[0]) && ret; i++)
+        {
+            if (!GetModuleBaseNameA(hProcess, hMod[i], smi.module, sizeof(smi.module)))
+                strcpy(smi.module, "--Unknown--");
+            if (!GetModuleFileNameExA(hProcess, hMod[i], smi.file, sizeof(smi.file)))
+                strcpy(smi.file, "--Unknown--");
+            if (!GetModuleInformation(hProcess, hMod[i], &mi, sizeof(mi)))
+            {
+                smi.dll_base = NULL;
+                smi.size = 0;
+                smi.entry_point = NULL;
+            }
+            else
+            {
+                smi.dll_base = mi.lpBaseOfDll;
+                smi.size = mi.SizeOfImage;
+                smi.entry_point = mi.EntryPoint;
+            }
+            ret = em(&smi, user);
+        }
+    }
+}
+
+/******************************************************************
+ *		enum_threads
+ *
+ *
+ */
+void    enum_threads(DWORD processID, EnumThreadsCB et, void* user)
+{
+    HANDLE              hSnap, hThread;
+    THREADENTRY32       te32;
+    struct thread_info  sti;
+    int                 ret = 1;
+
+    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+    if (!hSnap) return;
+    te32.dwSize = sizeof(te32);
+    if (Thread32First(hSnap, &te32))
+    {
+        do
+        {
+            if (te32.th32OwnerProcessID != processID) continue;
+            sti.windows_tid = te32.th32ThreadID;
+            sti.suspend_count = (DWORD)-2; /* error code */
+            hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
+            if (hThread != NULL)
+            {
+                ULONG   status;
+                if (GetExitCodeThread(hThread, &status))
+                {
+                    if (status == STILL_ACTIVE)
+                    {
+                        /* FIXME: this is a bit brutal...
+                         * some nicer way shall be found 
+                         */
+                        if (te32.th32ThreadID != GetCurrentThreadId())
+                        {
+                            status = SuspendThread(hThread);
+                            if (status != (DWORD)-1)
+                            {
+                                sti.suspend_count = status;
+                                ResumeThread(hThread);
+                            }
+                        } else sti.suspend_count = 0;
+                    } else sti.suspend_count = (DWORD)-1;
+                }
+            }
+            sti.priority = GetThreadPriority(hThread);
+            ret = et(&sti, user);
+            CloseHandle(hThread);
+        } while (ret && Thread32Next(hSnap, &te32));
+    }
+    CloseHandle(hSnap);
+}
+
+#if 1
+/******************************************************************
+ *		get_symbol
+ *
+ * Here it gets ugly :-(
+ * This is quick hack to get the address of first_dll in a running process
+ * We make the following assumptions:
+ *      - libwine (lib) is loaded in all processes at the same address (or 
+ *        at least at the same address at this process)
+ *      - we load the same libwine.so version in this process and in the
+ *        examined process
+ * Final address is gotten by: 1/ querying the address of a known exported 
+ * symbol out of libwine.so with dlsym, 2/ then querying nm on libwine.so to
+ * get the offset from the data segment of this known symbol and of first_dll,
+ * 3/ computing the actual address of first_dll by adding the result of 1/ and
+ * the delta of 2/.
+ * Ugly, yes, but it somehow works. We should replace that with debughlp 
+ * library, that'd be way better. Exporting first_dll from libwine.so would make
+ * this code simpler, but still ugly.
+ */
+/* FIXME: we only need those includes for the next function */
+#include <dlfcn.h> /* for RTLD_LAZY */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include "wine/library.h"
+
+void* get_symbol(HANDLE hProcess, const char* name, const char* lib)
+{
+    char                buffer[1024];
+    void*               h;
+    DWORD               addr = 0, tmp = 0;
+    FILE*               f;
+    char*               env;
+
+    if (!(h = wine_dlopen(lib, RTLD_LAZY, buffer, sizeof(buffer))))
+    {
+        printf("Couldn't load %s (%s)\n", lib, buffer);
+	return NULL;
+    }
+
+    env = getenv("LD_LIBRARY_PATH");
+    if (env)
+    {
+        char            *next, *ptr;
+        struct stat     s;
+
+        for (ptr = env = strdup(env); ptr; ptr = next)
+        {
+            next = strchr(ptr, ':');
+            if (next) *next++ = '\0';
+            sprintf(buffer, "nm %s", ptr);
+            if (buffer[strlen(buffer) - 1] != '/') strcat(buffer, "/");
+            strcat(buffer, lib);
+            if (stat(buffer + 3, &s) == 0) break;
+        }
+        free(env);
+        if (!ptr) 
+        {
+	    printf("Couldn't find %s in LD_LIBRARY_PATH\n", lib);
+	    return NULL;
+	}
+    }
+    if (!(f = popen(buffer, "r")))
+    {
+        printf("Cannot execute '%s'\n", buffer);
+	return NULL;
+    }
+
+    while (fgets(buffer, sizeof(buffer), f))
+    {
+        char *p = buffer + strlen(buffer) - 1;
+        if (p < buffer) continue;
+        if (*p == '\n') *p-- = 0;
+        if (p - buffer < 11) continue;
+        buffer[8] = '\0';
+        if (!strcmp(&buffer[11], name)) addr += strtol(buffer, NULL, 16);
+        if (buffer[9] == 'D' && !tmp && (tmp = (DWORD)wine_dlsym(h, &buffer[11], NULL, 0)) != 0)
+            addr += tmp - strtol(buffer, NULL, 16);
+    }
+    pclose(f);
+    return (char*)addr;
+}
+#else
+void* get_symbol(HANDLE hProcess, const char* name, const char* lib)
+{
+  printf("getSymbol: not implemented on this platform\n");
+  return NULL;
+}
+#endif
+
+struct dll_option_layout
+{
+    void*               next;
+    void*               prev;
+    char* const*        channels;
+    int                 nb_channels;
+};
+
+/******************************************************************
+ *		enum_channel
+ *
+ * Enumerates all known channels on process hProcess through callback
+ * ce.
+ */
+int enum_channel(HANDLE hProcess, EnumChannelCB ce, void* user)
+{
+    struct dll_option_layout    dol;
+    int                         i, j, ret = 1;
+    char*                       buf_addr;
+    unsigned char               buffer[32];
+    void*                       addr;
+    const char**                cache;
+    unsigned                    num_cache, used_cache;
+
+    addr = get_symbol(hProcess, "first_dll", "libwine.so");
+    if (!addr) return -1;
+    cache = malloc((num_cache = 32) * sizeof(char*));
+    used_cache = 0;
+
+    while (ret && addr && ReadProcessMemory(hProcess, addr, &dol, sizeof(dol), NULL))
+    {
+        for (i = 0; i < dol.nb_channels; i++)
+        {
+            if (ReadProcessMemory(hProcess, (void*)(dol.channels + i), &buf_addr, sizeof(buf_addr), NULL) &&
+                ReadProcessMemory(hProcess, buf_addr, buffer, sizeof(buffer), NULL))
+            {
+                /* since some channels are defined in multiple compilation units, 
+                 * they will appear several times...
+                 * so cache the channel's names we already reported and don't report
+                 * them again
+                 */
+                for (j = 0; j < used_cache; j++)
+                    if (!strcmp(cache[j], buffer + 1)) break;
+
+                if (j == used_cache)
+                {
+                    if (used_cache == num_cache)
+                        cache = realloc(cache, (num_cache *= 2) * sizeof(char*));
+                    cache[used_cache++] = strdup(buffer + 1);
+                    ret = ce(hProcess, buf_addr, buffer, user);
+                }
+            }
+        }
+        addr = dol.next;
+    }
+    for (j = 0; j < used_cache; j++) free((char*)cache[j]);
+    free(cache);
+    return 0;
+}
+
+struct cce_user
+{
+    const char* name;           /* channel to look for */
+    unsigned    value, mask;    /* how to change channel */
+    unsigned    done;           /* number of successful changes */
+};
+
+/******************************************************************
+ *		change_channel_CB
+ *
+ * Callback used for changing a given channel attributes
+ */
+static int change_channel_CB(HANDLE hProcess, void* addr, char* buffer, void* pmt)
+{
+    struct cce_user* user = (struct cce_user*)pmt;
+
+    if (!user->name || !strcmp(buffer + 1, user->name))
+    {
+        buffer[0] = (buffer[0] & ~user->mask) | (user->value & user->mask);
+        if (WriteProcessMemory(hProcess, addr, buffer, 1, NULL))
+        {
+            user->done++;
+        }
+        else printf("Couldn't write back memory (%lu) at %p\n", GetLastError(), addr);
+    }
+    return 1;
+}
+
+/******************************************************************
+ *		change_channel
+ *
+ * change state of a debug channel for a given process
+ */
+int     change_channel(HANDLE hProcess, const char* name, unsigned value, unsigned mask)
+{
+    struct cce_user     user;
+
+    user.name = name;
+    user.value = value;
+    user.mask = mask;
+    user.done = 0;
+
+    enum_channel(hProcess, change_channel_CB, &user);
+    return user.done;
+}
+
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/visual.c	2003-09-03 21:06:09.000000000 +0200
@@ -0,0 +1,835 @@
+#include <stdio.h>
+#include <windows.h>
+#include <commctrl.h>
+#include "wineproc.h"
+#include "wineprocres.h"
+#include "splitter.h"
+
+/* current selected process handled (if any) */
+static  DWORD           S_CurrPid;
+
+static struct config
+{
+    BOOL        horzSplit;
+    int         timer;          /* interval between updates in tenth of second, -1 means disabled */
+} config;
+
+/* a couple of UI dimensions definition */
+#define         TB_HEIGHT       32      /* height of toolbar */
+#define         SPACEX          5       /* horizontal separation of elements */
+#define         SPACEY          5       /* vertical separation of elements */
+
+#define NUM_OF(x)       (sizeof(x) / sizeof(x[0]))
+
+struct button_tb
+{
+    unsigned    ids;
+    unsigned    message;
+};
+
+static struct button_tb         button_tb[] =
+{
+    {IDS_TB_KILL_PROCESS,       IDM_KILL_PROCESS},
+    {IDS_TB_SUSPEND_THREAD,     IDM_SUSPEND_THREAD},
+    {IDS_TB_RESUME_THREAD,      IDM_RESUME_THREAD},
+    {IDS_TB_REFRESH,            IDM_REFRESH},
+    {0,                         0},
+    {IDS_TB_ABOUT,              IDM_ABOUT},
+};
+
+struct column_lv
+{
+    unsigned    ids_name;
+    unsigned    width;
+};
+
+static struct column_lv         process_lv[] =
+{
+    {IDS_PROCESS_PID, 40}, 
+    {IDS_PROCESS_PROCESS, 60}
+};
+static struct column_lv         module_lv[] =
+{
+    {IDS_DTL_MODULE_NAME, 20}, 
+    {IDS_DTL_MODULE_FILE, 60}, 
+    {IDS_DTL_MODULE_ADDRESS, 10}, 
+    {IDS_DTL_MODULE_SIZE, 10}
+};
+static struct column_lv         thread_lv[] =
+{
+    {IDS_DTL_THREAD_TID, 20},
+    {IDS_DTL_THREAD_STATE, 40},
+    {IDS_DTL_THREAD_PRIORITY, 40},
+};
+static struct column_lv         channel_lv[] =
+{
+    {IDS_DTL_CHANNEL_NAME, 40}, 
+    {IDS_DTL_CHANNEL_FIXME, 15}, 
+    {IDS_DTL_CHANNEL_ERR, 15}, 
+    {IDS_DTL_CHANNEL_WARN, 15}, 
+    {IDS_DTL_CHANNEL_TRACE, 15}
+};
+
+static void     update_module_listview(HWND hModuleLV);
+static void     update_thread_listview(HWND hThreadLV);
+static void     update_channel_listview(HWND hChannelLV);
+
+typedef struct
+{
+    struct column_lv*   columns;
+    unsigned            num_columns;
+    unsigned            idc;
+    unsigned            ids_name;
+    void                (*update)(HWND);
+} details_t;
+
+static  details_t       details[] =
+{
+    {process_lv, NUM_OF(process_lv), IDC_PROCESS_LV,    IDS_PROCESS_TAB,        NULL},
+    {module_lv,  NUM_OF(module_lv),  IDC_DTL_MODULE_LV, IDS_DTL_MODULE_TAB,     update_module_listview},
+    {thread_lv,  NUM_OF(thread_lv),  IDC_DTL_THREAD_LV, IDS_DTL_THREAD_TAB,     update_thread_listview},
+    {channel_lv, NUM_OF(channel_lv), IDC_DTL_CHANNEL_LV,IDS_DTL_CHANNEL_TAB,    update_channel_listview},
+};
+
+/* check that all definitions are in sync */
+
+#define CONCAT2(a,b) a##b
+#define CONCAT(a,b) CONCAT2(a,b)
+#define STATIC_CHECK(a) extern char CONCAT(dummy,__LINE__) [1 / ((a) ? 1 : 0)]
+STATIC_CHECK(NUM_OF(details) == (IDC_DTL_LAST - IDC_DTL_FIRST + 2));
+
+static void     change_current_detail(HWND hwndDlg, unsigned idc);
+
+/******************************************************************
+ *		init_listview
+ *
+ *
+ */
+static void     init_listview(HWND hwndDlg, details_t* d)
+{
+    HWND        hLV = GetDlgItem(hwndDlg, d->idc);
+    LVCOLUMN    lvc;
+    unsigned    i;
+    char        buffer[1024];
+
+    lvc.mask = LVCF_FMT | LVCF_TEXT;
+    lvc.fmt = LVCFMT_LEFT;
+    lvc.pszText = buffer;
+
+    for (i = 0; i < d->num_columns; i++)
+    {
+        if (LoadString(GetModuleHandle(NULL), d->columns[i].ids_name,
+                       buffer, sizeof(buffer)))
+            ListView_InsertColumn(hLV, i, &lvc);
+    }
+}
+
+/******************************************************************
+ *		adjust_listview
+ *
+ *
+ */
+static void     adjust_listview(HWND hLV, details_t* d, unsigned width)
+{
+    LVCOLUMN    lvc;
+    unsigned    i;
+
+    lvc.mask = LVCF_WIDTH;
+
+    for (i = 0; i < d->num_columns; i++)
+    {
+        lvc.cx = MulDiv(d->columns[i].width, width, 100);
+        ListView_SetColumn(hLV, i, &lvc);
+    }
+}
+
+/******************************************************************
+ *		create_toolbar
+ *
+ *
+ */
+static void     create_toolbar(HWND hwndDlg)
+{
+    HWND        hToolbar;
+    TBBUTTON    tbb[NUM_OF(button_tb)];
+    char        buffer[1024];
+    HINSTANCE   hInst = GetModuleHandle(NULL);
+    unsigned    i;
+
+    hToolbar = CreateWindow(TOOLBARCLASSNAME, NULL, 
+                            WS_CHILD | CCS_ADJUSTABLE | TBSTYLE_FLAT, 
+                            0, 0, 1, 1, hwndDlg, (HMENU)IDC_TOOLBAR, 
+                            GetModuleHandle(NULL), 0);
+
+
+    for (i = 0; i < NUM_OF(button_tb); i++)
+    {
+        tbb[i].idCommand = button_tb[i].message;
+        tbb[i].fsState = TBSTATE_ENABLED;
+        tbb[i].dwData = 0L;
+        if (button_tb[i].message)
+        {
+            tbb[i].iBitmap = I_IMAGENONE;
+            tbb[i].fsStyle = TBSTYLE_BUTTON;
+            LoadString(hInst, button_tb[i].ids, buffer, sizeof(buffer));
+            tbb[i].iString = SendMessage(hToolbar, TB_ADDSTRING, 0, (LPARAM)buffer);
+        }
+        else
+        {
+            tbb[i].iBitmap = 0;
+            tbb[i].fsStyle = BTNS_SEP;
+            tbb[i].iString = -1;
+        }
+    }
+
+    SendMessage(hToolbar, TB_BUTTONSTRUCTSIZE, (WPARAM) sizeof(TBBUTTON), 0); 
+    SendMessage(hToolbar, TB_ADDBUTTONS, NUM_OF(tbb), (LPARAM)&tbb);
+
+    ShowWindow(hToolbar, SW_SHOW);
+}
+
+/******************************************************************
+ *		create_tabs
+ *
+ *
+ */
+static void     create_tabs(HWND hwndDlg)
+{
+    HWND        hTabs;
+    TCITEM      tci;
+    unsigned    i;
+    char        buffer[1024];
+
+    hTabs = CreateWindow(WC_TABCONTROL, NULL, WS_CHILD | WS_VISIBLE, 
+                         0, 0, 1, 1, hwndDlg, (HMENU)IDC_DETAIL_TAB, 
+                         GetModuleHandle(NULL), 0);
+
+    tci.mask = TCIF_TEXT;
+    tci.pszText = buffer;
+
+    for (i = 1; i < NUM_OF(details); i++)
+    {
+        if (LoadString(GetModuleHandle(NULL), details[i].ids_name,
+                       buffer, sizeof(buffer)))
+            TabCtrl_InsertItem(hTabs, i, &tci);
+    }
+}
+
+struct lp_user
+{
+    HWND        hProcLV;
+    BOOL        found;
+};
+
+/******************************************************************
+ *		list_processes_CB
+ *
+ * Performs a basic list operation on running processes
+ */
+static int    list_processes_CB(HANDLE hProcess, DWORD pid, void* user)
+{
+    char        spid[16], bufA[MAX_PATH];
+    LVITEM      lvi;
+    int         index;
+    struct lp_user*     lpu = (struct lp_user*)user;
+
+    get_process_name(hProcess, bufA, sizeof(bufA));
+
+    memset(&lvi, 0, sizeof(lvi));
+
+    lvi.mask = LVIF_TEXT | LVIF_PARAM;
+    lvi.pszText = spid;
+    sprintf(spid, "%ld", pid);
+    lvi.lParam = pid;
+
+    if (pid == S_CurrPid)
+    {
+        lvi.mask |= LVIF_STATE;
+        lvi.state = LVIS_SELECTED;
+        lvi.stateMask = LVIS_SELECTED;
+        lpu->found = TRUE;
+    }
+    index = ListView_InsertItem(lpu->hProcLV, &lvi);
+    if (index != -1) ListView_SetItemText(lpu->hProcLV, index, 1, bufA);
+    return 1;
+}
+
+/******************************************************************
+ *		update_process_listview
+ *
+ *
+ */
+static void     update_process_listview(HWND hProcLV)
+{
+    struct lp_user      lpu;
+
+    ListView_DeleteAllItems(hProcLV);
+    lpu.hProcLV = hProcLV;
+    lpu.found = FALSE;
+    enum_processes(list_processes_CB, PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, (void*)&lpu);
+    /* current process disapeared, likely it has terminated */
+    if (S_CurrPid && !lpu.found) S_CurrPid = 0;
+    change_current_detail(GetParent(hProcLV), 0);
+}
+
+/******************************************************************
+ *		kill_process
+ *
+ *
+ */
+static void     kill_process(HWND hwndDlg)
+{
+    HANDLE      hProcess = open_process(S_CurrPid, PROCESS_TERMINATE);
+    if (!hProcess) return; /* FIXME message box */
+    if (!TerminateProcess(hProcess, 0))
+    {
+        MessageBox(NULL, "Couldn't kill process", "Error", MB_OK);
+        return;
+    }
+    S_CurrPid = 0;
+    CloseHandle(hProcess);
+    update_process_listview(GetDlgItem(hwndDlg, IDC_PROCESS_LV));
+}
+
+/******************************************************************
+ *		update_module_listviewCB
+ *
+ *
+ */
+static int      update_module_listviewCB(struct module_info* smi, void* user)
+{
+    char        buffer[16];
+    LVITEM      lvi;
+    int         index;
+    HWND        hModuleLV = (HWND)user;
+
+    memset(&lvi, 0, sizeof(lvi));
+
+    lvi.mask = LVIF_TEXT;
+    lvi.pszText = smi->module;
+    if ((index = ListView_InsertItem(hModuleLV, &lvi)) == -1) return 0;
+
+    ListView_SetItemText(hModuleLV, index, 1, smi->file);
+
+    sprintf(buffer, "%p", smi->dll_base);
+    ListView_SetItemText(hModuleLV, index, 2, buffer);
+
+    sprintf(buffer, "0x%x", smi->size);
+    ListView_SetItemText(hModuleLV, index, 3, buffer);
+    
+    return 1;
+}
+
+/******************************************************************
+ *		update_module_listview
+ *
+ *
+ */
+static void     update_module_listview(HWND hModuleLV)
+{
+    HANDLE              hProcess;
+
+    ListView_DeleteAllItems(hModuleLV);
+
+    hProcess = open_process(S_CurrPid, PROCESS_QUERY_INFORMATION|PROCESS_VM_READ);
+    if (!hProcess) return; /* FIXME MessageBox */
+    enum_modules(hProcess, update_module_listviewCB, (void*)hModuleLV);
+    CloseHandle(hProcess);
+}
+
+/******************************************************************
+ *		update_thread_listviewCB
+ *
+ *
+ */
+static int      update_thread_listviewCB(struct thread_info* sti, void* user)
+{
+    char        buffer[32];
+    LVITEM      lvi;
+    int         index;
+    HWND        hThreadLV = (HWND)user;
+
+    memset(&lvi, 0, sizeof(lvi));
+
+    lvi.mask = LVIF_TEXT | LVIF_PARAM;
+    lvi.pszText = buffer;
+    lvi.lParam = sti->windows_tid;
+    sprintf(buffer, "%u", sti->windows_tid);
+    if ((index = ListView_InsertItem(hThreadLV, &lvi)) == -1) return 0;
+
+    switch (sti->suspend_count)
+    {
+    case -2:
+        LoadString(GetModuleHandle(NULL), 
+                   IDS_DTL_THREAD_STATE_UNKNOWN, 
+                   buffer, sizeof(buffer));
+        break;
+    case -1:
+        LoadString(GetModuleHandle(NULL), 
+                   IDS_DTL_THREAD_STATE_TERMINATED, 
+                   buffer, sizeof(buffer));
+        break;
+    case 0:
+        LoadString(GetModuleHandle(NULL), 
+                   IDS_DTL_THREAD_STATE_RUNNING,
+                   buffer, sizeof(buffer));
+        break;
+    default:
+         LoadString(GetModuleHandle(NULL), 
+                   IDS_DTL_THREAD_STATE_SUSPENDED, 
+                   buffer, sizeof(buffer));
+         sprintf(buffer + strlen(buffer), "<%d>", sti->suspend_count);
+         break;
+    }
+    ListView_SetItemText(hThreadLV, index, 1, buffer);
+    
+    sprintf(buffer, "%u", sti->priority);
+    ListView_SetItemText(hThreadLV, index, 2, buffer);
+
+    return 1;
+}
+
+/******************************************************************
+ *		update_thread_listview
+ *
+ *
+ */
+static void     update_thread_listview(HWND hThreadLV)
+{
+    ListView_DeleteAllItems(hThreadLV);
+    enum_threads(S_CurrPid, update_thread_listviewCB, (void*)hThreadLV);
+}
+
+/******************************************************************
+ *		get_selected_thread
+ *
+ *
+ */
+static ULONG    get_selected_thread(HWND hThreadLV)
+{
+    int         idx;
+    LVITEM      lvi;
+
+    idx = TabCtrl_GetCurSel(GetDlgItem(GetParent(hThreadLV), IDC_DETAIL_TAB));
+
+    if (S_CurrPid == 0 || (IDC_DTL_FIRST + idx != IDC_DTL_THREAD_LV) ||
+        (idx = ListView_GetSelectionMark(hThreadLV) == -1))
+    {
+        MessageBox(GetParent(hThreadLV), "No thread selected", "Error", MB_OK);
+        return (ULONG)-1;;
+    }
+    lvi.mask = LVIF_PARAM;
+    lvi.iItem = idx;
+    lvi.iSubItem = 0;
+    if (ListView_GetItem(hThreadLV, &lvi)) return lvi.lParam;
+    return (ULONG)-1;
+}
+
+/******************************************************************
+ *		suspend_thread
+ *
+ *
+ */
+static void     suspend_thread(HWND hwndDlg)
+{
+    HWND        hThreadLV = GetDlgItem(hwndDlg, IDC_DTL_THREAD_LV);
+    ULONG       tid;
+    HANDLE      hThread;
+
+    tid = get_selected_thread(hThreadLV);
+    if (tid == (ULONG)-1) return;
+        
+    if (tid == GetCurrentThreadId())
+        MessageBox(hwndDlg, "Cannot suspend self", "Error", MB_OK);
+    else
+    {
+        hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
+        if (hThread != NULL)
+        {
+            if (SuspendThread(hThread) == (DWORD)-1)
+                MessageBox(hwndDlg, "Cannot suspend thread", "Error", MB_OK);
+            CloseHandle(hThread);
+        }
+        else MessageBox(hwndDlg, "Cannot open thread", "Error", MB_OK);
+    }
+    update_thread_listview(hThreadLV);
+}
+
+/******************************************************************
+ *		resume_thread
+ *
+ *
+ */
+static void     resume_thread(HWND hwndDlg)
+{
+    HWND        hThreadLV = GetDlgItem(hwndDlg, IDC_DTL_THREAD_LV);
+    ULONG       tid;
+    HANDLE      hThread;
+
+    tid = get_selected_thread(hThreadLV);
+    if (tid == (ULONG)-1) return;
+        
+    hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid);
+    if (hThread != NULL)
+    {
+        if (ResumeThread(hThread) == (DWORD)-1)
+            MessageBox(hwndDlg, "Cannot suspend thread", "Error", MB_OK);
+        CloseHandle(hThread);
+    }
+    else MessageBox(hwndDlg, "Cannot open thread", "Error", MB_OK);
+    update_thread_listview(hThreadLV);
+}
+
+/******************************************************************
+ *		list_channel_CB
+ *
+ *
+ */
+static int     list_channel_CB(HANDLE hProcess, void* addr, char* buffer, void* user)
+{
+    int         j;
+    char        val[2];
+    LVITEM      lvi;
+    int         index;
+    HWND        hChannelLV = (HWND)user;
+
+    memset(&lvi, 0, sizeof(lvi));
+
+    lvi.mask = LVIF_TEXT;
+    lvi.pszText = buffer + 1;
+    val[1] = '\0';
+
+    index = ListView_InsertItem(hChannelLV, &lvi);
+    if (index == -1) return 0;
+
+    for (j = 0; j < 4; j++)
+    {
+        val[0] = (buffer[0] & (1 << j)) ? 'x' : ' ';
+        ListView_SetItemText(hChannelLV, index, j + 1, val);
+    }
+    return 1;
+}
+
+/******************************************************************
+ *		update_channel_listview
+ *
+ *
+ */
+static void     update_channel_listview(HWND hChannelLV)
+{
+    HANDLE      hProcess;
+
+    ListView_DeleteAllItems(hChannelLV);
+    hProcess = open_process(S_CurrPid, PROCESS_VM_OPERATION | PROCESS_VM_READ);
+    if (!hProcess) return; /* FIXME messagebox */
+    enum_channel(hProcess, list_channel_CB, (void*)hChannelLV);
+    CloseHandle(hProcess);
+}
+
+/******************************************************************
+ *		interact_listview
+ *
+ *
+ */
+static void     interact_listview(HWND hwndDlg, NMITEMACTIVATE* nmia)
+{
+    LVHITTESTINFO       lhti;
+    HWND                hChannelLV;
+    HANDLE              hProcess;
+
+    switch (nmia->hdr.idFrom)
+    {
+    case IDC_DTL_CHANNEL_LV:
+        hProcess = open_process(S_CurrPid, PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE);
+        if (!hProcess) return; /* FIXME message box */
+        lhti.pt = nmia->ptAction;
+        hChannelLV = GetDlgItem(hwndDlg, IDC_DTL_CHANNEL_LV);
+        SendMessage(hChannelLV, LVM_SUBITEMHITTEST, 0, (LPARAM)&lhti);
+        if (nmia->iSubItem >= 1 && nmia->iSubItem <= 4)
+        {
+            char        val[2];
+            char        name[32];
+            unsigned    bitmask = 1 << (lhti.iSubItem - 1);
+
+            ListView_GetItemText(hChannelLV, lhti.iItem, 0, name, sizeof(name));
+            ListView_GetItemText(hChannelLV, lhti.iItem, lhti.iSubItem, val, sizeof(val));
+            if (change_channel(hProcess, name, val[0] == 'x' ? 0 : bitmask, bitmask))
+            {
+                val[0] ^= ('x' ^ ' ');
+                ListView_SetItemText(hChannelLV, lhti.iItem, lhti.iSubItem, val);
+            }
+        }
+        CloseHandle(hProcess);
+        break;
+    }
+}
+
+/******************************************************************
+ *		change_current_detail
+ *
+ *
+ */
+static void     change_current_detail(HWND hwndDlg, unsigned idc_new)
+{
+    static      unsigned curr_tab;
+
+    unsigned idc;
+
+    if (idc_new)
+    {
+        if (curr_tab == idc_new) return;
+        curr_tab = idc_new;
+    }
+    for (idc = IDC_DTL_FIRST; idc <= IDC_DTL_LAST; idc++)
+        ShowWindow(GetDlgItem(hwndDlg, idc), (idc == curr_tab) ? SW_SHOW : SW_HIDE);
+
+    if (curr_tab >= IDC_DTL_FIRST && curr_tab <= IDC_DTL_LAST)
+    {
+        HWND    hLV = GetDlgItem(hwndDlg, curr_tab);
+
+        SendMessage(hLV, WM_SETREDRAW, FALSE, 0);
+        details[curr_tab - IDC_DTL_FIRST + 1].update(hLV);
+        SendMessage(hLV, WM_SETREDRAW, TRUE, 0);
+    }
+}
+
+/******************************************************************
+ *		change_current_process
+ *
+ *
+ */
+static void     change_current_process(HWND hwndDlg, NMITEMACTIVATE* nmia)
+{
+    HWND        hProcLV = GetDlgItem(hwndDlg, IDC_PROCESS_LV);
+    LVITEM      lvi;
+
+    if (nmia->iItem == -1) return;
+
+    lvi.mask = LVIF_PARAM;
+    lvi.iItem = nmia->iItem;
+    lvi.iSubItem = 0;
+    if (ListView_GetItem(hProcLV, &lvi))
+    {
+        S_CurrPid = lvi.lParam;
+        change_current_detail(hwndDlg, 0);
+    }
+}
+
+/******************************************************************
+ *		recompute_position
+ *
+ *
+ */
+static void     recompute_position(HWND hwndDlg, BOOL init_split, BOOL repaint)
+{
+    HWND        hTabs      = GetDlgItem(hwndDlg, IDC_DETAIL_TAB);
+    RECT        r;
+    LONG        w, h, split;
+    int         idc;
+
+    /* adjust two main windows */
+    GetClientRect(hwndDlg, &r);
+
+    if (config.horzSplit)
+    {
+        w = r.right - r.left - 2 * SPACEX;
+        h = r.bottom - r.top - TB_HEIGHT - 4 * SPACEY;
+
+        if (init_split)
+        {
+            split = h / 2;
+            MoveWindow(GetDlgItem(hwndDlg, IDC_SPLITTER), SPACEX, TB_HEIGHT + 2 * SPACEY + split, w, SPACEY, repaint);
+        }
+        else
+        {
+            SendDlgItemMessage(hwndDlg, IDC_SPLITTER, SPM_GETPOS, 0L, (LPARAM)&split);
+            split -= TB_HEIGHT + 2 * SPACEY;
+            /* may also have to change splitter width (on size modification) */
+        }
+
+        MoveWindow(GetDlgItem(hwndDlg, IDC_PROCESS_LV), SPACEX, TB_HEIGHT + 2 * SPACEY, w, split, repaint);
+        MoveWindow(hTabs, SPACEX, TB_HEIGHT + 3 * SPACEY + split, w, h - split, repaint);
+        adjust_listview(GetDlgItem(hwndDlg, IDC_PROCESS_LV), &details[0], w);
+
+        /* adjust second listview in tab control */
+        GetClientRect(hTabs, &r);
+        TabCtrl_AdjustRect(hTabs, FALSE, &r);
+        for (idc = IDC_DTL_FIRST; idc <= IDC_DTL_LAST; idc++)
+        {
+            HWND    hLV = GetDlgItem(hwndDlg, idc);
+            MoveWindow(hLV, SPACEX, TB_HEIGHT + 3 * SPACEY + split + r.top, 
+                       r.right - r.left, r.bottom - r.top, repaint);
+            adjust_listview(hLV, &details[idc - IDC_DTL_FIRST + 1], w);
+        }
+    }
+    else
+    {
+        w = r.right - r.left - 4 * SPACEX;
+        h = r.bottom - r.top - TB_HEIGHT - 2 * SPACEY;
+
+        if (init_split)
+        {
+            split = w / 2;
+            MoveWindow(GetDlgItem(hwndDlg, IDC_SPLITTER), SPACEX + split, TB_HEIGHT + 2 * SPACEY, SPACEX, h, repaint);
+        }
+        else
+        {
+            SendDlgItemMessage(hwndDlg, IDC_SPLITTER, SPM_GETPOS, 0L, (LPARAM)&split);
+            split -= SPACEX;
+            /* may also have to change splitter height (on size modification) */
+        }
+
+        MoveWindow(GetDlgItem(hwndDlg, IDC_PROCESS_LV), SPACEX, TB_HEIGHT + 2 * SPACEY, split, h, repaint);
+        MoveWindow(hTabs, 2 * SPACEX + split, TB_HEIGHT + 2 * SPACEY, w - split, h, repaint);
+        adjust_listview(GetDlgItem(hwndDlg, IDC_PROCESS_LV), &details[0], split);
+
+        /* adjust second listview in tab control */
+        GetClientRect(hTabs, &r);
+        TabCtrl_AdjustRect(hTabs, FALSE, &r);
+        for (idc = IDC_DTL_FIRST; idc <= IDC_DTL_LAST; idc++)
+        {
+            HWND    hLV = GetDlgItem(hwndDlg, idc);
+            MoveWindow(hLV, 2 * SPACEX + split + r.left, TB_HEIGHT + 2 * SPACEY + r.top, 
+                       r.right - r.left, r.bottom - r.top, repaint);
+            adjust_listview(hLV, &details[idc - IDC_DTL_FIRST + 1], w - split);
+        }
+    }
+    MoveWindow(GetDlgItem(hwndDlg, IDC_TOOLBAR), SPACEX, SPACEY, w, TB_HEIGHT, repaint);
+}
+
+/******************************************************************
+ *		init_dlg
+ *
+ *
+ */
+static void     init_dlg(HWND hwndDlg)
+{
+    unsigned    i;
+
+    InitCommonControls(); 
+    init_splitter();
+
+    for (i = 0; i < NUM_OF(details); i++)
+    {
+        /* LVS_SINGLESEL */
+        CreateWindow(WC_LISTVIEW, NULL, 
+                     WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS | /*LVS_SORTASCENDING | */WS_VISIBLE,
+                     0, 0, 1, 1, hwndDlg, (HMENU)details[i].idc, GetModuleHandle(NULL), 0);
+        init_listview(hwndDlg, &details[i]);
+    }
+    create_toolbar(hwndDlg);
+    create_tabs(hwndDlg);
+    CreateWindow("MySplitter", NULL, WS_CHILD | WS_VISIBLE | (config.horzSplit ? SPS_HORIZONTAL : SPS_VERTICAL),
+                 0, 0, 1, 1, hwndDlg, (HMENU)IDC_SPLITTER, GetModuleHandle(NULL), 0);
+
+    recompute_position(hwndDlg, TRUE, FALSE);
+    if (config.timer != -1) SetTimer(hwndDlg, 0, config.timer * 100, NULL);
+    ListView_SetExtendedListViewStyle(GetDlgItem(hwndDlg, IDC_PROCESS_LV),
+                                      LVS_EX_FULLROWSELECT);
+    ListView_SetExtendedListViewStyle(GetDlgItem(hwndDlg, IDC_DTL_THREAD_LV),
+                                      LVS_EX_FULLROWSELECT);
+
+    update_process_listview(GetDlgItem(hwndDlg, IDC_PROCESS_LV));
+}
+
+/******************************************************************
+ *		DlgProc
+ *
+ *
+ */
+static BOOL CALLBACK DlgProc(HWND hwndDlg, UINT message, 
+                             WPARAM wParam, LPARAM lParam) 
+{ 
+    NMHDR*      nmh;
+
+    switch (message) 
+    { 
+    case WM_INITDIALOG:
+        init_dlg(hwndDlg);
+        return TRUE;
+    case WM_COMMAND:
+        switch (LOWORD(wParam)) 
+        { 
+        case IDOK: 
+        case IDCANCEL: 
+            KillTimer(hwndDlg, 0);
+            EndDialog(hwndDlg, wParam);
+            break;
+        case IDM_KILL_PROCESS:
+            kill_process(hwndDlg);
+            break;
+        case IDM_SUSPEND_THREAD:
+            suspend_thread(hwndDlg);
+            break;
+        case IDM_RESUME_THREAD:
+            resume_thread(hwndDlg);
+            break;
+        case IDM_ABOUT:
+            MessageBox(hwndDlg, "NIY", "NIY", MB_OK);
+            break;
+        case IDM_REFRESH:
+            update_process_listview(GetDlgItem(hwndDlg, IDC_PROCESS_LV));
+            break;
+        default: 
+            printf("Got command %u\n", LOWORD(wParam));
+            break;
+        }
+        break;
+    case WM_NOTIFY:
+        nmh = (NMHDR*)lParam;
+        switch (nmh->code)
+        {
+        case NM_CLICK:
+            switch (nmh->idFrom)
+            {
+            case IDC_PROCESS_LV:
+                change_current_process(hwndDlg, (NMITEMACTIVATE*)lParam);
+                break;
+            case IDC_DETAIL_TAB:
+                change_current_detail(hwndDlg, IDC_DTL_FIRST + TabCtrl_GetCurSel(nmh->hwndFrom));
+                break;
+            case IDC_SPLITTER:
+                recompute_position(hwndDlg, FALSE, TRUE);
+                break;
+            }
+            if (nmh->idFrom >= IDC_DTL_FIRST && nmh->idFrom <= IDC_DTL_LAST)
+                interact_listview(hwndDlg, (NMITEMACTIVATE*)lParam);
+            break;
+        }
+        break;
+    case WM_SIZE:
+        recompute_position(hwndDlg, FALSE, TRUE);
+        return TRUE;
+    case WM_TIMER:
+        update_process_listview(GetDlgItem(hwndDlg, IDC_PROCESS_LV));
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+/******************************************************************
+ *		load_configuration
+ *
+ *
+ */
+static void     load_configuration(void)
+{
+    /* FIXME: should be moved to some configuration in registry */
+    config.horzSplit = TRUE;
+    config.timer = 30;
+}
+
+/******************************************************************
+ *		visual
+ *
+ *
+ */
+int             visual(void)
+{
+    load_configuration();
+    DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_MAIN_DIALOG), 
+              NULL, DlgProc);
+    return 0;
+}
+
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/wineproc.c	2003-09-01 22:13:12.000000000 +0200
@@ -0,0 +1,404 @@
+/*
+ * WineProc
+ *
+ *      a command-line tool for wine process management
+ *
+ * (c) Eric Pouech      2003
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include "wineproc.h"
+
+static int      usage(void)
+{
+    printf("WineProc (v 0.01), a command-line tool for wine process management\n\n"
+	   "Usage:\n"
+	   " wineproc debugmsg <pid> <c> change debug channel on process <pid>\n"
+           "                             <c> has the same syntax as on the -debugmsg option\n"
+	   " wineproc module <pid>       prints modules' information on process <pid>\n"
+	   " wineproc thread <pid>       prints thread' information on process <pid>\n"
+	   " wineproc kill <pid>         kills process <pid>\n"
+	   " wineproc list               prints a list of running processes\n"
+	   "\n"
+	   " <pid> is a unique identifier for a process. It can either be its process id \n"
+	   " (in hex or decimal), or the name of the process\n"
+	   "\n"
+           " wineproc                    launches the visual version of wineproc\n"
+	   );
+    return -1;
+}
+
+/******************************************************************
+ *		fatal
+ *
+ * Print out a fatal error message and exit
+ */
+static void     fatal(const char *msg, ...)
+{
+    va_list valist;
+    va_start(valist, msg);
+    vfprintf(stderr, msg, valist);
+    va_end(valist);
+    exit(-1);
+}
+
+struct openProcessStrUser 
+{
+    const char*         str_pid;
+    HANDLE              hProcess;
+    DWORD               pid;
+};
+
+/******************************************************************
+ *		open_process_from_string_CB
+ *
+ *
+ */
+static int      open_process_from_string_CB(HANDLE hProcess, DWORD pid, void* user)
+{
+    struct openProcessStrUser*	opsu = (struct openProcessStrUser*)user;
+    char			buffer[MAX_PATH];
+    char*			ptr;
+
+    get_process_name(hProcess, buffer, sizeof(buffer));
+    ptr = strrchr(buffer, '\\');
+    if (!ptr) ptr = buffer;
+    if (strcasecmp(opsu->str_pid, ptr) != 0)
+    {
+        char* ptr2 = strrchr(ptr, '.');
+	if (!ptr2) return 1;
+	*ptr2 = '\0';
+	if (strcasecmp(opsu->str_pid, ptr) != 0) return 1;
+    }
+    if (opsu->hProcess) return 1; /* FIXME need error handling here */
+    opsu->pid = pid;
+    DuplicateHandle(GetCurrentProcess(), hProcess, 
+		    GetCurrentProcess(), &opsu->hProcess, 
+		    0, FALSE, DUPLICATE_SAME_ACCESS);
+    return 1;
+}
+
+/******************************************************************
+ *		open_process_from_string
+ *
+ * Returns a handle to a running process from its pid (in string form), 
+ * with a given set of access rights
+ */
+static HANDLE   open_process_from_string(const char* str_pid, DWORD access, DWORD* ppid)
+{
+    char*       end;
+    DWORD       pid = strtol(str_pid, &end, 0);
+    HANDLE	hProcess;
+
+    if (*end != '\0')
+    {
+        struct openProcessStrUser	opsu;
+
+        opsu.str_pid = str_pid;
+        opsu.hProcess = NULL;
+        enum_processes(open_process_from_string_CB, access, &opsu);
+	hProcess = opsu.hProcess;
+        if (ppid) *ppid = opsu.pid;
+    }
+    else
+    {
+        hProcess = open_process(pid, access);
+        if (ppid) *ppid = pid;
+    }
+    if (!hProcess) fatal("Couldn't open process %s (%lu)\n", str_pid, GetLastError());
+    return hProcess;
+}
+
+/******************************************************************
+ *		open_thread_from_string
+ *
+ *
+ */
+static HANDLE   open_thread_from_string(const char* str_tid, DWORD access)
+{
+    char*       end;
+    DWORD       tid = strtol(str_tid, &end, 0);
+    HANDLE	hThread;
+
+    if (*end != '\0') fatal("Malformed tid %s\n", str_tid);
+    hThread = OpenThread(access, FALSE, tid);
+    if (!hThread) fatal("Couldn't open thread %s (%lu)\n", str_tid, GetLastError());
+    return hThread;
+}
+
+/******************************************************************
+ *		list_processes_CB
+ *
+ * Performs a basic list operation on running processes
+ */
+static int      list_processes_CB(HANDLE hProcess, DWORD pid, void* user)
+{
+    char        bufA[MAX_PATH];
+
+    get_process_name(hProcess, bufA, sizeof(bufA));
+    printf("%-8lx %s%s\n", pid,  bufA, GetCurrentProcessId() == pid ? " <-- self" : "");
+    return 1;
+}
+
+/******************************************************************
+ *		list_processes
+ *
+ *
+ */
+static int     list_processes(void)
+{
+    printf("Pid      Executable\n");
+    enum_processes(list_processes_CB, PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, NULL);
+    return 0;
+}
+
+/******************************************************************
+ *		 print_process_moduleCB
+ *
+ *
+ */
+static int      print_process_moduleCB(struct module_info* smi, void* user)
+{
+    printf("%-12s %-8lx %-8x %8p   %s\n",
+           smi->module, (DWORD)smi->dll_base, smi->size, smi->entry_point, smi->file);
+    return 1;
+}
+
+/******************************************************************
+ *		print_process_module
+ *
+ * List loaded modules from a given module
+ */
+static int     print_process_module(const char* str_pid)
+{
+    HANDLE              hProcess;
+
+    hProcess = open_process_from_string(str_pid, PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, NULL);
+    printf("Module       Base     Size     EntryPoint File\n");
+    enum_modules(hProcess, print_process_moduleCB, NULL);
+    CloseHandle(hProcess);
+    return 0;
+}
+
+/******************************************************************
+ *		 print_process_threadCB
+ *
+ *
+ */
+static int      print_process_threadCB(struct thread_info* sti, void* user)
+{
+    char        buffer[32];
+
+    switch (sti->suspend_count)
+    {
+    case -2: strcpy(buffer, "--Unknown--"); break;
+    case -1: strcpy(buffer, "Terminated"); break;
+    case 0:  strcpy(buffer, "Running"); break;
+    default: sprintf(buffer, "Suspended<%d>", sti->suspend_count); break;
+    }
+
+    printf("%-10u %-8u %-15s\n", sti->windows_tid, sti->priority, buffer);
+    return 1;
+}
+
+/******************************************************************
+ *		print_process_thread
+ *
+ * List loaded threads from a given thread
+ */
+static int      print_process_thread(const char* str_pid)
+{
+    HANDLE              hProcess;
+    DWORD               pid;
+
+    hProcess = open_process_from_string(str_pid, PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, &pid);
+    printf("Win-TID    Priority State (%lu)\n", pid);
+    enum_threads(pid, print_process_threadCB, NULL);
+    CloseHandle(hProcess);
+    return 0;
+}
+
+/******************************************************************
+ *		kill_process
+ *
+ * Kill a running process
+ */
+static int      kill_process(const char* str_pid)
+{
+    HANDLE      hProcess = open_process_from_string(str_pid, PROCESS_TERMINATE, NULL);
+
+    if (!TerminateProcess(hProcess, 0))
+        fatal("Couldn't kill process (%lu)\n", GetLastError());
+    CloseHandle(hProcess);
+    return 0;
+}
+
+/******************************************************************
+ *		suspend_thread
+ *
+ *
+ */
+static int      suspend_thread(const char* str_tid)
+{
+    HANDLE      hThread = open_thread_from_string(str_tid, THREAD_ALL_ACCESS);
+    if (!hThread) return 0;
+    if (SuspendThread(hThread) == (ULONG)-1) 
+        fatal("Couldn't suspend %s (%lu)\n", str_tid, GetLastError());
+    CloseHandle(hThread);
+    return 0;
+}
+
+/******************************************************************
+ *		Resume_thread
+ *
+ *
+ */
+static int      resume_thread(const char* str_tid)
+{
+    HANDLE      hThread = open_thread_from_string(str_tid, THREAD_ALL_ACCESS);
+    if (!hThread) return -1;
+    if (ResumeThread(hThread) == (ULONG)-1) 
+        fatal("Couldn't resume %s (%lu)\n", str_tid, GetLastError());
+    CloseHandle(hThread);
+    return 0;
+}
+
+static const char * const debug_classes[] = { "fixme", "err", "warn", "trace" };
+
+/******************************************************************
+ *		list_channel_CB
+ *
+ * Callback used when listing known channels
+ */
+static int      list_channel_CB(HANDLE hProcess, void* addr, char* buffer, void* pmt)
+{
+    char        tmp[64];
+    unsigned    len = 0;
+    int         j;
+
+    for (j = 0; j < sizeof(debug_classes)/sizeof(debug_classes[0]); j++)
+    {
+        if (buffer[0] & (1 << j))
+        {
+            if (len) tmp[len++] = ',';
+            strcpy(&tmp[len], debug_classes[j]);
+            len += strlen(debug_classes[j]);
+        }
+    }
+    tmp[len] = '\0';
+    printf("Channel %s: %s\n", buffer + 1, tmp);
+    return 1;
+}
+
+/******************************************************************
+ *		list_channel
+ *
+ * List all known channels on process of pid str_pid
+ */
+static int      list_channel(const char* str_pid)
+{
+    HANDLE hProcess = open_process_from_string(str_pid, PROCESS_VM_OPERATION | PROCESS_VM_READ, NULL);
+    int    ret;
+
+    ret = enum_channel(hProcess, list_channel_CB, NULL);
+    CloseHandle(hProcess);
+    return ret;
+}
+
+/******************************************************************
+ *		set_channel
+ *
+ * Set new values on channels.
+ */
+static int      set_channel(const char* str_pid, const char* str)
+{
+    char *opt, *next, *options, *name;
+    int i;
+    HANDLE hProcess = open_process_from_string(str_pid, PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, NULL);
+    unsigned errors = 0, value, mask;
+
+    if (!(options = strdup(str))) return -1;
+    for (opt = options; opt; opt = next)
+    {
+        value = mask = 0;
+        if ((next = strchr(opt, ','))) *next++ = 0;
+
+        name = opt + strcspn(opt, "+-");
+        if (!name[0] || !name[1])  /* bad option, skip it */
+        {
+            errors++;
+            continue;
+        }
+
+        if (name > opt)
+        {
+            for (i = 0; i < sizeof(debug_classes)/sizeof(debug_classes[0]); i++)
+            {
+                int len = strlen(debug_classes[i]);
+                if (len != (name - opt)) continue;
+                if (!memcmp(opt, debug_classes[i], len))  /* found it */
+                {
+                    if (*name == '+') value |= 1 << i;
+                    mask |= 1 << i;
+                    break;
+                }
+            }
+            if (i == sizeof(debug_classes)/sizeof(debug_classes[0])) /* bad class name, skip it */
+            {
+                errors++;
+                continue;
+            }
+        }
+        else
+        {
+            if (*name == '+') value = 15;
+            mask = 15;
+        }
+        name++;
+        if (!strcmp(name, "all")) name = NULL;
+        if (!change_channel(hProcess, name, value, mask))
+        {
+            printf("Unable to find debug channel '%s'\n", name);
+            errors++;
+        }
+        /* FIXME: used to be list_channel_CB(hProcess, addr, buffer, NULL);
+         * but we need to print back at the user the new state of the changed channel
+         */
+    }
+    free(options);
+    CloseHandle(hProcess);
+    return errors;
+}
+
+/******************************************************************
+ *		main
+ *
+ */
+int     main(int argc, char* argv[])
+{
+    switch (argc)
+    {
+    case 1:
+        return visual();
+    case 2:
+        if (!strcmp(argv[1], "list")) return list_processes(); 
+        break;
+    case 3:
+        if (!strcmp(argv[1], "debugmsg")) return list_channel(argv[2]);
+        if (!strcmp(argv[1], "module")) return print_process_module(argv[2]);
+        if (!strcmp(argv[1], "thread")) return print_process_thread(argv[2]);
+        if (!strcmp(argv[1], "kill")) return kill_process(argv[2]);
+        if (!strcmp(argv[1], "suspend")) return suspend_thread(argv[2]);
+        if (!strcmp(argv[1], "resume")) return resume_thread(argv[2]);
+        break;
+    case 4:
+        if (!strcmp(argv[1], "debugmsg")) return set_channel(argv[2], argv[3]);
+        break;
+    }
+    return usage();
+}
+
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/wineproc_En.rc	2003-09-01 18:43:49.000000000 +0200
@@ -0,0 +1,37 @@
+LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
+
+STRINGTABLE
+BEGIN
+	IDS_PROCESS_TAB, 		"Processes"
+	IDS_PROCESS_PID,		"PID"
+	IDS_PROCESS_PROCESS,		"Process"
+
+	IDS_DTL_MODULE_TAB,		"Modules"
+	IDS_DTL_MODULE_NAME,		"Name"
+	IDS_DTL_MODULE_FILE,		"File"
+	IDS_DTL_MODULE_ADDRESS,		"Base address"
+	IDS_DTL_MODULE_SIZE,		"Size"
+
+	IDS_DTL_THREAD_TAB,		"Threads"
+	IDS_DTL_THREAD_TID,		"TID"
+	IDS_DTL_THREAD_PRIORITY,	"Priority"
+	IDS_DTL_THREAD_STATE,		"State"
+	
+	IDS_DTL_THREAD_STATE_RUNNING,	"Running"
+	IDS_DTL_THREAD_STATE_TERMINATED,"Terminated"
+	IDS_DTL_THREAD_STATE_SUSPENDED,	"Suspended"
+	IDS_DTL_THREAD_STATE_UNKNOWN,	"Unknown"
+
+	IDS_DTL_CHANNEL_TAB,		"Debug channels"
+	IDS_DTL_CHANNEL_NAME,		"Name"
+	IDS_DTL_CHANNEL_FIXME,		"Fixme"
+	IDS_DTL_CHANNEL_ERR,		"Err"
+	IDS_DTL_CHANNEL_WARN,		"Warn"
+	IDS_DTL_CHANNEL_TRACE,		"Trace"
+
+	IDS_TB_KILL_PROCESS             "Kill"
+	IDS_TB_SUSPEND_THREAD		"Suspend"
+	IDS_TB_RESUME_THREAD		"Resume"
+	IDS_TB_REFRESH			"Refresh"
+	IDS_TB_ABOUT                    "About"
+END
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/wineproc.h	2003-09-01 21:19:08.000000000 +0200
@@ -0,0 +1,39 @@
+#include "windows.h"
+#include "winbase.h"
+
+HANDLE  open_process(DWORD pid, DWORD access);
+
+void    get_process_name(HANDLE hProcess, char* buffer, unsigned size);
+
+typedef int (*EnumProcessesCB)(HANDLE, DWORD, void*);
+void    enum_processes(EnumProcessesCB epcb, DWORD access, void* user);
+
+struct module_info
+{
+    char        module[MAX_PATH];
+    char        file[MAX_PATH];
+    void*       dll_base;
+    unsigned    size;
+    void*       entry_point;
+};
+typedef int (*EnumModulesCB)(struct module_info* smi, void* user);
+void    enum_modules(HANDLE hProcess, EnumModulesCB em, void* user);
+
+struct thread_info
+{
+    unsigned    windows_tid;
+    unsigned    suspend_count;  /* 0 == running, > 0 suspended, -1 terminated, -2 don't know */
+    unsigned    priority;
+};
+typedef int (*EnumThreadsCB)(struct thread_info* sti, void* user);
+void    enum_threads(DWORD pid, EnumThreadsCB et, void* user);
+
+void*   get_symbol(HANDLE hProcess, const char* name, const char* lib);
+
+/* channel enumeration call back prototype */
+typedef int (*EnumChannelCB)(HANDLE, void*, char*, void*);
+int     enum_channel(HANDLE hProcess, EnumChannelCB ce, void* user);
+int     change_channel(HANDLE hProcess, const char* name, unsigned value, unsigned mask);
+
+int     visual(void);
+
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/wineproc.rc	2003-08-23 18:10:44.000000000 +0200
@@ -0,0 +1,18 @@
+#include "wineprocres.h"
+#include <windef.h>
+#include <winuser.h>
+#include <winnls.h>
+#include <commctrl.h>
+
+IDD_MAIN_DIALOG DIALOG DISCARDABLE 0, 0, 500, 400
+STYLE WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+CAPTION "Wine Process"
+FONT 8, "MS Sans Serif"
+BEGIN
+END
+
+#include "wineproc_En.rc"
+
+
+
+
--- /dev/null	1970-01-01 01:00:00.000000000 +0100
+++ programs/wineproc/wineprocres.h	2003-09-01 18:44:24.000000000 +0200
@@ -0,0 +1,55 @@
+/* ID for dialogs */
+#define IDD_MAIN_DIALOG                 0x1000
+#define IDD_ABOUT                       0x1001
+
+/* ID for controls */
+#define IDC_PROCESS_LV                  0x2000
+#define IDC_DETAIL_TAB                  0x2001
+#define IDC_TOOLBAR                     0x2002
+#define IDC_SPLITTER                    0x2003
+
+#define IDC_DTL_FIRST                   0x2100
+#define IDC_DTL_MODULE_LV               0x2100
+#define IDC_DTL_THREAD_LV               0x2101
+#define IDC_DTL_CHANNEL_LV              0x2102
+#define IDC_DTL_LAST                    0x2102
+
+/* ID for string */
+#define IDS_PROCESS_TAB                 0x3000
+#define IDS_PROCESS_PID                 0x3001
+#define IDS_PROCESS_PROCESS             0x3002
+
+#define IDS_DTL_MODULE_TAB              0x3010
+#define IDS_DTL_MODULE_NAME             0x3011
+#define IDS_DTL_MODULE_FILE             0x3012
+#define IDS_DTL_MODULE_ADDRESS          0x3013
+#define IDS_DTL_MODULE_SIZE             0x3014
+
+#define IDS_DTL_THREAD_TAB              0x3020
+#define IDS_DTL_THREAD_TID              0x3021
+#define IDS_DTL_THREAD_PRIORITY         0x3022
+#define IDS_DTL_THREAD_STATE            0x3023
+#define IDS_DTL_THREAD_STATE_RUNNING 	0x3024
+#define IDS_DTL_THREAD_STATE_TERMINATED 0x3025
+#define IDS_DTL_THREAD_STATE_SUSPENDED	0x3026
+#define IDS_DTL_THREAD_STATE_UNKNOWN	0x3027
+
+#define IDS_DTL_CHANNEL_TAB             0x3030
+#define IDS_DTL_CHANNEL_NAME            0x3031
+#define IDS_DTL_CHANNEL_FIXME           0x3032
+#define IDS_DTL_CHANNEL_ERR             0x3033
+#define IDS_DTL_CHANNEL_WARN            0x3034
+#define IDS_DTL_CHANNEL_TRACE           0x3035
+
+#define IDS_TB_KILL_PROCESS             0x3100
+#define IDS_TB_SUSPEND_THREAD           0x3101
+#define IDS_TB_RESUME_THREAD            0x3102
+#define IDS_TB_REFRESH                  0x3103
+#define IDS_TB_ABOUT                    0x3104
+
+/* ID for command message */
+#define IDM_KILL_PROCESS                0x4000
+#define IDM_SUSPEND_THREAD              0x4001
+#define IDM_RESUME_THREAD               0x4002
+#define IDM_REFRESH                     0x4003
+#define IDM_ABOUT                       0x4004


More information about the wine-patches mailing list