//===-- Host.cpp ----------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // C includes #include #include #include #include #ifndef _WIN32 #include #include #include #include #include #include #endif #if defined(__APPLE__) #include #include #include #endif #if defined(__linux__) || defined(__FreeBSD__) || \ defined(__FreeBSD_kernel__) || defined(__APPLE__) || \ defined(__NetBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) #if !defined(__ANDROID__) #include #endif #include #include #endif #if defined(__FreeBSD__) #include #endif #if defined(__NetBSD__) #include #endif #include #include "lldb/Host/FileAction.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Host/HostProcess.h" #include "lldb/Host/MonitoringProcessLauncher.h" #include "lldb/Host/ProcessLaunchInfo.h" #include "lldb/Host/ProcessLauncher.h" #include "lldb/Host/ThreadLauncher.h" #include "lldb/Host/posix/ConnectionFileDescriptorPosix.h" #include "lldb/Utility/DataBufferLLVM.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/Predicate.h" #include "lldb/Utility/ReproducerProvider.h" #include "lldb/Utility/Status.h" #include "lldb/lldb-private-forward.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Errno.h" #include "llvm/Support/FileSystem.h" #if defined(_WIN32) #include "lldb/Host/windows/ConnectionGenericFileWindows.h" #include "lldb/Host/windows/ProcessLauncherWindows.h" #else #include "lldb/Host/posix/ProcessLauncherPosixFork.h" #endif #if defined(__APPLE__) #ifndef _POSIX_SPAWN_DISABLE_ASLR #define _POSIX_SPAWN_DISABLE_ASLR 0x0100 #endif extern "C" { int __pthread_chdir(const char *path); int __pthread_fchdir(int fildes); } #endif using namespace lldb; using namespace lldb_private; #if !defined(__APPLE__) && !defined(_WIN32) struct MonitorInfo { lldb::pid_t pid; // The process ID to monitor Host::MonitorChildProcessCallback callback; // The callback function to call when "pid" exits or signals bool monitor_signals; // If true, call the callback when "pid" gets signaled. }; static thread_result_t MonitorChildProcessThreadFunction(void *arg); llvm::Expected Host::StartMonitoringChildProcess( const Host::MonitorChildProcessCallback &callback, lldb::pid_t pid, bool monitor_signals) { MonitorInfo *info_ptr = new MonitorInfo(); info_ptr->pid = pid; info_ptr->callback = callback; info_ptr->monitor_signals = monitor_signals; char thread_name[256]; ::snprintf(thread_name, sizeof(thread_name), "", pid); return ThreadLauncher::LaunchThread( thread_name, MonitorChildProcessThreadFunction, info_ptr, 0); } #ifndef __linux__ // Scoped class that will disable thread canceling when it is constructed, and // exception safely restore the previous value it when it goes out of scope. class ScopedPThreadCancelDisabler { public: ScopedPThreadCancelDisabler() { // Disable the ability for this thread to be cancelled int err = ::pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &m_old_state); if (err != 0) m_old_state = -1; } ~ScopedPThreadCancelDisabler() { // Restore the ability for this thread to be cancelled to what it // previously was. if (m_old_state != -1) ::pthread_setcancelstate(m_old_state, 0); } private: int m_old_state; // Save the old cancelability state. }; #endif // __linux__ #ifdef __linux__ #if defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)) static __thread volatile sig_atomic_t g_usr1_called; #else static thread_local volatile sig_atomic_t g_usr1_called; #endif static void SigUsr1Handler(int) { g_usr1_called = 1; } #endif // __linux__ static bool CheckForMonitorCancellation() { #ifdef __linux__ if (g_usr1_called) { g_usr1_called = 0; return true; } #else ::pthread_testcancel(); #endif return false; } static thread_result_t MonitorChildProcessThreadFunction(void *arg) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); const char *function = __FUNCTION__; LLDB_LOGF(log, "%s (arg = %p) thread starting...", function, arg); MonitorInfo *info = (MonitorInfo *)arg; const Host::MonitorChildProcessCallback callback = info->callback; const bool monitor_signals = info->monitor_signals; assert(info->pid <= UINT32_MAX); const ::pid_t pid = monitor_signals ? -1 * getpgid(info->pid) : info->pid; delete info; int status = -1; #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) #define __WALL 0 #endif const int options = __WALL; #ifdef __linux__ // This signal is only used to interrupt the thread from waitpid struct sigaction sigUsr1Action; memset(&sigUsr1Action, 0, sizeof(sigUsr1Action)); sigUsr1Action.sa_handler = SigUsr1Handler; ::sigaction(SIGUSR1, &sigUsr1Action, nullptr); #endif // __linux__ while (1) { log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); LLDB_LOGF(log, "%s ::waitpid (pid = %" PRIi32 ", &status, options = %i)...", function, pid, options); if (CheckForMonitorCancellation()) break; // Get signals from all children with same process group of pid const ::pid_t wait_pid = ::waitpid(pid, &status, options); if (CheckForMonitorCancellation()) break; if (wait_pid == -1) { if (errno == EINTR) continue; else { LLDB_LOG(log, "arg = {0}, thread exiting because waitpid failed ({1})...", arg, llvm::sys::StrError()); break; } } else if (wait_pid > 0) { bool exited = false; int signal = 0; int exit_status = 0; const char *status_cstr = nullptr; if (WIFSTOPPED(status)) { signal = WSTOPSIG(status); status_cstr = "STOPPED"; } else if (WIFEXITED(status)) { exit_status = WEXITSTATUS(status); status_cstr = "EXITED"; exited = true; } else if (WIFSIGNALED(status)) { signal = WTERMSIG(status); status_cstr = "SIGNALED"; if (wait_pid == abs(pid)) { exited = true; exit_status = -1; } } else { status_cstr = "(\?\?\?)"; } // Scope for pthread_cancel_disabler { #ifndef __linux__ ScopedPThreadCancelDisabler pthread_cancel_disabler; #endif log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); LLDB_LOGF(log, "%s ::waitpid (pid = %" PRIi32 ", &status, options = %i) => pid = %" PRIi32 ", status = 0x%8.8x (%s), signal = %i, exit_state = %i", function, pid, options, wait_pid, status, status_cstr, signal, exit_status); if (exited || (signal != 0 && monitor_signals)) { bool callback_return = false; if (callback) callback_return = callback(wait_pid, exited, signal, exit_status); // If our process exited, then this thread should exit if (exited && wait_pid == abs(pid)) { LLDB_LOGF(log, "%s (arg = %p) thread exiting because pid received " "exit signal...", __FUNCTION__, arg); break; } // If the callback returns true, it means this process should exit if (callback_return) { LLDB_LOGF(log, "%s (arg = %p) thread exiting because callback " "returned true...", __FUNCTION__, arg); break; } } } } } log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS); LLDB_LOGF(log, "%s (arg = %p) thread exiting...", __FUNCTION__, arg); return nullptr; } #endif // #if !defined (__APPLE__) && !defined (_WIN32) #if !defined(__APPLE__) void Host::SystemLog(SystemLogType type, const char *format, va_list args) { vfprintf(stderr, format, args); } #endif void Host::SystemLog(SystemLogType type, const char *format, ...) { { va_list args; va_start(args, format); SystemLog(type, format, args); va_end(args); } Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_HOST)); if (log && log->GetVerbose()) { // Log to log channel. This allows testcases to grep for log output. va_list args; va_start(args, format); log->VAPrintf(format, args); va_end(args); } } lldb::pid_t Host::GetCurrentProcessID() { return ::getpid(); } #ifndef _WIN32 lldb::thread_t Host::GetCurrentThread() { return lldb::thread_t(pthread_self()); } const char *Host::GetSignalAsCString(int signo) { switch (signo) { case SIGHUP: return "SIGHUP"; // 1 hangup case SIGINT: return "SIGINT"; // 2 interrupt case SIGQUIT: return "SIGQUIT"; // 3 quit case SIGILL: return "SIGILL"; // 4 illegal instruction (not reset when caught) case SIGTRAP: return "SIGTRAP"; // 5 trace trap (not reset when caught) case SIGABRT: return "SIGABRT"; // 6 abort() #if defined(SIGPOLL) #if !defined(SIGIO) || (SIGPOLL != SIGIO) // Under some GNU/Linux, SIGPOLL and SIGIO are the same. Causing the build to // fail with 'multiple define cases with same value' case SIGPOLL: return "SIGPOLL"; // 7 pollable event ([XSR] generated, not supported) #endif #endif #if defined(SIGEMT) case SIGEMT: return "SIGEMT"; // 7 EMT instruction #endif case SIGFPE: return "SIGFPE"; // 8 floating point exception case SIGKILL: return "SIGKILL"; // 9 kill (cannot be caught or ignored) case SIGBUS: return "SIGBUS"; // 10 bus error case SIGSEGV: return "SIGSEGV"; // 11 segmentation violation case SIGSYS: return "SIGSYS"; // 12 bad argument to system call case SIGPIPE: return "SIGPIPE"; // 13 write on a pipe with no one to read it case SIGALRM: return "SIGALRM"; // 14 alarm clock case SIGTERM: return "SIGTERM"; // 15 software termination signal from kill case SIGURG: return "SIGURG"; // 16 urgent condition on IO channel case SIGSTOP: return "SIGSTOP"; // 17 sendable stop signal not from tty case SIGTSTP: return "SIGTSTP"; // 18 stop signal from tty case SIGCONT: return "SIGCONT"; // 19 continue a stopped process case SIGCHLD: return "SIGCHLD"; // 20 to parent on child stop or exit case SIGTTIN: return "SIGTTIN"; // 21 to readers pgrp upon background tty read case SIGTTOU: return "SIGTTOU"; // 22 like TTIN for output if (tp->t_local<OSTOP) #if defined(SIGIO) case SIGIO: return "SIGIO"; // 23 input/output possible signal #endif case SIGXCPU: return "SIGXCPU"; // 24 exceeded CPU time limit case SIGXFSZ: return "SIGXFSZ"; // 25 exceeded file size limit case SIGVTALRM: return "SIGVTALRM"; // 26 virtual time alarm case SIGPROF: return "SIGPROF"; // 27 profiling time alarm #if defined(SIGWINCH) case SIGWINCH: return "SIGWINCH"; // 28 window size changes #endif #if defined(SIGINFO) case SIGINFO: return "SIGINFO"; // 29 information request #endif case SIGUSR1: return "SIGUSR1"; // 30 user defined signal 1 case SIGUSR2: return "SIGUSR2"; // 31 user defined signal 2 default: break; } return nullptr; } #endif #if !defined(__APPLE__) // see Host.mm bool Host::GetBundleDirectory(const FileSpec &file, FileSpec &bundle) { bundle.Clear(); return false; } bool Host::ResolveExecutableInBundle(FileSpec &file) { return false; } #endif #ifndef _WIN32 FileSpec Host::GetModuleFileSpecForHostAddress(const void *host_addr) { FileSpec module_filespec; #if !defined(__ANDROID__) Dl_info info; if (::dladdr(host_addr, &info)) { if (info.dli_fname) { module_filespec.SetFile(info.dli_fname, FileSpec::Style::native); FileSystem::Instance().Resolve(module_filespec); } } #endif return module_filespec; } #endif #if !defined(__linux__) bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) { return false; } #endif struct ShellInfo { ShellInfo() : process_reaped(false), pid(LLDB_INVALID_PROCESS_ID), signo(-1), status(-1) {} lldb_private::Predicate process_reaped; lldb::pid_t pid; int signo; int status; }; static bool MonitorShellCommand(std::shared_ptr shell_info, lldb::pid_t pid, bool exited, // True if the process did exit int signo, // Zero for no signal int status) // Exit value of process if signal is zero { shell_info->pid = pid; shell_info->signo = signo; shell_info->status = status; // Let the thread running Host::RunShellCommand() know that the process // exited and that ShellInfo has been filled in by broadcasting to it shell_info->process_reaped.SetValue(true, eBroadcastAlways); return true; } Status Host::RunShellCommand(llvm::StringRef command, const FileSpec &working_dir, int *status_ptr, int *signo_ptr, std::string *command_output_ptr, const Timeout &timeout, bool run_in_shell, bool hide_stderr) { return RunShellCommand(llvm::StringRef(), Args(command), working_dir, status_ptr, signo_ptr, command_output_ptr, timeout, run_in_shell, hide_stderr); } Status Host::RunShellCommand(llvm::StringRef shell_path, llvm::StringRef command, const FileSpec &working_dir, int *status_ptr, int *signo_ptr, std::string *command_output_ptr, const Timeout &timeout, bool run_in_shell, bool hide_stderr) { return RunShellCommand(shell_path, Args(command), working_dir, status_ptr, signo_ptr, command_output_ptr, timeout, run_in_shell, hide_stderr); } Status Host::RunShellCommand(const Args &args, const FileSpec &working_dir, int *status_ptr, int *signo_ptr, std::string *command_output_ptr, const Timeout &timeout, bool run_in_shell, bool hide_stderr) { return RunShellCommand(llvm::StringRef(), args, working_dir, status_ptr, signo_ptr, command_output_ptr, timeout, run_in_shell, hide_stderr); } Status Host::RunShellCommand(llvm::StringRef shell_path, const Args &args, const FileSpec &working_dir, int *status_ptr, int *signo_ptr, std::string *command_output_ptr, const Timeout &timeout, bool run_in_shell, bool hide_stderr) { Status error; ProcessLaunchInfo launch_info; launch_info.SetArchitecture(HostInfo::GetArchitecture()); if (run_in_shell) { // Run the command in a shell FileSpec shell = HostInfo::GetDefaultShell(); if (!shell_path.empty()) shell.SetPath(shell_path); launch_info.SetShell(shell); launch_info.GetArguments().AppendArguments(args); const bool will_debug = false; const bool first_arg_is_full_shell_command = false; launch_info.ConvertArgumentsForLaunchingInShell( error, will_debug, first_arg_is_full_shell_command, 0); } else { // No shell, just run it const bool first_arg_is_executable = true; launch_info.SetArguments(args, first_arg_is_executable); } launch_info.GetEnvironment() = Host::GetEnvironment(); if (working_dir) launch_info.SetWorkingDirectory(working_dir); llvm::SmallString<64> output_file_path; if (command_output_ptr) { // Create a temporary file to get the stdout/stderr and redirect the output // of the command into this file. We will later read this file if all goes // well and fill the data into "command_output_ptr" if (FileSpec tmpdir_file_spec = HostInfo::GetProcessTempDir()) { tmpdir_file_spec.AppendPathComponent("lldb-shell-output.%%%%%%"); llvm::sys::fs::createUniqueFile(tmpdir_file_spec.GetPath(), output_file_path); } else { llvm::sys::fs::createTemporaryFile("lldb-shell-output.%%%%%%", "", output_file_path); } } FileSpec output_file_spec(output_file_path.str()); // Set up file descriptors. launch_info.AppendSuppressFileAction(STDIN_FILENO, true, false); if (output_file_spec) launch_info.AppendOpenFileAction(STDOUT_FILENO, output_file_spec, false, true); else launch_info.AppendSuppressFileAction(STDOUT_FILENO, false, true); if (output_file_spec && !hide_stderr) launch_info.AppendDuplicateFileAction(STDOUT_FILENO, STDERR_FILENO); else launch_info.AppendSuppressFileAction(STDERR_FILENO, false, true); std::shared_ptr shell_info_sp(new ShellInfo()); const bool monitor_signals = false; launch_info.SetMonitorProcessCallback( std::bind(MonitorShellCommand, shell_info_sp, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), monitor_signals); error = LaunchProcess(launch_info); const lldb::pid_t pid = launch_info.GetProcessID(); if (error.Success() && pid == LLDB_INVALID_PROCESS_ID) error.SetErrorString("failed to get process ID"); if (error.Success()) { if (!shell_info_sp->process_reaped.WaitForValueEqualTo(true, timeout)) { error.SetErrorString("timed out waiting for shell command to complete"); // Kill the process since it didn't complete within the timeout specified Kill(pid, SIGKILL); // Wait for the monitor callback to get the message shell_info_sp->process_reaped.WaitForValueEqualTo( true, std::chrono::seconds(1)); } else { if (status_ptr) *status_ptr = shell_info_sp->status; if (signo_ptr) *signo_ptr = shell_info_sp->signo; if (command_output_ptr) { command_output_ptr->clear(); uint64_t file_size = FileSystem::Instance().GetByteSize(output_file_spec); if (file_size > 0) { if (file_size > command_output_ptr->max_size()) { error.SetErrorStringWithFormat( "shell command output is too large to fit into a std::string"); } else { auto Buffer = FileSystem::Instance().CreateDataBuffer(output_file_spec); if (error.Success()) command_output_ptr->assign(Buffer->GetChars(), Buffer->GetByteSize()); } } } } } llvm::sys::fs::remove(output_file_spec.GetPath()); return error; } // The functions below implement process launching for non-Apple-based // platforms #if !defined(__APPLE__) Status Host::LaunchProcess(ProcessLaunchInfo &launch_info) { std::unique_ptr delegate_launcher; #if defined(_WIN32) delegate_launcher.reset(new ProcessLauncherWindows()); #else delegate_launcher.reset(new ProcessLauncherPosixFork()); #endif MonitoringProcessLauncher launcher(std::move(delegate_launcher)); Status error; HostProcess process = launcher.LaunchProcess(launch_info, error); // TODO(zturner): It would be better if the entire HostProcess were returned // instead of writing it into this structure. launch_info.SetProcessID(process.GetProcessId()); return error; } #endif // !defined(__APPLE__) #ifndef _WIN32 void Host::Kill(lldb::pid_t pid, int signo) { ::kill(pid, signo); } #endif #if !defined(__APPLE__) bool Host::OpenFileInExternalEditor(const FileSpec &file_spec, uint32_t line_no) { return false; } #endif std::unique_ptr Host::CreateDefaultConnection(llvm::StringRef url) { #if defined(_WIN32) if (url.startswith("file://")) return std::unique_ptr(new ConnectionGenericFile()); #endif return std::unique_ptr(new ConnectionFileDescriptor()); } #if defined(LLVM_ON_UNIX) WaitStatus WaitStatus::Decode(int wstatus) { if (WIFEXITED(wstatus)) return {Exit, uint8_t(WEXITSTATUS(wstatus))}; else if (WIFSIGNALED(wstatus)) return {Signal, uint8_t(WTERMSIG(wstatus))}; else if (WIFSTOPPED(wstatus)) return {Stop, uint8_t(WSTOPSIG(wstatus))}; llvm_unreachable("Unknown wait status"); } #endif void llvm::format_provider::format(const WaitStatus &WS, raw_ostream &OS, StringRef Options) { if (Options == "g") { char type; switch (WS.type) { case WaitStatus::Exit: type = 'W'; break; case WaitStatus::Signal: type = 'X'; break; case WaitStatus::Stop: type = 'S'; break; } OS << formatv("{0}{1:x-2}", type, WS.status); return; } assert(Options.empty()); const char *desc; switch(WS.type) { case WaitStatus::Exit: desc = "Exited with status"; break; case WaitStatus::Signal: desc = "Killed by signal"; break; case WaitStatus::Stop: desc = "Stopped by signal"; break; } OS << desc << " " << int(WS.status); } uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &process_infos) { if (llvm::Optional infos = repro::GetReplayProcessInstanceInfoList()) { process_infos = *infos; return process_infos.size(); } uint32_t result = FindProcessesImpl(match_info, process_infos); if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) { g->GetOrCreate() .GetNewProcessInfoRecorder() ->Record(process_infos); } return result; }