1 | //===- KillTheDoctor - Prevent Dr. Watson from stopping tests ---*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This program provides an extremely hacky way to stop Dr. Watson from starting |
10 | // due to unhandled exceptions in child processes. |
11 | // |
12 | // This simply starts the program named in the first positional argument with |
13 | // the arguments following it under a debugger. All this debugger does is catch |
14 | // any unhandled exceptions thrown in the child process and close the program |
15 | // (and hopefully tells someone about it). |
16 | // |
17 | // This also provides another really hacky method to prevent assert dialog boxes |
18 | // from popping up. When --no-user32 is passed, if any process loads user32.dll, |
19 | // we assume it is trying to call MessageBoxEx and terminate it. The proper way |
20 | // to do this would be to actually set a break point, but there's quite a bit |
21 | // of code involved to get the address of MessageBoxEx in the remote process's |
22 | // address space due to Address space layout randomization (ASLR). This can be |
23 | // added if it's ever actually needed. |
24 | // |
25 | // If the subprocess exits for any reason other than successful termination, -1 |
26 | // is returned. If the process exits normally the value it returned is returned. |
27 | // |
28 | // I hate Windows. |
29 | // |
30 | //===----------------------------------------------------------------------===// |
31 | |
32 | #include "llvm/ADT/STLExtras.h" |
33 | #include "llvm/ADT/SmallString.h" |
34 | #include "llvm/ADT/SmallVector.h" |
35 | #include "llvm/ADT/StringExtras.h" |
36 | #include "llvm/ADT/StringRef.h" |
37 | #include "llvm/ADT/Twine.h" |
38 | #include "llvm/Support/CommandLine.h" |
39 | #include "llvm/Support/ManagedStatic.h" |
40 | #include "llvm/Support/Path.h" |
41 | #include "llvm/Support/PrettyStackTrace.h" |
42 | #include "llvm/Support/Signals.h" |
43 | #include "llvm/Support/WindowsError.h" |
44 | #include "llvm/Support/raw_ostream.h" |
45 | #include "llvm/Support/type_traits.h" |
46 | #include <algorithm> |
47 | #include <cerrno> |
48 | #include <cstdlib> |
49 | #include <map> |
50 | #include <string> |
51 | #include <system_error> |
52 | |
53 | // These includes must be last. |
54 | #include <windows.h> |
55 | #include <winerror.h> |
56 | #include <dbghelp.h> |
57 | #include <psapi.h> |
58 | |
59 | using namespace llvm; |
60 | |
61 | #undef max |
62 | |
63 | namespace { |
64 | cl::opt<std::string> ProgramToRun(cl::Positional, |
65 | cl::desc("<program to run>" )); |
66 | cl::list<std::string> Argv(cl::ConsumeAfter, |
67 | cl::desc("<program arguments>..." )); |
68 | cl::opt<bool> TraceExecution("x" , |
69 | cl::desc("Print detailed output about what is being run to stderr." )); |
70 | cl::opt<unsigned> Timeout("t" , cl::init(Val: 0), |
71 | cl::desc("Set maximum runtime in seconds. Defaults to infinite." )); |
72 | cl::opt<bool> NoUser32("no-user32" , |
73 | cl::desc("Terminate process if it loads user32.dll." )); |
74 | |
75 | StringRef ToolName; |
76 | |
77 | template <typename HandleType> |
78 | class ScopedHandle { |
79 | typedef typename HandleType::handle_type handle_type; |
80 | |
81 | handle_type Handle; |
82 | |
83 | public: |
84 | ScopedHandle() |
85 | : Handle(HandleType::GetInvalidHandle()) {} |
86 | |
87 | explicit ScopedHandle(handle_type handle) |
88 | : Handle(handle) {} |
89 | |
90 | ~ScopedHandle() { |
91 | HandleType::Destruct(Handle); |
92 | } |
93 | |
94 | ScopedHandle& operator=(handle_type handle) { |
95 | // Cleanup current handle. |
96 | if (!HandleType::isValid(Handle)) |
97 | HandleType::Destruct(Handle); |
98 | Handle = handle; |
99 | return *this; |
100 | } |
101 | |
102 | operator bool() const { |
103 | return HandleType::isValid(Handle); |
104 | } |
105 | |
106 | operator handle_type() { |
107 | return Handle; |
108 | } |
109 | }; |
110 | |
111 | // This implements the most common handle in the Windows API. |
112 | struct CommonHandle { |
113 | typedef HANDLE handle_type; |
114 | |
115 | static handle_type GetInvalidHandle() { |
116 | return INVALID_HANDLE_VALUE; |
117 | } |
118 | |
119 | static void Destruct(handle_type Handle) { |
120 | ::CloseHandle(Handle); |
121 | } |
122 | |
123 | static bool isValid(handle_type Handle) { |
124 | return Handle != GetInvalidHandle(); |
125 | } |
126 | }; |
127 | |
128 | struct FileMappingHandle { |
129 | typedef HANDLE handle_type; |
130 | |
131 | static handle_type GetInvalidHandle() { |
132 | return NULL; |
133 | } |
134 | |
135 | static void Destruct(handle_type Handle) { |
136 | ::CloseHandle(Handle); |
137 | } |
138 | |
139 | static bool isValid(handle_type Handle) { |
140 | return Handle != GetInvalidHandle(); |
141 | } |
142 | }; |
143 | |
144 | struct MappedViewOfFileHandle { |
145 | typedef LPVOID handle_type; |
146 | |
147 | static handle_type GetInvalidHandle() { |
148 | return NULL; |
149 | } |
150 | |
151 | static void Destruct(handle_type Handle) { |
152 | ::UnmapViewOfFile(Handle); |
153 | } |
154 | |
155 | static bool isValid(handle_type Handle) { |
156 | return Handle != GetInvalidHandle(); |
157 | } |
158 | }; |
159 | |
160 | struct ProcessHandle : CommonHandle {}; |
161 | struct ThreadHandle : CommonHandle {}; |
162 | struct TokenHandle : CommonHandle {}; |
163 | struct FileHandle : CommonHandle {}; |
164 | |
165 | typedef ScopedHandle<FileMappingHandle> FileMappingScopedHandle; |
166 | typedef ScopedHandle<MappedViewOfFileHandle> MappedViewOfFileScopedHandle; |
167 | typedef ScopedHandle<ProcessHandle> ProcessScopedHandle; |
168 | typedef ScopedHandle<ThreadHandle> ThreadScopedHandle; |
169 | typedef ScopedHandle<TokenHandle> TokenScopedHandle; |
170 | typedef ScopedHandle<FileHandle> FileScopedHandle; |
171 | } |
172 | |
173 | static std::error_code windows_error(DWORD E) { return mapWindowsError(E); } |
174 | |
175 | static std::error_code GetFileNameFromHandle(HANDLE FileHandle, |
176 | std::string &Name) { |
177 | char Filename[MAX_PATH+1]; |
178 | bool Success = false; |
179 | Name.clear(); |
180 | |
181 | // Get the file size. |
182 | LARGE_INTEGER FileSize; |
183 | Success = ::GetFileSizeEx(FileHandle, &FileSize); |
184 | |
185 | if (!Success) |
186 | return windows_error(::GetLastError()); |
187 | |
188 | // Create a file mapping object. |
189 | FileMappingScopedHandle FileMapping( |
190 | ::CreateFileMappingA(FileHandle, |
191 | NULL, |
192 | PAGE_READONLY, |
193 | 0, |
194 | 1, |
195 | NULL)); |
196 | |
197 | if (!FileMapping) |
198 | return windows_error(::GetLastError()); |
199 | |
200 | // Create a file mapping to get the file name. |
201 | MappedViewOfFileScopedHandle MappedFile( |
202 | ::MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 1)); |
203 | |
204 | if (!MappedFile) |
205 | return windows_error(::GetLastError()); |
206 | |
207 | Success = ::GetMappedFileNameA(::GetCurrentProcess(), MappedFile, Filename, |
208 | std::size(Filename) - 1); |
209 | |
210 | if (!Success) |
211 | return windows_error(::GetLastError()); |
212 | else { |
213 | Name = Filename; |
214 | return std::error_code(); |
215 | } |
216 | } |
217 | |
218 | /// Find program using shell lookup rules. |
219 | /// @param Program This is either an absolute path, relative path, or simple a |
220 | /// program name. Look in PATH for any programs that match. If no |
221 | /// extension is present, try all extensions in PATHEXT. |
222 | /// @return If ec == errc::success, The absolute path to the program. Otherwise |
223 | /// the return value is undefined. |
224 | static std::string FindProgram(const std::string &Program, |
225 | std::error_code &ec) { |
226 | char PathName[MAX_PATH + 1]; |
227 | typedef SmallVector<StringRef, 12> pathext_t; |
228 | pathext_t pathext; |
229 | // Check for the program without an extension (in case it already has one). |
230 | pathext.push_back(Elt: "" ); |
231 | SplitString(Source: std::getenv(name: "PATHEXT" ), OutFragments&: pathext, Delimiters: ";" ); |
232 | |
233 | for (pathext_t::iterator i = pathext.begin(), e = pathext.end(); i != e; ++i){ |
234 | SmallString<5> ext; |
235 | for (std::size_t ii = 0, e = i->size(); ii != e; ++ii) |
236 | ext.push_back(Elt: ::tolower(c: (*i)[ii])); |
237 | LPCSTR Extension = NULL; |
238 | if (ext.size() && ext[0] == '.') |
239 | Extension = ext.c_str(); |
240 | DWORD length = ::SearchPathA(NULL, Program.c_str(), Extension, |
241 | std::size(PathName), PathName, NULL); |
242 | if (length == 0) |
243 | ec = windows_error(::GetLastError()); |
244 | else if (length > std::size(PathName)) { |
245 | // This may have been the file, return with error. |
246 | ec = windows_error(ERROR_BUFFER_OVERFLOW); |
247 | break; |
248 | } else { |
249 | // We found the path! Return it. |
250 | ec = std::error_code(); |
251 | break; |
252 | } |
253 | } |
254 | |
255 | // Make sure PathName is valid. |
256 | PathName[MAX_PATH] = 0; |
257 | return PathName; |
258 | } |
259 | |
260 | static StringRef ExceptionCodeToString(DWORD ExceptionCode) { |
261 | switch(ExceptionCode) { |
262 | case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION" ; |
263 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: |
264 | return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED" ; |
265 | case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT" ; |
266 | case EXCEPTION_DATATYPE_MISALIGNMENT: |
267 | return "EXCEPTION_DATATYPE_MISALIGNMENT" ; |
268 | case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND" ; |
269 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO" ; |
270 | case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT" ; |
271 | case EXCEPTION_FLT_INVALID_OPERATION: |
272 | return "EXCEPTION_FLT_INVALID_OPERATION" ; |
273 | case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW" ; |
274 | case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK" ; |
275 | case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW" ; |
276 | case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION" ; |
277 | case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR" ; |
278 | case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO" ; |
279 | case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW" ; |
280 | case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION" ; |
281 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: |
282 | return "EXCEPTION_NONCONTINUABLE_EXCEPTION" ; |
283 | case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION" ; |
284 | case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP" ; |
285 | case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW" ; |
286 | default: return "<unknown>" ; |
287 | } |
288 | } |
289 | |
290 | int main(int argc, char **argv) { |
291 | // Print a stack trace if we signal out. |
292 | sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
293 | PrettyStackTraceProgram X(argc, argv); |
294 | llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. |
295 | |
296 | ToolName = argv[0]; |
297 | |
298 | cl::ParseCommandLineOptions(argc, argv, Overview: "Dr. Watson Assassin.\n" ); |
299 | if (ProgramToRun.size() == 0) { |
300 | cl::PrintHelpMessage(); |
301 | return -1; |
302 | } |
303 | |
304 | if (Timeout > std::numeric_limits<uint32_t>::max() / 1000) { |
305 | errs() << ToolName << ": Timeout value too large, must be less than: " |
306 | << std::numeric_limits<uint32_t>::max() / 1000 |
307 | << '\n'; |
308 | return -1; |
309 | } |
310 | |
311 | std::string CommandLine(ProgramToRun); |
312 | |
313 | std::error_code ec; |
314 | ProgramToRun = FindProgram(Program: ProgramToRun, ec); |
315 | if (ec) { |
316 | errs() << ToolName << ": Failed to find program: '" << CommandLine |
317 | << "': " << ec.message() << '\n'; |
318 | return -1; |
319 | } |
320 | |
321 | if (TraceExecution) |
322 | errs() << ToolName << ": Found Program: " << ProgramToRun << '\n'; |
323 | |
324 | for (const std::string &Arg : Argv) { |
325 | CommandLine.push_back(c: ' '); |
326 | CommandLine.append(str: Arg); |
327 | } |
328 | |
329 | if (TraceExecution) |
330 | errs() << ToolName << ": Program Image Path: " << ProgramToRun << '\n' |
331 | << ToolName << ": Command Line: " << CommandLine << '\n'; |
332 | |
333 | STARTUPINFOA StartupInfo; |
334 | PROCESS_INFORMATION ProcessInfo; |
335 | std::memset(s: &StartupInfo, c: 0, n: sizeof(StartupInfo)); |
336 | StartupInfo.cb = sizeof(StartupInfo); |
337 | std::memset(s: &ProcessInfo, c: 0, n: sizeof(ProcessInfo)); |
338 | |
339 | // Set error mode to not display any message boxes. The child process inherits |
340 | // this. |
341 | ::SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); |
342 | ::_set_error_mode(_OUT_TO_STDERR); |
343 | |
344 | BOOL success = ::CreateProcessA(ProgramToRun.c_str(), |
345 | const_cast<LPSTR>(CommandLine.c_str()), |
346 | NULL, |
347 | NULL, |
348 | FALSE, |
349 | DEBUG_PROCESS, |
350 | NULL, |
351 | NULL, |
352 | &StartupInfo, |
353 | &ProcessInfo); |
354 | if (!success) { |
355 | errs() << ToolName << ": Failed to run program: '" << ProgramToRun << "': " |
356 | << std::error_code(windows_error(::GetLastError())).message() |
357 | << '\n'; |
358 | return -1; |
359 | } |
360 | |
361 | // Make sure ::CloseHandle is called on exit. |
362 | std::map<DWORD, HANDLE> ProcessIDToHandle; |
363 | |
364 | DEBUG_EVENT DebugEvent; |
365 | std::memset(s: &DebugEvent, c: 0, n: sizeof(DebugEvent)); |
366 | DWORD dwContinueStatus = DBG_CONTINUE; |
367 | |
368 | // Run the program under the debugger until either it exits, or throws an |
369 | // exception. |
370 | if (TraceExecution) |
371 | errs() << ToolName << ": Debugging...\n" ; |
372 | |
373 | while(true) { |
374 | DWORD TimeLeft = INFINITE; |
375 | if (Timeout > 0) { |
376 | FILETIME CreationTime, ExitTime, KernelTime, UserTime; |
377 | ULARGE_INTEGER a, b; |
378 | success = ::GetProcessTimes(ProcessInfo.hProcess, |
379 | &CreationTime, |
380 | &ExitTime, |
381 | &KernelTime, |
382 | &UserTime); |
383 | if (!success) { |
384 | ec = windows_error(::GetLastError()); |
385 | |
386 | errs() << ToolName << ": Failed to get process times: " |
387 | << ec.message() << '\n'; |
388 | return -1; |
389 | } |
390 | a.LowPart = KernelTime.dwLowDateTime; |
391 | a.HighPart = KernelTime.dwHighDateTime; |
392 | b.LowPart = UserTime.dwLowDateTime; |
393 | b.HighPart = UserTime.dwHighDateTime; |
394 | // Convert 100-nanosecond units to milliseconds. |
395 | uint64_t TotalTimeMiliseconds = (a.QuadPart + b.QuadPart) / 10000; |
396 | // Handle the case where the process has been running for more than 49 |
397 | // days. |
398 | if (TotalTimeMiliseconds > std::numeric_limits<uint32_t>::max()) { |
399 | errs() << ToolName << ": Timeout Failed: Process has been running for" |
400 | "more than 49 days.\n" ; |
401 | return -1; |
402 | } |
403 | |
404 | // We check with > instead of using Timeleft because if |
405 | // TotalTimeMiliseconds is greater than Timeout * 1000, TimeLeft would |
406 | // underflow. |
407 | if (TotalTimeMiliseconds > (Timeout * 1000)) { |
408 | errs() << ToolName << ": Process timed out.\n" ; |
409 | ::TerminateProcess(ProcessInfo.hProcess, -1); |
410 | // Otherwise other stuff starts failing... |
411 | return -1; |
412 | } |
413 | |
414 | TimeLeft = (Timeout * 1000) - static_cast<uint32_t>(TotalTimeMiliseconds); |
415 | } |
416 | success = WaitForDebugEvent(&DebugEvent, TimeLeft); |
417 | |
418 | if (!success) { |
419 | DWORD LastError = ::GetLastError(); |
420 | ec = windows_error(LastError); |
421 | |
422 | if (LastError == ERROR_SEM_TIMEOUT || LastError == WSAETIMEDOUT) { |
423 | errs() << ToolName << ": Process timed out.\n" ; |
424 | ::TerminateProcess(ProcessInfo.hProcess, -1); |
425 | // Otherwise other stuff starts failing... |
426 | return -1; |
427 | } |
428 | |
429 | errs() << ToolName << ": Failed to wait for debug event in program: '" |
430 | << ProgramToRun << "': " << ec.message() << '\n'; |
431 | return -1; |
432 | } |
433 | |
434 | switch(DebugEvent.dwDebugEventCode) { |
435 | case CREATE_PROCESS_DEBUG_EVENT: |
436 | // Make sure we remove the handle on exit. |
437 | if (TraceExecution) |
438 | errs() << ToolName << ": Debug Event: CREATE_PROCESS_DEBUG_EVENT\n" ; |
439 | ProcessIDToHandle[DebugEvent.dwProcessId] = |
440 | DebugEvent.u.CreateProcessInfo.hProcess; |
441 | ::CloseHandle(DebugEvent.u.CreateProcessInfo.hFile); |
442 | break; |
443 | case EXIT_PROCESS_DEBUG_EVENT: { |
444 | if (TraceExecution) |
445 | errs() << ToolName << ": Debug Event: EXIT_PROCESS_DEBUG_EVENT\n" ; |
446 | |
447 | // If this is the process we originally created, exit with its exit |
448 | // code. |
449 | if (DebugEvent.dwProcessId == ProcessInfo.dwProcessId) |
450 | return DebugEvent.u.ExitProcess.dwExitCode; |
451 | |
452 | // Otherwise cleanup any resources we have for it. |
453 | std::map<DWORD, HANDLE>::iterator ExitingProcess = |
454 | ProcessIDToHandle.find(DebugEvent.dwProcessId); |
455 | if (ExitingProcess == ProcessIDToHandle.end()) { |
456 | errs() << ToolName << ": Got unknown process id!\n" ; |
457 | return -1; |
458 | } |
459 | ::CloseHandle(ExitingProcess->second); |
460 | ProcessIDToHandle.erase(ExitingProcess); |
461 | } |
462 | break; |
463 | case CREATE_THREAD_DEBUG_EVENT: |
464 | ::CloseHandle(DebugEvent.u.CreateThread.hThread); |
465 | break; |
466 | case LOAD_DLL_DEBUG_EVENT: { |
467 | // Cleanup the file handle. |
468 | FileScopedHandle DLLFile(DebugEvent.u.LoadDll.hFile); |
469 | std::string DLLName; |
470 | ec = GetFileNameFromHandle(DLLFile, DLLName); |
471 | if (ec) { |
472 | DLLName = "<failed to get file name from file handle> : " ; |
473 | DLLName += ec.message(); |
474 | } |
475 | if (TraceExecution) { |
476 | errs() << ToolName << ": Debug Event: LOAD_DLL_DEBUG_EVENT\n" ; |
477 | errs().indent(NumSpaces: ToolName.size()) << ": DLL Name : " << DLLName << '\n'; |
478 | } |
479 | |
480 | if (NoUser32 && sys::path::stem(path: DLLName) == "user32" ) { |
481 | // Program is loading user32.dll, in the applications we are testing, |
482 | // this only happens if an assert has fired. By now the message has |
483 | // already been printed, so simply close the program. |
484 | errs() << ToolName << ": user32.dll loaded!\n" ; |
485 | errs().indent(NumSpaces: ToolName.size()) |
486 | << ": This probably means that assert was called. Closing " |
487 | "program to prevent message box from popping up.\n" ; |
488 | dwContinueStatus = DBG_CONTINUE; |
489 | ::TerminateProcess(ProcessIDToHandle[DebugEvent.dwProcessId], -1); |
490 | return -1; |
491 | } |
492 | } |
493 | break; |
494 | case EXCEPTION_DEBUG_EVENT: { |
495 | // Close the application if this exception will not be handled by the |
496 | // child application. |
497 | if (TraceExecution) |
498 | errs() << ToolName << ": Debug Event: EXCEPTION_DEBUG_EVENT\n" ; |
499 | |
500 | EXCEPTION_DEBUG_INFO &Exception = DebugEvent.u.Exception; |
501 | if (Exception.dwFirstChance > 0) { |
502 | if (TraceExecution) { |
503 | errs().indent(NumSpaces: ToolName.size()) << ": Debug Info : " ; |
504 | errs() << "First chance exception at " |
505 | << Exception.ExceptionRecord.ExceptionAddress |
506 | << ", exception code: " |
507 | << ExceptionCodeToString( |
508 | Exception.ExceptionRecord.ExceptionCode) |
509 | << " (" << Exception.ExceptionRecord.ExceptionCode << ")\n" ; |
510 | } |
511 | dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED; |
512 | } else { |
513 | errs() << ToolName << ": Unhandled exception in: " << ProgramToRun |
514 | << "!\n" ; |
515 | errs().indent(NumSpaces: ToolName.size()) << ": location: " ; |
516 | errs() << Exception.ExceptionRecord.ExceptionAddress |
517 | << ", exception code: " |
518 | << ExceptionCodeToString( |
519 | Exception.ExceptionRecord.ExceptionCode) |
520 | << " (" << Exception.ExceptionRecord.ExceptionCode |
521 | << ")\n" ; |
522 | dwContinueStatus = DBG_CONTINUE; |
523 | ::TerminateProcess(ProcessIDToHandle[DebugEvent.dwProcessId], -1); |
524 | return -1; |
525 | } |
526 | } |
527 | break; |
528 | default: |
529 | // Do nothing. |
530 | if (TraceExecution) |
531 | errs() << ToolName << ": Debug Event: <unknown>\n" ; |
532 | break; |
533 | } |
534 | |
535 | success = ContinueDebugEvent(DebugEvent.dwProcessId, |
536 | DebugEvent.dwThreadId, |
537 | dwContinueStatus); |
538 | if (!success) { |
539 | ec = windows_error(::GetLastError()); |
540 | errs() << ToolName << ": Failed to continue debugging program: '" |
541 | << ProgramToRun << "': " << ec.message() << '\n'; |
542 | return -1; |
543 | } |
544 | |
545 | dwContinueStatus = DBG_CONTINUE; |
546 | } |
547 | |
548 | assert(0 && "Fell out of debug loop. This shouldn't be possible!" ); |
549 | return -1; |
550 | } |
551 | |