//===-- 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 #include #include #include #include #include #include #include #include // 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 Environ = std::move(*BufferOrError); // Get the command line used to start the process. BufferOrError = getProcFile(pid, "cmdline"); if (!BufferOrError) return false; std::unique_ptr 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"); }