brintos

brintos / llvm-project-archived public Read only

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