[KERNEL32]
[reactos.git] / rostests / winetests / kernel32 / debugger.c
index ac7c7e1..ff091e9 100644 (file)
 
 #include <stdio.h>
 #include <assert.h>
-#include <ntdef.h>
+
+#define WIN32_NO_STATUS
 #include <windows.h>
 #include <winreg.h>
+#include <ntndk.h>
 #include "wine/test.h"
 
 #ifndef STATUS_DEBUGGER_INACTIVE
 #define STATUS_DEBUGGER_INACTIVE         ((NTSTATUS) 0xC0000354)
 #endif
 
+#ifdef __GNUC__
+#define PRINTF_ATTR(fmt,args) __attribute__((format (printf,fmt,args)))
+#else
+#define PRINTF_ATTR(fmt,args)
+#endif
+
+#define child_ok (winetest_set_location(__FILE__, __LINE__), 0) ? (void)0 : test_child_ok
+
 static int    myARGC;
 static char** myARGV;
 
+static BOOL (WINAPI *pCheckRemoteDebuggerPresent)(HANDLE,PBOOL);
 static BOOL (WINAPI *pDebugActiveProcessStop)(DWORD);
 static BOOL (WINAPI *pDebugSetProcessKillOnExit)(BOOL);
+static BOOL (WINAPI *pIsDebuggerPresent)(void);
+static struct _TEB * (WINAPI *pNtCurrentTeb)(void);
+
+static LONG child_failures;
+
+static void PRINTF_ATTR(2, 3) test_child_ok(int condition, const char *msg, ...)
+{
+    va_list valist;
+
+    va_start(valist, msg);
+    winetest_vok(condition, msg, valist);
+    va_end(valist);
+    if (!condition) ++child_failures;
+}
 
 /* Copied from the process test */
 static void get_file_name(char* buf)
@@ -45,6 +70,40 @@ static void get_file_name(char* buf)
     GetTempFileNameA(path, "wt", 0, buf);
 }
 
+typedef struct tag_reg_save_value
+{
+    const char *name;
+    DWORD type;
+    BYTE *data;
+    DWORD size;
+} reg_save_value;
+
+static DWORD save_value(HKEY hkey, const char *value, reg_save_value *saved)
+{
+    DWORD ret;
+    saved->name=value;
+    saved->data=0;
+    saved->size=0;
+    ret=RegQueryValueExA(hkey, value, NULL, &saved->type, NULL, &saved->size);
+    if (ret == ERROR_SUCCESS)
+    {
+        saved->data=HeapAlloc(GetProcessHeap(), 0, saved->size);
+        RegQueryValueExA(hkey, value, NULL, &saved->type, saved->data, &saved->size);
+    }
+    return ret;
+}
+
+static void restore_value(HKEY hkey, reg_save_value *saved)
+{
+    if (saved->data)
+    {
+        RegSetValueExA(hkey, saved->name, 0, saved->type, saved->data, saved->size);
+        HeapFree(GetProcessHeap(), 0, saved->data);
+    }
+    else
+        RegDeleteValueA(hkey, saved->name);
+}
+
 static void get_events(const char* name, HANDLE *start_event, HANDLE *done_event)
 {
     const char* basename;
@@ -131,12 +190,13 @@ static void doDebugger(int argc, char** argv)
 {
     const char* logfile;
     debugger_blackbox_t blackbox;
-    HANDLE start_event, done_event, debug_event;
+    HANDLE start_event = 0, done_event = 0, debug_event;
 
     blackbox.argc=argc;
     logfile=(argc >= 4 ? argv[3] : NULL);
     blackbox.pid=(argc >= 5 ? atol(argv[4]) : 0);
 
+    blackbox.attach_err=0;
     if (strstr(myARGV[2], "attach"))
     {
         blackbox.attach_rc=DebugActiveProcess(blackbox.pid);
@@ -146,7 +206,8 @@ static void doDebugger(int argc, char** argv)
     else
         blackbox.attach_rc=TRUE;
 
-    debug_event=(argc >= 6 ? (HANDLE)atol(argv[5]) : NULL);
+    debug_event=(argc >= 6 ? (HANDLE)(INT_PTR)atol(argv[5]) : NULL);
+    blackbox.debug_err=0;
     if (debug_event && strstr(myARGV[2], "event"))
     {
         blackbox.debug_rc=SetEvent(debug_event);
@@ -156,13 +217,18 @@ static void doDebugger(int argc, char** argv)
     else
         blackbox.debug_rc=TRUE;
 
-    get_events(logfile, &start_event, &done_event);
+    if (logfile)
+    {
+        get_events(logfile, &start_event, &done_event);
+    }
+
     if (strstr(myARGV[2], "order"))
     {
         trace("debugger: waiting for the start signal...\n");
         WaitForSingleObject(start_event, INFINITE);
     }
 
+    blackbox.nokill_err=0;
     if (strstr(myARGV[2], "nokill"))
     {
         blackbox.nokill_rc=pDebugSetProcessKillOnExit(FALSE);
@@ -172,6 +238,7 @@ static void doDebugger(int argc, char** argv)
     else
         blackbox.nokill_rc=TRUE;
 
+    blackbox.detach_err=0;
     if (strstr(myARGV[2], "detach"))
     {
         blackbox.detach_rc=pDebugActiveProcessStop(blackbox.pid);
@@ -181,7 +248,10 @@ static void doDebugger(int argc, char** argv)
     else
         blackbox.detach_rc=TRUE;
 
-    save_blackbox(logfile, &blackbox, sizeof(blackbox));
+    if (logfile)
+    {
+        save_blackbox(logfile, &blackbox, sizeof(blackbox));
+    }
     trace("debugger: done debugging...\n");
     SetEvent(done_event);
 
@@ -235,13 +305,17 @@ static void crash_and_debug(HKEY hkey, const char* argv0, const char* dbgtasks)
         /* If, after attaching to the debuggee, the debugger exits without
          * detaching, then the debuggee gets a special exit code.
          */
-        ok(exit_code == 0xffffffff || /* Win 9x */
-           exit_code == 0x80 || /* NT4 */
-           exit_code == STATUS_DEBUGGER_INACTIVE, /* Win >= XP */
+        ok(exit_code == STATUS_DEBUGGER_INACTIVE ||
+           broken(exit_code == STATUS_ACCESS_VIOLATION) || /* Intermittent Vista+ */
+           broken(exit_code == 0xffffffff) || /* Win9x */
+           broken(exit_code == WAIT_ABANDONED), /* NT4, W2K */
            "wrong exit code : %08x\n", exit_code);
     }
     else
-        ok(exit_code == STATUS_ACCESS_VIOLATION, "exit code = %08x instead of STATUS_ACCESS_VIOLATION\n", exit_code);
+        ok(exit_code == STATUS_ACCESS_VIOLATION ||
+           broken(exit_code == WAIT_ABANDONED) || /* NT4, W2K, W2K3 */
+           broken(exit_code == 0xffffffff), /* Win9x, WinME */
+           "wrong exit code : %08x\n", exit_code);
     CloseHandle(info.hProcess);
 
     /* ...before the debugger */
@@ -298,14 +372,13 @@ static void crash_and_winedbg(HKEY hkey, const char* argv0)
 static void test_ExitCode(void)
 {
     static const char* AeDebug="Software\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug";
+    static const char* WineDbg="Software\\Wine\\WineDbg";
     char test_exe[MAX_PATH];
     DWORD ret;
     HKEY hkey;
     DWORD disposition;
-    LPBYTE auto_val=NULL;
-    DWORD auto_size, auto_type;
-    LPBYTE debugger_val=NULL;
-    DWORD debugger_size, debugger_type;
+    reg_save_value auto_value;
+    reg_save_value debugger_value;
 
     GetModuleFileNameA(GetModuleHandle(NULL), test_exe, sizeof(test_exe));
     if (GetFileAttributes(test_exe) == INVALID_FILE_ATTRIBUTES)
@@ -319,21 +392,9 @@ static void test_ExitCode(void)
     ret=RegCreateKeyExA(HKEY_LOCAL_MACHINE, AeDebug, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, &disposition);
     if (ret == ERROR_SUCCESS)
     {
-        auto_size=0;
-        ret=RegQueryValueExA(hkey, "auto", NULL, &auto_type, NULL, &auto_size);
-        if (ret == ERROR_SUCCESS)
-        {
-            auto_val=HeapAlloc(GetProcessHeap(), 0, auto_size);
-            RegQueryValueExA(hkey, "auto", NULL, &auto_type, auto_val, &auto_size);
-        }
-
-        debugger_size=0;
-        ret=RegQueryValueExA(hkey, "debugger", NULL, &debugger_type, NULL, &debugger_size);
-        if (ret == ERROR_SUCCESS)
-        {
-            debugger_val=HeapAlloc(GetProcessHeap(), 0, debugger_size);
-            RegQueryValueExA(hkey, "debugger", NULL, &debugger_type, debugger_val, &debugger_size);
-        }
+        save_value(hkey, "auto", &auto_value);
+        save_value(hkey, "debugger", &debugger_value);
+        trace("HKLM\\%s\\debugger is set to '%s'\n", AeDebug, debugger_value.data);
     }
     else if (ret == ERROR_ACCESS_DENIED)
     {
@@ -346,17 +407,45 @@ static void test_ExitCode(void)
         return;
     }
 
-    if (debugger_val && debugger_type == REG_SZ &&
-        strstr((char*)debugger_val, "winedbg --auto"))
-        crash_and_winedbg(hkey, test_exe);
+    if (debugger_value.data && debugger_value.type == REG_SZ &&
+        strstr((char*)debugger_value.data, "winedbg --auto"))
+    {
+        HKEY hkeyWinedbg;
+        ret=RegCreateKeyA(HKEY_CURRENT_USER, WineDbg, &hkeyWinedbg);
+        if (ret == ERROR_SUCCESS)
+        {
+            static DWORD zero;
+            reg_save_value crash_dlg_value;
+            save_value(hkeyWinedbg, "ShowCrashDialog", &crash_dlg_value);
+            RegSetValueExA(hkeyWinedbg, "ShowCrashDialog", 0, REG_DWORD, (BYTE *)&zero, sizeof(DWORD));
+            crash_and_winedbg(hkey, test_exe);
+            restore_value(hkeyWinedbg, &crash_dlg_value);
+            RegCloseKey(hkeyWinedbg);
+        }
+        else
+            ok(0, "Couldn't access WineDbg Key - error %u\n", ret);
+    }
 
-    crash_and_debug(hkey, test_exe, "dbg,none");
-    crash_and_debug(hkey, test_exe, "dbg,event,order");
+    if (winetest_interactive)
+        /* Since the debugging process never sets the debug event, it isn't recognized
+           as a valid debugger and, after the debugger exits, Windows will show a dialog box
+           asking the user what to do */
+        crash_and_debug(hkey, test_exe, "dbg,none");
+    else
+        skip("\"none\" debugger test needs user interaction\n");
+    if (disposition == REG_CREATED_NEW_KEY)
+        win_skip("'dbg,event,order' test doesn't finish on Win9x/WinMe\n");
+    else
+        crash_and_debug(hkey, test_exe, "dbg,event,order");
     crash_and_debug(hkey, test_exe, "dbg,attach,event,code2");
     if (pDebugSetProcessKillOnExit)
         crash_and_debug(hkey, test_exe, "dbg,attach,event,nokill");
+    else
+        win_skip("DebugSetProcessKillOnExit is not available\n");
     if (pDebugActiveProcessStop)
         crash_and_debug(hkey, test_exe, "dbg,attach,event,detach");
+    else
+        win_skip("DebugActiveProcessStop is not available\n");
 
     if (disposition == REG_CREATED_NEW_KEY)
     {
@@ -365,31 +454,181 @@ static void test_ExitCode(void)
     }
     else
     {
-        if (auto_val)
-        {
-            RegSetValueExA(hkey, "auto", 0, auto_type, auto_val, auto_size);
-            HeapFree(GetProcessHeap(), 0, auto_val);
-        }
-        else
-            RegDeleteValueA(hkey, "auto");
-        if (debugger_val)
-        {
-            RegSetValueExA(hkey, "debugger", 0, debugger_type, debugger_val, debugger_size);
-            HeapFree(GetProcessHeap(), 0, debugger_val);
-        }
-        else
-            RegDeleteValueA(hkey, "debugger");
+        restore_value(hkey, &auto_value);
+        restore_value(hkey, &debugger_value);
         RegCloseKey(hkey);
     }
 }
 
+static void test_RemoteDebugger(void)
+{
+    BOOL bret, present;
+    if(!pCheckRemoteDebuggerPresent)
+    {
+        win_skip("CheckRemoteDebuggerPresent is not available\n");
+        return;
+    }
+    present = TRUE;
+    SetLastError(0xdeadbeef);
+    bret = pCheckRemoteDebuggerPresent(GetCurrentProcess(),&present);
+    ok(bret , "expected CheckRemoteDebuggerPresent to succeed\n");
+    ok(0xdeadbeef == GetLastError(),
+       "expected error to be unchanged, got %d/%x\n",GetLastError(), GetLastError());
+
+    present = TRUE;
+    SetLastError(0xdeadbeef);
+    bret = pCheckRemoteDebuggerPresent(NULL,&present);
+    ok(!bret , "expected CheckRemoteDebuggerPresent to fail\n");
+    ok(present, "expected parameter to be unchanged\n");
+    ok(ERROR_INVALID_PARAMETER == GetLastError(),
+       "expected error ERROR_INVALID_PARAMETER, got %d/%x\n",GetLastError(), GetLastError());
+
+    SetLastError(0xdeadbeef);
+    bret = pCheckRemoteDebuggerPresent(GetCurrentProcess(),NULL);
+    ok(!bret , "expected CheckRemoteDebuggerPresent to fail\n");
+    ok(ERROR_INVALID_PARAMETER == GetLastError(),
+       "expected error ERROR_INVALID_PARAMETER, got %d/%x\n",GetLastError(), GetLastError());
+}
+
+struct child_blackbox
+{
+    LONG failures;
+};
+
+static void doChild(int argc, char **argv)
+{
+    struct child_blackbox blackbox;
+    const char *blackbox_file;
+    HANDLE parent;
+    DWORD ppid;
+    BOOL debug;
+    BOOL ret;
+
+    blackbox_file = argv[4];
+    sscanf(argv[3], "%08x", &ppid);
+
+    parent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ppid);
+    child_ok(!!parent, "OpenProcess failed, last error %#x.\n", GetLastError());
+
+    ret = pCheckRemoteDebuggerPresent(parent, &debug);
+    child_ok(ret, "CheckRemoteDebuggerPresent failed, last error %#x.\n", GetLastError());
+    child_ok(!debug, "Expected debug == 0, got %#x.\n", debug);
+
+    ret = DebugActiveProcess(ppid);
+    child_ok(ret, "DebugActiveProcess failed, last error %#x.\n", GetLastError());
+
+    ret = pCheckRemoteDebuggerPresent(parent, &debug);
+    child_ok(ret, "CheckRemoteDebuggerPresent failed, last error %#x.\n", GetLastError());
+    child_ok(debug, "Expected debug != 0, got %#x.\n", debug);
+
+    ret = pDebugActiveProcessStop(ppid);
+    child_ok(ret, "DebugActiveProcessStop failed, last error %#x.\n", GetLastError());
+
+    ret = pCheckRemoteDebuggerPresent(parent, &debug);
+    child_ok(ret, "CheckRemoteDebuggerPresent failed, last error %#x.\n", GetLastError());
+    child_ok(!debug, "Expected debug == 0, got %#x.\n", debug);
+
+    ret = CloseHandle(parent);
+    child_ok(ret, "CloseHandle failed, last error %#x.\n", GetLastError());
+
+    ret = pIsDebuggerPresent();
+    child_ok(ret, "Expected ret != 0, got %#x.\n", ret);
+    ret = pCheckRemoteDebuggerPresent(GetCurrentProcess(), &debug);
+    child_ok(ret, "CheckRemoteDebuggerPresent failed, last error %#x.\n", GetLastError());
+    child_ok(debug, "Expected debug != 0, got %#x.\n", debug);
+
+    if (pNtCurrentTeb)
+    {
+        pNtCurrentTeb()->ProcessEnvironmentBlock->BeingDebugged = FALSE;
+
+        ret = pIsDebuggerPresent();
+        child_ok(!ret, "Expected ret != 0, got %#x.\n", ret);
+        ret = pCheckRemoteDebuggerPresent(GetCurrentProcess(), &debug);
+        child_ok(ret, "CheckRemoteDebuggerPresent failed, last error %#x.\n", GetLastError());
+        child_ok(debug, "Expected debug != 0, got %#x.\n", debug);
+
+        pNtCurrentTeb()->ProcessEnvironmentBlock->BeingDebugged = TRUE;
+    }
+
+    blackbox.failures = child_failures;
+    save_blackbox(blackbox_file, &blackbox, sizeof(blackbox));
+}
+
+static void test_debug_loop(int argc, char **argv)
+{
+    const char *arguments = " debugger child ";
+    struct child_blackbox blackbox;
+    char blackbox_file[MAX_PATH];
+    PROCESS_INFORMATION pi;
+    STARTUPINFOA si;
+    BOOL debug;
+    DWORD pid;
+    char *cmd;
+    BOOL ret;
+
+    if (!pDebugActiveProcessStop || !pCheckRemoteDebuggerPresent)
+    {
+        win_skip("DebugActiveProcessStop or CheckRemoteDebuggerPresent not available, skipping test.\n");
+        return;
+    }
+
+    pid = GetCurrentProcessId();
+    ret = DebugActiveProcess(pid);
+    ok(!ret, "DebugActiveProcess() succeeded on own process.\n");
+
+    get_file_name(blackbox_file);
+    cmd = HeapAlloc(GetProcessHeap(), 0, strlen(argv[0]) + strlen(arguments) + strlen(blackbox_file) + 10);
+    sprintf(cmd, "%s%s%08x %s", argv[0], arguments, pid, blackbox_file);
+
+    memset(&si, 0, sizeof(si));
+    si.cb = sizeof(si);
+    ret = CreateProcessA(NULL, cmd, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi);
+    ok(ret, "CreateProcess failed, last error %#x.\n", GetLastError());
+
+    HeapFree(GetProcessHeap(), 0, cmd);
+
+    ret = pCheckRemoteDebuggerPresent(pi.hProcess, &debug);
+    ok(ret, "CheckRemoteDebuggerPresent failed, last error %#x.\n", GetLastError());
+    ok(debug, "Expected debug != 0, got %#x.\n", debug);
+
+    for (;;)
+    {
+        DEBUG_EVENT ev;
+
+        ret = WaitForDebugEvent(&ev, INFINITE);
+        ok(ret, "WaitForDebugEvent failed, last error %#x.\n", GetLastError());
+        if (!ret) break;
+
+        if (ev.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break;
+
+        ret = ContinueDebugEvent(ev.dwProcessId, ev.dwThreadId, DBG_CONTINUE);
+        ok(ret, "ContinueDebugEvent failed, last error %#x.\n", GetLastError());
+        if (!ret) break;
+    }
+
+    ret = CloseHandle(pi.hThread);
+    ok(ret, "CloseHandle failed, last error %#x.\n", GetLastError());
+    ret = CloseHandle(pi.hProcess);
+    ok(ret, "CloseHandle failed, last error %#x.\n", GetLastError());
+
+    load_blackbox(blackbox_file, &blackbox, sizeof(blackbox));
+    ok(!blackbox.failures, "Got %d failures from child process.\n", blackbox.failures);
+
+    ret = DeleteFileA(blackbox_file);
+    ok(ret, "DeleteFileA failed, last error %#x.\n", GetLastError());
+}
+
 START_TEST(debugger)
 {
     HMODULE hdll;
 
     hdll=GetModuleHandle("kernel32.dll");
+    pCheckRemoteDebuggerPresent=(void*)GetProcAddress(hdll, "CheckRemoteDebuggerPresent");
     pDebugActiveProcessStop=(void*)GetProcAddress(hdll, "DebugActiveProcessStop");
     pDebugSetProcessKillOnExit=(void*)GetProcAddress(hdll, "DebugSetProcessKillOnExit");
+    pIsDebuggerPresent=(void*)GetProcAddress(hdll, "IsDebuggerPresent");
+    hdll=GetModuleHandle("ntdll.dll");
+    if (hdll) pNtCurrentTeb = (void*)GetProcAddress(hdll, "NtCurrentTeb");
 
     myARGC=winetest_get_mainargs(&myARGV);
     if (myARGC >= 3 && strcmp(myARGV[2], "crash") == 0)
@@ -400,8 +639,14 @@ START_TEST(debugger)
     {
         doDebugger(myARGC, myARGV);
     }
+    else if (myARGC >= 5 && !strcmp(myARGV[2], "child"))
+    {
+        doChild(myARGC, myARGV);
+    }
     else
     {
         test_ExitCode();
+        test_RemoteDebugger();
+        test_debug_loop(myARGC, myARGV);
     }
 }