system_utils_posix.cpp (11683B)
1 // 2 // Copyright 2018 The ANGLE Project Authors. All rights reserved. 3 // Use of this source code is governed by a BSD-style license that can be 4 // found in the LICENSE file. 5 // 6 7 // system_utils_posix.cpp: Implementation of POSIX OS-specific functions. 8 9 #include "common/debug.h" 10 #include "system_utils.h" 11 12 #include <array> 13 #include <iostream> 14 15 #include <dlfcn.h> 16 #include <grp.h> 17 #include <inttypes.h> 18 #include <pwd.h> 19 #include <signal.h> 20 #include <string.h> 21 #include <sys/mman.h> 22 #include <sys/stat.h> 23 #include <sys/types.h> 24 #include <sys/wait.h> 25 #include <unistd.h> 26 27 #include "common/string_utils.h" 28 29 #ifdef ANGLE_PLATFORM_FUCHSIA 30 # include <zircon/process.h> 31 # include <zircon/syscalls.h> 32 #else 33 # include <sys/resource.h> 34 #endif 35 36 namespace angle 37 { 38 39 namespace 40 { 41 std::string GetModulePath(void *moduleOrSymbol) 42 { 43 Dl_info dlInfo; 44 if (dladdr(moduleOrSymbol, &dlInfo) == 0) 45 { 46 return ""; 47 } 48 49 return dlInfo.dli_fname; 50 } 51 52 void *OpenPosixLibrary(const std::string &fullPath, int extraFlags, std::string *errorOut) 53 { 54 void *module = dlopen(fullPath.c_str(), RTLD_NOW | extraFlags); 55 if (module) 56 { 57 if (errorOut) 58 { 59 *errorOut = fullPath; 60 } 61 } 62 else if (errorOut) 63 { 64 *errorOut = "dlopen("; 65 *errorOut += fullPath; 66 *errorOut += ") failed with error: "; 67 *errorOut += dlerror(); 68 struct stat sfile; 69 if (-1 == stat(fullPath.c_str(), &sfile)) 70 { 71 *errorOut += ", stat() call failed."; 72 } 73 else 74 { 75 *errorOut += ", stat() info: "; 76 struct passwd *pwuser = getpwuid(sfile.st_uid); 77 if (pwuser) 78 { 79 *errorOut += "owner: "; 80 *errorOut += pwuser->pw_name; 81 *errorOut += ", "; 82 } 83 struct group *grpnam = getgrgid(sfile.st_gid); 84 if (grpnam) 85 { 86 *errorOut += "group: "; 87 *errorOut += grpnam->gr_name; 88 *errorOut += ", "; 89 } 90 *errorOut += "perms: "; 91 *errorOut += std::to_string(sfile.st_mode); 92 *errorOut += ", links: "; 93 *errorOut += std::to_string(sfile.st_nlink); 94 *errorOut += ", size: "; 95 *errorOut += std::to_string(sfile.st_size); 96 } 97 } 98 return module; 99 } 100 } // namespace 101 102 Optional<std::string> GetCWD() 103 { 104 std::array<char, 4096> pathBuf; 105 char *result = getcwd(pathBuf.data(), pathBuf.size()); 106 if (result == nullptr) 107 { 108 return Optional<std::string>::Invalid(); 109 } 110 return std::string(pathBuf.data()); 111 } 112 113 bool SetCWD(const char *dirName) 114 { 115 return (chdir(dirName) == 0); 116 } 117 118 bool UnsetEnvironmentVar(const char *variableName) 119 { 120 return (unsetenv(variableName) == 0); 121 } 122 123 bool SetEnvironmentVar(const char *variableName, const char *value) 124 { 125 return (setenv(variableName, value, 1) == 0); 126 } 127 128 std::string GetEnvironmentVar(const char *variableName) 129 { 130 const char *value = getenv(variableName); 131 return (value == nullptr ? std::string() : std::string(value)); 132 } 133 134 const char *GetPathSeparatorForEnvironmentVar() 135 { 136 return ":"; 137 } 138 139 std::string GetModuleDirectoryAndGetError(std::string *errorOut) 140 { 141 std::string directory; 142 static int placeholderSymbol = 0; 143 std::string moduleName = GetModulePath(&placeholderSymbol); 144 if (!moduleName.empty()) 145 { 146 directory = moduleName.substr(0, moduleName.find_last_of('/') + 1); 147 } 148 149 // Ensure we return the full path to the module, not the relative path 150 if (!IsFullPath(directory)) 151 { 152 if (errorOut) 153 { 154 *errorOut += "Directory: '"; 155 *errorOut += directory; 156 *errorOut += "' is not full path"; 157 } 158 Optional<std::string> cwd = GetCWD(); 159 if (cwd.valid()) 160 { 161 directory = ConcatenatePath(cwd.value(), directory); 162 if (errorOut) 163 { 164 *errorOut += ", so it has been modified to: '"; 165 *errorOut += directory; 166 *errorOut += "'. "; 167 } 168 } 169 else if (errorOut) 170 { 171 *errorOut += " and getcwd was invalid. "; 172 } 173 } 174 return directory; 175 } 176 177 std::string GetModuleDirectory() 178 { 179 return GetModuleDirectoryAndGetError(nullptr); 180 } 181 182 void *OpenSystemLibraryWithExtensionAndGetError(const char *libraryName, 183 SearchType searchType, 184 std::string *errorOut) 185 { 186 std::string directory; 187 if (searchType == SearchType::ModuleDir) 188 { 189 #if ANGLE_PLATFORM_IOS 190 // On iOS, shared libraries must be loaded from within the app bundle. 191 directory = GetExecutableDirectory() + "/Frameworks/"; 192 #elif ANGLE_PLATFORM_FUCHSIA 193 // On Fuchsia the dynamic loader always looks up libraries in /pkg/lib 194 // and disallows loading of libraries via absolute paths. 195 directory = ""; 196 #else 197 directory = GetModuleDirectoryAndGetError(errorOut); 198 #endif 199 } 200 201 int extraFlags = 0; 202 if (searchType == SearchType::AlreadyLoaded) 203 { 204 extraFlags = RTLD_NOLOAD; 205 } 206 207 std::string fullPath = directory + libraryName; 208 return OpenPosixLibrary(fullPath, extraFlags, errorOut); 209 } 210 211 void *GetLibrarySymbol(void *libraryHandle, const char *symbolName) 212 { 213 if (!libraryHandle) 214 { 215 return nullptr; 216 } 217 218 return dlsym(libraryHandle, symbolName); 219 } 220 221 std::string GetLibraryPath(void *libraryHandle) 222 { 223 if (!libraryHandle) 224 { 225 return ""; 226 } 227 228 return GetModulePath(libraryHandle); 229 } 230 231 void CloseSystemLibrary(void *libraryHandle) 232 { 233 if (libraryHandle) 234 { 235 dlclose(libraryHandle); 236 } 237 } 238 239 bool IsDirectory(const char *filename) 240 { 241 struct stat st; 242 int result = stat(filename, &st); 243 return result == 0 && ((st.st_mode & S_IFDIR) == S_IFDIR); 244 } 245 246 bool IsDebuggerAttached() 247 { 248 // This could have a fuller implementation. 249 // See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc 250 return false; 251 } 252 253 void BreakDebugger() 254 { 255 // This could have a fuller implementation. 256 // See https://cs.chromium.org/chromium/src/base/debug/debugger_posix.cc 257 abort(); 258 } 259 260 const char *GetExecutableExtension() 261 { 262 return ""; 263 } 264 265 char GetPathSeparator() 266 { 267 return '/'; 268 } 269 270 std::string GetRootDirectory() 271 { 272 return "/"; 273 } 274 275 Optional<std::string> GetTempDirectory() 276 { 277 const char *tmp = getenv("TMPDIR"); 278 if (tmp != nullptr) 279 { 280 return std::string(tmp); 281 } 282 283 #if defined(ANGLE_PLATFORM_ANDROID) 284 // Not used right now in the ANGLE test runner. 285 // return PathService::Get(DIR_CACHE, path); 286 return Optional<std::string>::Invalid(); 287 #else 288 return std::string("/tmp"); 289 #endif 290 } 291 292 Optional<std::string> CreateTemporaryFileInDirectory(const std::string &directory) 293 { 294 std::string tempFileTemplate = directory + "/.angle.XXXXXX"; 295 296 char tempFile[1000]; 297 strcpy(tempFile, tempFileTemplate.c_str()); 298 299 int fd = mkstemp(tempFile); 300 close(fd); 301 302 if (fd != -1) 303 { 304 return std::string(tempFile); 305 } 306 307 return Optional<std::string>::Invalid(); 308 } 309 310 double GetCurrentProcessCpuTime() 311 { 312 #ifdef ANGLE_PLATFORM_FUCHSIA 313 static zx_handle_t me = zx_process_self(); 314 zx_info_task_runtime_t task_runtime; 315 zx_object_get_info(me, ZX_INFO_TASK_RUNTIME, &task_runtime, sizeof(task_runtime), nullptr, 316 nullptr); 317 return static_cast<double>(task_runtime.cpu_time) * 1e-9; 318 #else 319 // We could also have used /proc/stat, but that requires us to read the 320 // filesystem and convert from jiffies. /proc/stat also relies on jiffies 321 // (lower resolution) while getrusage can potentially use a sched_clock() 322 // underneath that has higher resolution. 323 struct rusage usage; 324 getrusage(RUSAGE_SELF, &usage); 325 double userTime = usage.ru_utime.tv_sec + usage.ru_utime.tv_usec * 1e-6; 326 double systemTime = usage.ru_stime.tv_sec + usage.ru_stime.tv_usec * 1e-6; 327 return userTime + systemTime; 328 #endif 329 } 330 331 namespace 332 { 333 bool SetMemoryProtection(uintptr_t start, size_t size, int protections) 334 { 335 int ret = mprotect(reinterpret_cast<void *>(start), size, protections); 336 if (ret < 0) 337 { 338 perror("mprotect failed"); 339 } 340 return ret == 0; 341 } 342 343 class PosixPageFaultHandler : public PageFaultHandler 344 { 345 public: 346 PosixPageFaultHandler(PageFaultCallback callback) : PageFaultHandler(callback) {} 347 ~PosixPageFaultHandler() override {} 348 349 bool enable() override; 350 bool disable() override; 351 void handle(int sig, siginfo_t *info, void *unused); 352 353 private: 354 struct sigaction mDefaultBusAction = {}; 355 struct sigaction mDefaultSegvAction = {}; 356 }; 357 358 PosixPageFaultHandler *gPosixPageFaultHandler = nullptr; 359 void SegfaultHandlerFunction(int sig, siginfo_t *info, void *unused) 360 { 361 gPosixPageFaultHandler->handle(sig, info, unused); 362 } 363 364 void PosixPageFaultHandler::handle(int sig, siginfo_t *info, void *unused) 365 { 366 bool found = false; 367 if ((sig == SIGSEGV || sig == SIGBUS) && 368 (info->si_code == SEGV_ACCERR || info->si_code == SEGV_MAPERR)) 369 { 370 found = mCallback(reinterpret_cast<uintptr_t>(info->si_addr)) == 371 PageFaultHandlerRangeType::InRange; 372 } 373 374 // Fall back to default signal handler 375 if (!found) 376 { 377 if (sig == SIGSEGV) 378 { 379 mDefaultSegvAction.sa_sigaction(sig, info, unused); 380 } 381 else if (sig == SIGBUS) 382 { 383 mDefaultBusAction.sa_sigaction(sig, info, unused); 384 } 385 else 386 { 387 UNREACHABLE(); 388 } 389 } 390 } 391 392 bool PosixPageFaultHandler::disable() 393 { 394 return sigaction(SIGSEGV, &mDefaultSegvAction, nullptr) == 0 && 395 sigaction(SIGBUS, &mDefaultBusAction, nullptr) == 0; 396 } 397 398 bool PosixPageFaultHandler::enable() 399 { 400 struct sigaction sigAction = {}; 401 sigAction.sa_flags = SA_SIGINFO; 402 sigAction.sa_sigaction = &SegfaultHandlerFunction; 403 sigemptyset(&sigAction.sa_mask); 404 405 // Some POSIX implementations use SIGBUS for mprotect faults 406 return sigaction(SIGSEGV, &sigAction, &mDefaultSegvAction) == 0 && 407 sigaction(SIGBUS, &sigAction, &mDefaultBusAction) == 0; 408 } 409 } // namespace 410 411 // Set write protection 412 bool ProtectMemory(uintptr_t start, size_t size) 413 { 414 return SetMemoryProtection(start, size, PROT_READ); 415 } 416 417 // Allow reading and writing 418 bool UnprotectMemory(uintptr_t start, size_t size) 419 { 420 return SetMemoryProtection(start, size, PROT_READ | PROT_WRITE); 421 } 422 423 size_t GetPageSize() 424 { 425 long pageSize = sysconf(_SC_PAGE_SIZE); 426 if (pageSize < 0) 427 { 428 perror("Could not get sysconf page size"); 429 return 0; 430 } 431 return static_cast<size_t>(pageSize); 432 } 433 434 PageFaultHandler *CreatePageFaultHandler(PageFaultCallback callback) 435 { 436 gPosixPageFaultHandler = new PosixPageFaultHandler(callback); 437 return gPosixPageFaultHandler; 438 } 439 440 uint64_t GetProcessMemoryUsageKB() 441 { 442 FILE *file = fopen("/proc/self/status", "r"); 443 444 if (!file) 445 { 446 return 0; 447 } 448 449 const char *kSearchString = "VmRSS:"; 450 constexpr size_t kMaxLineSize = 100; 451 std::array<char, kMaxLineSize> line = {}; 452 453 uint64_t kb = 0; 454 455 while (fgets(line.data(), line.size(), file) != nullptr) 456 { 457 if (strncmp(line.data(), kSearchString, strlen(kSearchString)) == 0) 458 { 459 std::vector<std::string> strings; 460 SplitStringAlongWhitespace(line.data(), &strings); 461 462 sscanf(strings[1].c_str(), "%" SCNu64, &kb); 463 break; 464 } 465 } 466 fclose(file); 467 468 return kb; 469 } 470 } // namespace angle