| //===-- source/Host/linux/Host.cpp ------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // C Includes |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/utsname.h> |
| |
| // C++ Includes |
| // Other libraries and framework includes |
| #include "llvm/Support/ScopedPrinter.h" |
| // Project includes |
| #include "lldb/Target/Process.h" |
| #include "lldb/Utility/Log.h" |
| #include "lldb/Utility/Status.h" |
| |
| #include "lldb/Host/Host.h" |
| #include "lldb/Host/HostInfo.h" |
| #include "lldb/Host/linux/Support.h" |
| #include "lldb/Utility/DataBufferHeap.h" |
| #include "lldb/Utility/DataExtractor.h" |
| |
| #include "lldb/Core/ModuleSpec.h" |
| #include "lldb/Symbol/ObjectFile.h" |
| |
| using namespace lldb; |
| using namespace lldb_private; |
| |
| namespace { |
| enum class ProcessState { |
| Unknown, |
| DiskSleep, |
| Paging, |
| Running, |
| Sleeping, |
| TracedOrStopped, |
| Zombie, |
| }; |
| } |
| |
| static bool GetStatusInfo(::pid_t Pid, ProcessInstanceInfo &ProcessInfo, |
| ProcessState &State, ::pid_t &TracerPid) { |
| auto BufferOrError = getProcFile(Pid, "status"); |
| if (!BufferOrError) |
| return false; |
| |
| llvm::StringRef Rest = BufferOrError.get()->getBuffer(); |
| while(!Rest.empty()) { |
| llvm::StringRef Line; |
| std::tie(Line, Rest) = Rest.split('\n'); |
| |
| if (Line.consume_front("Gid:")) { |
| // Real, effective, saved set, and file system GIDs. Read the first two. |
| Line = Line.ltrim(); |
| uint32_t RGid, EGid; |
| Line.consumeInteger(10, RGid); |
| Line = Line.ltrim(); |
| Line.consumeInteger(10, EGid); |
| |
| ProcessInfo.SetGroupID(RGid); |
| ProcessInfo.SetEffectiveGroupID(EGid); |
| } else if (Line.consume_front("Uid:")) { |
| // Real, effective, saved set, and file system UIDs. Read the first two. |
| Line = Line.ltrim(); |
| uint32_t RUid, EUid; |
| Line.consumeInteger(10, RUid); |
| Line = Line.ltrim(); |
| Line.consumeInteger(10, EUid); |
| |
| ProcessInfo.SetUserID(RUid); |
| ProcessInfo.SetEffectiveUserID(EUid); |
| } else if (Line.consume_front("PPid:")) { |
| ::pid_t PPid; |
| Line.ltrim().consumeInteger(10, PPid); |
| ProcessInfo.SetParentProcessID(PPid); |
| } else if (Line.consume_front("State:")) { |
| char S = Line.ltrim().front(); |
| switch (S) { |
| case 'R': |
| State = ProcessState::Running; |
| break; |
| case 'S': |
| State = ProcessState::Sleeping; |
| break; |
| case 'D': |
| State = ProcessState::DiskSleep; |
| break; |
| case 'Z': |
| State = ProcessState::Zombie; |
| break; |
| case 'T': |
| State = ProcessState::TracedOrStopped; |
| break; |
| case 'W': |
| State = ProcessState::Paging; |
| break; |
| } |
| } else if (Line.consume_front("TracerPid:")) { |
| Line = Line.ltrim(); |
| Line.consumeInteger(10, TracerPid); |
| } |
| } |
| return true; |
| } |
| |
| static bool IsDirNumeric(const char *dname) { |
| for (; *dname; dname++) { |
| if (!isdigit(*dname)) |
| return false; |
| } |
| return true; |
| } |
| |
| static bool GetELFProcessCPUType(llvm::StringRef exe_path, |
| ProcessInstanceInfo &process_info) { |
| // Clear the architecture. |
| process_info.GetArchitecture().Clear(); |
| |
| ModuleSpecList specs; |
| FileSpec filespec(exe_path, false); |
| const size_t num_specs = |
| ObjectFile::GetModuleSpecifications(filespec, 0, 0, specs); |
| // GetModuleSpecifications() could fail if the executable has been deleted or |
| // is locked. |
| // But it shouldn't return more than 1 architecture. |
| assert(num_specs <= 1 && "Linux plugin supports only a single architecture"); |
| if (num_specs == 1) { |
| ModuleSpec module_spec; |
| if (specs.GetModuleSpecAtIndex(0, module_spec) && |
| module_spec.GetArchitecture().IsValid()) { |
| process_info.GetArchitecture() = module_spec.GetArchitecture(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool GetProcessAndStatInfo(::pid_t pid, |
| ProcessInstanceInfo &process_info, |
| ProcessState &State, ::pid_t &tracerpid) { |
| tracerpid = 0; |
| process_info.Clear(); |
| |
| Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); |
| |
| // We can't use getProcFile here because proc/[pid]/exe is a symbolic link. |
| llvm::SmallString<64> ProcExe; |
| (llvm::Twine("/proc/") + llvm::Twine(pid) + "/exe").toVector(ProcExe); |
| std::string ExePath(PATH_MAX, '\0'); |
| |
| ssize_t len = readlink(ProcExe.c_str(), &ExePath[0], PATH_MAX); |
| if (len <= 0) { |
| LLDB_LOG(log, "failed to read link exe link for {0}: {1}", pid, |
| Status(errno, eErrorTypePOSIX)); |
| return false; |
| } |
| ExePath.resize(len); |
| |
| // If the binary has been deleted, the link name has " (deleted)" appended. |
| // Remove if there. |
| llvm::StringRef PathRef = ExePath; |
| PathRef.consume_back(" (deleted)"); |
| |
| GetELFProcessCPUType(PathRef, process_info); |
| |
| // Get the process environment. |
| auto BufferOrError = getProcFile(pid, "environ"); |
| if (!BufferOrError) |
| return false; |
| std::unique_ptr<llvm::MemoryBuffer> Environ = std::move(*BufferOrError); |
| |
| // Get the command line used to start the process. |
| BufferOrError = getProcFile(pid, "cmdline"); |
| if (!BufferOrError) |
| return false; |
| std::unique_ptr<llvm::MemoryBuffer> Cmdline = std::move(*BufferOrError); |
| |
| // Get User and Group IDs and get tracer pid. |
| if (!GetStatusInfo(pid, process_info, State, tracerpid)) |
| return false; |
| |
| process_info.SetProcessID(pid); |
| process_info.GetExecutableFile().SetFile(PathRef, false); |
| process_info.GetArchitecture().MergeFrom(HostInfo::GetArchitecture()); |
| |
| llvm::StringRef Rest = Environ->getBuffer(); |
| while (!Rest.empty()) { |
| llvm::StringRef Var; |
| std::tie(Var, Rest) = Rest.split('\0'); |
| process_info.GetEnvironmentEntries().AppendArgument(Var); |
| } |
| |
| llvm::StringRef Arg0; |
| std::tie(Arg0, Rest) = Cmdline->getBuffer().split('\0'); |
| process_info.SetArg0(Arg0); |
| while (!Rest.empty()) { |
| llvm::StringRef Arg; |
| std::tie(Arg, Rest) = Rest.split('\0'); |
| process_info.GetArguments().AppendArgument(Arg); |
| } |
| |
| return true; |
| } |
| |
| uint32_t Host::FindProcesses(const ProcessInstanceInfoMatch &match_info, |
| ProcessInstanceInfoList &process_infos) { |
| static const char procdir[] = "/proc/"; |
| |
| DIR *dirproc = opendir(procdir); |
| if (dirproc) { |
| struct dirent *direntry = NULL; |
| const uid_t our_uid = getuid(); |
| const lldb::pid_t our_pid = getpid(); |
| bool all_users = match_info.GetMatchAllUsers(); |
| |
| while ((direntry = readdir(dirproc)) != NULL) { |
| if (direntry->d_type != DT_DIR || !IsDirNumeric(direntry->d_name)) |
| continue; |
| |
| lldb::pid_t pid = atoi(direntry->d_name); |
| |
| // Skip this process. |
| if (pid == our_pid) |
| continue; |
| |
| ::pid_t tracerpid; |
| ProcessState State; |
| ProcessInstanceInfo process_info; |
| |
| if (!GetProcessAndStatInfo(pid, process_info, State, tracerpid)) |
| continue; |
| |
| // Skip if process is being debugged. |
| if (tracerpid != 0) |
| continue; |
| |
| if (State == ProcessState::Zombie) |
| continue; |
| |
| // Check for user match if we're not matching all users and not running as |
| // root. |
| if (!all_users && (our_uid != 0) && (process_info.GetUserID() != our_uid)) |
| continue; |
| |
| if (match_info.Matches(process_info)) { |
| process_infos.Append(process_info); |
| } |
| } |
| |
| closedir(dirproc); |
| } |
| |
| return process_infos.GetSize(); |
| } |
| |
| bool Host::FindProcessThreads(const lldb::pid_t pid, TidMap &tids_to_attach) { |
| bool tids_changed = false; |
| static const char procdir[] = "/proc/"; |
| static const char taskdir[] = "/task/"; |
| std::string process_task_dir = procdir + llvm::to_string(pid) + taskdir; |
| DIR *dirproc = opendir(process_task_dir.c_str()); |
| |
| if (dirproc) { |
| struct dirent *direntry = NULL; |
| while ((direntry = readdir(dirproc)) != NULL) { |
| if (direntry->d_type != DT_DIR || !IsDirNumeric(direntry->d_name)) |
| continue; |
| |
| lldb::tid_t tid = atoi(direntry->d_name); |
| TidMap::iterator it = tids_to_attach.find(tid); |
| if (it == tids_to_attach.end()) { |
| tids_to_attach.insert(TidPair(tid, false)); |
| tids_changed = true; |
| } |
| } |
| closedir(dirproc); |
| } |
| |
| return tids_changed; |
| } |
| |
| bool Host::GetProcessInfo(lldb::pid_t pid, ProcessInstanceInfo &process_info) { |
| ::pid_t tracerpid; |
| ProcessState State; |
| return GetProcessAndStatInfo(pid, process_info, State, tracerpid); |
| } |
| |
| size_t Host::GetEnvironment(StringList &env) { |
| char **host_env = environ; |
| char *env_entry; |
| size_t i; |
| for (i = 0; (env_entry = host_env[i]) != NULL; ++i) |
| env.AppendString(env_entry); |
| return i; |
| } |
| |
| Status Host::ShellExpandArguments(ProcessLaunchInfo &launch_info) { |
| return Status("unimplemented"); |
| } |