// // Copyright 2018 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // system_utils_posix.cpp: Implementation of POSIX OS-specific functions. #include "common/debug.h" #include "system_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/string_utils.h" #ifdef ANGLE_PLATFORM_FUCHSIA # include # include #else # include #endif namespace angle { namespace { std::string GetModulePath(void *moduleOrSymbol) { Dl_info dlInfo; if (dladdr(moduleOrSymbol, &dlInfo) == 0) { return ""; } #ifdef ANGLE_PLATFORM_LINUX // Chrome changes process title on Linux that causes dladdr returns wrong module // file name for executable binary, so return GetExecutablePath() if dli_fname // doesn't exist. struct stat buf; if (stat(dlInfo.dli_fname, &buf) != 0) { return GetExecutablePath(); } #endif return dlInfo.dli_fname; } void *OpenPosixLibrary(const std::string &fullPath, int extraFlags, std::string *errorOut) { void *module = dlopen(fullPath.c_str(), RTLD_NOW | extraFlags); if (module) { if (errorOut) { *errorOut = fullPath; } } else if (errorOut) { *errorOut = "dlopen("; *errorOut += fullPath; *errorOut += ") failed with error: "; *errorOut += dlerror(); struct stat sfile; if (-1 == stat(fullPath.c_str(), &sfile)) { *errorOut += ", stat() call failed."; } else { *errorOut += ", stat() info: "; struct passwd *pwuser = getpwuid(sfile.st_uid); if (pwuser) { *errorOut += "owner: "; *errorOut += pwuser->pw_name; *errorOut += ", "; } struct group *grpnam = getgrgid(sfile.st_gid); if (grpnam) { *errorOut += "group: "; *errorOut += grpnam->gr_name; *errorOut += ", "; } *errorOut += "perms: "; *errorOut += std::to_string(sfile.st_mode); *errorOut += ", links: "; *errorOut += std::to_string(sfile.st_nlink); *errorOut += ", size: "; *errorOut += std::to_string(sfile.st_size); } } return module; } } // namespace Optional GetCWD() { std::array pathBuf; char *result = getcwd(pathBuf.data(), pathBuf.size()); if (result == nullptr) { return Optional::Invalid(); } return std::string(pathBuf.data()); } bool SetCWD(const char *dirName) { return (chdir(dirName) == 0); } bool UnsetEnvironmentVar(const char *variableName) { return (unsetenv(variableName) == 0); } bool SetEnvironmentVar(const char *variableName, const char *value) { return (setenv(variableName, value, 1) == 0); } std::string GetEnvironmentVar(const char *variableName) { const char *value = getenv(variableName); return (value == nullptr ? std::string() : std::string(value)); } const char *GetPathSeparatorForEnvironmentVar() { return ":"; } std::string GetModuleDirectoryAndGetError(std::string *errorOut) { std::string directory; static int placeholderSymbol = 0; std::string moduleName = GetModulePath(&placeholderSymbol); if (!moduleName.empty()) { directory = moduleName.substr(0, moduleName.find_last_of('/') + 1); } // Ensure we return the full path to the module, not the relative path if (!IsFullPath(directory)) { if (errorOut) { *errorOut += "Directory: '"; *errorOut += directory; *errorOut += "' is not full path"; } Optional cwd = GetCWD(); if (cwd.valid()) { directory = ConcatenatePath(cwd.value(), directory); if (errorOut) { *errorOut += ", so it has been modified to: '"; *errorOut += directory; *errorOut += "'. "; } } else if (errorOut) { *errorOut += " and getcwd was invalid. "; } } return directory; } std::string GetModuleDirectory() { return GetModuleDirectoryAndGetError(nullptr); } void *OpenSystemLibraryWithExtensionAndGetError(const char *libraryName, SearchType searchType, std::string *errorOut) { std::string directory; if (searchType == SearchType::ModuleDir) { #if ANGLE_PLATFORM_IOS_FAMILY // On iOS, shared libraries must be loaded from within the app bundle. directory = GetExecutableDirectory() + "/Frameworks/"; #elif ANGLE_PLATFORM_FUCHSIA // On Fuchsia the dynamic loader always looks up libraries in /pkg/lib // and disallows loading of libraries via absolute paths. directory = ""; #else directory = GetModuleDirectoryAndGetError(errorOut); #endif } int extraFlags = 0; if (searchType == SearchType::AlreadyLoaded) { extraFlags = RTLD_NOLOAD; } std::string fullPath = directory + libraryName; return OpenPosixLibrary(fullPath, extraFlags, errorOut); } void *GetLibrarySymbol(void *libraryHandle, const char *symbolName) { if (!libraryHandle) { return nullptr; } return dlsym(libraryHandle, symbolName); } std::string GetLibraryPath(void *libraryHandle) { if (!libraryHandle) { return ""; } return GetModulePath(libraryHandle); } void CloseSystemLibrary(void *libraryHandle) { if (libraryHandle) { dlclose(libraryHandle); } } bool IsDirectory(const char *filename) { struct stat st; int result = stat(filename, &st); return result == 0 && ((st.st_mode & S_IFDIR) == S_IFDIR); } bool IsDebuggerAttached() { // This could have a fuller implementation. // See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc return false; } void BreakDebugger() { // This could have a fuller implementation. // See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc abort(); } const char *GetExecutableExtension() { return ""; } char GetPathSeparator() { return '/'; } std::string GetRootDirectory() { return "/"; } Optional GetTempDirectory() { const char *tmp = getenv("TMPDIR"); if (tmp != nullptr) { return std::string(tmp); } #if defined(ANGLE_PLATFORM_ANDROID) // Not used right now in the ANGLE test runner. // return PathService::Get(DIR_CACHE, path); return Optional::Invalid(); #else return std::string("/tmp"); #endif } Optional CreateTemporaryFileInDirectory(const std::string &directory) { return CreateTemporaryFileInDirectoryWithExtension(directory, std::string()); } Optional CreateTemporaryFileInDirectoryWithExtension(const std::string &directory, const std::string &extension) { std::string tempFileTemplate = directory + "/.angle.XXXXXX" + extension; int fd = mkstemps(&tempFileTemplate[0], static_cast(extension.size())); close(fd); if (fd != -1) { return tempFileTemplate; } return Optional::Invalid(); } double GetCurrentProcessCpuTime() { #ifdef ANGLE_PLATFORM_FUCHSIA static zx_handle_t me = zx_process_self(); zx_info_task_runtime_t task_runtime; zx_object_get_info(me, ZX_INFO_TASK_RUNTIME, &task_runtime, sizeof(task_runtime), nullptr, nullptr); return static_cast(task_runtime.cpu_time) * 1e-9; #else // We could also have used /proc/stat, but that requires us to read the // filesystem and convert from jiffies. /proc/stat also relies on jiffies // (lower resolution) while getrusage can potentially use a sched_clock() // underneath that has higher resolution. struct rusage usage; getrusage(RUSAGE_SELF, &usage); double userTime = usage.ru_utime.tv_sec + usage.ru_utime.tv_usec * 1e-6; double systemTime = usage.ru_stime.tv_sec + usage.ru_stime.tv_usec * 1e-6; return userTime + systemTime; #endif } namespace { bool SetMemoryProtection(uintptr_t start, size_t size, int protections) { int ret = mprotect(reinterpret_cast(start), size, protections); if (ret < 0) { perror("mprotect failed"); } return ret == 0; } class PosixPageFaultHandler : public PageFaultHandler { public: PosixPageFaultHandler(PageFaultCallback callback) : PageFaultHandler(callback) {} ~PosixPageFaultHandler() override {} bool enable() override; bool disable() override; void handle(int sig, siginfo_t *info, void *unused); private: struct sigaction mDefaultBusAction = {}; struct sigaction mDefaultSegvAction = {}; }; PosixPageFaultHandler *gPosixPageFaultHandler = nullptr; void SegfaultHandlerFunction(int sig, siginfo_t *info, void *unused) { gPosixPageFaultHandler->handle(sig, info, unused); } void PosixPageFaultHandler::handle(int sig, siginfo_t *info, void *unused) { bool found = false; if ((sig == SIGSEGV || sig == SIGBUS) && (info->si_code == SEGV_ACCERR || info->si_code == SEGV_MAPERR)) { found = mCallback(reinterpret_cast(info->si_addr)) == PageFaultHandlerRangeType::InRange; } // Fall back to default signal handler if (!found) { if (sig == SIGSEGV) { mDefaultSegvAction.sa_sigaction(sig, info, unused); } else if (sig == SIGBUS) { mDefaultBusAction.sa_sigaction(sig, info, unused); } else { UNREACHABLE(); } } } bool PosixPageFaultHandler::disable() { return sigaction(SIGSEGV, &mDefaultSegvAction, nullptr) == 0 && sigaction(SIGBUS, &mDefaultBusAction, nullptr) == 0; } bool PosixPageFaultHandler::enable() { struct sigaction sigAction = {}; sigAction.sa_flags = SA_SIGINFO; sigAction.sa_sigaction = &SegfaultHandlerFunction; sigemptyset(&sigAction.sa_mask); // Some POSIX implementations use SIGBUS for mprotect faults return sigaction(SIGSEGV, &sigAction, &mDefaultSegvAction) == 0 && sigaction(SIGBUS, &sigAction, &mDefaultBusAction) == 0; } } // namespace // Set write protection bool ProtectMemory(uintptr_t start, size_t size) { return SetMemoryProtection(start, size, PROT_READ); } // Allow reading and writing bool UnprotectMemory(uintptr_t start, size_t size) { return SetMemoryProtection(start, size, PROT_READ | PROT_WRITE); } size_t GetPageSize() { long pageSize = sysconf(_SC_PAGE_SIZE); if (pageSize < 0) { perror("Could not get sysconf page size"); return 0; } return static_cast(pageSize); } PageFaultHandler *CreatePageFaultHandler(PageFaultCallback callback) { gPosixPageFaultHandler = new PosixPageFaultHandler(callback); return gPosixPageFaultHandler; } uint64_t GetProcessMemoryUsageKB() { FILE *file = fopen("/proc/self/status", "r"); if (!file) { return 0; } const char *kSearchString = "VmRSS:"; constexpr size_t kMaxLineSize = 100; std::array line = {}; uint64_t kb = 0; while (fgets(line.data(), line.size(), file) != nullptr) { if (strncmp(line.data(), kSearchString, strlen(kSearchString)) == 0) { std::vector strings; SplitStringAlongWhitespace(line.data(), &strings); sscanf(strings[1].c_str(), "%" SCNu64, &kb); break; } } fclose(file); return kb; } } // namespace angle