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