• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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