#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)
/* 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 == WAIT_ABANDONED, /* win2k3 */
- "exit code = %08x instead of STATUS_ACCESS_VIOLATION or WAIT_ABANDONED\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 */
crash_and_debug(hkey, test_exe, "dbg,none");
else
skip("\"none\" debugger test needs user interaction\n");
- crash_and_debug(hkey, test_exe, "dbg,event,order");
+ 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)
{
}
}
+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)
{
doDebugger(myARGC, myARGV);
}
+ else if (myARGC >= 5 && !strcmp(myARGV[2], "child"))
+ {
+ doChild(myARGC, myARGV);
+ }
else
{
test_ExitCode();
+ test_RemoteDebugger();
+ test_debug_loop(myARGC, myARGV);
}
}