diff options
author | Jose Fonseca <jfonseca@vmware.com> | 2016-05-05 13:55:29 +0100 |
---|---|---|
committer | Jose Fonseca <jfonseca@vmware.com> | 2016-05-05 13:55:29 +0100 |
commit | efc5d91d983a71e8749fe2201a96dc4ca82f4389 (patch) | |
tree | 2de4654c031e141adb528dda8aed1fe4f6e9909e /lib | |
parent | bceafecaa6a3e606dfc31c0016cecb9e82d5cab8 (diff) |
os: Move to lib.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/CMakeLists.txt | 3 | ||||
-rw-r--r-- | lib/os/CMakeLists.txt | 40 | ||||
-rw-r--r-- | lib/os/os.hpp | 106 | ||||
-rw-r--r-- | lib/os/os_backtrace.cpp | 486 | ||||
-rw-r--r-- | lib/os/os_backtrace.hpp | 44 | ||||
-rw-r--r-- | lib/os/os_binary.hpp | 59 | ||||
-rw-r--r-- | lib/os/os_crtdbg.cpp | 92 | ||||
-rw-r--r-- | lib/os/os_crtdbg.hpp | 57 | ||||
-rw-r--r-- | lib/os/os_dl.hpp | 87 | ||||
-rw-r--r-- | lib/os/os_memory.hpp | 78 | ||||
-rw-r--r-- | lib/os/os_osx.mm | 41 | ||||
-rw-r--r-- | lib/os/os_posix.cpp | 468 | ||||
-rw-r--r-- | lib/os/os_process.hpp | 89 | ||||
-rw-r--r-- | lib/os/os_string.hpp | 437 | ||||
-rw-r--r-- | lib/os/os_thread.hpp | 539 | ||||
-rw-r--r-- | lib/os/os_thread_test.cpp | 161 | ||||
-rw-r--r-- | lib/os/os_time.hpp | 104 | ||||
-rw-r--r-- | lib/os/os_version.hpp | 61 | ||||
-rw-r--r-- | lib/os/os_win32.cpp | 405 | ||||
-rw-r--r-- | lib/os/thread_pool.hpp | 113 |
20 files changed, 3469 insertions, 1 deletions
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 813e180a..23be7605 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,3 +1,4 @@ -add_subdirectory (guids) +add_subdirectory (os) add_subdirectory (highlight) +add_subdirectory (guids) add_subdirectory (image) diff --git a/lib/os/CMakeLists.txt b/lib/os/CMakeLists.txt new file mode 100644 index 00000000..222411e0 --- /dev/null +++ b/lib/os/CMakeLists.txt @@ -0,0 +1,40 @@ +include_directories ( + ${CMAKE_SOURCE_DIR}/thirdparty +) + +if (WIN32) + set (os os_win32.cpp) +elseif (APPLE) + set (os os_posix.cpp os_osx.mm) +else () + set (os os_posix.cpp) +endif () + +add_convenience_library (os + ${os} + os_backtrace.cpp + os_crtdbg.cpp +) + +target_link_libraries (os + ${LIBBACKTRACE_LIBRARIES} +) +if (WIN32) + target_link_libraries (os + shell32 + ) +endif () +if (ANDROID) + target_link_libraries (os + log + ) +endif () + +if (APPLE) + target_link_libraries (os + "-framework Foundation" + ) +endif () + +add_gtest (os_thread_test os_thread_test.cpp) +target_link_libraries (os_thread_test os) diff --git a/lib/os/os.hpp b/lib/os/os.hpp new file mode 100644 index 00000000..b9d3fc21 --- /dev/null +++ b/lib/os/os.hpp @@ -0,0 +1,106 @@ +/************************************************************************** + * + * Copyright 2010 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Simple OS abstraction layer. + */ + +#pragma once + +#include <stdlib.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#ifdef _WIN32 +#ifndef snprintf +#define snprintf _snprintf +#endif +#ifndef vsnprintf +#define vsnprintf _vsnprintf +#endif +#ifndef strcasecmp +#define strcasecmp stricmp +#endif +#endif /* !_WIN32 */ + +namespace os { + +void log(const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif +; + +#if defined _WIN32 || defined __CYGWIN__ + /* We always use .def files on windows for now */ + #if 0 + #define PUBLIC __declspec(dllexport) + #else + #define PUBLIC + #endif + #define PRIVATE +#else + #define PUBLIC __attribute__ ((visibility("default"))) + #define PRIVATE __attribute__ ((visibility("hidden"))) +#endif + +/** + * Exit immediately. + * + * This should be called only from the wrappers, when there is no safe way of + * failing gracefully. + */ +// coverity[+kill] +#ifdef _MSC_VER +__declspec(noreturn) +#endif +void abort(void) +#ifdef __GNUC__ + __attribute__((__noreturn__)) +#endif +; + +void +breakpoint(void); + + +void setExceptionCallback(void (*callback)(void)); +void resetExceptionCallback(void); + +/** + * Returns a pseudo-random integer in the range 0 to RAND_MAX. + */ +static inline int +random(void) { +#ifdef _WIN32 + return ::rand(); +#else + return ::random(); +#endif +} + +} /* namespace os */ + diff --git a/lib/os/os_backtrace.cpp b/lib/os/os_backtrace.cpp new file mode 100644 index 00000000..c95692bd --- /dev/null +++ b/lib/os/os_backtrace.cpp @@ -0,0 +1,486 @@ +/************************************************************************** + * + * Copyright 2013 Samsung + * Contributed by Eugene Velesevich + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Wrapper for platform-specific code for obtaining symbolic backtraces + * on Android and Linux + */ + + +#include "os_backtrace.hpp" + +#include <set> +#include <vector> +#include "os.hpp" + +#if defined(ANDROID) +# include <dlfcn.h> +#elif HAVE_BACKTRACE +# include <stdint.h> +# include <dlfcn.h> +# include <unistd.h> +# include <map> +# include <vector> +# include <cxxabi.h> +# include <backtrace.h> +#endif + + +using trace::Id; + +namespace os { + +/* + * Pascal string (with zero terminator optionally omitted) + * This is a helper class for storing a set of exact strings or prefixes + * to match a zero-terminated string against later. + * Two zero-terminated pstrings compare equal iff they are the same. + * Otherwise, they compare equal iff one is a prefix of the other + * (a zero-terminated pstring cannot be a prefix) + */ + +struct pstring { + const char* s; + int n; + pstring(const char* s, int n) + { + this->s = s; + this->n = n; + } + bool operator<(const pstring q2) const + { + return memcmp(s, q2.s, n < q2.n? n : q2.n) < 0; + } +}; + + +class StringPrefixes { +private: + std::set<pstring> pset; + + void addPrefix(char* startbuf, int n) { + std::set<pstring>::iterator elem = pset.find(pstring(startbuf, n)); + bool replace = elem != pset.end() && n < elem->n; + if (replace) { + pset.erase(elem); + } + if (replace || elem == pset.end()) { + pset.insert(pstring(startbuf, n)); + } + } +public: + StringPrefixes(); + + bool contain(const char* s) { + return pset.find(pstring(s, strlen(s) + 1)) != pset.end(); + } +}; + +StringPrefixes::StringPrefixes() { + char *list = getenv("APITRACE_BACKTRACE"); + if (!list) + return; + for (char *t = strdup(list); ; t = NULL) { + char *tok = strtok(t, " \t\r\n"); + if (!tok) + break; + if (tok[0] == '#') + continue; + if (tok[strlen(tok) - 1] == '*') + addPrefix(tok, strlen(tok) - 1); + else + addPrefix(tok, strlen(tok) + 1); + } +} + + +bool backtrace_is_needed(const char* fname) { + static StringPrefixes backtraceFunctionNamePrefixes; + return backtraceFunctionNamePrefixes.contain(fname); +} + +#if defined(ANDROID) + +/* The following two declarations are copied from Android sources */ + +enum DebugTargetKind { + kDebugTargetUnknown = 0, + kDebugTargetLog, + kDebugTargetFile, +}; + +struct DebugOutputTarget { + DebugTargetKind which; + + union { + struct { + int priority; + const char* tag; + } log; + struct { + FILE* fp; + } file; + } data; +}; + +#define THREAD_SELF_NAME "_Z13dvmThreadSelfv" +#define CREATE_DEBUG_TARGET_NAME "_Z25dvmCreateFileOutputTargetP17DebugOutputTargetP7__sFILE" +#define DUMP_BACKTRACE_NAME "_Z18dvmDumpThreadStackPK17DebugOutputTargetP6Thread" + + +class DalvikBacktraceProvider { +private: + bool errorOccured; + void* (*threadself)(void); + FILE* streamInMemory; + char* buf; + size_t bufSize; + void (*dumpBacktrace)(const DebugOutputTarget*, void*); + DebugOutputTarget debugTarget; + Id nextFrameId; +public: + DalvikBacktraceProvider() { + nextFrameId = 0; + FILE* (*open_memstream_exp)(char**, size_t*); + void (*createDebugTarget)(DebugOutputTarget*, FILE*); + void* handle = dlopen("/system/lib/libdvm.so", 0); + errorOccured = true; + if (!handle) { + os::log("dlopen failed\n"); + return; + } + threadself = (void* (*)())dlsym(handle, THREAD_SELF_NAME); + if (threadself == NULL) { + os::log("dlsym ThreadSelf failed\n"); + return; + } + createDebugTarget = (void (*)(DebugOutputTarget*, FILE*))dlsym(handle, CREATE_DEBUG_TARGET_NAME); + if (createDebugTarget == NULL) { + os::log("dlsym CreateFileOutput failed\n"); + return; + } + dumpBacktrace = (void (*)(const DebugOutputTarget*, void*))dlsym(handle, DUMP_BACKTRACE_NAME); + if (dumpBacktrace == NULL) { + os::log("dlsym DumpThreadStack failed\n"); + return; + } + void* handle2 = dlopen("/system/lib/libcutils.so", 0); + if (!handle2) { + os::log("dlopen failed\n"); + return; + } + open_memstream_exp = (FILE* (*)(char**, size_t*))dlsym(handle2, "open_memstream"); + if (open_memstream_exp == NULL) { + os::log("dlsym open_memstream failed\n"); + return; + } + streamInMemory = open_memstream_exp(&buf, &bufSize); + if (streamInMemory == NULL) { + os::log("open_memstream failed\n"); + return; + } + createDebugTarget(&debugTarget, streamInMemory); + errorOccured = false; + } + + inline char* getBacktrace() { + if (errorOccured) { + return NULL; + } + rewind(streamInMemory); + dumpBacktrace(&debugTarget, threadself()); + fflush(streamInMemory); + return buf; + } +/* + * Parse a stack frame, expecting: + * " at android.view.HardwareRenderer$GlRenderer.initializeEgl(HardwareRenderer.java:547)" + * or + * " at android.view.HardwareRenderer$GlRenderer.initializeEgl(Native Method)" + */ + std::vector<RawStackFrame> parseBacktrace(char *rawBacktrace) { + std::vector<RawStackFrame> parsedBacktrace; + char* rawBacktrace_it = rawBacktrace; + while (*rawBacktrace_it != '\0') { + RawStackFrame stackFrame; + // TODO: Keep a cache of stack frames + stackFrame.id = nextFrameId++; + /* skip leading space */ + while (*rawBacktrace_it == ' ') { + rawBacktrace_it++; + } + /* Skip "at " */ + rawBacktrace_it += 3; + stackFrame.function = rawBacktrace_it; + while (*rawBacktrace_it != '(') { + rawBacktrace_it++; + } + *rawBacktrace_it = '\0'; + stackFrame.filename = rawBacktrace_it + 1; + while (*rawBacktrace_it != ':' && *rawBacktrace_it != ')') { + rawBacktrace_it++; + } + if (*rawBacktrace_it == ':') { + const char *linenumber = rawBacktrace_it + 1; + *rawBacktrace_it = '\0'; + while (*rawBacktrace_it != ')') { + rawBacktrace_it++; + } + *rawBacktrace_it = '\0'; + rawBacktrace_it++; + stackFrame.linenumber = atoi(linenumber); + } + else { + stackFrame.filename = NULL; + while (*rawBacktrace_it != '\n') { + rawBacktrace_it++; + } + } + while (*rawBacktrace_it == '\n' || *rawBacktrace_it == ' ') { + rawBacktrace_it++; + } + parsedBacktrace.push_back(stackFrame); /* module */ + } + return parsedBacktrace; + } +}; + +std::vector<RawStackFrame> get_backtrace() { + static DalvikBacktraceProvider backtraceProvider; + return backtraceProvider.parseBacktrace(backtraceProvider.getBacktrace()); +} + +void dump_backtrace() { + /* TODO */ +} + + +#elif HAVE_BACKTRACE + + +static char* format(uintptr_t num, int base, char *buf, int maxlen) +{ + static const char digits[] = "0123456789abcdef"; + buf += maxlen; + do { + *--buf = digits[num % base]; + num /= base; + maxlen--; + } while (num != 0 && maxlen != 0); + return buf; +} + +static void dump(const char *str, int len) +{ + static int fd = dup(STDERR_FILENO); + if (write(fd, str, len) != len) { + // Do nothing + } +} + +static void dumpFrame(const RawStackFrame &frame) +{ + char buf[sizeof(long long) * 2], *p; +#define DUMP(string) dump(string, strlen(string)) + DUMP(frame.module ? frame.module : "?"); + if (frame.function) { + DUMP(": "); + DUMP(frame.function); + } + if (frame.offset >= 0) { + DUMP("+0x"); + p = format((uintptr_t) frame.offset, 16, buf, sizeof(buf)); + dump(p, buf + sizeof(buf) - p); + } + if (frame.filename) { + DUMP(": "); + DUMP(frame.filename); + if (frame.linenumber >= 0) { + DUMP(":"); + p = format((uintptr_t) frame.linenumber, 10, buf, sizeof(buf)); + dump(p, buf + sizeof(buf) - p); + } + } + DUMP("\n"); +#undef DUMP +} + + +#define BT_DEPTH 10 + +class libbacktraceProvider { + struct backtrace_state *state; + int skipFrames; + Id nextFrameId; + std::map<uintptr_t, std::vector<RawStackFrame> > cache; + std::vector<RawStackFrame> *current, *current_frames; + RawStackFrame *current_frame; + bool missingDwarf; + + static void bt_err_callback(void *vdata, const char *msg, int errnum) + { + libbacktraceProvider *this_ = (libbacktraceProvider*)vdata; + if (errnum == -1) + this_->missingDwarf = true; + else if (errnum) + os::log("libbacktrace: %s: %s\n", msg, strerror(errnum)); + else + os::log("libbacktrace: %s\n", msg); + } + + static int bt_countskip(void *vdata, uintptr_t pc) + { + libbacktraceProvider *this_ = (libbacktraceProvider*)vdata; + Dl_info info1, info2; + if (!dladdr((void*)bt_countskip, &info2)) { + os::log("dladdr failed, cannot cull stack traces\n"); + return 1; + } + if (!dladdr((void*)pc, &info1)) + return 1; + if (info1.dli_fbase != info2.dli_fbase) + return 1; + this_->skipFrames++; + return 0; + } + + static int bt_full_callback(void *vdata, uintptr_t pc, + const char *file, int line, const char *func) + { + libbacktraceProvider *this_ = (libbacktraceProvider*)vdata; + RawStackFrame frame = *this_->current_frame; + frame.id = this_->nextFrameId++; + frame.filename = file; + frame.linenumber = line; + if (func) + frame.function = func; + int status; + if (func && (func = abi::__cxa_demangle(func, NULL, NULL, &status))) + frame.function = func; + this_->current_frames->push_back(frame); + return 0; + } + + static void dl_fill(RawStackFrame *frame, uintptr_t pc) + { + Dl_info info = {0}; + dladdr((void*)pc, &info); + frame->module = info.dli_fname; + frame->function = info.dli_sname; + frame->offset = info.dli_saddr ? pc - (uintptr_t)info.dli_saddr + : pc - (uintptr_t)info.dli_fbase; + } + + static int bt_callback(void *vdata, uintptr_t pc) + { + libbacktraceProvider *this_ = (libbacktraceProvider*)vdata; + std::vector<RawStackFrame> &frames = this_->cache[pc]; + if (!frames.size()) { + RawStackFrame frame; + dl_fill(&frame, pc); + this_->current_frame = &frame; + this_->current_frames = &frames; + backtrace_pcinfo(this_->state, pc, bt_full_callback, bt_err_callback, vdata); + if (!frames.size()) { + frame.id = this_->nextFrameId++; + frames.push_back(frame); + } + } + this_->current->insert(this_->current->end(), frames.begin(), frames.end()); + return this_->current->size() >= BT_DEPTH; + } + + static int bt_full_dump_callback(void *vdata, uintptr_t pc, + const char *file, int line, const char *func) + { + libbacktraceProvider *this_ = (libbacktraceProvider*)vdata; + RawStackFrame *frame = this_->current_frame; + frame->filename = file; + frame->linenumber = line; + if (func) + frame->function = func; + dumpFrame(*frame); + return 0; + } + + static int bt_dump_callback(void *vdata, uintptr_t pc) + { + libbacktraceProvider *this_ = (libbacktraceProvider*)vdata; + RawStackFrame frame; + dl_fill(&frame, pc); + this_->current_frame = &frame; + this_->missingDwarf = false; + backtrace_pcinfo(this_->state, pc, bt_full_dump_callback, bt_err_callback, vdata); + if (this_->missingDwarf) + dumpFrame(frame); + return 0; + } + +public: + libbacktraceProvider(): + state(backtrace_create_state(NULL, 0, bt_err_callback, NULL)) + { + backtrace_simple(state, 0, bt_countskip, bt_err_callback, this); + } + + std::vector<RawStackFrame> getParsedBacktrace() + { + std::vector<RawStackFrame> parsedBacktrace; + current = &parsedBacktrace; + backtrace_simple(state, skipFrames, bt_callback, bt_err_callback, this); + return parsedBacktrace; + } + + void dumpBacktrace() + { + backtrace_simple(state, 0, bt_dump_callback, bt_err_callback, this); + } +}; + +std::vector<RawStackFrame> get_backtrace() { + static libbacktraceProvider backtraceProvider; + return backtraceProvider.getParsedBacktrace(); +} + +void dump_backtrace() { + static libbacktraceProvider backtraceProvider; + backtraceProvider.dumpBacktrace(); +} + + +#else /* !HAVE_BACKTRACE */ + +std::vector<RawStackFrame> get_backtrace() { + return std::vector<RawStackFrame>(); +} + +void dump_backtrace() { +} + +#endif + + +} /* namespace os */ diff --git a/lib/os/os_backtrace.hpp b/lib/os/os_backtrace.hpp new file mode 100644 index 00000000..72869c26 --- /dev/null +++ b/lib/os/os_backtrace.hpp @@ -0,0 +1,44 @@ +/************************************************************************** + * + * Copyright 2013 Samsung + * Contributed by Eugene Velesevich + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +#pragma once + +#include <vector> + +#include "trace_model.hpp" + +namespace os { + +using trace::RawStackFrame; + + +std::vector<RawStackFrame> get_backtrace(); +bool backtrace_is_needed(const char* fname); + +void dump_backtrace(); + + +} /* namespace os */ diff --git a/lib/os/os_binary.hpp b/lib/os/os_binary.hpp new file mode 100644 index 00000000..c3a1d6ea --- /dev/null +++ b/lib/os/os_binary.hpp @@ -0,0 +1,59 @@ +/************************************************************************** + * + * Copyright 2011 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Force binary mode standard files on Windows. + */ + +#pragma once + + +#include <stdio.h> + +#ifdef _WIN32 +#include <assert.h> + +#include <fcntl.h> +#include <io.h> +#endif + + +namespace os { + + +void setBinaryMode(FILE *fp) { +#ifdef _WIN32 + fflush(fp); + int mode = _setmode(_fileno(fp), _O_BINARY); + assert(mode != -1); + (void)mode; +#else + (void)fp; +#endif +} + + +} /* namespace os */ + diff --git a/lib/os/os_crtdbg.cpp b/lib/os/os_crtdbg.cpp new file mode 100644 index 00000000..21dec6ec --- /dev/null +++ b/lib/os/os_crtdbg.cpp @@ -0,0 +1,92 @@ +/************************************************************************** + * + * Copyright 2015 VMware, Inc + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#include "os_crtdbg.hpp" + +#include <assert.h> +#include <stdio.h> + +#ifdef _WIN32 +#include <windows.h> +#include <crtdbg.h> +#endif + +#include "os.hpp" + + +namespace os { + + +#ifdef _WIN32 + +static void +invalidParameterHandler(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t pReserved) +{ + fprintf(stderr, "Invalid parameter detected in function %S. File: %S Line: %d\n", function, file, line); + fprintf(stderr, "Expression: %S\n", expression); + if (IsDebuggerPresent()) { + os::breakpoint(); + } + os::abort(); +} + +#endif // _WIN32 + + +void +setDebugOutput(Output output) +{ + +#ifdef _WIN32 + // Disable assertion failure message box + // http://msdn.microsoft.com/en-us/library/sas1dkb2.aspx + _set_error_mode(_OUT_TO_STDERR); +#ifdef _MSC_VER + // Disable abort message box + // http://msdn.microsoft.com/en-us/library/e631wekh.aspx + _set_abort_behavior(0, _WRITE_ABORT_MSG); + // Direct debug reports to stderr + // https://msdn.microsoft.com/en-us/library/1y71x448.aspx + for (int reportType = 0; reportType < _CRT_ERRCNT; ++reportType) { + _CrtSetReportMode(reportType, _CRTDBG_MODE_FILE); + _CrtSetReportFile(reportType, _CRTDBG_FILE_STDERR); + } +#endif /* _MSC_VER */ + // Set our own invalid_parameter handler + // https://msdn.microsoft.com/en-us/library/a9yf33zb.aspx + _set_invalid_parameter_handler(invalidParameterHandler); +#endif /* _WIN32 */ + + + assert(output == OUTPUT_STDERR); +} + + +} /* namespace os */ diff --git a/lib/os/os_crtdbg.hpp b/lib/os/os_crtdbg.hpp new file mode 100644 index 00000000..87a018ba --- /dev/null +++ b/lib/os/os_crtdbg.hpp @@ -0,0 +1,57 @@ +/************************************************************************** + * + * Copyright 2015 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Abstrations to force where CRT debug output should go. + */ + +#pragma once + + +#include <stdio.h> + +#ifdef _WIN32 +#include <assert.h> + +#include <fcntl.h> +#include <io.h> +#endif + + +namespace os { + + +enum Output { + OUTPUT_STDERR, + OUTPUT_OUTDBG +}; + + +void +setDebugOutput(Output output); + + +} /* namespace os */ + diff --git a/lib/os/os_dl.hpp b/lib/os/os_dl.hpp new file mode 100644 index 00000000..bf79d507 --- /dev/null +++ b/lib/os/os_dl.hpp @@ -0,0 +1,87 @@ +/************************************************************************** + * + * Copyright 2012 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Dynamic library linking abstraction. + */ + +#pragma once + + +#if defined(_WIN32) +#include <windows.h> +#else +#include <dlfcn.h> +#endif + + +#if defined(_WIN32) +#define OS_LIBRARY_EXTENSION ".dll" +#elif defined(__APPLE__) +#define OS_LIBRARY_EXTENSION ".dylib" +#else +#define OS_LIBRARY_EXTENSION ".so" +#endif + + +namespace os { + + // TODO: Wrap in a class +#if defined(_WIN32) + typedef HMODULE Library; +#else + typedef void * Library; +#endif + + inline Library + openLibrary(const char *filename) { +#if defined(_WIN32) + return LoadLibraryA(filename); +#else + return dlopen(filename, RTLD_LOCAL | RTLD_LAZY); +#endif + } + + inline void * + getLibrarySymbol(Library library, const char *symbol) { +#if defined(_WIN32) + return (void *)GetProcAddress(library, symbol); +#else + return dlsym(library, symbol); +#endif + } + + inline void + closeLibrary(Library library) { +#if defined(_WIN32) + FreeLibrary(library); +#else + dlclose(library); +#endif + } + + +} /* namespace os */ + diff --git a/lib/os/os_memory.hpp b/lib/os/os_memory.hpp new file mode 100644 index 00000000..5a852516 --- /dev/null +++ b/lib/os/os_memory.hpp @@ -0,0 +1,78 @@ +/************************************************************************** + * + * Copyright (C) 2013 Intel Corporation. All rights reversed. + * Author: Shuang He <shuang.he@intel.com> + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Simple OS time measurement abstraction. + */ + +#pragma once + +#ifdef HAVE_READPROC_H +#include <proc/readproc.h> +#endif + +namespace os { + +#if defined(HAVE_READPROC_H) + + inline long long + getVsize(void) { + proc_t proc; + look_up_our_self(&proc); + return proc.vsize; + } + + inline long long + getRss(void) { + proc_t proc; + look_up_our_self(&proc); + return proc.rss; + } + +#elif defined(__ANDROID__) + + long long + getVsize(void); + + long long + getRss(void); + +#else + + inline long long + getVsize(void) { + return 0; + } + + inline long long + getRss(void) { + return 0; + } + +#endif + +} /* namespace os */ + diff --git a/lib/os/os_osx.mm b/lib/os/os_osx.mm new file mode 100644 index 00000000..9901708e --- /dev/null +++ b/lib/os/os_osx.mm @@ -0,0 +1,41 @@ +/************************************************************************** + * + * Copyright 2016 VMware, Inc. + * Copyright 2016 Rolan Reznik + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#include "os_string.hpp" + +#include <Foundation/Foundation.h> + +namespace os { + +String +getTemporaryDirectoryPath(void) { + NSString *tempDir = NSTemporaryDirectory(); + String result([tempDir fileSystemRepresentation]); + return result; +} + +} diff --git a/lib/os/os_posix.cpp b/lib/os/os_posix.cpp new file mode 100644 index 00000000..74594c3f --- /dev/null +++ b/lib/os/os_posix.cpp @@ -0,0 +1,468 @@ +/************************************************************************** + * + * Copyright 2010-2011 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +#ifndef _WIN32 + +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <unistd.h> +#include <errno.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <pwd.h> +#include <fcntl.h> +#include <signal.h> + +#if defined(__linux__) +#include <linux/limits.h> // PATH_MAX +#endif + +#ifdef __APPLE__ +#include <sys/syslimits.h> // PATH_MAX +#include <mach-o/dyld.h> +#endif + +#ifdef ANDROID +#include <android/log.h> +#endif + +#ifndef PATH_MAX +#warning PATH_MAX undefined +#define PATH_MAX 4096 +#endif + +#ifdef __QNXNTO__ +#define SA_RESTART 0 // QNX does not have SA_RESTART +#endif + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# include <sanitizer/asan_interface.h> +#endif + +#include "os.hpp" +#include "os_string.hpp" +#include "os_backtrace.hpp" + + +namespace os { + + +String +getProcessName(void) +{ + String path; + size_t size = PATH_MAX; + char *buf = path.buf(size); + + // http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe +#ifdef __APPLE__ + uint32_t len = size; + if (_NSGetExecutablePath(buf, &len) != 0) { + // grow buf and retry + buf = path.buf(len); + _NSGetExecutablePath(buf, &len); + } + len = strlen(buf); +#else + ssize_t len; + +#ifdef ANDROID + // On Android, we are almost always interested in the actual process title + // rather than path to the VM kick-off executable + // (/system/bin/app_process). + len = 0; +#else + len = readlink("/proc/self/exe", buf, size - 1); +#endif + + if (len <= 0) { + // /proc/self/exe is not available on setuid processes, so fallback to + // /proc/self/cmdline. + int fd = open("/proc/self/cmdline", O_RDONLY); + if (fd >= 0) { + // buf already includes trailing zero + len = read(fd, buf, size); + close(fd); + if (len >= 0) { + len = strlen(buf); + } + } + } + +#ifdef __GLIBC__ + // fallback to `program_invocation_name` + if (len <= 0) { + len = strlen(program_invocation_name); + buf = path.buf(len + 1); + strcpy(buf, program_invocation_name); + } +#endif + + // fallback to process ID + if (len <= 0) { + len = snprintf(buf, size, "%i", (int)getpid()); + if (len >= size) { + len = size - 1; + } + } +#endif + path.truncate(len); + + return path; +} + +String +getCurrentDir(void) +{ + String path; + size_t size = PATH_MAX; + char *buf = path.buf(size); + + if (getcwd(buf, size)) { + buf[size - 1] = 0; + path.truncate(); + } else { + path.truncate(0); + } + + return path; +} + +String +getConfigDir(void) +{ + String path; + +#ifdef __APPLE__ + // Library/Preferences + const char *homeDir = getenv("HOME"); + assert(homeDir); + if (homeDir) { + path = homeDir; + path.join("Library/Preferences"); + } +#else + // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + const char *configHomeDir = getenv("XDG_CONFIG_HOME"); + if (configHomeDir) { + path = configHomeDir; + } else { + const char *homeDir = getenv("HOME"); + if (!homeDir) { + struct passwd *user = getpwuid(getuid()); + if (user != NULL) { + homeDir = user->pw_dir; + } + } + assert(homeDir); + if (homeDir) { + path = homeDir; +#if !defined(ANDROID) + path.join(".config"); +#endif + } + } +#endif + + return path; +} + +bool +createDirectory(const String &path) +{ + return mkdir(path, 0777) == 0; +} + +bool +String::exists(void) const +{ + struct stat st; + int err; + + err = stat(str(), &st); + if (err) { + return false; + } + + return true; +} + +int execute(char * const * args) +{ + pid_t pid = fork(); + if (pid == 0) { + // child + execvp(args[0], args); + fprintf(stderr, "error: failed to execute:"); + for (unsigned i = 0; args[i]; ++i) { + fprintf(stderr, " %s", args[i]); + } + fprintf(stderr, "\n"); + exit(-1); + } else { + // parent + if (pid == -1) { + fprintf(stderr, "error: failed to fork\n"); + return -1; + } + int status = -1; + int ret; + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + ret = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + // match shell return code + ret = WTERMSIG(status) + 128; + } else { + ret = 128; + } + return ret; + } +} + +static volatile bool logging = false; + +#ifndef HAVE_EXTERNAL_OS_LOG +void +log(const char *format, ...) +{ + logging = true; + va_list ap; + va_start(ap, format); + fflush(stdout); +#ifdef ANDROID + __android_log_vprint(ANDROID_LOG_DEBUG, "apitrace", format, ap); +#else + static FILE *log = NULL; + if (!log) { + // Duplicate stderr file descriptor, to prevent applications from + // redirecting our debug messages to somewhere else. + // + // Another alternative would be to log to /dev/tty when available. + log = fdopen(dup(STDERR_FILENO), "at"); + } + vfprintf(log, format, ap); + fflush(log); +#endif + va_end(ap); + logging = false; +} +#endif /* !HAVE_EXTERNAL_OS_LOG */ + +#if defined(__APPLE__) +long long timeFrequency = 0LL; +#endif + +void +abort(void) +{ + _exit(1); +} + + +void +breakpoint(void) +{ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + asm("int3"); +#else + kill(getpid(), SIGTRAP); +#endif +} + + +static void (*gCallback)(void) = NULL; + +#define NUM_SIGNALS 16 + +struct sigaction old_actions[NUM_SIGNALS]; + + +/* + * See also: + * - http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c + * - http://ggi.cvs.sourceforge.net/viewvc/ggi/ggi-core/libgg/gg/cleanup.c?view=markup + */ +static void +signalHandler(int sig, siginfo_t *info, void *context) +{ + /* + * There are several signals that can happen when logging to stdout/stderr. + * For example, SIGPIPE will be emitted if stderr is a pipe with no + * readers. Therefore ignore any signal while logging by returning + * immediately, to prevent deadlocks. + */ + if (logging) { + return; + } + + static int recursion_count = 0; + + log("apitrace: warning: caught signal %i\n", sig); + + if (recursion_count) { + log("apitrace: warning: recursion handling signal %i\n", sig); + } else { + ++recursion_count; + if (gCallback) + gCallback(); + os::dump_backtrace(); + --recursion_count; + } + + struct sigaction *old_action; + if (sig >= NUM_SIGNALS) { + /* This should never happen */ + log("error: unexpected signal %i\n", sig); + raise(SIGKILL); + } + old_action = &old_actions[sig]; + + if (old_action->sa_flags & SA_SIGINFO) { + // Handler is in sa_sigaction + old_action->sa_sigaction(sig, info, context); + } else { + if (old_action->sa_handler == SIG_DFL) { + log("apitrace: info: taking default action for signal %i\n", sig); + +#if 1 + struct sigaction dfl_action; + dfl_action.sa_handler = SIG_DFL; + sigemptyset (&dfl_action.sa_mask); + dfl_action.sa_flags = 0; + sigaction(sig, &dfl_action, NULL); + + raise(sig); +#else + raise(SIGKILL); +#endif + } else if (old_action->sa_handler == SIG_IGN) { + /* ignore */ + } else { + /* dispatch to handler */ + old_action->sa_handler(sig); + } + } +} + +void +setExceptionCallback(void (*callback)(void)) +{ + assert(!gCallback); + if (!gCallback) { + gCallback = callback; + + struct sigaction new_action; + new_action.sa_sigaction = signalHandler; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO | SA_RESTART; + + + for (int sig = 1; sig < NUM_SIGNALS; ++sig) { + // SIGKILL and SIGSTOP can't be handled. + if (sig == SIGKILL || sig == SIGSTOP) { + continue; + } + + /* + * SIGPIPE can be emitted when writing to stderr that is redirected + * to a pipe without readers. It is also very unlikely to ocurr + * inside graphics APIs, and most applications where it can occur + * normally already ignore it. In summary, it is unlikely that a + * SIGPIPE will cause abnormal termination, which it is likely that + * intercepting here will cause problems, so simple don't intercept + * it here. + */ + if (sig == SIGPIPE) { + continue; + } + + if (sigaction(sig, NULL, &old_actions[sig]) >= 0) { + sigaction(sig, &new_action, NULL); + } + } + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + __asan_set_death_callback(callback); +#endif + } +} + +void +resetExceptionCallback(void) +{ + gCallback = NULL; +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + __asan_set_death_callback(NULL); +#endif +} + +#ifdef __ANDROID__ +#include "os_memory.hpp" +#include <cassert> +#include <cstdio> + +#include <fcntl.h> +#include <unistd.h> + +char statmBuff[256]; +static __uint64_t pageSize = sysconf(_SC_PAGESIZE); + +static long size, resident; + +static inline void parseStatm() +{ + int fd = open("/proc/self/statm", O_RDONLY, 0); + int sz = read(fd, statmBuff, 255); + close(fd); + statmBuff[sz] = 0; + sz = sscanf(statmBuff, "%ld %ld", + &size, &resident); + assert(sz == 2); +} + +long long getVsize() +{ + parseStatm(); + return pageSize * size; +} + +long long getRss() +{ + parseStatm(); + return pageSize * resident; +} +#endif + +} /* namespace os */ + +#endif // !defined(_WIN32) diff --git a/lib/os/os_process.hpp b/lib/os/os_process.hpp new file mode 100644 index 00000000..880242f8 --- /dev/null +++ b/lib/os/os_process.hpp @@ -0,0 +1,89 @@ +/************************************************************************** + * + * Copyright 2011 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Sub-process abstraction. + */ + +#pragma once + + +#ifdef _WIN32 +#include <windows.h> +#else +#include <sys/types.h> +#include <unistd.h> +#endif + +#include "os.hpp" + + +namespace os { + + +typedef +#ifdef _WIN32 + DWORD +#else + pid_t +#endif +ProcessId; + + +static inline ProcessId +getCurrentProcessId(void) { +#ifdef _WIN32 + return GetCurrentProcessId(); +#else + return getpid(); +#endif +} + + +static inline void +setEnvironment(const char *name, const char *value) { +#ifdef _WIN32 + SetEnvironmentVariableA(name, value); +#else + setenv(name, value, 1); +#endif +} + + +static inline void +unsetEnvironment(const char *name) { +#ifdef _WIN32 + SetEnvironmentVariableA(name, NULL); +#else + unsetenv(name); +#endif +} + + +int execute(char * const * args); + + +} /* namespace os */ + diff --git a/lib/os/os_string.hpp b/lib/os/os_string.hpp new file mode 100644 index 00000000..2eb797bd --- /dev/null +++ b/lib/os/os_string.hpp @@ -0,0 +1,437 @@ +/************************************************************************** + * + * Copyright 2011 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * String manipulation. + */ + +#pragma once + + +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stddef.h> + +#ifdef __MINGW32__ +// Some versions of MinGW are missing _vscprintf's declaration, although they +// still provide the symbol in the import library. +extern "C" _CRTIMP int _vscprintf(const char *format, va_list argptr); +#endif + +#ifndef va_copy +#ifdef __va_copy +#define va_copy(dest, src) __va_copy((dest), (src)) +#else +#define va_copy(dest, src) (dest) = (src) +#endif +#endif + +#include <vector> + +#include "os.hpp" + + +#ifdef _WIN32 +#define OS_DIR_SEP '\\' +#define OS_PATH_SEP ';' +#else /* !_WIN32 */ +#define OS_DIR_SEP '/' +#define OS_PATH_SEP ':' +#endif /* !_WIN32 */ + + +namespace os { + + +/** + * Class to represent zero-terminated strings, based upon std::vector<char>, + * suitable for passing strings or paths to/from OS calls. + * + * Both Win32 and POSIX APIs return strings as zero length buffers. Although + * std::string provides an easy method to obtain a read-only pointer to a zero + * terminated string, it lacks the ability to return a read-write pointer. So + * there is no way to tell OS calls to write into a std::string directly -- a + * temporary malloc'ed string would be necessary --, which would be + * unnecessarily inefficient, specially considering that these strings would + * ultimately passed back to the OS, which would again expect zero-terminated + * strings. + * + * This class is not, however, a full replacement for std::string, which should + * be otherwise used whenever possible. + */ +class String { +protected: + typedef std::vector<char> Buffer; + + /** + * The buffer's last element is always the '\0' character, therefore the + * buffer must never be empty. + */ + Buffer buffer; + + Buffer::iterator find(char c) { + Buffer::iterator it = buffer.begin(); + assert(it != buffer.end()); + while (it != buffer.end()) { + if (*it == c) { + return it; + } + ++it; + } + return buffer.end(); + } + + Buffer::iterator rfind(char c) { + Buffer::iterator it = buffer.end(); + + // Skip trailing '\0' + assert(it != buffer.begin()); + --it; + assert(*it == '\0'); + + while (it != buffer.begin()) { + --it; + if (*it == c) { + return it; + } + } + + return buffer.end(); + } + + String(size_t size) : + buffer(size) { + } + + char *buf(void) { + return &buffer[0]; + } + + inline bool + isSep(char c) { + if (c == '/') { + return true; + } +#ifdef _WIN32 + if (c == '\\') { + return true; + } +#endif + return false; + } + +public: + + Buffer::iterator rfindSep(bool skipTrailing = true) { + Buffer::iterator it = end(); + + // Skip trailing separators + if (skipTrailing) { + while (it != buffer.begin()) { + --it; + if (isSep(*it)) { + // Halt if find the root + if (it == buffer.begin()) { + return it; + } + } else { + break; + } + } + } + + // Advance to the last separator + while (it != buffer.begin()) { + --it; + if (isSep(*it)) { + return it; + } + } + + return end(); + } + + /* + * Constructors + */ + + String(void) { + buffer.push_back(0); + } + + String(const char *s) : + buffer(s, s + strlen(s) + 1) + {} + + String(const String &other) : + buffer(other.buffer) + {} + + template <class InputIterator> + String(InputIterator first, InputIterator last) : + buffer(first, last) + { + buffer.push_back(0); + } + + /** + * From a printf-like format string + */ + static String + format(const char *format, ...) +#ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) +#endif + { + + va_list args; + + va_start(args, format); + + int length; + va_list args_copy; + va_copy(args_copy, args); +#ifdef _WIN32 + /* We need to use _vscprintf to calculate the length as vsnprintf returns -1 + * if the number of characters to write is greater than count. + */ + length = _vscprintf(format, args_copy); +#else + char dummy; + length = vsnprintf(&dummy, sizeof dummy, format, args_copy); +#endif + va_end(args_copy); + + assert(length >= 0); + size_t size = length + 1; + + String path(size); + + va_start(args, format); + vsnprintf(path.buf(), size, format, args); + va_end(args); + + return path; + } + + /* + * Conversion to ordinary C strings. + */ + + const char *str(void) const { + assert(buffer.back() == 0); + return &buffer[0]; + } + + operator const char *(void) const { + return str(); + } + + /* + * Iterators + */ + + typedef Buffer::const_iterator const_iterator; + typedef Buffer::iterator iterator; + + const_iterator begin(void) const { + return buffer.begin(); + } + + iterator begin(void) { + return buffer.begin(); + } + + const_iterator end(void) const { + const_iterator it = buffer.end(); + assert(it != buffer.begin()); + --it; // skip null + return it; + } + + iterator end(void) { + iterator it = buffer.end(); + assert(it != buffer.begin()); + --it; // skip null + return it; + } + + /* + * Operations + */ + + void insert(iterator position, char c) { + buffer.insert(position, c); + } + + template <class InputIterator> + void insert(iterator position, InputIterator first, InputIterator last) { + buffer.insert(position, first, last); + } + + void insert(iterator position, const char *s) { + assert(s); + insert(position, s, s + strlen(s)); + } + + void insert(iterator position, const String & other) { + insert(position, other.begin(), other.end()); + } + + void append(char c) { + insert(end(), c); + } + + template <class InputIterator> + void append(InputIterator first, InputIterator last) { + insert(end(), first, last); + } + + void append(const char *s) { + insert(end(), s); + } + + void append(const String & other) { + insert(end(), other); + } + + template <class InputIterator> + void erase(InputIterator first, InputIterator last) { + buffer.erase(first, last); + } + + /** + * Get a writable buffer with the specified size. + * + * truncate() must be called after the buffer is written, and before any other + * method is called. + * + * Between the call to buf() and truncate() methods, the `buffer.back() == + * 0` invariant will not hold true. + */ + char *buf(size_t size) { + buffer.resize(size); + return &buffer[0]; + } + + size_t length(void) const { + size_t size = buffer.size(); + assert(size > 0); + assert(buffer[size - 1] == 0); + return size - 1; + } + + /** + * Truncate the string to the specified length. + */ + void truncate(size_t length) { + assert(length < buffer.size()); + buffer[length] = 0; + assert(strlen(&buffer[0]) == length); + buffer.resize(length + 1); + } + + /** + * Truncate the string to the first zero character. + */ + void truncate(void) { + truncate(strlen(&buffer[0])); + } + + + /* + * Path manipulation + */ + + bool + exists(void) const; + + /* Trim directory (leaving base filename). + */ + void trimDirectory(void) { + iterator sep = rfindSep(); + if (sep != end()) { + buffer.erase(buffer.begin(), sep + 1); + } + } + + /* Trim filename component (leaving containing directory). + * + * - trailing separators are ignored + * - a path with no separator at all yields "." + * - a path consisting of just the root directory is left unchanged + */ + void trimFilename(void) { + iterator sep = rfindSep(); + + // No separator found, so return '.' + if (sep == end()) { + buffer.resize(2); + buffer[0] = '.'; + buffer[1] = 0; + return; + } + + // Root. Nothing to do. + if (sep == buffer.begin()) { + return; + } + + // Trim filename + buffer.erase(sep, end()); + } + + void trimExtension(void) { + iterator dot = rfind('.'); + if (dot != buffer.end()) { + buffer.erase(dot, end()); + } + } + + void join(const String & other) { + if (length() && end()[-1] != OS_DIR_SEP) { + append(OS_DIR_SEP); + } + append(other.begin(), other.end()); + } +}; + + +String getProcessName(); +String getCurrentDir(); + +String getConfigDir(); + +bool createDirectory(const String &path); + +bool copyFile(const String &srcFileName, const String &dstFileName, bool override = true); + +bool removeFile(const String &fileName); + +String getTemporaryDirectoryPath(void); + +} /* namespace os */ + diff --git a/lib/os/os_thread.hpp b/lib/os/os_thread.hpp new file mode 100644 index 00000000..fa080b91 --- /dev/null +++ b/lib/os/os_thread.hpp @@ -0,0 +1,539 @@ +/************************************************************************** + * + * Copyright 2011-2012 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * OS native thread abstraction. + * + * Mimics/leverages C++11 threads. + */ + +#pragma once + + +/* XXX: We still use our own implementation: + * + * - MSVC's C++11 threads implementation are hardcoded to use C++ exceptions + * + * - MinGW's C++11 threads implementation is often either missing or relies on + * winpthreads + */ + +#if !defined(_WIN32) +#define HAVE_CXX11_THREADS +#endif + + +#ifdef HAVE_CXX11_THREADS + +#include <thread> +#include <mutex> +#include <condition_variable> + +namespace os { + + using std::mutex; + using std::recursive_mutex; + using std::unique_lock; + using std::condition_variable; + using std::thread; + +} /* namespace os */ + + +#else /* !HAVE_CXX11_THREADS */ + + +#include <assert.h> +#ifdef _WIN32 +# include <process.h> +# include <windows.h> +# if _WIN32_WINNT >= 0x0600 +# define HAVE_WIN32_CONDITION_VARIABLES +# endif +#else +# include <pthread.h> +# include <unistd.h> +#endif + +#include <functional> + + +namespace os { + + + /** + * Base class for mutex and recursive_mutex. + */ + class _base_mutex + { + public: +#ifdef _WIN32 + typedef CRITICAL_SECTION native_handle_type; +#else + typedef pthread_mutex_t native_handle_type; +#endif + + protected: + _base_mutex(void) { + } + + public: + ~_base_mutex() { +#ifdef _WIN32 + DeleteCriticalSection(&_native_handle); +#else + pthread_mutex_destroy(&_native_handle); +#endif + } + + inline void + lock(void) { +#ifdef _WIN32 + EnterCriticalSection(&_native_handle); +#else + pthread_mutex_lock(&_native_handle); +#endif + } + + inline void + unlock(void) { +#ifdef _WIN32 + LeaveCriticalSection(&_native_handle); +#else + pthread_mutex_unlock(&_native_handle); +#endif + } + + native_handle_type & native_handle() { + return _native_handle; + } + + protected: + native_handle_type _native_handle; + }; + + + /** + * Same interface as std::mutex. + */ + class mutex : public _base_mutex + { + public: + inline + mutex(void) { +#ifdef _WIN32 + InitializeCriticalSection(&_native_handle); +#else + pthread_mutex_init(&_native_handle, NULL); +#endif + } + }; + + + /** + * Same interface as std::recursive_mutex. + */ + class recursive_mutex : public _base_mutex + { + public: + inline + recursive_mutex(void) { +#ifdef _WIN32 + InitializeCriticalSection(&_native_handle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&_native_handle, &attr); + pthread_mutexattr_destroy(&attr); +#endif + } + }; + + + /** + * Same interface as std::unique_lock; + */ + template< class Mutex > + class unique_lock + { + public: + typedef Mutex mutex_type; + + inline explicit + unique_lock(mutex_type &m) : + _mutex(m) + { + _mutex.lock(); + } + + inline + ~unique_lock() { + _mutex.unlock(); + } + + inline void + lock() { + _mutex.lock(); + } + + inline void + unlock() { + _mutex.unlock(); + } + + mutex_type * + mutex() const { + return &_mutex; + } + + protected: + mutex_type &_mutex; + }; + + + /** + * Same interface as std::condition_variable + */ + class condition_variable + { + private: +#ifdef _WIN32 +# ifdef HAVE_WIN32_CONDITION_VARIABLES + // Only supported on Vista an higher. Not yet supported by WINE. + typedef CONDITION_VARIABLE native_handle_type; + native_handle_type _native_handle; +#else + // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + LONG cWaiters; + enum { + EVENT_ONE = 0, + EVENT_ALL, + EVENT_COUNT + }; + HANDLE hEvents[EVENT_COUNT]; +#endif +#else + typedef pthread_cond_t native_handle_type; + native_handle_type _native_handle; +#endif + + public: + condition_variable() { +#ifdef _WIN32 +# ifdef HAVE_WIN32_CONDITION_VARIABLES + InitializeConditionVariable(&_native_handle); +# else + cWaiters = 0; + hEvents[EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + hEvents[EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); +# endif +#else + pthread_cond_init(&_native_handle, NULL); +#endif + } + + ~condition_variable() { +#ifdef _WIN32 +# ifdef HAVE_WIN32_CONDITION_VARIABLES + /* No-op */ +# else + CloseHandle(hEvents[EVENT_ALL]); + CloseHandle(hEvents[EVENT_ONE]); +# endif +#else + pthread_cond_destroy(&_native_handle); +#endif + } + + inline void + notify_one(void) { +#ifdef _WIN32 +# ifdef HAVE_WIN32_CONDITION_VARIABLES + WakeConditionVariable(&_native_handle); +# else + if (cWaiters) { + SetEvent(hEvents[EVENT_ONE]); + } +# endif +#else + pthread_cond_signal(&_native_handle); +#endif + } + + inline void + notify_all(void) { +#ifdef _WIN32 +# ifdef HAVE_WIN32_CONDITION_VARIABLES + WakeAllConditionVariable(&_native_handle); +# else + if (cWaiters) { + SetEvent(hEvents[EVENT_ALL]); + } +# endif +#else + pthread_cond_broadcast(&_native_handle); +#endif + } + + inline void + wait(unique_lock<mutex> & lock) { + mutex::native_handle_type & mutex_native_handle = lock.mutex()->native_handle(); +#ifdef _WIN32 +# ifdef HAVE_WIN32_CONDITION_VARIABLES + SleepConditionVariableCS(&_native_handle, &mutex_native_handle, INFINITE); +# else + InterlockedIncrement(&cWaiters); + LeaveCriticalSection(&mutex_native_handle); + DWORD dwResult; + dwResult = WaitForMultipleObjects(EVENT_COUNT, hEvents, FALSE, INFINITE); + EnterCriticalSection(&mutex_native_handle); + if (InterlockedDecrement(&cWaiters) == 0 && + dwResult == WAIT_OBJECT_0 + EVENT_ALL) { + ResetEvent(hEvents[EVENT_ALL]); + } +# endif +#else + pthread_cond_wait(&_native_handle, &mutex_native_handle); +#endif + } + + inline void + wait(unique_lock<mutex> & lock, std::function<bool()> pred) { + while (!pred) { + wait(lock); + } + } + }; + + + /** + * Implement TLS through OS threading API. + * + * This will only work when T is a pointer, intptr_t, or uintptr_t. + */ + template <typename T> + class thread_specific + { + private: + static_assert(sizeof(T) == sizeof(void *), "Size mismatch"); + +#ifdef _WIN32 + DWORD dwTlsIndex; +#else + pthread_key_t key; +#endif + + public: + thread_specific(void) { +#ifdef _WIN32 + dwTlsIndex = TlsAlloc(); +#else + pthread_key_create(&key, NULL); +#endif + } + + ~thread_specific() { +#ifdef _WIN32 + TlsFree(dwTlsIndex); +#else + pthread_key_delete(key); +#endif + } + + inline T + get(void) const { + void *ptr; +#ifdef _WIN32 + ptr = TlsGetValue(dwTlsIndex); +#else + ptr = pthread_getspecific(key); +#endif + return reinterpret_cast<T>(ptr); + } + + inline + operator T (void) const + { + return get(); + } + + inline T + operator -> (void) const + { + return get(); + } + + inline T + operator = (T new_value) + { + set(new_value); + return new_value; + } + + inline void + set(T new_value) { + void *new_ptr = reinterpret_cast<void *>(new_value); +#ifdef _WIN32 + TlsSetValue(dwTlsIndex, new_ptr); +#else + pthread_setspecific(key, new_ptr); +#endif + } + }; + + + /** + * Same interface as std::thread + */ + class thread { + public: +#ifdef _WIN32 + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + inline + thread() : + _native_handle(0) + { + } + + inline + thread(thread &&other) : + _native_handle(other._native_handle) + { + other._native_handle = 0; + } + + thread(const thread &other) = delete; + + inline + ~thread() { + } + + static unsigned + hardware_concurrency(void) { +#ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwNumberOfProcessors; +#else + return sysconf(_SC_NPROCESSORS_ONLN); +#endif + } + + template< class Function, class... Args > + explicit thread(Function &&f, Args&&... args) { + auto bound = std::bind(std::forward<Function>(f), std::forward<Args>(args)...); + auto data = new decltype(bound) (std::move(bound)); + _native_handle = _create(data); + } + + inline thread & + operator =(thread &&other) { + assert(_native_handle == 0); + _native_handle = other._native_handle; + other._native_handle = 0; + return *this; + } + + inline bool + joinable(void) const { + return _native_handle != 0; + } + + inline void + join() { +#ifdef _WIN32 + WaitForSingleObject(_native_handle, INFINITE); +#else + pthread_join(_native_handle, NULL); +#endif + } + + private: + native_handle_type _native_handle; + + template< typename Param > + static +#ifdef _WIN32 + unsigned __stdcall +#else + void * +#endif + _callback(void *lpParameter) { + Param *pParam = static_cast<Param *>(lpParameter); + (*pParam)(); + delete pParam; + return 0; + } + + template< typename Param > + static inline native_handle_type + _create(Param *function) { +#ifdef _WIN32 + uintptr_t handle =_beginthreadex(NULL, 0, &_callback<Param>, function, 0, NULL); + return reinterpret_cast<HANDLE>(handle); +#else + pthread_t t; + pthread_create(&t, NULL, &_callback<Param>, function); + return t; +#endif + } + }; + +} /* namespace os */ + + +#endif /* !HAVE_CXX11_THREADS */ + + +/** + * Compiler TLS. + * + * It's not portable to use for DLLs on Windows XP, or non-POD types. + * + * See also: + * - http://gcc.gnu.org/onlinedocs/gcc-4.6.3/gcc/Thread_002dLocal.html + * - http://msdn.microsoft.com/en-us/library/9w1sdazb.aspx + * - https://msdn.microsoft.com/en-us/library/y5f6w579.aspx + */ +#if defined(__GNUC__) +# define OS_THREAD_LOCAL __thread +#elif defined(_MSC_VER) +# define OS_THREAD_LOCAL __declspec(thread) +#else +# error +#endif + +#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600 +# define OS_THREAD_SPECIFIC(_type) os::thread_specific< _type > +#else +# define OS_THREAD_SPECIFIC(_type) OS_THREAD_LOCAL _type +#endif + +#define OS_THREAD_SPECIFIC_PTR(_type) OS_THREAD_SPECIFIC(_type *) diff --git a/lib/os/os_thread_test.cpp b/lib/os/os_thread_test.cpp new file mode 100644 index 00000000..7385b00c --- /dev/null +++ b/lib/os/os_thread_test.cpp @@ -0,0 +1,161 @@ +/************************************************************************** + * + * Copyright 2016 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#include "os_thread.hpp" + +#include "gtest/gtest.h" + +TEST(os_thread, hardware_concurrency) +{ + EXPECT_GT(os::thread::hardware_concurrency(), 0); +} + + +static bool b0; +static void f0(void) { + b0 = true; +} + +static bool b1; +static void f1(int a1) { + b1 = a1 == 1; +} + +static bool b2; +static void f2(int a2, int a3) { + b2 = a2 == 2 && a3 == 3; +} + +TEST(os_thread, thread) +{ + os::thread t0(&f0); + t0.join(); + EXPECT_TRUE(b0); + + os::thread t1(&f1, 1); + t1.join(); + EXPECT_TRUE(b1); + + os::thread t2(&f2, 2, 3); + t2.join(); + EXPECT_TRUE(b2); +} + +#define NUM_THREADS 4 + + +struct Data +{ + bool notified[NUM_THREADS]; + os::condition_variable cv; + os::mutex mutex; + bool notify = false; + volatile unsigned waiting = 0; + volatile unsigned c = 0; +}; + +#if 0 + static os::mutex cerr_mutex; +# define WITH_CERR_MUTEX(_stmts) \ + { \ + os::unique_lock<os::mutex> cerr_lock(cerr_mutex); \ + _stmts \ + } +#else +# define WITH_CERR_MUTEX(_stmts) +#endif + + +static void cvf(Data *data, unsigned idx) +{ + WITH_CERR_MUTEX( std::cerr << idx << " started.\n"; ) + + { + os::unique_lock<os::mutex> l(data->mutex); + data->waiting += 1; + } + + WITH_CERR_MUTEX( std::cerr << idx << " waiting...\n"; ) + + { + os::unique_lock<os::mutex> lock(data->mutex); + while (!data->notify) { + data->cv.wait(lock); + } + data->notified[idx] = true; + } + + WITH_CERR_MUTEX( std::cerr << idx << " notified.\n"; ) +} + + +TEST(os_thread, condition_variable) +{ + os::thread t[NUM_THREADS]; + Data data; + unsigned i; + + for (i = 0; i < NUM_THREADS; ++i) { + data.notified[i] = false; + t[i] = os::thread(cvf, &data, i); + } + + for (i = 0; i < NUM_THREADS; ++i) { + EXPECT_FALSE(data.notified[i]); + } + + WITH_CERR_MUTEX( std::cerr << "M waiting...\n"; ) + + do { + os::unique_lock<os::mutex> l(data.mutex); + if (data.waiting == NUM_THREADS) { + break; + } + } while(true); + + WITH_CERR_MUTEX( std::cerr << "M notifying...\n"; ) + + { + os::unique_lock<os::mutex> l(data.mutex); + data.notify = true; + } + + data.cv.notify_all(); + + WITH_CERR_MUTEX( std::cerr << "M joining...\n"; ) + for (i = 0; i < NUM_THREADS; ++i) { + t[i].join(); + EXPECT_TRUE(data.notified[i]); + } +} + + +int +main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/lib/os/os_time.hpp b/lib/os/os_time.hpp new file mode 100644 index 00000000..e3f63aff --- /dev/null +++ b/lib/os/os_time.hpp @@ -0,0 +1,104 @@ +/************************************************************************** + * + * Copyright 2011 Jose Fonseca + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +/* + * Simple OS time measurement abstraction. + */ + +#pragma once + + +#if defined(_WIN32) +# include <windows.h> +#else +# if defined(__linux__) +# include <time.h> +# elif defined(__APPLE__) +# include <mach/mach_time.h> +# else +# include <sys/time.h> +# endif +# include <unistd.h> +#endif + + +namespace os { + + // OS dependent time frequency +#if defined(_WIN32) || defined(__APPLE__) + // runtime variable on Windows and MacOSX + extern long long timeFrequency; +#elif defined(__linux__) + // nanoseconds on Linux + static const long long timeFrequency = 1000000000LL; +#else + // microseconds on Unices + static const long long timeFrequency = 1000000LL; +#endif + + // Time from an unknown base in a unit determined by timeFrequency + inline long long + getTime(void) { +#if defined(_WIN32) + if (!timeFrequency) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + timeFrequency = frequency.QuadPart; + } + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return counter.QuadPart; +#elif defined(__linux__) + struct timespec tp; + if (clock_gettime(CLOCK_MONOTONIC, &tp) == -1) { + return 0; + } + return tp.tv_sec * 1000000000LL + tp.tv_nsec; +#elif defined(__APPLE__) + if (!timeFrequency) { + mach_timebase_info_data_t timebaseInfo; + mach_timebase_info(&timebaseInfo); + timeFrequency = 1000000000LL * timebaseInfo.denom / timebaseInfo.numer; + } + return mach_absolute_time(); +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000LL + tv.tv_usec; +#endif + } + + // Suspend execution + inline void + sleep(unsigned long usecs) { +#if defined(_WIN32) + Sleep((usecs + 999) / 1000); +#else + usleep(usecs); +#endif + } + +} /* namespace os */ + diff --git a/lib/os/os_version.hpp b/lib/os/os_version.hpp new file mode 100644 index 00000000..ad85de7d --- /dev/null +++ b/lib/os/os_version.hpp @@ -0,0 +1,61 @@ +/************************************************************************** + * + * Copyright 2014 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#pragma once + + +#ifdef _WIN32 + + +#include <windows.h> + + +/** + * VersionHelpers.h is not yet widely available (only available on certain MSVC + * and Windows SDK versions), so just define our own helpers. + * + * See http://msdn.microsoft.com/en-gb/library/windows/desktop/ms725491.aspx + */ +static inline bool +IsWindows8OrGreater() +{ + OSVERSIONINFOEXW osvi; + ZeroMemory(&osvi, sizeof osvi); + osvi.dwOSVersionInfoSize = sizeof osvi; + osvi.dwMajorVersion = 6; + osvi.dwMinorVersion = 2; + osvi.wServicePackMajor = 0; + DWORDLONG dwlConditionMask = 0; + VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + return VerifyVersionInfoW(&osvi, + VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, + dwlConditionMask); +} + + +#endif /* _WIN32 */ diff --git a/lib/os/os_win32.cpp b/lib/os/os_win32.cpp new file mode 100644 index 00000000..66492ca8 --- /dev/null +++ b/lib/os/os_win32.cpp @@ -0,0 +1,405 @@ +/************************************************************************** + * + * Copyright 2010-2015 VMware, Inc. + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + +#ifdef _WIN32 + +#include <windows.h> +#include <shlobj.h> + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +#include <string> + +#include "os.hpp" +#include "os_string.hpp" + + +namespace os { + + +String +getProcessName(void) +{ + String path; + DWORD size = MAX_PATH; + char *buf = path.buf(size); + + DWORD nWritten = GetModuleFileNameA(NULL, buf, size); + (void)nWritten; + + path.truncate(); + + return path; +} + +String +getCurrentDir(void) +{ + String path; + DWORD size = MAX_PATH; + char *buf = path.buf(size); + + DWORD ret = GetCurrentDirectoryA(size, buf); + (void)ret; + + buf[size - 1] = 0; + path.truncate(); + + return path; +} + +String +getConfigDir(void) +{ + String path; + char *buf = path.buf(MAX_PATH); + HRESULT hr = SHGetFolderPathA(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, buf); + if (SUCCEEDED(hr)) { + path.truncate(); + } else { + path.truncate(0); + } + return path; +} + +bool +createDirectory(const String &path) +{ + return CreateDirectoryA(path, NULL); +} + +bool +String::exists(void) const +{ + DWORD attrs = GetFileAttributesA(str()); + return attrs != INVALID_FILE_ATTRIBUTES; +} + +bool +copyFile(const String &srcFileName, const String &dstFileName, bool override) +{ + return CopyFileA(srcFileName, dstFileName, !override); +} + +bool +removeFile(const String &srcFilename) +{ + return DeleteFileA(srcFilename); +} + +/** + * Determine whether an argument should be quoted. + */ +static bool +needsQuote(const char *arg) +{ + char c; + while (true) { + c = *arg++; + if (c == '\0') { + break; + } + if (c == ' ' || c == '\t' || c == '\"') { + return true; + } + if (c == '\\') { + c = *arg++; + if (c == '\0') { + break; + } + if (c == '"') { + return true; + } + } + } + return false; +} + +static void +quoteArg(std::string &s, const char *arg) +{ + char c; + unsigned backslashes = 0; + + s.push_back('"'); + while (true) { + c = *arg++; + if (c == '\0') { + break; + } else if (c == '"') { + while (backslashes) { + s.push_back('\\'); + --backslashes; + } + s.push_back('\\'); + } else { + if (c == '\\') { + ++backslashes; + } else { + backslashes = 0; + } + } + s.push_back(c); + } + s.push_back('"'); +} + +int execute(char * const * args) +{ + std::string commandLine; + + const char *arg0 = *args; + const char *arg; + char sep = 0; + while ((arg = *args++) != NULL) { + if (sep) { + commandLine.push_back(sep); + } + + if (needsQuote(arg)) { + quoteArg(commandLine, arg); + } else { + commandLine.append(arg); + } + + sep = ' '; + } + + STARTUPINFOA startupInfo; + memset(&startupInfo, 0, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + + PROCESS_INFORMATION processInformation; + + if (!CreateProcessA(NULL, + const_cast<char *>(commandLine.c_str()), // only modified by CreateProcessW + 0, // process attributes + 0, // thread attributes + FALSE, // inherit handles + 0, // creation flags, + NULL, // environment + NULL, // current directory + &startupInfo, + &processInformation + )) { + log("error: failed to execute %s\n", arg0); + return -1; + } + + WaitForSingleObject(processInformation.hProcess, INFINITE); + + DWORD exitCode = ~0UL; + GetExitCodeProcess(processInformation.hProcess, &exitCode); + + CloseHandle(processInformation.hProcess); + CloseHandle(processInformation.hThread); + + return (int)exitCode; +} + +#ifndef HAVE_EXTERNAL_OS_LOG +void +log(const char *format, ...) +{ + char buf[4096]; + + va_list ap; + va_start(ap, format); + fflush(stdout); + vsnprintf(buf, sizeof buf, format, ap); + va_end(ap); + + OutputDebugStringA(buf); + + /* + * Also write the message to stderr, when a debugger is not present (to + * avoid duplicate messages in command line debuggers). + */ +#if _WIN32_WINNT > 0x0400 + if (!IsDebuggerPresent()) { + fflush(stdout); + fputs(buf, stderr); + fflush(stderr); + } +#endif +} +#endif /* !HAVE_EXTERNAL_OS_LOG */ + +long long timeFrequency = 0LL; + +void +abort(void) +{ + TerminateProcess(GetCurrentProcess(), 3); +#if defined(__GNUC__) + __builtin_unreachable(); +#endif +} + + +void +breakpoint(void) +{ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + asm("int3"); +#elif defined(_MSC_VER) + __debugbreak(); +#else + DebugBreak(); +#endif +} + + +#ifndef DBG_PRINTEXCEPTION_C +#define DBG_PRINTEXCEPTION_C 0x40010006 +#endif + +#ifndef DBG_PRINTEXCEPTION_WIDE_C +#define DBG_PRINTEXCEPTION_WIDE_C 0x4001000A +#endif + +static PVOID prevExceptionFilter = NULL; +static void (*gCallback)(void) = NULL; + +static LONG CALLBACK +unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) +{ + /* + * Before Vista KiUserExceptionDispatcher does not clear the direction + * flag. + * + * See also: + * - https://bugs.chromium.org/p/nativeclient/issues/detail?id=1495 + */ +#ifdef _MSC_VER +#ifndef _WIN64 + __asm cld; +#endif +#else + asm("cld"); +#endif + + PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord; + DWORD ExceptionCode = pExceptionRecord->ExceptionCode; + + // https://msdn.microsoft.com/en-us/library/het71c37.aspx + switch (ExceptionCode >> 30) { + case 0: // success + return EXCEPTION_CONTINUE_SEARCH; + case 1: // informational + case 2: // warning + case 3: // error + break; + } + + /* + * Ignore OutputDebugStringA/W exceptions. + */ + if (ExceptionCode == DBG_PRINTEXCEPTION_C || + ExceptionCode == DBG_PRINTEXCEPTION_WIDE_C) { + return EXCEPTION_CONTINUE_SEARCH; + } + + /* + * Ignore C++ exceptions, as some applications generate a lot of these with + * no harm. But don't ignore them on debug builds, as bugs in apitrace can + * easily lead the applicationa and/or runtime to raise them, and catching + * them helps debugging. + * + * See also: + * - http://blogs.msdn.com/b/oldnewthing/archive/2010/07/30/10044061.aspx + * - http://support.microsoft.com/kb/185294 + */ +#ifdef NDEBUG + if (ExceptionCode == 0xe06d7363) { + return EXCEPTION_CONTINUE_SEARCH; + } +#endif + + /* + * Ignore thread naming exception. + * + * http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx + */ + if (ExceptionCode == 0x406d1388) { + return EXCEPTION_CONTINUE_SEARCH; + } + + /* + * Ignore .NET exceptions. + * + * http://ig2600.blogspot.co.uk/2011/01/why-do-i-keep-getting-exception-code.html + * https://github.com/dotnet/coreclr/blob/master/src/jit/importer.cpp + */ + if (ExceptionCode == 0xe0434352 || + ExceptionCode == 0xe0564552) { + return EXCEPTION_CONTINUE_SEARCH; + } + + log("apitrace: warning: caught exception 0x%08lx\n", pExceptionRecord->ExceptionCode); + + static int recursion_count = 0; + if (recursion_count) { + fputs("apitrace: warning: recursion handling exception\n", stderr); + } else { + if (gCallback) { + ++recursion_count; + gCallback(); + --recursion_count; + } + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +void +setExceptionCallback(void (*callback)(void)) +{ + assert(!gCallback); + + if (!gCallback) { + gCallback = callback; + + assert(!prevExceptionFilter); + + prevExceptionFilter = AddVectoredExceptionHandler(0, unhandledExceptionHandler); + } +} + +void +resetExceptionCallback(void) +{ + if (gCallback) { + RemoveVectoredExceptionHandler(prevExceptionFilter); + gCallback = NULL; + } +} + + +} /* namespace os */ + +#endif // defined(_WIN32) diff --git a/lib/os/thread_pool.hpp b/lib/os/thread_pool.hpp new file mode 100644 index 00000000..9ccba267 --- /dev/null +++ b/lib/os/thread_pool.hpp @@ -0,0 +1,113 @@ +/************************************************************************** + * + * Copyright (c) 2012 Jakob Progsch, Václav Zeman + * Copyright (c) 2015 Emmanuel Gil Peyrot, Collabora Ltd. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + **************************************************************************/ + + +#pragma once + +#include <algorithm> +#include <cstddef> +#include <functional> +#include <queue> +#include <vector> + +#include "os_thread.hpp" + + +class ThreadPool { +public: + ThreadPool(size_t); + template<class F, class... Args> + void enqueue(F&& f, Args&&... args); + ~ThreadPool(); +private: + // need to keep track of threads so we can join them + std::vector<os::thread> workers; + // the task queue + std::queue<std::function<void()>> tasks; + + // synchronization + os::mutex queue_mutex; + os::condition_variable condition; + bool stop; +}; + + +// the constructor just launches some amount of workers +inline ThreadPool::ThreadPool(size_t threads) + : stop(false) +{ + for(size_t i = 0;i<threads;++i) + workers.emplace_back( + [this] + { + for(;;) + { + std::function<void()> task; + + { + os::unique_lock<os::mutex> lock(this->queue_mutex); + this->condition.wait(lock, + [this]{ return this->stop || !this->tasks.empty(); }); + if(this->stop && this->tasks.empty()) + return; + task = std::move(this->tasks.front()); + this->tasks.pop(); + } + + task(); + } + } + ); +} + +// add new work item to the pool +template<class F, class... Args> +void ThreadPool::enqueue(F&& f, Args&&... args) +{ + auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...); + + { + os::unique_lock<os::mutex> lock(queue_mutex); + + // don't allow enqueueing after stopping the pool + assert(!stop); + + tasks.emplace(task); + } + condition.notify_one(); +} + +// the destructor joins all threads +inline ThreadPool::~ThreadPool() +{ + { + os::unique_lock<os::mutex> lock(queue_mutex); + stop = true; + } + condition.notify_all(); + for(os::thread &worker: workers) + worker.join(); +} |