diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /mozglue | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'mozglue')
69 files changed, 15363 insertions, 0 deletions
diff --git a/mozglue/android/APKOpen.cpp b/mozglue/android/APKOpen.cpp new file mode 100644 index 0000000000..1aabc155eb --- /dev/null +++ b/mozglue/android/APKOpen.cpp @@ -0,0 +1,465 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This custom library loading code is only meant to be called + * during initialization. As a result, it takes no special + * precautions to be threadsafe. Any of the library loading functions + * like mozload should not be available to other code. + */ + +#include <jni.h> +#include <android/log.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <sys/limits.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <zlib.h> +#include "dlfcn.h" +#include "APKOpen.h" +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/prctl.h> +#include "sqlite3.h" +#include "SQLiteBridge.h" +#include "NSSBridge.h" +#include "ElfLoader.h" +#include "application.ini.h" + +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "XREChildData.h" + +/* Android headers don't define RUSAGE_THREAD */ +#ifndef RUSAGE_THREAD +#define RUSAGE_THREAD 1 +#endif + +#ifndef RELEASE_OR_BETA +/* Official builds have the debuggable flag set to false, which disables + * the backtrace dumper from bionic. However, as it is useful for native + * crashes happening before the crash reporter is registered, re-enable + * it on non release builds (i.e. nightly and aurora). + * Using a constructor so that it is re-enabled as soon as libmozglue.so + * is loaded. + */ +__attribute__((constructor)) +void make_dumpable() { + prctl(PR_SET_DUMPABLE, 1); +} +#endif + +extern "C" { +/* + * To work around http://code.google.com/p/android/issues/detail?id=23203 + * we don't link with the crt objects. In some configurations, this means + * a lack of the __dso_handle symbol because it is defined there, and + * depending on the android platform and ndk versions used, it may or may + * not be defined in libc.so. In the latter case, we fail to link. Defining + * it here as weak makes us provide the symbol when it's not provided by + * the crt objects, making the change transparent for future NDKs that + * would fix the original problem. On older NDKs, it is not a problem + * either because the way __dso_handle was used was already broken (and + * the custom linker works around it). + */ + NS_EXPORT __attribute__((weak)) void *__dso_handle; +} + +typedef int mozglueresult; + +enum StartupEvent { +#define mozilla_StartupTimeline_Event(ev, z) ev, +#include "StartupTimeline.h" +#undef mozilla_StartupTimeline_Event + MAX_STARTUP_EVENT_ID +}; + +using namespace mozilla; + +static const int MAX_MAPPING_INFO = 32; +static mapping_info lib_mapping[MAX_MAPPING_INFO]; + +NS_EXPORT const struct mapping_info * +getLibraryMapping() +{ + return lib_mapping; +} + +void +JNI_Throw(JNIEnv* jenv, const char* classname, const char* msg) +{ + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Throw\n"); + jclass cls = jenv->FindClass(classname); + if (cls == nullptr) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't find exception class (or exception pending) %s\n", classname); + exit(FAILURE); + } + int rc = jenv->ThrowNew(cls, msg); + if (rc < 0) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Error throwing exception %s\n", msg); + exit(FAILURE); + } + jenv->DeleteLocalRef(cls); +} + +namespace { + JavaVM* sJavaVM; + pthread_t sJavaUiThread; +} + +void +abortThroughJava(const char* msg) +{ + struct sigaction sigact = {}; + if (SEGVHandler::__wrap_sigaction(SIGSEGV, nullptr, &sigact)) { + return; // sigaction call failed. + } + + Dl_info info = {}; + if ((sigact.sa_flags & SA_SIGINFO) && + __wrap_dladdr(reinterpret_cast<void*>(sigact.sa_sigaction), &info) && + info.dli_fname && strstr(info.dli_fname, "libxul.so")) { + + return; // Existing signal handler is in libxul (i.e. we have crash reporter). + } + + JNIEnv* env = nullptr; + if (!sJavaVM || sJavaVM->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + return; + } + + if (!env || env->PushLocalFrame(2) != JNI_OK) { + return; + } + + jclass loader = env->FindClass("org/mozilla/gecko/mozglue/GeckoLoader"); + if (!loader) { + return; + } + + jmethodID method = env->GetStaticMethodID(loader, "abort", "(Ljava/lang/String;)V"); + jstring str = env->NewStringUTF(msg); + + if (method && str) { + env->CallStaticVoidMethod(loader, method, str); + } + + env->PopLocalFrame(nullptr); +} + +NS_EXPORT pthread_t +getJavaUiThread() +{ + return sJavaUiThread; +} + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_GeckoThread_registerUiThread(JNIEnv*, jclass) +{ + sJavaUiThread = pthread_self(); +} + +static void * xul_handle = nullptr; +#ifndef MOZ_FOLD_LIBS +static void * sqlite_handle = nullptr; +static void * nspr_handle = nullptr; +static void * plc_handle = nullptr; +#else +#define sqlite_handle nss_handle +#define nspr_handle nss_handle +#define plc_handle nss_handle +#endif +static void * nss_handle = nullptr; + +template <typename T> inline void +xul_dlsym(const char *symbolName, T *value) +{ + *value = (T) (uintptr_t) __wrap_dlsym(xul_handle, symbolName); +} + +static int mapping_count = 0; + +extern "C" void +report_mapping(char *name, void *base, uint32_t len, uint32_t offset) +{ + if (mapping_count >= MAX_MAPPING_INFO) + return; + + struct mapping_info *info = &lib_mapping[mapping_count++]; + info->name = strdup(name); + info->base = (uintptr_t)base; + info->len = len; + info->offset = offset; +} + +extern "C" void +delete_mapping(const char *name) +{ + for (int pos = 0; pos < mapping_count; ++pos) { + struct mapping_info *info = &lib_mapping[pos]; + if (!strcmp(info->name, name)) { + struct mapping_info *last = &lib_mapping[mapping_count - 1]; + free(info->name); + *info = *last; + --mapping_count; + break; + } + } +} + +static void* +dlopenAPKLibrary(const char* apkName, const char* libraryName) +{ +#define APK_ASSETS_PATH "!/assets/" ANDROID_CPU_ARCH "/" + size_t filenameLength = strlen(apkName) + + sizeof(APK_ASSETS_PATH) + // includes \0 terminator + strlen(libraryName); + auto file = MakeUnique<char[]>(filenameLength); + snprintf(file.get(), filenameLength, "%s" APK_ASSETS_PATH "%s", + apkName, libraryName); + return __wrap_dlopen(file.get(), RTLD_GLOBAL | RTLD_LAZY); +#undef APK_ASSETS_PATH +} +static mozglueresult +loadGeckoLibs(const char *apkName) +{ + TimeStamp t0 = TimeStamp::Now(); + struct rusage usage1_thread, usage1; + getrusage(RUSAGE_THREAD, &usage1_thread); + getrusage(RUSAGE_SELF, &usage1); + + xul_handle = dlopenAPKLibrary(apkName, "libxul.so"); + if (!xul_handle) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libxul!"); + return FAILURE; + } + + void (*XRE_StartupTimelineRecord)(int, TimeStamp); + xul_dlsym("XRE_StartupTimelineRecord", &XRE_StartupTimelineRecord); + + TimeStamp t1 = TimeStamp::Now(); + struct rusage usage2_thread, usage2; + getrusage(RUSAGE_THREAD, &usage2_thread); + getrusage(RUSAGE_SELF, &usage2); + +#define RUSAGE_TIMEDIFF(u1, u2, field) \ + ((u2.ru_ ## field.tv_sec - u1.ru_ ## field.tv_sec) * 1000 + \ + (u2.ru_ ## field.tv_usec - u1.ru_ ## field.tv_usec) / 1000) + + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %fms total, %ldms(%ldms) user, %ldms(%ldms) system, %ld(%ld) faults", + (t1 - t0).ToMilliseconds(), + RUSAGE_TIMEDIFF(usage1_thread, usage2_thread, utime), + RUSAGE_TIMEDIFF(usage1, usage2, utime), + RUSAGE_TIMEDIFF(usage1_thread, usage2_thread, stime), + RUSAGE_TIMEDIFF(usage1, usage2, stime), + usage2_thread.ru_majflt - usage1_thread.ru_majflt, + usage2.ru_majflt - usage1.ru_majflt); + + XRE_StartupTimelineRecord(LINKER_INITIALIZED, t0); + XRE_StartupTimelineRecord(LIBRARIES_LOADED, t1); + return SUCCESS; +} + +static mozglueresult loadNSSLibs(const char *apkName); + +static mozglueresult +loadSQLiteLibs(const char *apkName) +{ + if (sqlite_handle) + return SUCCESS; + +#ifdef MOZ_FOLD_LIBS + if (loadNSSLibs(apkName) != SUCCESS) + return FAILURE; +#else + + sqlite_handle = dlopenAPKLibrary(apkName, "libmozsqlite3.so"); + if (!sqlite_handle) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libmozsqlite3!"); + return FAILURE; + } +#endif + + setup_sqlite_functions(sqlite_handle); + return SUCCESS; +} + +static mozglueresult +loadNSSLibs(const char *apkName) +{ + if (nss_handle && nspr_handle && plc_handle) + return SUCCESS; + + nss_handle = dlopenAPKLibrary(apkName, "libnss3.so"); + +#ifndef MOZ_FOLD_LIBS + nspr_handle = dlopenAPKLibrary(apkName, "libnspr4.so"); + + plc_handle = dlopenAPKLibrary(apkName, "libplc4.so"); +#endif + + if (!nss_handle) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libnss3!"); + return FAILURE; + } + +#ifndef MOZ_FOLD_LIBS + if (!nspr_handle) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libnspr4!"); + return FAILURE; + } + + if (!plc_handle) { + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libplc4!"); + return FAILURE; + } +#endif + + return setup_nss_functions(nss_handle, nspr_handle, plc_handle); +} + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_extractGeckoLibsNative( + JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) +{ + MOZ_ALWAYS_TRUE(!jenv->GetJavaVM(&sJavaVM)); + + const char* apkName = jenv->GetStringUTFChars(jApkName, nullptr); + if (apkName == nullptr) { + return; + } + + // Extract and cache native lib to allow for efficient startup from cache. + void* handle = dlopenAPKLibrary(apkName, "libxul.so"); + if (handle) { + __android_log_print(ANDROID_LOG_INFO, "GeckoLibLoad", + "Extracted and cached libxul.so."); + // We have extracted and cached the lib, we can close it now. + __wrap_dlclose(handle); + } else { + JNI_Throw(jenv, "java/lang/Exception", "Error extracting gecko libraries"); + } + + jenv->ReleaseStringUTFChars(jApkName, apkName); +} + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_loadGeckoLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) +{ + jenv->GetJavaVM(&sJavaVM); + + const char* str; + // XXX: java doesn't give us true UTF8, we should figure out something + // better to do here + str = jenv->GetStringUTFChars(jApkName, nullptr); + if (str == nullptr) + return; + + int res = loadGeckoLibs(str); + if (res != SUCCESS) { + JNI_Throw(jenv, "java/lang/Exception", "Error loading gecko libraries"); + } + jenv->ReleaseStringUTFChars(jApkName, str); +} + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_loadSQLiteLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) { + const char* str; + // XXX: java doesn't give us true UTF8, we should figure out something + // better to do here + str = jenv->GetStringUTFChars(jApkName, nullptr); + if (str == nullptr) + return; + + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load sqlite start\n"); + mozglueresult rv = loadSQLiteLibs(str); + if (rv != SUCCESS) { + JNI_Throw(jenv, "java/lang/Exception", "Error loading sqlite libraries"); + } + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load sqlite done\n"); + jenv->ReleaseStringUTFChars(jApkName, str); +} + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_loadNSSLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName) { + const char* str; + // XXX: java doesn't give us true UTF8, we should figure out something + // better to do here + str = jenv->GetStringUTFChars(jApkName, nullptr); + if (str == nullptr) + return; + + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load nss start\n"); + mozglueresult rv = loadNSSLibs(str); + if (rv != SUCCESS) { + JNI_Throw(jenv, "java/lang/Exception", "Error loading nss libraries"); + } + __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Load nss done\n"); + jenv->ReleaseStringUTFChars(jApkName, str); +} + +typedef void (*GeckoStart_t)(JNIEnv*, char*, const nsXREAppData*); + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jstring jargs) +{ + GeckoStart_t GeckoStart; + xul_dlsym("GeckoStart", &GeckoStart); + if (GeckoStart == nullptr) + return; + // XXX: java doesn't give us true UTF8, we should figure out something + // better to do here + int len = jenv->GetStringUTFLength(jargs); + // GeckoStart needs to write in the args buffer, so we need a copy. + char *args = (char *) malloc(len + 1); + jenv->GetStringUTFRegion(jargs, 0, len, args); + args[len] = '\0'; + ElfLoader::Singleton.ExpectShutdown(false); + GeckoStart(jenv, args, &sAppData); + ElfLoader::Singleton.ExpectShutdown(true); + free(args); +} + +typedef int GeckoProcessType; + +extern "C" NS_EXPORT mozglueresult +ChildProcessInit(int argc, char* argv[]) +{ + int i; + for (i = 0; i < (argc - 1); i++) { + if (strcmp(argv[i], "-greomni")) + continue; + + i = i + 1; + break; + } + + if (loadNSSLibs(argv[i]) != SUCCESS) { + return FAILURE; + } + if (loadSQLiteLibs(argv[i]) != SUCCESS) { + return FAILURE; + } + if (loadGeckoLibs(argv[i]) != SUCCESS) { + return FAILURE; + } + + void (*fXRE_SetProcessType)(char*); + xul_dlsym("XRE_SetProcessType", &fXRE_SetProcessType); + + mozglueresult (*fXRE_InitChildProcess)(int, char**, void*); + xul_dlsym("XRE_InitChildProcess", &fXRE_InitChildProcess); + + fXRE_SetProcessType(argv[--argc]); + + XREChildData childData; + return fXRE_InitChildProcess(argc, argv, &childData); +} + diff --git a/mozglue/android/APKOpen.h b/mozglue/android/APKOpen.h new file mode 100644 index 0000000000..f28458feb6 --- /dev/null +++ b/mozglue/android/APKOpen.h @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef APKOpen_h +#define APKOpen_h + +#include <jni.h> +#include <pthread.h> + +#ifndef NS_EXPORT +#define NS_EXPORT __attribute__ ((visibility("default"))) +#endif + +struct mapping_info { + char * name; + uintptr_t base; + size_t len; + size_t offset; +}; + +NS_EXPORT const struct mapping_info * getLibraryMapping(); +NS_EXPORT void abortThroughJava(const char* msg); +NS_EXPORT pthread_t getJavaUiThread(); + +static const int SUCCESS = 0; +static const int FAILURE = 1; +void JNI_Throw(JNIEnv* jenv, const char* classname, const char* msg); + +// Bug 1207642 - Work around Dalvik bug by realigning stack on JNI entry +#ifndef MOZ_JNICALL +# ifdef __i386__ +# define MOZ_JNICALL JNICALL __attribute__((force_align_arg_pointer)) +# else +# define MOZ_JNICALL JNICALL +# endif +#endif + +#endif /* APKOpen_h */ diff --git a/mozglue/android/NSSBridge.cpp b/mozglue/android/NSSBridge.cpp new file mode 100644 index 0000000000..2a9b5771df --- /dev/null +++ b/mozglue/android/NSSBridge.cpp @@ -0,0 +1,295 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdlib.h> +#include "dlfcn.h" +#include "NSSBridge.h" +#include "APKOpen.h" +#ifdef ANDROID +#include <jni.h> +#include <android/log.h> +#endif + +#include "ElfLoader.h" + +#ifdef DEBUG +#define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x) +#else +#define LOG(x...) +#endif + +static bool initialized = false; + +#define NSS_WRAPPER_INT(name) name ## _t f_ ## name; +NSS_WRAPPER_INT(NSS_Initialize) +NSS_WRAPPER_INT(NSS_Shutdown) +NSS_WRAPPER_INT(SECITEM_ZfreeItem) +NSS_WRAPPER_INT(PK11SDR_Encrypt) +NSS_WRAPPER_INT(PK11SDR_Decrypt) +NSS_WRAPPER_INT(PK11_GetInternalKeySlot) +NSS_WRAPPER_INT(PK11_NeedUserInit) +NSS_WRAPPER_INT(PK11_InitPin) +NSS_WRAPPER_INT(PR_ErrorToString) +NSS_WRAPPER_INT(PR_GetError) +NSS_WRAPPER_INT(PR_Free) +NSS_WRAPPER_INT(PL_Base64Encode) +NSS_WRAPPER_INT(PL_Base64Decode) +NSS_WRAPPER_INT(PL_strfree) + +int +setup_nss_functions(void *nss_handle, + void *nspr_handle, + void *plc_handle) +{ + if (nss_handle == nullptr || nspr_handle == nullptr || plc_handle == nullptr) { + LOG("Missing handle\n"); + return FAILURE; + } +#define GETFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(nss_handle, #name); \ + if (!f_ ##name) { __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "missing %s", #name); return FAILURE; } + GETFUNC(NSS_Initialize); + GETFUNC(NSS_Shutdown); + GETFUNC(PK11SDR_Encrypt); + GETFUNC(PK11SDR_Decrypt); + GETFUNC(PK11_GetInternalKeySlot); + GETFUNC(PK11_NeedUserInit); + GETFUNC(PK11_InitPin); + GETFUNC(SECITEM_ZfreeItem); +#undef GETFUNC +#define NSPRFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(nspr_handle, #name); \ + if (!f_ ##name) { __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "missing %s", #name); return FAILURE; } + NSPRFUNC(PR_ErrorToString); + NSPRFUNC(PR_GetError); + NSPRFUNC(PR_Free); +#undef NSPRFUNC +#define PLCFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(plc_handle, #name); \ + if (!f_ ##name) { __android_log_print(ANDROID_LOG_ERROR, "GeckoJNI", "missing %s", #name); return FAILURE; } + PLCFUNC(PL_Base64Encode); + PLCFUNC(PL_Base64Decode); + PLCFUNC(PL_strfree); +#undef PLCFUNC + + return SUCCESS; +} + +/* Throws the current NSS error. */ +static void +throwError(JNIEnv* jenv, const char * funcString) { + char *msg; + + PRErrorCode perr = f_PR_GetError(); + char * errString = f_PR_ErrorToString(perr, 0); + asprintf(&msg, "%s returned error %d: %s\n", funcString, perr, errString); + LOG("Throwing error: %s\n", msg); + + JNI_Throw(jenv, "java/lang/Exception", msg); + free(msg); + LOG("Error thrown\n"); +} + +extern "C" NS_EXPORT jstring MOZ_JNICALL +Java_org_mozilla_gecko_NSSBridge_nativeEncrypt(JNIEnv* jenv, jclass, + jstring jPath, + jstring jValue) +{ + jstring ret = jenv->NewStringUTF(""); + + const char* path; + path = jenv->GetStringUTFChars(jPath, nullptr); + + const char* value; + value = jenv->GetStringUTFChars(jValue, nullptr); + + char* result; + SECStatus rv = doCrypto(jenv, path, value, &result, true); + if (rv == SECSuccess) { + ret = jenv->NewStringUTF(result); + free(result); + } + + jenv->ReleaseStringUTFChars(jValue, value); + jenv->ReleaseStringUTFChars(jPath, path); + + return ret; +} + +extern "C" NS_EXPORT jstring MOZ_JNICALL +Java_org_mozilla_gecko_NSSBridge_nativeDecrypt(JNIEnv* jenv, jclass, + jstring jPath, + jstring jValue) +{ + jstring ret = jenv->NewStringUTF(""); + + const char* path; + path = jenv->GetStringUTFChars(jPath, nullptr); + + const char* value; + value = jenv->GetStringUTFChars(jValue, nullptr); + + char* result; + SECStatus rv = doCrypto(jenv, path, value, &result, false); + if (rv == SECSuccess) { + ret = jenv->NewStringUTF(result); + free(result); + } + + jenv->ReleaseStringUTFChars(jValue, value); + jenv->ReleaseStringUTFChars(jPath, path); + + return ret; +} + + +/* Encrypts or decrypts a string. result should be freed with free() when done */ +SECStatus +doCrypto(JNIEnv* jenv, const char *path, const char *value, char** result, bool encrypt) +{ + SECStatus rv; + PK11SlotInfo *slot; + if (!initialized) { + LOG("Initialize crypto in %s\n", path); + rv = f_NSS_Initialize(path, "", "", "secmod.db", NSS_INIT_NOROOTINIT); + if (rv != SECSuccess) { + throwError(jenv, "NSS_Initialize"); + return rv; + } + initialized = true; + } + + slot = f_PK11_GetInternalKeySlot(); + if (!slot) { + throwError(jenv, "PK11_GetInternalKeySlot"); + return SECFailure; + } + + if (f_PK11_NeedUserInit(slot)) { + LOG("Initializing key3.db with default blank password.\n"); + rv = f_PK11_InitPin(slot, nullptr, nullptr); + if (rv != SECSuccess) { + throwError(jenv, "PK11_InitPin"); + return rv; + } + } + + SECItem request; + SECItem reply; + + reply.data = 0; + reply.len = 0; + + if (encrypt) { + // This can print sensitive data. Uncomment if you need it. + // LOG("Encrypting: %s\n", value); + request.data = (unsigned char*)value; + request.len = strlen(value); + + SECItem keyid; + keyid.data = 0; + keyid.len = 0; + rv = f_PK11SDR_Encrypt(&keyid, &request, &reply, nullptr); + + if (rv != SECSuccess) { + throwError(jenv, "PK11SDR_Encrypt"); + goto done; + } + + rv = encode(reply.data, reply.len, result); + if (rv != SECSuccess) { + throwError(jenv, "encode"); + goto done; + } + LOG("Encrypted: %s\n", *result); + } else { + LOG("Decoding: %s\n", value); + rv = decode(value, &request.data, (int32_t*)&request.len); + if (rv != SECSuccess) { + throwError(jenv, "decode"); + return rv; + } + + rv = f_PK11SDR_Decrypt(&request, &reply, nullptr); + if (rv != SECSuccess) { + throwError(jenv, "PK11SDR_Decrypt"); + goto done; + } + + *result = (char *)malloc(reply.len+1); + strncpy(*result, (char *)reply.data, reply.len); + (*result)[reply.len] = '\0'; + + // This can print sensitive data. Uncomment if you need it. + // LOG("Decoded %i letters: %s\n", reply.len, *result); + free(request.data); + } + +done: + f_SECITEM_ZfreeItem(&reply, false); + return rv; +} + +/* + * Base64 encodes the data passed in. The caller must deallocate _retval using free(); + */ +SECStatus +encode(const unsigned char *data, int32_t dataLen, char **_retval) +{ + SECStatus rv = SECSuccess; + char *encoded = f_PL_Base64Encode((const char *)data, dataLen, nullptr); + if (!encoded) + rv = SECFailure; + if (!*encoded) + rv = SECFailure; + + if (rv == SECSuccess) { + *_retval = (char *)malloc(strlen(encoded)+1); + strcpy(*_retval, encoded); + } + + if (encoded) { + f_PR_Free(encoded); + } + + return rv; +} + +/* + * Base64 decodes the data passed in. The caller must deallocate result using free(); + */ +SECStatus +decode(const char *data, unsigned char **result, int32_t *length) +{ + SECStatus rv = SECSuccess; + uint32_t len = strlen(data); + int adjust = 0; + + /* Compute length adjustment */ + if (len > 0 && data[len-1] == '=') { + adjust++; + if (data[len-2] == '=') adjust++; + } + + char *decoded; + decoded = f_PL_Base64Decode(data, len, nullptr); + if (!decoded) { + return SECFailure; + } + if (!*decoded) { + return SECFailure; + } + + *length = (len*3)/4 - adjust; + LOG("Decoded %i chars into %i chars\n", len, *length); + + *result = (unsigned char*)malloc((size_t)len); + + if (!*result) { + rv = SECFailure; + } else { + memcpy((char*)*result, decoded, len); + } + f_PR_Free(decoded); + return rv; +} + + diff --git a/mozglue/android/NSSBridge.h b/mozglue/android/NSSBridge.h new file mode 100644 index 0000000000..77bcd11724 --- /dev/null +++ b/mozglue/android/NSSBridge.h @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSSBridge_h +#define NSSBridge_h + +#include "nss.h" +#include "pk11func.h" +#include "pk11sdr.h" +#include "seccomon.h" +#include "secitem.h" +#include "secmodt.h" + +#include "prerror.h" +#include "plstr.h" +#include "prmem.h" + +#include <jni.h> + +int setup_nss_functions(void *nss_handle, void *nssutil_handle, void *plc_handle); + +#define NSS_WRAPPER(name, return_type, args...) \ +typedef return_type (*name ## _t)(args); \ +extern name ## _t f_ ## name; + +NSS_WRAPPER(NSS_Initialize, SECStatus, const char*, const char*, const char*, const char*, uint32_t) +NSS_WRAPPER(NSS_Shutdown, void, void) +NSS_WRAPPER(PK11SDR_Encrypt, SECStatus, SECItem *, SECItem *, SECItem *, void *) +NSS_WRAPPER(PK11SDR_Decrypt, SECStatus, SECItem *, SECItem *, void *) +NSS_WRAPPER(SECITEM_ZfreeItem, void, SECItem*, PRBool) +NSS_WRAPPER(PR_ErrorToString, char *, PRErrorCode, PRLanguageCode) +NSS_WRAPPER(PR_GetError, PRErrorCode, void) +NSS_WRAPPER(PR_Free, PRErrorCode, char *) +NSS_WRAPPER(PL_Base64Encode, char*, const char*, uint32_t, char*) +NSS_WRAPPER(PL_Base64Decode, char*, const char*, uint32_t, char*) +NSS_WRAPPER(PL_strfree, void, char*) +NSS_WRAPPER(PK11_GetInternalKeySlot, PK11SlotInfo *, void) +NSS_WRAPPER(PK11_NeedUserInit, PRBool, PK11SlotInfo *) +NSS_WRAPPER(PK11_InitPin, SECStatus, PK11SlotInfo*, const char*, const char*) + +bool setPassword(PK11SlotInfo *slot); +SECStatus doCrypto(JNIEnv* jenv, const char *path, const char *value, char** result, bool doEncrypt); +SECStatus encode(const unsigned char *data, int32_t dataLen, char **_retval); +SECStatus decode(const char *data, unsigned char **result, int32_t * _retval); +#endif /* NSS_h */ diff --git a/mozglue/android/NativeCrypto.cpp b/mozglue/android/NativeCrypto.cpp new file mode 100644 index 0000000000..9a3632e8d0 --- /dev/null +++ b/mozglue/android/NativeCrypto.cpp @@ -0,0 +1,132 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NativeCrypto.h" +#include "APKOpen.h" + +#include <jni.h> + +#include <errno.h> +#include <stdlib.h> +#include <inttypes.h> + +#include "mozilla/SHA1.h" +#include "pbkdf2_sha256.h" + +/** + * Helper function to invoke native PBKDF2 function with JNI + * arguments. + */ +extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_pbkdf2SHA256 + (JNIEnv *env, jclass jc, jbyteArray jpassword, jbyteArray jsalt, jint c, jint dkLen) { + if (dkLen < 0) { + env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"), + "dkLen should not be less than 0"); + return nullptr; + } + + jbyte *password = env->GetByteArrayElements(jpassword, nullptr); + size_t passwordLen = env->GetArrayLength(jpassword); + + jbyte *salt = env->GetByteArrayElements(jsalt, nullptr); + size_t saltLen = env->GetArrayLength(jsalt); + + uint8_t hashResult[dkLen]; + PBKDF2_SHA256((uint8_t *) password, passwordLen, (uint8_t *) salt, saltLen, + (uint64_t) c, hashResult, (size_t) dkLen); + + env->ReleaseByteArrayElements(jpassword, password, JNI_ABORT); + env->ReleaseByteArrayElements(jsalt, salt, JNI_ABORT); + + jbyteArray out = env->NewByteArray(dkLen); + if (out == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(out, 0, dkLen, (jbyte *) hashResult); + + return out; +} + +using namespace mozilla; + +/** + * Helper function to invoke native SHA-1 function with JNI arguments. + */ +extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha1 + (JNIEnv *env, jclass jc, jbyteArray jstr) { + jbyte *str = env->GetByteArrayElements(jstr, nullptr); + size_t strLen = env->GetArrayLength(jstr); + + SHA1Sum sha1; + SHA1Sum::Hash hashResult; + sha1.update((void *) str, (uint32_t) strLen); + sha1.finish(hashResult); + + env->ReleaseByteArrayElements(jstr, str, JNI_ABORT); + + jbyteArray out = env->NewByteArray(SHA1Sum::kHashSize); + if (out == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(out, 0, SHA1Sum::kHashSize, (jbyte *) hashResult); + + return out; +} + +/** + * Helper function to invoke native SHA-256 init with JNI arguments. + */ +extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256init + (JNIEnv *env, jclass jc) { + jbyteArray out = env->NewByteArray(sizeof(SHA256_CTX)); + if (nullptr == out) { + return nullptr; + } + + SHA256_CTX *shaContext = (SHA256_CTX*)env->GetByteArrayElements(out, nullptr); + SHA256_Init(shaContext); + + env->ReleaseByteArrayElements(out, (jbyte*)shaContext, 0); + + return out; +} + +/** + * Helper function to invoke native SHA-256 update with JNI arguments. + */ +extern "C" JNIEXPORT void MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256update + (JNIEnv *env, jclass jc, jbyteArray jctx, jbyteArray jstr, jint len) { + jbyte *str = env->GetByteArrayElements(jstr, nullptr); + + SHA256_CTX *shaContext = (SHA256_CTX*)env->GetByteArrayElements(jctx, nullptr); + + SHA256_Update(shaContext, (void*)str, (size_t) len); + + env->ReleaseByteArrayElements(jstr, str, JNI_ABORT); + env->ReleaseByteArrayElements(jctx, (jbyte*)shaContext, 0); + + return; +} + +/** + * Helper function to invoke native SHA-256 finalize with JNI arguments. + */ +extern "C" JNIEXPORT jbyteArray MOZ_JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256finalize + (JNIEnv *env, jclass jc, jbyteArray jctx) { + SHA256_CTX *shaContext = (SHA256_CTX*)env->GetByteArrayElements(jctx, nullptr); + + unsigned char* digest = new unsigned char[32]; + SHA256_Final(digest, shaContext); + + env->ReleaseByteArrayElements(jctx, (jbyte*)shaContext, JNI_ABORT); + + jbyteArray out = env->NewByteArray(32); + if (nullptr != out) { + env->SetByteArrayRegion(out, 0, 32, (jbyte*)digest); + } + + delete[] digest; + + return out; +} diff --git a/mozglue/android/NativeCrypto.h b/mozglue/android/NativeCrypto.h new file mode 100644 index 0000000000..2850e2bb7a --- /dev/null +++ b/mozglue/android/NativeCrypto.h @@ -0,0 +1,53 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class org_mozilla_gecko_background_nativecode_NativeCrypto */ + +#ifndef _Included_org_mozilla_gecko_background_nativecode_NativeCrypto +#define _Included_org_mozilla_gecko_background_nativecode_NativeCrypto +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_mozilla_gecko_background_nativecode_NativeCrypto + * Method: pbkdf2SHA256 + * Signature: ([B[BII)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_pbkdf2SHA256 + (JNIEnv *, jclass, jbyteArray, jbyteArray, jint, jint); + +/* + * Class: org_mozilla_gecko_background_nativecode_NativeCrypto + * Method: sha1 + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha1 + (JNIEnv *, jclass, jbyteArray); + +/* + * Class: org_mozilla_gecko_background_nativecode_NativeCrypto + * Method: sha256init + * Signature: ()[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256init + (JNIEnv *, jclass); + +/* + * Class: org_mozilla_gecko_background_nativecode_NativeCrypto + * Method: sha256update + * Signature: ([B[B)V + */ +JNIEXPORT void JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256update + (JNIEnv *, jclass, jbyteArray, jbyteArray, jint); + +/* + * Class: org_mozilla_gecko_background_nativecode_NativeCrypto + * Method: sha256finalize + * Signature: ([B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_mozilla_gecko_background_nativecode_NativeCrypto_sha256finalize + (JNIEnv *, jclass, jbyteArray); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/mozglue/android/SQLiteBridge.cpp b/mozglue/android/SQLiteBridge.cpp new file mode 100644 index 0000000000..187900bad9 --- /dev/null +++ b/mozglue/android/SQLiteBridge.cpp @@ -0,0 +1,413 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdlib.h> +#include <stdio.h> +#include <jni.h> +#include <android/log.h> +#include "dlfcn.h" +#include "APKOpen.h" +#include "ElfLoader.h" +#include "SQLiteBridge.h" + +#ifdef DEBUG +#define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x) +#else +#define LOG(x...) +#endif + +#define SQLITE_WRAPPER_INT(name) name ## _t f_ ## name; + +SQLITE_WRAPPER_INT(sqlite3_open) +SQLITE_WRAPPER_INT(sqlite3_errmsg) +SQLITE_WRAPPER_INT(sqlite3_prepare_v2) +SQLITE_WRAPPER_INT(sqlite3_bind_parameter_count) +SQLITE_WRAPPER_INT(sqlite3_bind_null) +SQLITE_WRAPPER_INT(sqlite3_bind_text) +SQLITE_WRAPPER_INT(sqlite3_step) +SQLITE_WRAPPER_INT(sqlite3_column_count) +SQLITE_WRAPPER_INT(sqlite3_finalize) +SQLITE_WRAPPER_INT(sqlite3_close) +SQLITE_WRAPPER_INT(sqlite3_column_name) +SQLITE_WRAPPER_INT(sqlite3_column_type) +SQLITE_WRAPPER_INT(sqlite3_column_blob) +SQLITE_WRAPPER_INT(sqlite3_column_bytes) +SQLITE_WRAPPER_INT(sqlite3_column_text) +SQLITE_WRAPPER_INT(sqlite3_changes) +SQLITE_WRAPPER_INT(sqlite3_last_insert_rowid) + +void setup_sqlite_functions(void *sqlite_handle) +{ +#define GETFUNC(name) f_ ## name = (name ## _t) (uintptr_t) __wrap_dlsym(sqlite_handle, #name) + GETFUNC(sqlite3_open); + GETFUNC(sqlite3_errmsg); + GETFUNC(sqlite3_prepare_v2); + GETFUNC(sqlite3_bind_parameter_count); + GETFUNC(sqlite3_bind_null); + GETFUNC(sqlite3_bind_text); + GETFUNC(sqlite3_step); + GETFUNC(sqlite3_column_count); + GETFUNC(sqlite3_finalize); + GETFUNC(sqlite3_close); + GETFUNC(sqlite3_column_name); + GETFUNC(sqlite3_column_type); + GETFUNC(sqlite3_column_blob); + GETFUNC(sqlite3_column_bytes); + GETFUNC(sqlite3_column_text); + GETFUNC(sqlite3_changes); + GETFUNC(sqlite3_last_insert_rowid); +#undef GETFUNC +} + +static bool initialized = false; +static jclass stringClass; +static jclass objectClass; +static jclass byteBufferClass; +static jclass cursorClass; +static jmethodID jByteBufferAllocateDirect; +static jmethodID jCursorConstructor; +static jmethodID jCursorAddRow; + +static jobject sqliteInternalCall(JNIEnv* jenv, sqlite3 *db, jstring jQuery, + jobjectArray jParams, jlongArray jQueryRes); + +static void throwSqliteException(JNIEnv* jenv, const char* aFormat, ...) +{ + va_list ap; + va_start(ap, aFormat); + char* msg = nullptr; + vasprintf(&msg, aFormat, ap); + LOG("Error in SQLiteBridge: %s\n", msg); + JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", msg); + free(msg); + va_end(ap); +} + +static void +JNI_Setup(JNIEnv* jenv) +{ + if (initialized) return; + + jclass lObjectClass = jenv->FindClass("java/lang/Object"); + jclass lStringClass = jenv->FindClass("java/lang/String"); + jclass lByteBufferClass = jenv->FindClass("java/nio/ByteBuffer"); + jclass lCursorClass = jenv->FindClass("org/mozilla/gecko/sqlite/MatrixBlobCursor"); + + if (lStringClass == nullptr + || lObjectClass == nullptr + || lByteBufferClass == nullptr + || lCursorClass == nullptr) { + throwSqliteException(jenv, "FindClass error"); + return; + } + + // Those are only local references. Make them global so they work + // across calls and threads. + objectClass = (jclass)jenv->NewGlobalRef(lObjectClass); + stringClass = (jclass)jenv->NewGlobalRef(lStringClass); + byteBufferClass = (jclass)jenv->NewGlobalRef(lByteBufferClass); + cursorClass = (jclass)jenv->NewGlobalRef(lCursorClass); + + if (stringClass == nullptr || objectClass == nullptr + || byteBufferClass == nullptr + || cursorClass == nullptr) { + throwSqliteException(jenv, "NewGlobalRef error"); + return; + } + + // public static ByteBuffer allocateDirect(int capacity) + jByteBufferAllocateDirect = + jenv->GetStaticMethodID(byteBufferClass, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"); + // new MatrixBlobCursor(String []) + jCursorConstructor = + jenv->GetMethodID(cursorClass, "<init>", "([Ljava/lang/String;)V"); + // public void addRow (Object[] columnValues) + jCursorAddRow = + jenv->GetMethodID(cursorClass, "addRow", "([Ljava/lang/Object;)V"); + + if (jByteBufferAllocateDirect == nullptr + || jCursorConstructor == nullptr + || jCursorAddRow == nullptr) { + throwSqliteException(jenv, "GetMethodId error"); + return; + } + + initialized = true; +} + +extern "C" NS_EXPORT jobject MOZ_JNICALL +Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass, + jstring jDb, + jstring jQuery, + jobjectArray jParams, + jlongArray jQueryRes) +{ + JNI_Setup(jenv); + + int rc; + jobject jCursor = nullptr; + const char* dbPath; + sqlite3 *db; + + dbPath = jenv->GetStringUTFChars(jDb, nullptr); + rc = f_sqlite3_open(dbPath, &db); + jenv->ReleaseStringUTFChars(jDb, dbPath); + if (rc != SQLITE_OK) { + throwSqliteException(jenv, + "Can't open database: %s", f_sqlite3_errmsg(db)); + f_sqlite3_close(db); // close db even if open failed + return nullptr; + } + jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); + f_sqlite3_close(db); + return jCursor; +} + +extern "C" NS_EXPORT jobject MOZ_JNICALL +Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCallWithDb(JNIEnv* jenv, jclass, + jlong jDb, + jstring jQuery, + jobjectArray jParams, + jlongArray jQueryRes) +{ + JNI_Setup(jenv); + + jobject jCursor = nullptr; + sqlite3 *db = (sqlite3*)jDb; + jCursor = sqliteInternalCall(jenv, db, jQuery, jParams, jQueryRes); + return jCursor; +} + +extern "C" NS_EXPORT jlong MOZ_JNICALL +Java_org_mozilla_gecko_sqlite_SQLiteBridge_openDatabase(JNIEnv* jenv, jclass, + jstring jDb) +{ + JNI_Setup(jenv); + + int rc; + const char* dbPath; + sqlite3 *db; + + dbPath = jenv->GetStringUTFChars(jDb, nullptr); + rc = f_sqlite3_open(dbPath, &db); + jenv->ReleaseStringUTFChars(jDb, dbPath); + if (rc != SQLITE_OK) { + throwSqliteException(jenv, + "Can't open database: %s", f_sqlite3_errmsg(db)); + f_sqlite3_close(db); // close db even if open failed + return 0; + } + return (jlong)db; +} + +extern "C" NS_EXPORT void MOZ_JNICALL +Java_org_mozilla_gecko_sqlite_SQLiteBridge_closeDatabase(JNIEnv* jenv, jclass, + jlong jDb) +{ + JNI_Setup(jenv); + + sqlite3 *db = (sqlite3*)jDb; + f_sqlite3_close(db); +} + +static jobject +sqliteInternalCall(JNIEnv* jenv, + sqlite3 *db, + jstring jQuery, + jobjectArray jParams, + jlongArray jQueryRes) +{ + JNI_Setup(jenv); + + jobject jCursor = nullptr; + jsize numPars = 0; + + const char *pzTail; + sqlite3_stmt *ppStmt; + int rc; + + const char* queryStr; + queryStr = jenv->GetStringUTFChars(jQuery, nullptr); + + rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail); + if (rc != SQLITE_OK || ppStmt == nullptr) { + throwSqliteException(jenv, + "Can't prepare statement: %s", f_sqlite3_errmsg(db)); + return nullptr; + } + jenv->ReleaseStringUTFChars(jQuery, queryStr); + + // Check if number of parameters matches + if (jParams != nullptr) { + numPars = jenv->GetArrayLength(jParams); + } + int sqlNumPars; + sqlNumPars = f_sqlite3_bind_parameter_count(ppStmt); + if (numPars != sqlNumPars) { + throwSqliteException(jenv, + "Passed parameter count (%d) " + "doesn't match SQL parameter count (%d)", + numPars, sqlNumPars); + return nullptr; + } + + if (jParams != nullptr) { + // Bind parameters, if any + if (numPars > 0) { + for (int i = 0; i < numPars; i++) { + jobject jObjectParam = jenv->GetObjectArrayElement(jParams, i); + // IsInstanceOf or isAssignableFrom? String is final, so IsInstanceOf + // should be OK. + jboolean isString = jenv->IsInstanceOf(jObjectParam, stringClass); + if (isString != JNI_TRUE) { + throwSqliteException(jenv, + "Parameter is not of String type"); + return nullptr; + } + + // SQLite parameters index from 1. + if (jObjectParam == nullptr) { + rc = f_sqlite3_bind_null(ppStmt, i + 1); + } else { + jstring jStringParam = (jstring) jObjectParam; + const char* paramStr = jenv->GetStringUTFChars(jStringParam, nullptr); + rc = f_sqlite3_bind_text(ppStmt, i + 1, paramStr, -1, SQLITE_TRANSIENT); + jenv->ReleaseStringUTFChars(jStringParam, paramStr); + } + + if (rc != SQLITE_OK) { + throwSqliteException(jenv, "Error binding query parameter"); + return nullptr; + } + } + } + } + + // Execute the query and step through the results + rc = f_sqlite3_step(ppStmt); + if (rc != SQLITE_ROW && rc != SQLITE_DONE) { + throwSqliteException(jenv, + "Can't step statement: (%d) %s", rc, f_sqlite3_errmsg(db)); + return nullptr; + } + + // Get the column count and names + int cols; + cols = f_sqlite3_column_count(ppStmt); + + { + // Allocate a String[cols] + jobjectArray jStringArray = jenv->NewObjectArray(cols, + stringClass, + nullptr); + if (jStringArray == nullptr) { + throwSqliteException(jenv, "Can't allocate String[]"); + return nullptr; + } + + // Assign column names to the String[] + for (int i = 0; i < cols; i++) { + const char* colName = f_sqlite3_column_name(ppStmt, i); + jstring jStr = jenv->NewStringUTF(colName); + jenv->SetObjectArrayElement(jStringArray, i, jStr); + } + + // Construct the MatrixCursor(String[]) with given column names + jCursor = jenv->NewObject(cursorClass, + jCursorConstructor, + jStringArray); + if (jCursor == nullptr) { + throwSqliteException(jenv, "Can't allocate MatrixBlobCursor"); + return nullptr; + } + } + + // Return the id and number of changed rows in jQueryRes + { + jlong id = f_sqlite3_last_insert_rowid(db); + jenv->SetLongArrayRegion(jQueryRes, 0, 1, &id); + + jlong changed = f_sqlite3_changes(db); + jenv->SetLongArrayRegion(jQueryRes, 1, 1, &changed); + } + + // For each row, add an Object[] to the passed ArrayList, + // with that containing either String or ByteArray objects + // containing the columns + while (rc != SQLITE_DONE) { + // Process row + // Construct Object[] + jobjectArray jRow = jenv->NewObjectArray(cols, + objectClass, + nullptr); + if (jRow == nullptr) { + throwSqliteException(jenv, "Can't allocate jRow Object[]"); + return nullptr; + } + + for (int i = 0; i < cols; i++) { + int colType = f_sqlite3_column_type(ppStmt, i); + if (colType == SQLITE_BLOB) { + // Treat as blob + const void* blob = f_sqlite3_column_blob(ppStmt, i); + int colLen = f_sqlite3_column_bytes(ppStmt, i); + + // Construct ByteBuffer of correct size + jobject jByteBuffer = + jenv->CallStaticObjectMethod(byteBufferClass, + jByteBufferAllocateDirect, + colLen); + if (jByteBuffer == nullptr) { + throwSqliteException(jenv, + "Failure calling ByteBuffer.allocateDirect"); + return nullptr; + } + + // Get its backing array + void* bufferArray = jenv->GetDirectBufferAddress(jByteBuffer); + if (bufferArray == nullptr) { + throwSqliteException(jenv, + "Failure calling GetDirectBufferAddress"); + return nullptr; + } + memcpy(bufferArray, blob, colLen); + + jenv->SetObjectArrayElement(jRow, i, jByteBuffer); + jenv->DeleteLocalRef(jByteBuffer); + } else if (colType == SQLITE_NULL) { + jenv->SetObjectArrayElement(jRow, i, nullptr); + } else { + // Treat everything else as text + const char* txt = (const char*)f_sqlite3_column_text(ppStmt, i); + jstring jStr = jenv->NewStringUTF(txt); + jenv->SetObjectArrayElement(jRow, i, jStr); + jenv->DeleteLocalRef(jStr); + } + } + + // Append Object[] to Cursor + jenv->CallVoidMethod(jCursor, jCursorAddRow, jRow); + + // Clean up + jenv->DeleteLocalRef(jRow); + + // Get next row + rc = f_sqlite3_step(ppStmt); + // Real error? + if (rc != SQLITE_ROW && rc != SQLITE_DONE) { + throwSqliteException(jenv, + "Can't re-step statement:(%d) %s", rc, f_sqlite3_errmsg(db)); + return nullptr; + } + } + + rc = f_sqlite3_finalize(ppStmt); + if (rc != SQLITE_OK) { + throwSqliteException(jenv, + "Can't finalize statement: %s", f_sqlite3_errmsg(db)); + return nullptr; + } + + return jCursor; +} diff --git a/mozglue/android/SQLiteBridge.h b/mozglue/android/SQLiteBridge.h new file mode 100644 index 0000000000..f2ede820f0 --- /dev/null +++ b/mozglue/android/SQLiteBridge.h @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SQLiteBridge_h +#define SQLiteBridge_h + +#include "sqlite3.h" + +void setup_sqlite_functions(void *sqlite_handle); + +#define SQLITE_WRAPPER(name, return_type, args...) \ +typedef return_type (*name ## _t)(args); \ +extern name ## _t f_ ## name; + +SQLITE_WRAPPER(sqlite3_open, int, const char*, sqlite3**) +SQLITE_WRAPPER(sqlite3_errmsg, const char*, sqlite3*) +SQLITE_WRAPPER(sqlite3_prepare_v2, int, sqlite3*, const char*, int, sqlite3_stmt**, const char**) +SQLITE_WRAPPER(sqlite3_bind_parameter_count, int, sqlite3_stmt*) +SQLITE_WRAPPER(sqlite3_bind_text, int, sqlite3_stmt*, int, const char*, int, void(*)(void*)) +SQLITE_WRAPPER(sqlite3_bind_null, int, sqlite3_stmt*, int) +SQLITE_WRAPPER(sqlite3_step, int, sqlite3_stmt*) +SQLITE_WRAPPER(sqlite3_column_count, int, sqlite3_stmt*) +SQLITE_WRAPPER(sqlite3_finalize, int, sqlite3_stmt*) +SQLITE_WRAPPER(sqlite3_close, int, sqlite3*) +SQLITE_WRAPPER(sqlite3_column_name, const char*, sqlite3_stmt*, int) +SQLITE_WRAPPER(sqlite3_column_type, int, sqlite3_stmt*, int) +SQLITE_WRAPPER(sqlite3_column_blob, const void*, sqlite3_stmt*, int) +SQLITE_WRAPPER(sqlite3_column_bytes, int, sqlite3_stmt*, int) +SQLITE_WRAPPER(sqlite3_column_text, const unsigned char*, sqlite3_stmt*, int) +SQLITE_WRAPPER(sqlite3_changes, int, sqlite3*) +SQLITE_WRAPPER(sqlite3_last_insert_rowid, sqlite3_int64, sqlite3*) + +#endif /* SQLiteBridge_h */ diff --git a/mozglue/android/SharedMemNatives.cpp b/mozglue/android/SharedMemNatives.cpp new file mode 100644 index 0000000000..d186d6e21e --- /dev/null +++ b/mozglue/android/SharedMemNatives.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <jni.h> +#include <string.h> +#include <sys/mman.h> + +extern "C" { + +JNIEXPORT +void JNICALL +Java_org_mozilla_gecko_mozglue_SharedMemBuffer_nativeReadFromDirectBuffer(JNIEnv* jenv, jclass, jobject src, jlong dest, jint offset, jint size) +{ + uint8_t* from = static_cast<uint8_t*>(jenv->GetDirectBufferAddress(src)); + if (from == nullptr) { + jenv->ThrowNew(jenv->FindClass("java/lang/NullPointerException"), "Null direct buffer"); + return; + } + + void* to = reinterpret_cast<void*>(dest); + if (to == nullptr) { + jenv->ThrowNew(jenv->FindClass("java/lang/NullPointerException"), "Null shared memory buffer"); + return; + } + + memcpy(to, from + offset, size); +} + +JNIEXPORT +void JNICALL +Java_org_mozilla_gecko_mozglue_SharedMemBuffer_nativeWriteToDirectBuffer(JNIEnv* jenv, jclass, jlong src, jobject dest, jint offset, jint size) +{ + uint8_t* from = reinterpret_cast<uint8_t*>(src); + if (from == nullptr) { + jenv->ThrowNew(jenv->FindClass("java/lang/NullPointerException"), "Null shared memory buffer"); + return; + } + + void* to = jenv->GetDirectBufferAddress(dest); + if (to == nullptr) { + jenv->ThrowNew(jenv->FindClass("java/lang/NullPointerException"), "Null direct buffer"); + return; + } + + memcpy(to, from + offset, size); +} + +JNIEXPORT +jlong JNICALL +Java_org_mozilla_gecko_mozglue_SharedMemory_map(JNIEnv *env, jobject jobj, jint fd, jint length) +{ + void* address = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + return jlong(address); +} + +JNIEXPORT +void JNICALL +Java_org_mozilla_gecko_mozglue_SharedMemory_unmap(JNIEnv *env, jobject jobj, jlong address, jint size) +{ + munmap((void*)address, (size_t)size); +} + +}
\ No newline at end of file diff --git a/mozglue/android/moz.build b/mozglue/android/moz.build new file mode 100644 index 0000000000..cdc5ffb3cb --- /dev/null +++ b/mozglue/android/moz.build @@ -0,0 +1,58 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'APKOpen.h', +] + +SOURCES += [ + 'APKOpen.cpp', + 'NativeCrypto.cpp', + 'nsGeckoUtils.cpp', + 'NSSBridge.cpp', + 'pbkdf2_sha256.c', + 'SharedMemNatives.cpp', + 'SQLiteBridge.cpp', +] + +FINAL_LIBRARY = 'mozglue' + +for var in ('ANDROID_PACKAGE_NAME', + 'ANDROID_CPU_ARCH'): + DEFINES[var] = '"%s"' % CONFIG[var] + +if CONFIG['MOZ_FOLD_LIBS']: + DEFINES['MOZ_FOLD_LIBS'] = True + +LOCAL_INCLUDES += [ + '!/build', + '../linker', + '/db/sqlite3/src', + '/ipc/chromium/src', + '/nsprpub/lib/ds', + '/nsprpub/lib/libc/include', + '/nsprpub/pr/include', + '/security/nss/lib/base', + '/security/nss/lib/certdb', + '/security/nss/lib/cryptohi', + '/security/nss/lib/dev', + '/security/nss/lib/freebl', + '/security/nss/lib/nss', + '/security/nss/lib/pk11wrap', + '/security/nss/lib/pkcs7', + '/security/nss/lib/pki', + '/security/nss/lib/smime', + '/security/nss/lib/softoken', + '/security/nss/lib/ssl', + '/security/nss/lib/util', + '/toolkit/components/startup', + '/xpcom/build', +] + +DISABLE_STL_WRAPPING = True + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/mozglue/android/nsGeckoUtils.cpp b/mozglue/android/nsGeckoUtils.cpp new file mode 100644 index 0000000000..22818a746f --- /dev/null +++ b/mozglue/android/nsGeckoUtils.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <jni.h> + +#include <stdlib.h> +#include <fcntl.h> +#include "APKOpen.h" +#include "Zip.h" +#include "mozilla/RefPtr.h" + +extern "C" +__attribute__ ((visibility("default"))) +void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_GeckoLoader_putenv(JNIEnv *jenv, jclass, jstring map) +{ + const char* str; + // XXX: java doesn't give us true UTF8, we should figure out something + // better to do here + str = jenv->GetStringUTFChars(map, nullptr); + if (str == nullptr) + return; + putenv(strdup(str)); + jenv->ReleaseStringUTFChars(map, str); +} + +extern "C" +__attribute__ ((visibility("default"))) +jobject MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_DirectBufferAllocator_nativeAllocateDirectBuffer(JNIEnv *jenv, jclass, jlong size) +{ + jobject buffer = nullptr; + void* mem = malloc(size); + if (mem) { + buffer = jenv->NewDirectByteBuffer(mem, size); + if (!buffer) + free(mem); + } + return buffer; +} + +extern "C" +__attribute__ ((visibility("default"))) +void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_DirectBufferAllocator_nativeFreeDirectBuffer(JNIEnv *jenv, jclass, jobject buf) +{ + free(jenv->GetDirectBufferAddress(buf)); +} + +extern "C" +__attribute__ ((visibility("default"))) +jlong MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_NativeZip_getZip(JNIEnv *jenv, jclass, jstring path) +{ + const char* str; + str = jenv->GetStringUTFChars(path, nullptr); + if (!str || !*str) { + if (str) + jenv->ReleaseStringUTFChars(path, str); + JNI_Throw(jenv, "java/lang/IllegalArgumentException", "Invalid path"); + return 0; + } + RefPtr<Zip> zip = ZipCollection::GetZip(str); + jenv->ReleaseStringUTFChars(path, str); + if (!zip) { + JNI_Throw(jenv, "java/lang/IllegalArgumentException", "Invalid path or invalid zip"); + return 0; + } + return reinterpret_cast<jlong>(zip.forget().take()); +} + +extern "C" +__attribute__ ((visibility("default"))) +jlong MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_NativeZip_getZipFromByteBuffer(JNIEnv *jenv, jclass, jobject buffer) +{ + void *buf = jenv->GetDirectBufferAddress(buffer); + size_t size = jenv->GetDirectBufferCapacity(buffer); + RefPtr<Zip> zip = Zip::Create(buf, size); + if (!zip) { + JNI_Throw(jenv, "java/lang/IllegalArgumentException", "Invalid zip"); + return 0; + } + return reinterpret_cast<jlong>(zip.forget().take()); +} + + extern "C" +__attribute__ ((visibility("default"))) +void MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_NativeZip__1release(JNIEnv *jenv, jclass, jlong obj) +{ + Zip *zip = (Zip *)obj; + zip->Release(); +} + +extern "C" +__attribute__ ((visibility("default"))) +jobject MOZ_JNICALL +Java_org_mozilla_gecko_mozglue_NativeZip__1getInputStream(JNIEnv *jenv, jobject jzip, jlong obj, jstring path) +{ + Zip *zip = (Zip *)obj; + const char* str; + str = jenv->GetStringUTFChars(path, nullptr); + + Zip::Stream stream; + bool res = zip->GetStream(str, &stream); + jenv->ReleaseStringUTFChars(path, str); + if (!res) { + return nullptr; + } + jobject buf = jenv->NewDirectByteBuffer(const_cast<void *>(stream.GetBuffer()), stream.GetSize()); + if (!buf) { + JNI_Throw(jenv, "java/lang/RuntimeException", "Failed to create ByteBuffer"); + return nullptr; + } + jclass nativeZip = jenv->GetObjectClass(jzip); + jmethodID method = jenv->GetMethodID(nativeZip, "createInputStream", "(Ljava/nio/ByteBuffer;I)Ljava/io/InputStream;"); + // Since this function is only expected to be called from Java, it is safe + // to skip exception checking for the method call below, as long as no + // other Native -> Java call doesn't happen before returning to Java. + return jenv->CallObjectMethod(jzip, method, buf, (jint) stream.GetType()); +} diff --git a/mozglue/android/pbkdf2_sha256.c b/mozglue/android/pbkdf2_sha256.c new file mode 100644 index 0000000000..8e90f386af --- /dev/null +++ b/mozglue/android/pbkdf2_sha256.c @@ -0,0 +1,432 @@ +/*- + * Copyright 2005,2007,2009 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <sys/types.h> + +#include <stdint.h> +#include <string.h> + +#include <sys/endian.h> + +#include "pbkdf2_sha256.h" + +static inline uint32_t +be32dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint32_t)(p[3]) + + ((uint32_t)(p[2]) << 8) + + ((uint32_t)(p[1]) << 16) + + ((uint32_t)(p[0]) << 24)); +} + +static inline void +be32enc(void *pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} + +/* + * Encode a length len/4 vector of (uint32_t) into a length len vector of + * (unsigned char) in big-endian form. Assumes len is a multiple of 4. + */ +static void +be32enc_vect(unsigned char *dst, const uint32_t *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + be32enc(dst + i * 4, src[i]); +} + +/* + * Decode a big-endian length len vector of (unsigned char) into a length + * len/4 vector of (uint32_t). Assumes len is a multiple of 4. + */ +static void +be32dec_vect(uint32_t *dst, const unsigned char *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + dst[i] = be32dec(src + i * 4); +} + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + t0 = h + S1(e) + Ch(e, f, g) + k; \ + t1 = S0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, k) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], \ + S[(66 - i) % 8], S[(67 - i) % 8], \ + S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], \ + W[i] + k) + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void +SHA256_Transform(uint32_t * state, const unsigned char block[64]) +{ + uint32_t W[64]; + uint32_t S[8]; + uint32_t t0, t1; + int i; + + /* 1. Prepare message schedule W. */ + be32dec_vect(W, block, 64); + for (i = 16; i < 64; i++) + W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16]; + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + RNDr(S, W, 0, 0x428a2f98); + RNDr(S, W, 1, 0x71374491); + RNDr(S, W, 2, 0xb5c0fbcf); + RNDr(S, W, 3, 0xe9b5dba5); + RNDr(S, W, 4, 0x3956c25b); + RNDr(S, W, 5, 0x59f111f1); + RNDr(S, W, 6, 0x923f82a4); + RNDr(S, W, 7, 0xab1c5ed5); + RNDr(S, W, 8, 0xd807aa98); + RNDr(S, W, 9, 0x12835b01); + RNDr(S, W, 10, 0x243185be); + RNDr(S, W, 11, 0x550c7dc3); + RNDr(S, W, 12, 0x72be5d74); + RNDr(S, W, 13, 0x80deb1fe); + RNDr(S, W, 14, 0x9bdc06a7); + RNDr(S, W, 15, 0xc19bf174); + RNDr(S, W, 16, 0xe49b69c1); + RNDr(S, W, 17, 0xefbe4786); + RNDr(S, W, 18, 0x0fc19dc6); + RNDr(S, W, 19, 0x240ca1cc); + RNDr(S, W, 20, 0x2de92c6f); + RNDr(S, W, 21, 0x4a7484aa); + RNDr(S, W, 22, 0x5cb0a9dc); + RNDr(S, W, 23, 0x76f988da); + RNDr(S, W, 24, 0x983e5152); + RNDr(S, W, 25, 0xa831c66d); + RNDr(S, W, 26, 0xb00327c8); + RNDr(S, W, 27, 0xbf597fc7); + RNDr(S, W, 28, 0xc6e00bf3); + RNDr(S, W, 29, 0xd5a79147); + RNDr(S, W, 30, 0x06ca6351); + RNDr(S, W, 31, 0x14292967); + RNDr(S, W, 32, 0x27b70a85); + RNDr(S, W, 33, 0x2e1b2138); + RNDr(S, W, 34, 0x4d2c6dfc); + RNDr(S, W, 35, 0x53380d13); + RNDr(S, W, 36, 0x650a7354); + RNDr(S, W, 37, 0x766a0abb); + RNDr(S, W, 38, 0x81c2c92e); + RNDr(S, W, 39, 0x92722c85); + RNDr(S, W, 40, 0xa2bfe8a1); + RNDr(S, W, 41, 0xa81a664b); + RNDr(S, W, 42, 0xc24b8b70); + RNDr(S, W, 43, 0xc76c51a3); + RNDr(S, W, 44, 0xd192e819); + RNDr(S, W, 45, 0xd6990624); + RNDr(S, W, 46, 0xf40e3585); + RNDr(S, W, 47, 0x106aa070); + RNDr(S, W, 48, 0x19a4c116); + RNDr(S, W, 49, 0x1e376c08); + RNDr(S, W, 50, 0x2748774c); + RNDr(S, W, 51, 0x34b0bcb5); + RNDr(S, W, 52, 0x391c0cb3); + RNDr(S, W, 53, 0x4ed8aa4a); + RNDr(S, W, 54, 0x5b9cca4f); + RNDr(S, W, 55, 0x682e6ff3); + RNDr(S, W, 56, 0x748f82ee); + RNDr(S, W, 57, 0x78a5636f); + RNDr(S, W, 58, 0x84c87814); + RNDr(S, W, 59, 0x8cc70208); + RNDr(S, W, 60, 0x90befffa); + RNDr(S, W, 61, 0xa4506ceb); + RNDr(S, W, 62, 0xbef9a3f7); + RNDr(S, W, 63, 0xc67178f2); + + /* 4. Mix local working variables into global state. */ + for (i = 0; i < 8; i++) + state[i] += S[i]; + + /* Clean the stack. */ + memset(W, 0, 256); + memset(S, 0, 32); + t0 = t1 = 0; +} + +static unsigned char PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* Add padding and terminating bit-count. */ +static void +SHA256_Pad(SHA256_CTX * ctx) +{ + unsigned char len[8]; + uint32_t r, plen; + + /* + * Convert length to a vector of bytes -- we do this now rather + * than later because the length will change after we pad. + */ + be32enc_vect(len, ctx->count, 8); + + /* Add 1--64 bytes so that the resulting length is 56 mod 64. */ + r = (ctx->count[1] >> 3) & 0x3f; + plen = (r < 56) ? (56 - r) : (120 - r); + SHA256_Update(ctx, PAD, (size_t)plen); + + /* Add the terminating bit-count. */ + SHA256_Update(ctx, len, 8); +} + +/* SHA-256 initialization. Begins a SHA-256 operation. */ +void +SHA256_Init(SHA256_CTX * ctx) +{ + + /* Zero bits processed so far. */ + ctx->count[0] = ctx->count[1] = 0; + + /* Magic initialization constants. */ + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +/* Add bytes into the hash. */ +void +SHA256_Update(SHA256_CTX * ctx, const void *in, size_t len) +{ + uint32_t bitlen[2]; + uint32_t r; + const unsigned char *src = in; + + /* Number of bytes left in the buffer from previous updates. */ + r = (ctx->count[1] >> 3) & 0x3f; + + /* Convert the length into a number of bits. */ + bitlen[1] = ((uint32_t)len) << 3; + bitlen[0] = (uint32_t)(len >> 29); + + /* Update number of bits. */ + if ((ctx->count[1] += bitlen[1]) < bitlen[1]) + ctx->count[0]++; + ctx->count[0] += bitlen[0]; + + /* Handle the case where we don't need to perform any transforms. */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block. */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks. */ + while (len >= 64) { + SHA256_Transform(ctx->state, src); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer. */ + memcpy(ctx->buf, src, len); +} + +/* + * SHA-256 finalization. Pads the input data, exports the hash value, + * and clears the context state. + */ +void +SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx) +{ + + /* Add padding. */ + SHA256_Pad(ctx); + + /* Write the hash. */ + be32enc_vect(digest, ctx->state, 32); + + /* Clear the context state. */ + memset((void *)ctx, 0, sizeof(*ctx)); +} + +/* Initialize an HMAC-SHA256 operation with the given key. */ +void +HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen) +{ + unsigned char pad[64]; + unsigned char khash[32]; + const unsigned char * K = _K; + size_t i; + + /* If Klen > 64, the key is really SHA256(K). */ + if (Klen > 64) { + SHA256_Init(&ctx->ictx); + SHA256_Update(&ctx->ictx, K, Klen); + SHA256_Final(khash, &ctx->ictx); + K = khash; + Klen = 32; + } + + /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ + SHA256_Init(&ctx->ictx); + memset(pad, 0x36, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->ictx, pad, 64); + + /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ + SHA256_Init(&ctx->octx); + memset(pad, 0x5c, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->octx, pad, 64); + + /* Clean the stack. */ + memset(khash, 0, 32); +} + +/* Add bytes to the HMAC-SHA256 operation. */ +void +HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void *in, size_t len) +{ + + /* Feed data to the inner SHA256 operation. */ + SHA256_Update(&ctx->ictx, in, len); +} + +/* Finish an HMAC-SHA256 operation. */ +void +HMAC_SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX * ctx) +{ + unsigned char ihash[32]; + + /* Finish the inner SHA256 operation. */ + SHA256_Final(ihash, &ctx->ictx); + + /* Feed the inner hash to the outer SHA256 operation. */ + SHA256_Update(&ctx->octx, ihash, 32); + + /* Finish the outer SHA256 operation. */ + SHA256_Final(digest, &ctx->octx); + + /* Clean the stack. */ + memset(ihash, 0, 32); +} + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void +PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, + size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) +{ + HMAC_SHA256_CTX PShctx, hctx; + size_t i; + uint8_t ivec[4]; + uint8_t U[32]; + uint8_t T[32]; + uint64_t j; + int k; + size_t clen; + + /* Compute HMAC state after processing P and S. */ + HMAC_SHA256_Init(&PShctx, passwd, passwdlen); + HMAC_SHA256_Update(&PShctx, salt, saltlen); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivec, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); + HMAC_SHA256_Update(&hctx, ivec, 4); + HMAC_SHA256_Final(U, &hctx); + + /* T_i = U_1 ... */ + memcpy(T, U, 32); + + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + HMAC_SHA256_Init(&hctx, passwd, passwdlen); + HMAC_SHA256_Update(&hctx, U, 32); + HMAC_SHA256_Final(U, &hctx); + + /* ... xor U_j ... */ + for (k = 0; k < 32; k++) + T[k] ^= U[k]; + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen - i * 32; + if (clen > 32) + clen = 32; + memcpy(&buf[i * 32], T, clen); + } + + /* Clean PShctx, since we never called _Final on it. */ + memset(&PShctx, 0, sizeof(HMAC_SHA256_CTX)); +} diff --git a/mozglue/android/pbkdf2_sha256.h b/mozglue/android/pbkdf2_sha256.h new file mode 100644 index 0000000000..ca2af7a8f0 --- /dev/null +++ b/mozglue/android/pbkdf2_sha256.h @@ -0,0 +1,70 @@ +/*- + * Copyright 2005,2007,2009 Colin Percival + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/lib/libmd/sha256.h,v 1.2 2006/01/17 15:35:56 phk Exp $ + */ + +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +#include <stdint.h> + +typedef struct SHA256Context { + uint32_t state[8]; + uint32_t count[2]; + unsigned char buf[64]; +} SHA256_CTX; + +typedef struct HMAC_SHA256Context { + SHA256_CTX ictx; + SHA256_CTX octx; +} HMAC_SHA256_CTX; + +void SHA256_Init(SHA256_CTX *); +void SHA256_Update(SHA256_CTX *, const void *, size_t); +void SHA256_Final(unsigned char [32], SHA256_CTX *); +void HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t); +void HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t); +void HMAC_SHA256_Final(unsigned char [32], HMAC_SHA256_CTX *); + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, + uint64_t, uint8_t *, size_t); + +#ifdef __cplusplus +} +#endif + +#endif /* !_SHA256_H_ */ diff --git a/mozglue/build/AsanOptions.cpp b/mozglue/build/AsanOptions.cpp new file mode 100644 index 0000000000..729c61ef31 --- /dev/null +++ b/mozglue/build/AsanOptions.cpp @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" + +#ifndef _MSC_VER // Not supported by clang-cl yet + +// When running with AddressSanitizer, we need to explicitly set some +// options specific to our codebase to prevent errors during runtime. +// To override these, set the ASAN_OPTIONS environment variable. +// +// Currently, these are: +// +// allow_user_segv_handler=1 - Tell ASan to allow our code to use its +// own SIGSEGV handlers. This is required by ASM.js internally. +// +// alloc_dealloc_mismatch=0 - Disable alloc-dealloc mismatch checking +// in ASan. This is required because we define our own new/delete +// operators that are backed by malloc/free. If one of them gets inlined +// while the other doesn't, ASan will report false positives. +// +// detect_leaks=0 - Disable LeakSanitizer. This is required because +// otherwise leak checking will be enabled for various building and +// testing executables where we don't care much about leaks. Enabling +// this will also likely require setting LSAN_OPTIONS with a suppression +// file, as in build/sanitizers/lsan_suppressions.txt. +// +extern "C" MOZ_ASAN_BLACKLIST +const char* __asan_default_options() { + return "allow_user_segv_handler=1:alloc_dealloc_mismatch=0:detect_leaks=0"; +} + +#endif diff --git a/mozglue/build/BionicGlue.cpp b/mozglue/build/BionicGlue.cpp new file mode 100644 index 0000000000..208fcce68c --- /dev/null +++ b/mozglue/build/BionicGlue.cpp @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <pthread.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <android/log.h> +#include <sys/syscall.h> + +#include "mozilla/Alignment.h" + +#include <vector> + +#define NS_EXPORT __attribute__ ((visibility("default"))) + +#if ANDROID_VERSION < 17 || defined(MOZ_WIDGET_ANDROID) +/* Android doesn't have pthread_atfork(), so we need to use our own. */ +struct AtForkFuncs { + void (*prepare)(void); + void (*parent)(void); + void (*child)(void); +}; + +/* jemalloc's initialization calls pthread_atfork. When pthread_atfork (see + * further below) stores the corresponding data, it's going to allocate memory, + * which will loop back to jemalloc's initialization, leading to a dead-lock. + * So, for that specific vector, we use a special allocator that returns a + * static buffer for small sizes, and force the initial vector capacity to + * a size enough to store one atfork function table. */ +template <typename T> +struct SpecialAllocator: public std::allocator<T> +{ + SpecialAllocator(): bufUsed(false) {} + + inline typename std::allocator<T>::pointer allocate(typename std::allocator<T>::size_type n, const void * = 0) { + if (!bufUsed && n == 1) { + bufUsed = true; + return buf.addr(); + } + return reinterpret_cast<T *>(::operator new(sizeof(T) * n)); + } + + inline void deallocate(typename std::allocator<T>::pointer p, typename std::allocator<T>::size_type n) { + if (p == buf.addr()) + bufUsed = false; + else + ::operator delete(p); + } + + template<typename U> + struct rebind { + typedef SpecialAllocator<U> other; + }; + +private: + mozilla::AlignedStorage2<T> buf; + bool bufUsed; +}; + +static std::vector<AtForkFuncs, SpecialAllocator<AtForkFuncs> > atfork; +#endif + +#ifdef MOZ_WIDGET_GONK +#include "cpuacct.h" + +#if ANDROID_VERSION < 17 || defined(MOZ_WIDGET_ANDROID) +extern "C" NS_EXPORT int +timer_create(clockid_t, struct sigevent*, timer_t*) +{ + __android_log_print(ANDROID_LOG_ERROR, "BionicGlue", "timer_create not supported!"); + abort(); + return -1; +} +#endif + +#else +#define cpuacct_add(x) +#endif + +#if ANDROID_VERSION < 17 || defined(MOZ_WIDGET_ANDROID) +extern "C" NS_EXPORT int +pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) +{ + AtForkFuncs funcs; + funcs.prepare = prepare; + funcs.parent = parent; + funcs.child = child; + if (!atfork.capacity()) + atfork.reserve(1); + atfork.push_back(funcs); + return 0; +} + +extern "C" NS_EXPORT pid_t __fork(void); + +extern "C" NS_EXPORT pid_t +fork(void) +{ + pid_t pid; + for (auto it = atfork.rbegin(); + it < atfork.rend(); ++it) + if (it->prepare) + it->prepare(); + + switch ((pid = syscall(__NR_clone, SIGCHLD, NULL, NULL, NULL, NULL))) { + case 0: + cpuacct_add(getuid()); + for (auto it = atfork.begin(); + it < atfork.end(); ++it) + if (it->child) + it->child(); + break; + default: + for (auto it = atfork.begin(); + it < atfork.end(); ++it) + if (it->parent) + it->parent(); + } + return pid; +} +#endif + +extern "C" NS_EXPORT int +raise(int sig) +{ + // Bug 741272: Bionic incorrectly uses kill(), which signals the + // process, and thus could signal another thread (and let this one + // return "successfully" from raising a fatal signal). + // + // Bug 943170: POSIX specifies pthread_kill(pthread_self(), sig) as + // equivalent to raise(sig), but Bionic also has a bug with these + // functions, where a forked child will kill its parent instead. + + extern pid_t gettid(void); + return syscall(__NR_tgkill, getpid(), gettid(), sig); +} + +/* Flash plugin uses symbols that are not present in Android >= 4.4 */ +#ifndef MOZ_WIDGET_GONK +namespace android { + namespace VectorImpl { + NS_EXPORT void reservedVectorImpl1(void) { } + NS_EXPORT void reservedVectorImpl2(void) { } + NS_EXPORT void reservedVectorImpl3(void) { } + NS_EXPORT void reservedVectorImpl4(void) { } + NS_EXPORT void reservedVectorImpl5(void) { } + NS_EXPORT void reservedVectorImpl6(void) { } + NS_EXPORT void reservedVectorImpl7(void) { } + NS_EXPORT void reservedVectorImpl8(void) { } + } +} +#endif + diff --git a/mozglue/build/Makefile.in b/mozglue/build/Makefile.in new file mode 100644 index 0000000000..1cfa41ea04 --- /dev/null +++ b/mozglue/build/Makefile.in @@ -0,0 +1,23 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# For FORCE_SHARED_LIB +include $(topsrcdir)/config/config.mk + +ifeq (WINNT,$(OS_TARGET)) +mozglue.def: mozglue.def.in $(GLOBAL_DEPS) + $(call py_action,preprocessor,$(if $(MOZ_REPLACE_MALLOC),-DMOZ_REPLACE_MALLOC) $(ACDEFINES) $< -o $@) + +GARBAGE += mozglue.def +endif + +include $(topsrcdir)/mozglue/build/replace_malloc.mk + +ifdef MOZ_LINKER +ifeq (arm, $(TARGET_CPU)) +OS_LDFLAGS += -Wl,-version-script,$(srcdir)/arm-eabi-filter +endif + +endif diff --git a/mozglue/build/SSE.cpp b/mozglue/build/SSE.cpp new file mode 100644 index 0000000000..8ad228684c --- /dev/null +++ b/mozglue/build/SSE.cpp @@ -0,0 +1,206 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use SSE instructions */ + +#include "SSE.h" + +#ifdef HAVE_CPUID_H +// cpuid.h is available on gcc 4.3 and higher on i386 and x86_64 +#include <cpuid.h> +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) +// MSVC 2005 or newer on x86-32 or x86-64 +#include <intrin.h> +#endif + +namespace { + +// SSE.h has parallel #ifs which declare MOZILLA_SSE_HAVE_CPUID_DETECTION. +// We can't declare these functions in the header file, however, because +// <intrin.h> conflicts with <windows.h> on MSVC 2005, and some files want to +// include both SSE.h and <windows.h>. + +#ifdef HAVE_CPUID_H + +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +static bool +has_cpuid_bits(unsigned int level, CPUIDRegister reg, unsigned int bits) +{ + unsigned int regs[4]; + unsigned int eax, ebx, ecx, edx; + unsigned max = __get_cpuid_max(0, NULL); + if (level > max) + return false; + __cpuid_count(level, 0, eax, ebx, ecx, edx); + regs[0] = eax; + regs[1] = ebx; + regs[2] = ecx; + regs[3] = edx; + return (regs[reg] & bits) == bits; +} + +#if !defined(MOZILLA_PRESUME_AVX) +static uint64_t xgetbv(uint32_t xcr) { + uint32_t eax, edx; + __asm__ ( ".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(xcr)); + return (uint64_t)(edx) << 32 | eax; +} +#endif + +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) + +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +static bool +has_cpuid_bits(unsigned int level, CPUIDRegister reg, unsigned int bits) +{ + // Check that the level in question is supported. + int regs[4]; + __cpuid(regs, level & 0x80000000u); + if (unsigned(regs[0]) < level) + return false; + + // "The __cpuid intrinsic clears the ECX register before calling the cpuid instruction." + __cpuid(regs, level); + return (unsigned(regs[reg]) & bits) == bits; +} + +#if !defined(MOZILLA_PRESUME_AVX) +static uint64_t xgetbv(uint32_t xcr) { return _xgetbv(xcr); } +#endif + +#elif (defined(__GNUC__) || defined(__SUNPRO_CC)) && (defined(__i386) || defined(__x86_64__)) + +enum CPUIDRegister { eax = 0, ebx = 1, ecx = 2, edx = 3 }; + +#ifdef __i386 +static void +moz_cpuid(int CPUInfo[4], int InfoType) +{ + asm ( + "xchg %esi, %ebx\n" + "xor %ecx, %ecx\n" // ecx is the sub-leaf (we only ever need 0) + "cpuid\n" + "movl %eax, (%edi)\n" + "movl %ebx, 4(%edi)\n" + "movl %ecx, 8(%edi)\n" + "movl %edx, 12(%edi)\n" + "xchg %esi, %ebx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %edi + : "%ecx", "%edx", "%esi" + ); +} +#else +static void +moz_cpuid(int CPUInfo[4], int InfoType) +{ + asm ( + "xchg %rsi, %rbx\n" + "xor %ecx, %ecx\n" // ecx is the sub-leaf (we only ever need 0) + "cpuid\n" + "movl %eax, (%rdi)\n" + "movl %ebx, 4(%rdi)\n" + "movl %ecx, 8(%rdi)\n" + "movl %edx, 12(%rdi)\n" + "xchg %rsi, %rbx\n" + : + : "a"(InfoType), // %eax + "D"(CPUInfo) // %rdi + : "%ecx", "%edx", "%rsi" + ); +} +#endif + +static bool +has_cpuid_bits(unsigned int level, CPUIDRegister reg, unsigned int bits) +{ + // Check that the level in question is supported. + volatile int regs[4]; + moz_cpuid((int *)regs, level & 0x80000000u); + if (unsigned(regs[0]) < level) + return false; + + moz_cpuid((int *)regs, level); + return (unsigned(regs[reg]) & bits) == bits; +} + +#endif // end CPUID declarations + +} // namespace + +namespace mozilla { + +namespace sse_private { + +#if defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) + +#if !defined(MOZILLA_PRESUME_MMX) + bool mmx_enabled = has_cpuid_bits(1u, edx, (1u<<23)); +#endif + +#if !defined(MOZILLA_PRESUME_SSE) + bool sse_enabled = has_cpuid_bits(1u, edx, (1u<<25)); +#endif + +#if !defined(MOZILLA_PRESUME_SSE2) + bool sse2_enabled = has_cpuid_bits(1u, edx, (1u<<26)); +#endif + +#if !defined(MOZILLA_PRESUME_SSE3) + bool sse3_enabled = has_cpuid_bits(1u, ecx, (1u<<0)); +#endif + +#if !defined(MOZILLA_PRESUME_SSSE3) + bool ssse3_enabled = has_cpuid_bits(1u, ecx, (1u<<9)); +#endif + +#if !defined(MOZILLA_PRESUME_SSE4A) + bool sse4a_enabled = has_cpuid_bits(0x80000001u, ecx, (1u<<6)); +#endif + +#if !defined(MOZILLA_PRESUME_SSE4_1) + bool sse4_1_enabled = has_cpuid_bits(1u, ecx, (1u<<19)); +#endif + +#if !defined(MOZILLA_PRESUME_SSE4_2) + bool sse4_2_enabled = has_cpuid_bits(1u, ecx, (1u<<20)); +#endif + +#if !defined(MOZILLA_PRESUME_AVX) || !defined(MOZILLA_PRESUME_AVX2) + static bool has_avx() + { +#if defined(MOZILLA_PRESUME_AVX) + return true; +#else + const unsigned AVX = 1u << 28; + const unsigned OSXSAVE = 1u << 27; + const unsigned XSAVE = 1u << 26; + + const unsigned XMM_STATE = 1u << 1; + const unsigned YMM_STATE = 1u << 2; + const unsigned AVX_STATE = XMM_STATE | YMM_STATE; + + return has_cpuid_bits(1u, ecx, AVX | OSXSAVE | XSAVE) && + // ensure the OS supports XSAVE of YMM registers + (xgetbv(0) & AVX_STATE) == AVX_STATE; +#endif // MOZILLA_PRESUME_AVX + } +#endif // !MOZILLA_PRESUME_AVX || !MOZILLA_PRESUME_AVX2 + +#if !defined(MOZILLA_PRESUME_AVX) + bool avx_enabled = has_avx(); +#endif + +#if !defined(MOZILLA_PRESUME_AVX2) + bool avx2_enabled = has_avx() && has_cpuid_bits(7u, ebx, (1u<<5)); +#endif + +#endif + +} // namespace sse_private +} // namespace mozilla diff --git a/mozglue/build/SSE.h b/mozglue/build/SSE.h new file mode 100644 index 0000000000..fe77886409 --- /dev/null +++ b/mozglue/build/SSE.h @@ -0,0 +1,332 @@ +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use SSE instructions */ + +#ifndef mozilla_SSE_h_ +#define mozilla_SSE_h_ + +// for definition of MFBT_DATA +#include "mozilla/Types.h" + +/** + * The public interface of this header consists of a set of macros and + * functions for Intel CPU features. + * + * DETECTING ISA EXTENSIONS + * ======================== + * + * This header provides the following functions for determining whether the + * current CPU supports a particular instruction set extension: + * + * mozilla::supports_mmx + * mozilla::supports_sse + * mozilla::supports_sse2 + * mozilla::supports_sse3 + * mozilla::supports_ssse3 + * mozilla::supports_sse4a + * mozilla::supports_sse4_1 + * mozilla::supports_sse4_2 + * mozilla::supports_avx + * mozilla::supports_avx2 + * + * If you're writing code using inline assembly, you should guard it with a + * call to one of these functions. For instance: + * + * if (mozilla::supports_sse2()) { + * asm(" ... "); + * } + * else { + * ... + * } + * + * Note that these functions depend on cpuid intrinsics only available in gcc + * 4.3 or later and MSVC 8.0 (Visual C++ 2005) or later, so they return false + * in older compilers. (This could be fixed by replacing the code with inline + * assembly.) + * + * + * USING INTRINSICS + * ================ + * + * This header also provides support for coding using CPU intrinsics. + * + * For each mozilla::supports_abc function, we define a MOZILLA_MAY_SUPPORT_ABC + * macro which indicates that the target/compiler combination we're using is + * compatible with the ABC extension. For instance, x86_64 with MSVC 2003 is + * compatible with SSE2 but not SSE3, since although there exist x86_64 CPUs + * with SSE3 support, MSVC 2003 only supports through SSE2. + * + * Until gcc fixes #pragma target [1] [2] or our x86 builds require SSE2, + * you'll need to separate code using intrinsics into a file separate from your + * regular code. Here's the recommended pattern: + * + * #ifdef MOZILLA_MAY_SUPPORT_ABC + * namespace mozilla { + * namespace ABC { + * void foo(); + * } + * } + * #endif + * + * void foo() { + * #ifdef MOZILLA_MAY_SUPPORT_ABC + * if (mozilla::supports_abc()) { + * mozilla::ABC::foo(); // in a separate file + * return; + * } + * #endif + * + * foo_unvectorized(); + * } + * + * You'll need to define mozilla::ABC::foo() in a separate file and add the + * -mabc flag when using gcc. + * + * [1] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=39787 and + * [2] http://gcc.gnu.org/bugzilla/show_bug.cgi?id=41201 being fixed. + * + */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +#ifdef __MMX__ + // It's ok to use MMX instructions based on the -march option (or + // the default for x86_64 or for Intel Mac). + #define MOZILLA_PRESUME_MMX 1 +#endif +#ifdef __SSE__ + // It's ok to use SSE instructions based on the -march option (or + // the default for x86_64 or for Intel Mac). + #define MOZILLA_PRESUME_SSE 1 +#endif +#ifdef __SSE2__ + // It's ok to use SSE2 instructions based on the -march option (or + // the default for x86_64 or for Intel Mac). + #define MOZILLA_PRESUME_SSE2 1 +#endif +#ifdef __SSE3__ + // It's ok to use SSE3 instructions based on the -march option (or the + // default for Intel Mac). + #define MOZILLA_PRESUME_SSE3 1 +#endif +#ifdef __SSSE3__ + // It's ok to use SSSE3 instructions based on the -march option. + #define MOZILLA_PRESUME_SSSE3 1 +#endif +#ifdef __SSE4A__ + // It's ok to use SSE4A instructions based on the -march option. + #define MOZILLA_PRESUME_SSE4A 1 +#endif +#ifdef __SSE4_1__ + // It's ok to use SSE4.1 instructions based on the -march option. + #define MOZILLA_PRESUME_SSE4_1 1 +#endif +#ifdef __SSE4_2__ + // It's ok to use SSE4.2 instructions based on the -march option. + #define MOZILLA_PRESUME_SSE4_2 1 +#endif +#ifdef __AVX__ + // It's ok to use AVX instructions based on the -march option. + #define MOZILLA_PRESUME_AVX 1 +#endif +#ifdef __AVX2__ + // It's ok to use AVX instructions based on the -march option. + #define MOZILLA_PRESUME_AVX2 1 +#endif + + + +#ifdef HAVE_CPUID_H + #define MOZILLA_SSE_HAVE_CPUID_DETECTION +#endif + +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64)) + +#define MOZILLA_SSE_HAVE_CPUID_DETECTION + +#if defined(_M_IX86_FP) + +#if _M_IX86_FP >= 1 + // It's ok to use SSE instructions based on the /arch option + #define MOZILLA_PRESUME_SSE +#endif +#if _M_IX86_FP >= 2 + // It's ok to use SSE2 instructions based on the /arch option + #define MOZILLA_PRESUME_SSE2 +#endif + +#elif defined(_M_AMD64) + // MSVC for AMD64 doesn't support MMX, so don't presume it here. + + // SSE is always available on AMD64. + #define MOZILLA_PRESUME_SSE + // SSE2 is always available on AMD64. + #define MOZILLA_PRESUME_SSE2 +#endif + +#elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__)) +// Sun Studio on x86 or amd64 + +#define MOZILLA_SSE_HAVE_CPUID_DETECTION + +#if defined(__x86_64__) + // MMX is always available on AMD64. + #define MOZILLA_PRESUME_MMX + // SSE is always available on AMD64. + #define MOZILLA_PRESUME_SSE + // SSE2 is always available on AMD64. + #define MOZILLA_PRESUME_SSE2 +#endif + +#endif + +namespace mozilla { + + namespace sse_private { +#if defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#if !defined(MOZILLA_PRESUME_MMX) + extern bool MFBT_DATA mmx_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSE) + extern bool MFBT_DATA sse_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSE2) + extern bool MFBT_DATA sse2_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSE3) + extern bool MFBT_DATA sse3_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSSE3) + extern bool MFBT_DATA ssse3_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSE4A) + extern bool MFBT_DATA sse4a_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSE4_1) + extern bool MFBT_DATA sse4_1_enabled; +#endif +#if !defined(MOZILLA_PRESUME_SSE4_2) + extern bool MFBT_DATA sse4_2_enabled; +#endif +#if !defined(MOZILLA_PRESUME_AVX) + extern bool MFBT_DATA avx_enabled; +#endif +#if !defined(MOZILLA_PRESUME_AVX2) + extern bool MFBT_DATA avx2_enabled; +#endif + + +#endif + } // namespace sse_private + +#if defined(MOZILLA_PRESUME_MMX) +#define MOZILLA_MAY_SUPPORT_MMX 1 + inline bool supports_mmx() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#if !(defined(_MSC_VER) && defined(_M_AMD64)) + // Define MOZILLA_MAY_SUPPORT_MMX only if we're not on MSVC for + // AMD64, since that compiler doesn't support MMX. +#define MOZILLA_MAY_SUPPORT_MMX 1 +#endif + inline bool supports_mmx() { return sse_private::mmx_enabled; } +#else + inline bool supports_mmx() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE) +#define MOZILLA_MAY_SUPPORT_SSE 1 + inline bool supports_sse() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSE 1 + inline bool supports_sse() { return sse_private::sse_enabled; } +#else + inline bool supports_sse() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE2) +#define MOZILLA_MAY_SUPPORT_SSE2 1 + inline bool supports_sse2() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSE2 1 + inline bool supports_sse2() { return sse_private::sse2_enabled; } +#else + inline bool supports_sse2() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE3) +#define MOZILLA_MAY_SUPPORT_SSE3 1 + inline bool supports_sse3() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSE3 1 + inline bool supports_sse3() { return sse_private::sse3_enabled; } +#else + inline bool supports_sse3() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSSE3) +#define MOZILLA_MAY_SUPPORT_SSSE3 1 + inline bool supports_ssse3() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSSE3 1 + inline bool supports_ssse3() { return sse_private::ssse3_enabled; } +#else + inline bool supports_ssse3() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE4A) +#define MOZILLA_MAY_SUPPORT_SSE4A 1 + inline bool supports_sse4a() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSE4A 1 + inline bool supports_sse4a() { return sse_private::sse4a_enabled; } +#else + inline bool supports_sse4a() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE4_1) +#define MOZILLA_MAY_SUPPORT_SSE4_1 1 + inline bool supports_sse4_1() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSE4_1 1 + inline bool supports_sse4_1() { return sse_private::sse4_1_enabled; } +#else + inline bool supports_sse4_1() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_SSE4_2) +#define MOZILLA_MAY_SUPPORT_SSE4_2 1 + inline bool supports_sse4_2() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_SSE4_2 1 + inline bool supports_sse4_2() { return sse_private::sse4_2_enabled; } +#else + inline bool supports_sse4_2() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_AVX) +#define MOZILLA_MAY_SUPPORT_AVX 1 + inline bool supports_avx() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_AVX 1 + inline bool supports_avx() { return sse_private::avx_enabled; } +#else + inline bool supports_avx() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_AVX2) +#define MOZILLA_MAY_SUPPORT_AVX2 1 + inline bool supports_avx2() { return true; } +#elif defined(MOZILLA_SSE_HAVE_CPUID_DETECTION) +#define MOZILLA_MAY_SUPPORT_AVX2 1 + inline bool supports_avx2() { return sse_private::avx2_enabled; } +#else + inline bool supports_avx2() { return false; } +#endif + + +} // namespace mozilla + +#endif /* !defined(mozilla_SSE_h_) */ diff --git a/mozglue/build/WindowsDllBlocklist.cpp b/mozglue/build/WindowsDllBlocklist.cpp new file mode 100644 index 0000000000..a3c6627233 --- /dev/null +++ b/mozglue/build/WindowsDllBlocklist.cpp @@ -0,0 +1,831 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef MOZ_MEMORY +#define MOZ_MEMORY_IMPL +#include "mozmemory_wrap.h" +#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC +// See mozmemory_wrap.h for more details. This file is part of libmozglue, so +// it needs to use _impl suffixes. +#define MALLOC_DECL(name, return_type, ...) \ + extern "C" MOZ_MEMORY_API return_type name ## _impl(__VA_ARGS__); +#include "malloc_decls.h" +#endif + +#include <windows.h> +#include <winternl.h> +#include <io.h> + +#pragma warning( push ) +#pragma warning( disable : 4275 4530 ) // See msvc-stl-wrapper.template.h +#include <map> +#pragma warning( pop ) + +#include "nsAutoPtr.h" + +#include "nsWindowsDllInterceptor.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsVersion.h" +#include "nsWindowsHelpers.h" +#include "WindowsDllBlocklist.h" + +using namespace mozilla; + +#define ALL_VERSIONS ((unsigned long long)-1LL) + +// DLLs sometimes ship without a version number, particularly early +// releases. Blocking "version <= 0" has the effect of blocking unversioned +// DLLs (since the call to get version info fails), but not blocking +// any versioned instance. +#define UNVERSIONED ((unsigned long long)0LL) + +// Convert the 4 (decimal) components of a DLL version number into a +// single unsigned long long, as needed by the blocklist +#define MAKE_VERSION(a,b,c,d)\ + ((a##ULL << 48) + (b##ULL << 32) + (c##ULL << 16) + d##ULL) + +struct DllBlockInfo { + // The name of the DLL -- in LOWERCASE! It will be compared to + // a lowercase version of the DLL name only. + const char *name; + + // If maxVersion is ALL_VERSIONS, we'll block all versions of this + // dll. Otherwise, we'll block all versions less than or equal to + // the given version, as queried by GetFileVersionInfo and + // VS_FIXEDFILEINFO's dwFileVersionMS and dwFileVersionLS fields. + // + // Note that the version is usually 4 components, which is A.B.C.D + // encoded as 0x AAAA BBBB CCCC DDDD ULL (spaces added for clarity), + // but it's not required to be of that format. + // + // If the USE_TIMESTAMP flag is set, then we use the timestamp from + // the IMAGE_FILE_HEADER in lieu of a version number. + unsigned long long maxVersion; + + enum { + FLAGS_DEFAULT = 0, + BLOCK_WIN8PLUS_ONLY = 1, + BLOCK_XP_ONLY = 2, + USE_TIMESTAMP = 4, + } flags; +}; + +static DllBlockInfo sWindowsDllBlocklist[] = { + // EXAMPLE: + // { "uxtheme.dll", ALL_VERSIONS }, + // { "uxtheme.dll", 0x0000123400000000ULL }, + // The DLL name must be in lowercase! + // The version field is a maximum, that is, we block anything that is + // less-than or equal to that version. + + // NPFFAddon - Known malware + { "npffaddon.dll", ALL_VERSIONS}, + + // AVG 8 - Antivirus vendor AVG, old version, plugin already blocklisted + {"avgrsstx.dll", MAKE_VERSION(8,5,0,401)}, + + // calc.dll - Suspected malware + {"calc.dll", MAKE_VERSION(1,0,0,1)}, + + // hook.dll - Suspected malware + {"hook.dll", ALL_VERSIONS}, + + // GoogleDesktopNetwork3.dll - Extremely old, unversioned instances + // of this DLL cause crashes + {"googledesktopnetwork3.dll", UNVERSIONED}, + + // rdolib.dll - Suspected malware + {"rdolib.dll", MAKE_VERSION(6,0,88,4)}, + + // fgjk4wvb.dll - Suspected malware + {"fgjk4wvb.dll", MAKE_VERSION(8,8,8,8)}, + + // radhslib.dll - Naomi internet filter - unmaintained since 2006 + {"radhslib.dll", UNVERSIONED}, + + // Music download filter for vkontakte.ru - old instances + // of this DLL cause crashes + {"vksaver.dll", MAKE_VERSION(2,2,2,0)}, + + // Topcrash in Firefox 4.0b1 + {"rlxf.dll", MAKE_VERSION(1,2,323,1)}, + + // psicon.dll - Topcrashes in Thunderbird, and some crashes in Firefox + // Adobe photoshop library, now redundant in later installations + {"psicon.dll", ALL_VERSIONS}, + + // Topcrash in Firefox 4 betas (bug 618899) + {"accelerator.dll", MAKE_VERSION(3,2,1,6)}, + + // Topcrash with Roboform in Firefox 8 (bug 699134) + {"rf-firefox.dll", MAKE_VERSION(7,6,1,0)}, + {"roboform.dll", MAKE_VERSION(7,6,1,0)}, + + // Topcrash with Babylon Toolbar on FF16+ (bug 721264) + {"babyfox.dll", ALL_VERSIONS}, + + // sprotector.dll crashes, bug 957258 + {"sprotector.dll", ALL_VERSIONS}, + + // leave these two in always for tests + { "mozdllblockingtest.dll", ALL_VERSIONS }, + { "mozdllblockingtest_versioned.dll", 0x0000000400000000ULL }, + + // Windows Media Foundation FLAC decoder and type sniffer (bug 839031). + { "mfflac.dll", ALL_VERSIONS }, + + // Older Relevant Knowledge DLLs cause us to crash (bug 904001). + { "rlnx.dll", MAKE_VERSION(1, 3, 334, 9) }, + { "pmnx.dll", MAKE_VERSION(1, 3, 334, 9) }, + { "opnx.dll", MAKE_VERSION(1, 3, 334, 9) }, + { "prnx.dll", MAKE_VERSION(1, 3, 334, 9) }, + + // Older belgian ID card software causes Firefox to crash or hang on + // shutdown, bug 831285 and 918399. + { "beid35cardlayer.dll", MAKE_VERSION(3, 5, 6, 6968) }, + + // bug 925459, bitguard crashes + { "bitguard.dll", ALL_VERSIONS }, + + // bug 812683 - crashes in Windows library when Asus Gamer OSD is installed + // Software is discontinued/unsupported + { "atkdx11disp.dll", ALL_VERSIONS }, + + // Topcrash with Conduit SearchProtect, bug 944542 + { "spvc32.dll", ALL_VERSIONS }, + + // XP topcrash with F-Secure, bug 970362 + { "fs_ccf_ni_umh32.dll", MAKE_VERSION(1, 42, 101, 0), DllBlockInfo::BLOCK_XP_ONLY }, + + // Topcrash with V-bates, bug 1002748 and bug 1023239 + { "libinject.dll", UNVERSIONED }, + { "libinject2.dll", 0x537DDC93, DllBlockInfo::USE_TIMESTAMP }, + { "libredir2.dll", 0x5385B7ED, DllBlockInfo::USE_TIMESTAMP }, + + // Crashes with RoboForm2Go written against old SDK, bug 988311/1196859 + { "rf-firefox-22.dll", ALL_VERSIONS }, + { "rf-firefox-40.dll", ALL_VERSIONS }, + + // Crashes with DesktopTemperature, bug 1046382 + { "dtwxsvc.dll", 0x53153234, DllBlockInfo::USE_TIMESTAMP }, + + // Startup crashes with Lenovo Onekey Theater, bug 1123778 + { "activedetect32.dll", UNVERSIONED }, + { "activedetect64.dll", UNVERSIONED }, + { "windowsapihookdll32.dll", UNVERSIONED }, + { "windowsapihookdll64.dll", UNVERSIONED }, + + // Flash crashes with RealNetworks RealDownloader, bug 1132663 + { "rndlnpshimswf.dll", ALL_VERSIONS }, + { "rndlmainbrowserrecordplugin.dll", ALL_VERSIONS }, + + // Startup crashes with RealNetworks Browser Record Plugin, bug 1170141 + { "nprpffbrowserrecordext.dll", ALL_VERSIONS }, + { "nprndlffbrowserrecordext.dll", ALL_VERSIONS }, + + // Crashes with CyberLink YouCam, bug 1136968 + { "ycwebcamerasource.ax", MAKE_VERSION(2, 0, 0, 1611) }, + + // Old version of WebcamMax crashes WebRTC, bug 1130061 + { "vwcsource.ax", MAKE_VERSION(1, 5, 0, 0) }, + + // NetOp School, discontinued product, bug 763395 + { "nlsp.dll", MAKE_VERSION(6, 23, 2012, 19) }, + + // Orbit Downloader, bug 1222819 + { "grabdll.dll", MAKE_VERSION(2, 6, 1, 0) }, + { "grabkernel.dll", MAKE_VERSION(1, 0, 0, 1) }, + + // ESET, bug 1229252 + { "eoppmonitor.dll", ALL_VERSIONS }, + + // SS2OSD, bug 1262348 + { "ss2osd.dll", ALL_VERSIONS }, + { "ss2devprops.dll", ALL_VERSIONS }, + + // NHASUSSTRIXOSD.DLL, bug 1269244 + { "nhasusstrixosd.dll", ALL_VERSIONS }, + { "nhasusstrixdevprops.dll", ALL_VERSIONS }, + + // Crashes with PremierOpinion/RelevantKnowledge, bug 1277846 + { "opls.dll", ALL_VERSIONS }, + { "opls64.dll", ALL_VERSIONS }, + { "pmls.dll", ALL_VERSIONS }, + { "pmls64.dll", ALL_VERSIONS }, + { "prls.dll", ALL_VERSIONS }, + { "prls64.dll", ALL_VERSIONS }, + { "rlls.dll", ALL_VERSIONS }, + { "rlls64.dll", ALL_VERSIONS }, + + // Vorbis DirectShow filters, bug 1239690. + { "vorbis.acm", MAKE_VERSION(0, 0, 3, 6) }, + + // AhnLab Internet Security, bug 1311969 + { "nzbrcom.dll", ALL_VERSIONS }, + + // K7TotalSecurity, bug 1339083. + { "k7pswsen.dll", MAKE_VERSION(15, 2, 2, 95) }, + + { nullptr, 0 } +}; + +#ifndef STATUS_DLL_NOT_FOUND +#define STATUS_DLL_NOT_FOUND ((DWORD)0xC0000135L) +#endif + +// define this for very verbose dll load debug spew +#undef DEBUG_very_verbose + +static const char kBlockedDllsParameter[] = "BlockedDllList="; +static const int kBlockedDllsParameterLen = + sizeof(kBlockedDllsParameter) - 1; + +static const char kBlocklistInitFailedParameter[] = "BlocklistInitFailed=1\n"; +static const int kBlocklistInitFailedParameterLen = + sizeof(kBlocklistInitFailedParameter) - 1; + +static const char kUser32BeforeBlocklistParameter[] = "User32BeforeBlocklist=1\n"; +static const int kUser32BeforeBlocklistParameterLen = + sizeof(kUser32BeforeBlocklistParameter) - 1; + +static DWORD sThreadLoadingXPCOMModule; +static bool sBlocklistInitAttempted; +static bool sBlocklistInitFailed; +static bool sUser32BeforeBlocklist; + +// Duplicated from xpcom glue. Ideally this should be shared. +void +printf_stderr(const char *fmt, ...) +{ + if (IsDebuggerPresent()) { + char buf[2048]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + buf[sizeof(buf) - 1] = '\0'; + va_end(args); + OutputDebugStringA(buf); + } + + FILE *fp = _fdopen(_dup(2), "a"); + if (!fp) + return; + + va_list args; + va_start(args, fmt); + vfprintf(fp, fmt, args); + va_end(args); + + fclose(fp); +} + +namespace { + +typedef NTSTATUS (NTAPI *LdrLoadDll_func) (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle); + +static LdrLoadDll_func stub_LdrLoadDll = 0; + +template <class T> +struct RVAMap { + RVAMap(HANDLE map, DWORD offset) { + SYSTEM_INFO info; + GetSystemInfo(&info); + + DWORD alignedOffset = (offset / info.dwAllocationGranularity) * + info.dwAllocationGranularity; + + MOZ_ASSERT(offset - alignedOffset < info.dwAllocationGranularity, "Wtf"); + + mRealView = ::MapViewOfFile(map, FILE_MAP_READ, 0, alignedOffset, + sizeof(T) + (offset - alignedOffset)); + + mMappedView = mRealView ? reinterpret_cast<T*>((char*)mRealView + (offset - alignedOffset)) : + nullptr; + } + ~RVAMap() { + if (mRealView) { + ::UnmapViewOfFile(mRealView); + } + } + operator const T*() const { return mMappedView; } + const T* operator->() const { return mMappedView; } +private: + const T* mMappedView; + void* mRealView; +}; + +bool +CheckASLR(const wchar_t* path) +{ + bool retval = false; + + HANDLE file = ::CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr); + if (file != INVALID_HANDLE_VALUE) { + HANDLE map = ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, + nullptr); + if (map) { + RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0); + if (peHeader) { + RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew); + if (ntHeader) { + // If the DLL has no code, permit it regardless of ASLR status. + if (ntHeader->OptionalHeader.SizeOfCode == 0) { + retval = true; + } + // Check to see if the DLL supports ASLR + else if ((ntHeader->OptionalHeader.DllCharacteristics & + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) != 0) { + retval = true; + } + } + } + ::CloseHandle(map); + } + ::CloseHandle(file); + } + + return retval; +} + +DWORD +GetTimestamp(const wchar_t* path) +{ + DWORD timestamp = 0; + + HANDLE file = ::CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr); + if (file != INVALID_HANDLE_VALUE) { + HANDLE map = ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, + nullptr); + if (map) { + RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0); + if (peHeader) { + RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew); + if (ntHeader) { + timestamp = ntHeader->FileHeader.TimeDateStamp; + } + } + ::CloseHandle(map); + } + ::CloseHandle(file); + } + + return timestamp; +} + +// This lock protects both the reentrancy sentinel and the crash reporter +// data structures. +static CRITICAL_SECTION sLock; + +/** + * Some versions of Windows call LoadLibraryEx to get the version information + * for a DLL, which causes our patched LdrLoadDll implementation to re-enter + * itself and cause infinite recursion and a stack-exhaustion crash. We protect + * against reentrancy by allowing recursive loads of the same DLL. + * + * Note that we don't use __declspec(thread) because that doesn't work in DLLs + * loaded via LoadLibrary and there can be a limited number of TLS slots, so + * we roll our own. + */ +class ReentrancySentinel +{ +public: + explicit ReentrancySentinel(const char* dllName) + { + DWORD currentThreadId = GetCurrentThreadId(); + AutoCriticalSection lock(&sLock); + mPreviousDllName = (*sThreadMap)[currentThreadId]; + + // If there is a DLL currently being loaded and it has the same name + // as the current attempt, we're re-entering. + mReentered = mPreviousDllName && !stricmp(mPreviousDllName, dllName); + (*sThreadMap)[currentThreadId] = dllName; + } + + ~ReentrancySentinel() + { + DWORD currentThreadId = GetCurrentThreadId(); + AutoCriticalSection lock(&sLock); + (*sThreadMap)[currentThreadId] = mPreviousDllName; + } + + bool BailOut() const + { + return mReentered; + }; + + static void InitializeStatics() + { + InitializeCriticalSection(&sLock); + sThreadMap = new std::map<DWORD, const char*>; + } + +private: + static std::map<DWORD, const char*>* sThreadMap; + + const char* mPreviousDllName; + bool mReentered; +}; + +std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap; + +/** + * This is a linked list of DLLs that have been blocked. It doesn't use + * mozilla::LinkedList because this is an append-only list and doesn't need + * to be doubly linked. + */ +class DllBlockSet +{ +public: + static void Add(const char* name, unsigned long long version); + + // Write the list of blocked DLLs to a file HANDLE. This method is run after + // a crash occurs and must therefore not use the heap, etc. + static void Write(HANDLE file); + +private: + DllBlockSet(const char* name, unsigned long long version) + : mName(name) + , mVersion(version) + , mNext(nullptr) + { + } + + const char* mName; // points into the sWindowsDllBlocklist string + unsigned long long mVersion; + DllBlockSet* mNext; + + static DllBlockSet* gFirst; +}; + +DllBlockSet* DllBlockSet::gFirst; + +void +DllBlockSet::Add(const char* name, unsigned long long version) +{ + AutoCriticalSection lock(&sLock); + for (DllBlockSet* b = gFirst; b; b = b->mNext) { + if (0 == strcmp(b->mName, name) && b->mVersion == version) { + return; + } + } + // Not already present + DllBlockSet* n = new DllBlockSet(name, version); + n->mNext = gFirst; + gFirst = n; +} + +void +DllBlockSet::Write(HANDLE file) +{ + // It would be nicer to use AutoCriticalSection here. However, its destructor + // might not run if an exception occurs, in which case we would never leave + // the critical section. (MSVC warns about this possibility.) So we + // enter and leave manually. + ::EnterCriticalSection(&sLock); + + // Because this method is called after a crash occurs, and uses heap memory, + // protect this entire block with a structured exception handler. + MOZ_SEH_TRY { + DWORD nBytes; + for (DllBlockSet* b = gFirst; b; b = b->mNext) { + // write name[,v.v.v.v]; + WriteFile(file, b->mName, strlen(b->mName), &nBytes, nullptr); + if (b->mVersion != -1) { + WriteFile(file, ",", 1, &nBytes, nullptr); + uint16_t parts[4]; + parts[0] = b->mVersion >> 48; + parts[1] = (b->mVersion >> 32) & 0xFFFF; + parts[2] = (b->mVersion >> 16) & 0xFFFF; + parts[3] = b->mVersion & 0xFFFF; + for (int p = 0; p < 4; ++p) { + char buf[32]; + ltoa(parts[p], buf, 10); + WriteFile(file, buf, strlen(buf), &nBytes, nullptr); + if (p != 3) { + WriteFile(file, ".", 1, &nBytes, nullptr); + } + } + } + WriteFile(file, ";", 1, &nBytes, nullptr); + } + } + MOZ_SEH_EXCEPT (EXCEPTION_EXECUTE_HANDLER) { } + + ::LeaveCriticalSection(&sLock); +} + +static UniquePtr<wchar_t[]> +getFullPath (PWCHAR filePath, wchar_t* fname) +{ + // In Windows 8, the first parameter seems to be used for more than just the + // path name. For example, its numerical value can be 1. Passing a non-valid + // pointer to SearchPathW will cause a crash, so we need to check to see if we + // are handed a valid pointer, and otherwise just pass nullptr to SearchPathW. + PWCHAR sanitizedFilePath = nullptr; + if ((uintptr_t(filePath) >= 65536) && ((uintptr_t(filePath) & 1) == 0)) { + sanitizedFilePath = filePath; + } + + // figure out the length of the string that we need + DWORD pathlen = SearchPathW(sanitizedFilePath, fname, L".dll", 0, nullptr, + nullptr); + if (pathlen == 0) { + return nullptr; + } + + auto full_fname = MakeUnique<wchar_t[]>(pathlen+1); + if (!full_fname) { + // couldn't allocate memory? + return nullptr; + } + + // now actually grab it + SearchPathW(sanitizedFilePath, fname, L".dll", pathlen + 1, full_fname.get(), + nullptr); + return full_fname; +} + +// No builtin function to find the last character matching a set +static wchar_t* lastslash(wchar_t* s, int len) +{ + for (wchar_t* c = s + len - 1; c >= s; --c) { + if (*c == L'\\' || *c == L'/') { + return c; + } + } + return nullptr; +} + +static NTSTATUS NTAPI +patched_LdrLoadDll (PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle) +{ + // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename portion +#define DLLNAME_MAX 128 + char dllName[DLLNAME_MAX+1]; + wchar_t *dll_part; + char *dot; + DllBlockInfo *info; + + int len = moduleFileName->Length / 2; + wchar_t *fname = moduleFileName->Buffer; + UniquePtr<wchar_t[]> full_fname; + + // The filename isn't guaranteed to be null terminated, but in practice + // it always will be; ensure that this is so, and bail if not. + // This is done instead of the more robust approach because of bug 527122, + // where lots of weird things were happening when we tried to make a copy. + if (moduleFileName->MaximumLength < moduleFileName->Length+2 || + fname[len] != 0) + { +#ifdef DEBUG + printf_stderr("LdrLoadDll: non-null terminated string found!\n"); +#endif + goto continue_loading; + } + + dll_part = lastslash(fname, len); + if (dll_part) { + dll_part = dll_part + 1; + len -= dll_part - fname; + } else { + dll_part = fname; + } + +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: dll_part '%S' %d\n", dll_part, len); +#endif + + // if it's too long, then, we assume we won't want to block it, + // since DLLNAME_MAX should be at least long enough to hold the longest + // entry in our blocklist. + if (len > DLLNAME_MAX) { +#ifdef DEBUG + printf_stderr("LdrLoadDll: len too long! %d\n", len); +#endif + goto continue_loading; + } + + // copy over to our char byte buffer, lowercasing ASCII as we go + for (int i = 0; i < len; i++) { + wchar_t c = dll_part[i]; + + if (c > 0x7f) { + // welp, it's not ascii; if we need to add non-ascii things to + // our blocklist, we'll have to remove this limitation. + goto continue_loading; + } + + // ensure that dll name is all lowercase + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + dllName[i] = (char) c; + } + + dllName[len] = 0; + +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: dll name '%s'\n", dllName); +#endif + + // Block a suspicious binary that uses various 12-digit hex strings + // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138) + dot = strchr(dllName, '.'); + if (dot && (strchr(dot+1, '.') == dot+13)) { + char * end = nullptr; + _strtoui64(dot+1, &end, 16); + if (end == dot+13) { + return STATUS_DLL_NOT_FOUND; + } + } + // Block binaries where the filename is at least 16 hex digits + if (dot && ((dot - dllName) >= 16)) { + char * current = dllName; + while (current < dot && isxdigit(*current)) { + current++; + } + if (current == dot) { + return STATUS_DLL_NOT_FOUND; + } + } + + // then compare to everything on the blocklist + info = &sWindowsDllBlocklist[0]; + while (info->name) { + if (strcmp(info->name, dllName) == 0) + break; + + info++; + } + + if (info->name) { + bool load_ok = false; + +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: info->name: '%s'\n", info->name); +#endif + + if ((info->flags == DllBlockInfo::BLOCK_WIN8PLUS_ONLY) && + !IsWin8OrLater()) { + goto continue_loading; + } + + if ((info->flags == DllBlockInfo::BLOCK_XP_ONLY) && + IsWin2003OrLater()) { + goto continue_loading; + } + + unsigned long long fVersion = ALL_VERSIONS; + + if (info->maxVersion != ALL_VERSIONS) { + ReentrancySentinel sentinel(dllName); + if (sentinel.BailOut()) { + goto continue_loading; + } + + full_fname = getFullPath(filePath, fname); + if (!full_fname) { + // uh, we couldn't find the DLL at all, so... + printf_stderr("LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find it?)\n", dllName); + return STATUS_DLL_NOT_FOUND; + } + + if (info->flags & DllBlockInfo::USE_TIMESTAMP) { + fVersion = GetTimestamp(full_fname.get()); + if (fVersion > info->maxVersion) { + load_ok = true; + } + } else { + DWORD zero; + DWORD infoSize = GetFileVersionInfoSizeW(full_fname.get(), &zero); + + // If we failed to get the version information, we block. + + if (infoSize != 0) { + auto infoData = MakeUnique<unsigned char[]>(infoSize); + VS_FIXEDFILEINFO *vInfo; + UINT vInfoLen; + + if (GetFileVersionInfoW(full_fname.get(), 0, infoSize, infoData.get()) && + VerQueryValueW(infoData.get(), L"\\", (LPVOID*) &vInfo, &vInfoLen)) + { + fVersion = + ((unsigned long long)vInfo->dwFileVersionMS) << 32 | + ((unsigned long long)vInfo->dwFileVersionLS); + + // finally do the version check, and if it's greater than our block + // version, keep loading + if (fVersion > info->maxVersion) + load_ok = true; + } + } + } + } + + if (!load_ok) { + printf_stderr("LdrLoadDll: Blocking load of '%s' -- see http://www.mozilla.com/en-US/blocklist/\n", dllName); + DllBlockSet::Add(info->name, fVersion); + return STATUS_DLL_NOT_FOUND; + } + } + +continue_loading: +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: continuing load... ('%S')\n", moduleFileName->Buffer); +#endif + + if (GetCurrentThreadId() == sThreadLoadingXPCOMModule) { + // Check to ensure that the DLL has ASLR. + full_fname = getFullPath(filePath, fname); + if (!full_fname) { + // uh, we couldn't find the DLL at all, so... + printf_stderr("LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find it?)\n", dllName); + return STATUS_DLL_NOT_FOUND; + } + + if (IsVistaOrLater() && !CheckASLR(full_fname.get())) { + printf_stderr("LdrLoadDll: Blocking load of '%s'. XPCOM components must support ASLR.\n", dllName); + return STATUS_DLL_NOT_FOUND; + } + } + + return stub_LdrLoadDll(filePath, flags, moduleFileName, handle); +} + +WindowsDllInterceptor NtDllIntercept; + +} // namespace + +MFBT_API void +DllBlocklist_Initialize() +{ + if (sBlocklistInitAttempted) { + return; + } + sBlocklistInitAttempted = true; + + if (GetModuleHandleA("user32.dll")) { + sUser32BeforeBlocklist = true; + } + + NtDllIntercept.Init("ntdll.dll"); + + ReentrancySentinel::InitializeStatics(); + + // We specifically use a detour, because there are cases where external + // code also tries to hook LdrLoadDll, and doesn't know how to relocate our + // nop space patches. (Bug 951827) + bool ok = NtDllIntercept.AddDetour("LdrLoadDll", reinterpret_cast<intptr_t>(patched_LdrLoadDll), (void**) &stub_LdrLoadDll); + + if (!ok) { + sBlocklistInitFailed = true; +#ifdef DEBUG + printf_stderr ("LdrLoadDll hook failed, no dll blocklisting active\n"); +#endif + } +} + +MFBT_API void +DllBlocklist_SetInXPCOMLoadOnMainThread(bool inXPCOMLoadOnMainThread) +{ + if (inXPCOMLoadOnMainThread) { + MOZ_ASSERT(sThreadLoadingXPCOMModule == 0, "Only one thread should be doing this"); + sThreadLoadingXPCOMModule = GetCurrentThreadId(); + } else { + sThreadLoadingXPCOMModule = 0; + } +} + +MFBT_API void +DllBlocklist_WriteNotes(HANDLE file) +{ + DWORD nBytes; + + WriteFile(file, kBlockedDllsParameter, kBlockedDllsParameterLen, &nBytes, nullptr); + DllBlockSet::Write(file); + WriteFile(file, "\n", 1, &nBytes, nullptr); + + if (sBlocklistInitFailed) { + WriteFile(file, kBlocklistInitFailedParameter, + kBlocklistInitFailedParameterLen, &nBytes, nullptr); + } + + if (sUser32BeforeBlocklist) { + WriteFile(file, kUser32BeforeBlocklistParameter, + kUser32BeforeBlocklistParameterLen, &nBytes, nullptr); + } +} + +MFBT_API bool +DllBlocklist_CheckStatus() +{ + if (sBlocklistInitFailed || sUser32BeforeBlocklist) + return false; + return true; +} diff --git a/mozglue/build/WindowsDllBlocklist.h b/mozglue/build/WindowsDllBlocklist.h new file mode 100644 index 0000000000..5afe6b8cee --- /dev/null +++ b/mozglue/build/WindowsDllBlocklist.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_windowsdllblocklist_h +#define mozilla_windowsdllblocklist_h + +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + +#include <windows.h> +#include "mozilla/GuardObjects.h" +#include "mozilla/Attributes.h" + +#define HAS_DLL_BLOCKLIST + +MFBT_API void DllBlocklist_Initialize(); +MFBT_API void DllBlocklist_SetInXPCOMLoadOnMainThread(bool inXPCOMLoadOnMainThread); +MFBT_API void DllBlocklist_WriteNotes(HANDLE file); +MFBT_API bool DllBlocklist_CheckStatus(); + +class MOZ_RAII AutoSetXPCOMLoadOnMainThread +{ + public: + AutoSetXPCOMLoadOnMainThread(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + DllBlocklist_SetInXPCOMLoadOnMainThread(true); + } + + ~AutoSetXPCOMLoadOnMainThread() { + DllBlocklist_SetInXPCOMLoadOnMainThread(false); + } + + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +#endif // defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) +#endif // mozilla_windowsdllblocklist_h diff --git a/mozglue/build/arm-eabi-filter b/mozglue/build/arm-eabi-filter new file mode 100644 index 0000000000..401454ee88 --- /dev/null +++ b/mozglue/build/arm-eabi-filter @@ -0,0 +1,4 @@ +{ + local: + __aeabi*; +}; diff --git a/mozglue/build/arm.cpp b/mozglue/build/arm.cpp new file mode 100644 index 0000000000..74b856a8f6 --- /dev/null +++ b/mozglue/build/arm.cpp @@ -0,0 +1,147 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use various ARM extensions */ + +#include "arm.h" + +#if defined(MOZILLA_ARM_HAVE_CPUID_DETECTION) + +// arm.h has parallel #ifs which declare MOZILLA_ARM_HAVE_CPUID_DETECTION. +// We don't check it here so that we get compile errors if it's defined, but +// we don't compile one of these detection methods. The detection code here is +// based on the CPU detection in libtheora. + +# if defined(__linux__) || defined(ANDROID) +# include <stdio.h> +# include <stdlib.h> +# include <string.h> + +enum{ + MOZILLA_HAS_EDSP_FLAG=1, + MOZILLA_HAS_ARMV6_FLAG=2, + MOZILLA_HAS_ARMV7_FLAG=4, + MOZILLA_HAS_NEON_FLAG=8 +}; + +static unsigned +get_arm_cpu_flags(void) +{ + unsigned flags; + FILE *fin; + bool armv6_processor = false; + flags = 0; + /*Reading /proc/self/auxv would be easier, but that doesn't work reliably on + Android. This also means that detection will fail in Scratchbox, which is + desirable, as NEON does not work in the qemu shipped with the Maemo 5 SDK. + I don't know if /proc/self/auxv would do any better in that case, anyway, + or if it would return random flags from the host CPU.*/ + fin = fopen ("/proc/cpuinfo","r"); + if (fin != nullptr) + { + /*512 should be enough for anybody (it's even enough for all the flags that + x86 has accumulated... so far).*/ + char buf[512]; + while (fgets(buf, 511, fin) != nullptr) + { + if (memcmp(buf, "Features", 8) == 0) + { + char *p; + p = strstr(buf, " edsp"); + if (p != nullptr && (p[5] == ' ' || p[5] == '\n')) + flags |= MOZILLA_HAS_EDSP_FLAG; + p = strstr(buf, " neon"); + if( p != nullptr && (p[5] == ' ' || p[5] == '\n')) + flags |= MOZILLA_HAS_NEON_FLAG; + } + if (memcmp(buf, "CPU architecture:", 17) == 0) + { + int version; + version = atoi(buf + 17); + if (version >= 6) + flags |= MOZILLA_HAS_ARMV6_FLAG; + if (version >= 7) + flags |= MOZILLA_HAS_ARMV7_FLAG; + } + /* media/webrtc/trunk/src/system_wrappers/source/cpu_features_arm.c + * Unfortunately, it seems that certain ARMv6-based CPUs + * report an incorrect architecture number of 7! + * + * We try to correct this by looking at the 'elf_format' + * field reported by the 'Processor' field, which is of the + * form of "(v7l)" for an ARMv7-based CPU, and "(v6l)" for + * an ARMv6-one. + */ + if (memcmp(buf, "Processor\t:", 11) == 0) { + if (strstr(buf, "(v6l)") != 0) { + armv6_processor = true; + } + } + } + fclose(fin); + } + if (armv6_processor) { + // ARMv6 pretending to be ARMv7? clear flag + if (flags & MOZILLA_HAS_ARMV7_FLAG) { + flags &= ~MOZILLA_HAS_ARMV7_FLAG; + } + } + return flags; +} + +// Cache a local copy so we only have to read /proc/cpuinfo once. +static unsigned arm_cpu_flags = get_arm_cpu_flags(); + +# if !defined(MOZILLA_PRESUME_EDSP) +static bool +check_edsp(void) +{ + return (arm_cpu_flags & MOZILLA_HAS_EDSP_FLAG) != 0; +} +# endif + +# if !defined(MOZILLA_PRESUME_ARMV6) +static bool +check_armv6(void) +{ + return (arm_cpu_flags & MOZILLA_HAS_ARMV6_FLAG) != 0; +} +# endif + +# if !defined(MOZILLA_PRESUME_ARMV7) +static bool +check_armv7(void) +{ + return (arm_cpu_flags & MOZILLA_HAS_ARMV7_FLAG) != 0; +} +# endif + +# if !defined(MOZILLA_PRESUME_NEON) +static bool +check_neon(void) +{ + return (arm_cpu_flags & MOZILLA_HAS_NEON_FLAG) != 0; +} +# endif + +# endif // defined(__linux__) || defined(ANDROID) + +namespace mozilla { + namespace arm_private { +# if !defined(MOZILLA_PRESUME_EDSP) + bool edsp_enabled = check_edsp(); +# endif +# if !defined(MOZILLA_PRESUME_ARMV6) + bool armv6_enabled = check_armv6(); +# endif +# if !defined(MOZILLA_PRESUME_ARMV7) + bool armv7_enabled = check_armv7(); +# endif +# if !defined(MOZILLA_PRESUME_NEON) + bool neon_enabled = check_neon(); +# endif + } // namespace arm_private +} // namespace mozilla + +#endif // MOZILLA_ARM_HAVE_CPUID_DETECTION diff --git a/mozglue/build/arm.h b/mozglue/build/arm.h new file mode 100644 index 0000000000..e3379f67b0 --- /dev/null +++ b/mozglue/build/arm.h @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use SSE instructions */ + +#ifndef mozilla_arm_h_ +#define mozilla_arm_h_ + +// for definition of MFBT_DATA +#include "mozilla/Types.h" + +/* This is patterned after SSE.h, but provides ARMv5E, ARMv6, and NEON + detection. For reasons similar to the SSE code, code using NEON (even just + in inline asm) needs to be in a separate compilation unit from the regular + code, because it requires an ".fpu neon" directive which can't be undone. + ARMv5E and ARMv6 code may also require an .arch directive, since by default + the assembler refuses to generate code for opcodes outside of its current + .arch setting. + + TODO: Add Thumb, Thumb2, VFP, iwMMX, etc. detection, if we need it. */ + +#if defined(__GNUC__) && defined(__arm__) + +# define MOZILLA_ARM_ARCH 3 + +# if defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) \ + || defined(_ARM_ARCH_4) +# undef MOZILLA_ARM_ARCH +# define MOZILLA_ARM_ARCH 4 +# endif + +# if defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__) \ + || defined(__ARM_ARCH_5E__) || defined(__ARM_ARCH_5TE__) \ + || defined(__ARM_ARCH_5TEJ__) || defined(_ARM_ARCH_5) +# undef MOZILLA_ARM_ARCH +# define MOZILLA_ARM_ARCH 5 +# endif + +# if defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) \ + || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) \ + || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) \ + || defined(__ARM_ARCH_6M__) || defined(_ARM_ARCH_6) +# undef MOZILLA_ARM_ARCH +# define MOZILLA_ARM_ARCH 6 +# endif + +# if defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) \ + || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) \ + || defined(__ARM_ARCH_7EM__) || defined(_ARM_ARCH_7) +# undef MOZILLA_ARM_ARCH +# define MOZILLA_ARM_ARCH 7 +# endif + + +# ifdef __GNUC__ +# define MOZILLA_MAY_SUPPORT_EDSP 1 + +# if defined(HAVE_ARM_SIMD) +# define MOZILLA_MAY_SUPPORT_ARMV6 1 +# endif + +# if defined(HAVE_ARM_NEON) +# define MOZILLA_MAY_SUPPORT_NEON 1 +# endif + +# if defined(HAVE_ARM_SIMD) +# define MOZILLA_MAY_SUPPORT_ARMV7 1 +# endif +# endif + + // When using -mfpu=neon, gcc generates neon instructions. + +# if defined(__ARM_NEON__) +# define MOZILLA_PRESUME_NEON 1 +# endif + + // Currently we only have CPU detection for Linux via /proc/cpuinfo +# if defined(__linux__) || defined(ANDROID) +# define MOZILLA_ARM_HAVE_CPUID_DETECTION 1 +# endif + +#endif + +namespace mozilla { + + namespace arm_private { +#if defined(MOZILLA_ARM_HAVE_CPUID_DETECTION) +#if !defined(MOZILLA_PRESUME_EDSP) + extern bool MFBT_DATA edsp_enabled; +#endif +#if !defined(MOZILLA_PRESUME_ARMV6) + extern bool MFBT_DATA armv6_enabled; +#endif +#if !defined(MOZILLA_PRESUME_ARMV7) + extern bool MFBT_DATA armv7_enabled; +#endif +#if !defined(MOZILLA_PRESUME_NEON) + extern bool MFBT_DATA neon_enabled; +#endif +#endif + } // namespace arm_private + +#if defined(MOZILLA_PRESUME_EDSP) +# define MOZILLA_MAY_SUPPORT_EDSP 1 + inline bool supports_edsp() { return true; } +#elif defined(MOZILLA_MAY_SUPPORT_EDSP) \ + && defined(MOZILLA_ARM_HAVE_CPUID_DETECTION) + inline bool supports_edsp() { return arm_private::edsp_enabled; } +#else + inline bool supports_edsp() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_ARMV6) +# define MOZILLA_MAY_SUPPORT_ARMV6 1 + inline bool supports_armv6() { return true; } +#elif defined(MOZILLA_MAY_SUPPORT_ARMV6) \ + && defined(MOZILLA_ARM_HAVE_CPUID_DETECTION) + inline bool supports_armv6() { return arm_private::armv6_enabled; } +#else + inline bool supports_armv6() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_ARMV7) +# define MOZILLA_MAY_SUPPORT_ARMV7 1 + inline bool supports_armv7() { return true; } +#elif defined(MOZILLA_MAY_SUPPORT_ARMV7) \ + && defined(MOZILLA_ARM_HAVE_CPUID_DETECTION) + inline bool supports_armv7() { return arm_private::armv7_enabled; } +#else + inline bool supports_armv7() { return false; } +#endif + +#if defined(MOZILLA_PRESUME_NEON) +# define MOZILLA_MAY_SUPPORT_NEON 1 + inline bool supports_neon() { return true; } +#elif defined(MOZILLA_MAY_SUPPORT_NEON) \ + && defined(MOZILLA_ARM_HAVE_CPUID_DETECTION) + inline bool supports_neon() { return arm_private::neon_enabled; } +#else + inline bool supports_neon() { return false; } +#endif + +} // namespace mozilla + +#endif /* !defined(mozilla_arm_h_) */ diff --git a/mozglue/build/cpuacct.c b/mozglue/build/cpuacct.c new file mode 100644 index 0000000000..ce7d98edac --- /dev/null +++ b/mozglue/build/cpuacct.c @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <unistd.h> +#include <errno.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include "cpuacct.h" + +int cpuacct_add(uid_t uid) +{ + int count; + int fd; + char buf[80]; + + count = snprintf(buf, sizeof(buf), "/acct/uid/%d/tasks", uid); + fd = open(buf, O_RDWR|O_CREAT|O_TRUNC|O_SYNC); + if (fd < 0) { + /* Note: sizeof("tasks") returns 6, which includes the NULL char */ + buf[count - sizeof("tasks")] = 0; + if (mkdir(buf, 0775) < 0) + return -errno; + + /* Note: sizeof("tasks") returns 6, which includes the NULL char */ + buf[count - sizeof("tasks")] = '/'; + fd = open(buf, O_RDWR|O_CREAT|O_TRUNC|O_SYNC); + } + if (fd < 0) + return -errno; + + write(fd, "0", 2); + if (close(fd)) + return -errno; + + return 0; +} diff --git a/mozglue/build/cpuacct.h b/mozglue/build/cpuacct.h new file mode 100644 index 0000000000..8e24c8cc89 --- /dev/null +++ b/mozglue/build/cpuacct.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _BIONIC_CPUACCT_H +#define _BIONIC_CPUACCT_H + +#include <sys/cdefs.h> +#include <sys/types.h> + +__BEGIN_DECLS + +extern int cpuacct_add(uid_t uid); + +__END_DECLS + +#endif /* _BIONIC_CPUACCT_H */ diff --git a/mozglue/build/dummy.cpp b/mozglue/build/dummy.cpp new file mode 100644 index 0000000000..47866547ff --- /dev/null +++ b/mozglue/build/dummy.cpp @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +void _dummy(void) { +} diff --git a/mozglue/build/mips.cpp b/mozglue/build/mips.cpp new file mode 100644 index 0000000000..6a015db8af --- /dev/null +++ b/mozglue/build/mips.cpp @@ -0,0 +1,47 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use MIPS-specific extensions */ + +#include "mips.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +enum{ + MIPS_FLAG_LOONGSON3 = 1, +}; + +static unsigned +get_mips_cpu_flags(void) +{ + unsigned flags = 0; + FILE *fin; + + fin = fopen("/proc/cpuinfo","r"); + if (fin != nullptr) { + char buf[1024]; + memset(buf, 0, sizeof(buf)); + fread(buf, sizeof(char), sizeof(buf) - 1, fin); + fclose(fin); + if (strstr(buf, "Loongson-3")) + flags |= MIPS_FLAG_LOONGSON3; + } + return flags; +} + +static bool +check_loongson3(void) +{ + // Cache a local copy so we only have to read /proc/cpuinfo once. + static unsigned mips_cpu_flags = get_mips_cpu_flags(); + return (mips_cpu_flags & MIPS_FLAG_LOONGSON3) != 0; +} + +namespace mozilla { + namespace mips_private { + bool isLoongson3 = check_loongson3(); + } // namespace mips_private +} // namespace mozilla diff --git a/mozglue/build/mips.h b/mozglue/build/mips.h new file mode 100644 index 0000000000..ff31cbc875 --- /dev/null +++ b/mozglue/build/mips.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* compile-time and runtime tests for whether to use MIPS-specific extensions */ + +#ifndef mozilla_mips_h_ +#define mozilla_mips_h_ + +// for definition of MFBT_DATA +#include "mozilla/Types.h" + +namespace mozilla { + + namespace mips_private { + extern bool MFBT_DATA isLoongson3; + } // namespace mips_private + + inline bool supports_mmi() { +#ifdef __mips__ + return mips_private::isLoongson3; +#else + return false; +#endif + } + +} // namespace mozilla + +#endif /* !defined(mozilla_mips_h_) */ diff --git a/mozglue/build/moz.build b/mozglue/build/moz.build new file mode 100644 index 0000000000..d289747785 --- /dev/null +++ b/mozglue/build/moz.build @@ -0,0 +1,108 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Build mozglue as a shared lib on Windows, OSX and Android. +# If this is ever changed, update MOZ_SHARED_MOZGLUE in browser/installer/Makefile.in +if CONFIG['OS_TARGET'] in ('WINNT', 'Darwin', 'Android'): + SharedLibrary('mozglue') +else: + Library('mozglue') + +SDK_LIBRARY = True + +if CONFIG['OS_TARGET'] == 'Android': + SOURCES += [ + 'BionicGlue.cpp', + ] + +if CONFIG['MOZ_ASAN']: + SOURCES += [ + 'AsanOptions.cpp', + ] + +if CONFIG['OS_TARGET'] == 'WINNT': + DEFFILE = 'mozglue.def' + # We'll break the DLL blocklist if we immediately load user32.dll + DELAYLOAD_DLLS += [ + 'user32.dll', + ] + +if not CONFIG['JS_STANDALONE']: + + if CONFIG['MOZ_MEMORY'] and (CONFIG['MOZ_SYSTEM_JEMALLOC'] or FORCE_SHARED_LIB): + pass + # TODO: SHARED_LIBRARY_LIBS go here + else: + # Temporary, until bug 662814 lands + NO_VISIBILITY_FLAGS = True + SOURCES += [ + 'dummy.cpp', + ] + + if CONFIG['OS_TARGET'] == 'WINNT': + LOCAL_INCLUDES += [ + '/memory/build', + ] + SOURCES += [ + 'WindowsDllBlocklist.cpp', + ] + DISABLE_STL_WRAPPING = True + OS_LIBS += [ + 'version', + ] + + EXPORTS.mozilla += [ + 'arm.h', + 'mips.h', + 'SSE.h', + 'WindowsDllBlocklist.h', + ] + + if CONFIG['CPU_ARCH'].startswith('x86'): + SOURCES += [ + 'SSE.cpp', + ] + + if CONFIG['CPU_ARCH'] == 'arm': + SOURCES += [ + 'arm.cpp', + ] + + if CONFIG['CPU_ARCH'].startswith('mips'): + SOURCES += [ + 'mips.cpp', + ] + + if CONFIG['MOZ_LINKER']: + USE_LIBS += [ + 'zlib', + ] + + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + SOURCES += [ + 'cpuacct.c', + ] + +USE_LIBS += [ + 'mfbt', +] + +DEFINES['IMPL_MFBT'] = True +LIBRARY_DEFINES['MOZ_HAS_MOZGLUE'] = True + +LDFLAGS += CONFIG['MOZ_GLUE_WRAP_LDFLAGS'] + +if CONFIG['OS_TARGET'] == 'Darwin': + # On OSX 10.10.3, a dead lock happens in some cases involving dynamic + # symbol resolution for symbols that jemalloc itself uses. While it + # might be possible to find a way to avoid all such symbol resolutions, + # it's currently not possible because at the very least there's a call + # to pthread_self from tsd_init_check_recursion, which is necessary + # because somehow clang doesn't want to accept the __thread keyword + # for TLS. + LDFLAGS += ['-Wl,-bind_at_load'] + +DIST_INSTALL = True diff --git a/mozglue/build/mozglue.def.in b/mozglue/build/mozglue.def.in new file mode 100644 index 0000000000..62eb3caed4 --- /dev/null +++ b/mozglue/build/mozglue.def.in @@ -0,0 +1,39 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LIBRARY mozglue.dll + +EXPORTS +#ifdef MOZ_MEMORY + ; symbols that are actually useful +#ifdef MOZ_REPLACE_MALLOC + malloc=malloc_impl + calloc=calloc_impl + realloc=realloc_impl + free=free_impl + posix_memalign=posix_memalign_impl + malloc_usable_size=malloc_usable_size_impl + malloc_good_size=malloc_good_size_impl + _aligned_free=free_impl +#else + malloc=je_malloc + calloc=je_calloc + realloc=je_realloc + free=je_free + posix_memalign=je_posix_memalign + malloc_usable_size=je_malloc_usable_size + malloc_good_size=je_malloc_good_size + _aligned_free=je_free +#endif + _aligned_malloc + strndup=wrap_strndup + strdup=wrap_strdup + _strdup=wrap_strdup + wcsdup=wrap_wcsdup + _wcsdup=wrap_wcsdup + jemalloc_stats + jemalloc_free_dirty_pages + ; A hack to work around the CRT (see giant comment in Makefile.in) + frex=dumb_free_thunk +#endif diff --git a/mozglue/build/replace_malloc.mk b/mozglue/build/replace_malloc.mk new file mode 100644 index 0000000000..7f436f2eea --- /dev/null +++ b/mozglue/build/replace_malloc.mk @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +ifeq (Darwin_1,$(OS_TARGET)_$(MOZ_REPLACE_MALLOC)) +OS_LDFLAGS += \ + -Wl,-U,_replace_init \ + -Wl,-U,_replace_get_bridge \ + -Wl,-U,_replace_malloc \ + -Wl,-U,_replace_posix_memalign \ + -Wl,-U,_replace_aligned_alloc \ + -Wl,-U,_replace_calloc \ + -Wl,-U,_replace_realloc \ + -Wl,-U,_replace_free \ + -Wl,-U,_replace_memalign \ + -Wl,-U,_replace_valloc \ + -Wl,-U,_replace_malloc_usable_size \ + -Wl,-U,_replace_malloc_good_size \ + -Wl,-U,_replace_jemalloc_stats \ + -Wl,-U,_replace_jemalloc_purge_freed_pages \ + -Wl,-U,_replace_jemalloc_free_dirty_pages \ + $(NULL) + +ifneq ($(MOZ_REPLACE_MALLOC_LINKAGE),compiler support) +OS_LDFLAGS += -flat_namespace +endif +ifeq ($(MOZ_REPLACE_MALLOC_LINKAGE),dummy library) +OS_LDFLAGS += -Wl,-weak_library,$(DEPTH)/memory/replace/dummy/$(DLL_PREFIX)dummy_replace_malloc$(DLL_SUFFIX) +endif + +EXTRA_DEPS += $(topsrcdir)/mozglue/build/replace_malloc.mk +endif diff --git a/mozglue/linker/BaseElf.cpp b/mozglue/linker/BaseElf.cpp new file mode 100644 index 0000000000..8a78285ef6 --- /dev/null +++ b/mozglue/linker/BaseElf.cpp @@ -0,0 +1,215 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BaseElf.h" +#include "Elfxx.h" +#include "Logging.h" +#include "mozilla/RefPtr.h" + +using namespace Elf; +using namespace mozilla; + +unsigned long +BaseElf::Hash(const char *symbol) +{ + const unsigned char *sym = reinterpret_cast<const unsigned char *>(symbol); + unsigned long h = 0, g; + while (*sym) { + h = (h << 4) + *sym++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + return h; +} + +void * +BaseElf::GetSymbolPtr(const char *symbol) const +{ + return GetSymbolPtr(symbol, Hash(symbol)); +} + +void * +BaseElf::GetSymbolPtr(const char *symbol, unsigned long hash) const +{ + const Sym *sym = GetSymbol(symbol, hash); + void *ptr = nullptr; + if (sym && sym->st_shndx != SHN_UNDEF) + ptr = GetPtr(sym->st_value); + DEBUG_LOG("BaseElf::GetSymbolPtr(%p [\"%s\"], \"%s\") = %p", + reinterpret_cast<const void *>(this), GetPath(), symbol, ptr); + return ptr; +} + +const Sym * +BaseElf::GetSymbol(const char *symbol, unsigned long hash) const +{ + /* Search symbol with the buckets and chains tables. + * The hash computed from the symbol name gives an index in the buckets + * table. The corresponding value in the bucket table is an index in the + * symbols table and in the chains table. + * If the corresponding symbol in the symbols table matches, we're done. + * Otherwise, the corresponding value in the chains table is a new index + * in both tables, which corresponding symbol is tested and so on and so + * forth */ + size_t bucket = hash % buckets.numElements(); + for (size_t y = buckets[bucket]; y != STN_UNDEF; y = chains[y]) { + if (strcmp(symbol, strtab.GetStringAt(symtab[y].st_name))) + continue; + return &symtab[y]; + } + return nullptr; +} + +bool +BaseElf::Contains(void *addr) const +{ + return base.Contains(addr); +} + +#ifdef __ARM_EABI__ +const void * +BaseElf::FindExidx(int *pcount) const +{ + if (arm_exidx) { + *pcount = arm_exidx.numElements(); + return arm_exidx; + } + *pcount = 0; + return nullptr; +} +#endif + +already_AddRefed<LibHandle> +LoadedElf::Create(const char *path, void *base_addr) +{ + DEBUG_LOG("LoadedElf::Create(\"%s\", %p) = ...", path, base_addr); + + uint8_t mapped; + /* If the page is not mapped, mincore returns an error. If base_addr is + * nullptr, as would happen if the corresponding binary is prelinked with + * the prelink look (but not with the android apriori tool), no page being + * mapped there (right?), mincore returns an error, too, which makes + * prelinked libraries on glibc unsupported. This is not an interesting + * use case for now, so don't try supporting that case. + */ + if (mincore(const_cast<void*>(base_addr), PageSize(), &mapped)) + return nullptr; + + RefPtr<LoadedElf> elf = new LoadedElf(path); + + const Ehdr *ehdr = Ehdr::validate(base_addr); + if (!ehdr) + return nullptr; + + Addr min_vaddr = (Addr) -1; // We want to find the lowest and biggest + Addr max_vaddr = 0; // virtual address used by this Elf. + const Phdr *dyn = nullptr; +#ifdef __ARM_EABI__ + const Phdr *arm_exidx_phdr = nullptr; +#endif + + Array<Phdr> phdrs(reinterpret_cast<const char *>(ehdr) + ehdr->e_phoff, + ehdr->e_phnum); + for (auto phdr = phdrs.begin(); phdr < phdrs.end(); ++phdr) { + switch (phdr->p_type) { + case PT_LOAD: + if (phdr->p_vaddr < min_vaddr) + min_vaddr = phdr->p_vaddr; + if (max_vaddr < phdr->p_vaddr + phdr->p_memsz) + max_vaddr = phdr->p_vaddr + phdr->p_memsz; + break; + case PT_DYNAMIC: + dyn = &*phdr; + break; +#ifdef __ARM_EABI__ + case PT_ARM_EXIDX: + /* We cannot initialize arm_exidx here + because we don't have a base yet */ + arm_exidx_phdr = &*phdr; + break; +#endif + } + } + + /* If the lowest PT_LOAD virtual address in headers is not 0, then the ELF + * is either prelinked or a non-PIE executable. The former case is not + * possible, because base_addr would be nullptr and the mincore test above + * would already have made us return. + * For a non-PIE executable, PT_LOADs contain absolute addresses, so in + * practice, this means min_vaddr should be equal to base_addr. max_vaddr + * can thus be adjusted accordingly. + */ + if (min_vaddr != 0) { + void *min_vaddr_ptr = reinterpret_cast<void *>( + static_cast<uintptr_t>(min_vaddr)); + if (min_vaddr_ptr != base_addr) { + LOG("%s: %p != %p", elf->GetPath(), min_vaddr_ptr, base_addr); + return nullptr; + } + max_vaddr -= min_vaddr; + } + if (!dyn) { + LOG("%s: No PT_DYNAMIC segment found", elf->GetPath()); + return nullptr; + } + + elf->base.Assign(base_addr, max_vaddr); + + if (!elf->InitDyn(dyn)) + return nullptr; + +#ifdef __ARM_EABI__ + if (arm_exidx_phdr) + elf->arm_exidx.InitSize(elf->GetPtr(arm_exidx_phdr->p_vaddr), + arm_exidx_phdr->p_memsz); +#endif + + DEBUG_LOG("LoadedElf::Create(\"%s\", %p) = %p", path, base_addr, + static_cast<void *>(elf)); + + ElfLoader::Singleton.Register(elf); + return elf.forget(); +} + +bool +LoadedElf::InitDyn(const Phdr *pt_dyn) +{ + Array<Dyn> dyns; + dyns.InitSize(GetPtr<Dyn>(pt_dyn->p_vaddr), pt_dyn->p_filesz); + + size_t symnum = 0; + for (auto dyn = dyns.begin(); dyn < dyns.end() && dyn->d_tag; ++dyn) { + switch (dyn->d_tag) { + case DT_HASH: + { + DEBUG_LOG("%s 0x%08" PRIxAddr, "DT_HASH", dyn->d_un.d_val); + const Elf::Word *hash_table_header = \ + GetPtr<Elf::Word>(dyn->d_un.d_ptr); + symnum = hash_table_header[1]; + buckets.Init(&hash_table_header[2], hash_table_header[0]); + chains.Init(&*buckets.end()); + } + break; + case DT_STRTAB: + DEBUG_LOG("%s 0x%08" PRIxAddr, "DT_STRTAB", dyn->d_un.d_val); + strtab.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case DT_SYMTAB: + DEBUG_LOG("%s 0x%08" PRIxAddr, "DT_SYMTAB", dyn->d_un.d_val); + symtab.Init(GetPtr(dyn->d_un.d_ptr)); + break; + } + } + if (!buckets || !symnum) { + ERROR("%s: Missing or broken DT_HASH", GetPath()); + } else if (!strtab) { + ERROR("%s: Missing DT_STRTAB", GetPath()); + } else if (!symtab) { + ERROR("%s: Missing DT_SYMTAB", GetPath()); + } else { + return true; + } + return false; +} diff --git a/mozglue/linker/BaseElf.h b/mozglue/linker/BaseElf.h new file mode 100644 index 0000000000..19f9061bce --- /dev/null +++ b/mozglue/linker/BaseElf.h @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BaseElf_h +#define BaseElf_h + +#include "ElfLoader.h" +#include "Elfxx.h" + + +/** + * Base class for ELF libraries. This class includes things that will be + * common between SystemElfs and CustomElfs. + */ +class BaseElf: public LibHandle +{ +public: + /** + * Hash function for symbol lookup, as defined in ELF standard for System V. + */ + static unsigned long Hash(const char *symbol); + + /** + * Returns the address corresponding to the given symbol name (with a + * pre-computed hash). + */ + void *GetSymbolPtr(const char *symbol, unsigned long hash) const; + + /** + * Returns a pointer to the Elf Symbol in the Dynamic Symbol table + * corresponding to the given symbol name (with a pre-computed hash). + */ + const Elf::Sym *GetSymbol(const char *symbol, unsigned long hash) const; + + BaseElf(const char *path, Mappable *mappable = nullptr) + : LibHandle(path) + , mappable(mappable) + { + } + +protected: + /** + * Inherited from LibHandle. Those are temporary and are not supposed to + * be used. + */ + virtual void *GetSymbolPtr(const char *symbol) const; + virtual bool Contains(void *addr) const; + virtual void *GetBase() const { return GetPtr(0); } + +#ifdef __ARM_EABI__ + virtual const void *FindExidx(int *pcount) const; +#endif + + virtual Mappable *GetMappable() const { return NULL; }; + +public: +/* private: */ + /** + * Returns a pointer relative to the base address where the library is + * loaded. + */ + void *GetPtr(const Elf::Addr offset) const + { + if (reinterpret_cast<void *>(offset) > base) + return reinterpret_cast<void *>(offset); + return base + offset; + } + + /** + * Like the above, but returns a typed (const) pointer + */ + template <typename T> + const T *GetPtr(const Elf::Addr offset) const + { + if (reinterpret_cast<void *>(offset) > base) + return reinterpret_cast<const T *>(offset); + return reinterpret_cast<const T *>(base + offset); + } + + /* Appropriated Mappable */ + /* /!\ we rely on this being nullptr for BaseElf instances, but not + * CustomElf instances. */ + RefPtr<Mappable> mappable; + + /* Base address where the library is loaded */ + MappedPtr base; + + /* Buckets and chains for the System V symbol hash table */ + Array<Elf::Word> buckets; + UnsizedArray<Elf::Word> chains; + +/* protected: */ + /* String table */ + Elf::Strtab strtab; + + /* Symbol table */ + UnsizedArray<Elf::Sym> symtab; + +#ifdef __ARM_EABI__ + /* ARM.exidx information used by FindExidx */ + Array<uint32_t[2]> arm_exidx; +#endif +}; + + +/** + * Class for ELF libraries that already loaded in memory. + */ +class LoadedElf: public BaseElf +{ +public: + /** + * Returns a LoadedElf corresponding to the already loaded ELF + * at the given base address. + */ + static already_AddRefed<LibHandle> Create(const char *path, + void *base_addr); + +private: + LoadedElf(const char *path) + : BaseElf(path) { } + + ~LoadedElf() + { + /* Avoid base's destructor unmapping something that doesn't actually + * belong to it. */ + base.release(); + ElfLoader::Singleton.Forget(this); + } + + /** + * Initializes the library according to information found in the given + * PT_DYNAMIC header. + * Returns whether this succeeded or failed. + */ + bool InitDyn(const Elf::Phdr *pt_dyn); +}; + + +#endif /* BaseElf_h */ diff --git a/mozglue/linker/CustomElf.cpp b/mozglue/linker/CustomElf.cpp new file mode 100644 index 0000000000..dbab0cf0d1 --- /dev/null +++ b/mozglue/linker/CustomElf.cpp @@ -0,0 +1,774 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <cstring> +#include <sys/mman.h> +#include <vector> +#include <dlfcn.h> +#include <signal.h> +#include <string.h> +#include "CustomElf.h" +#include "BaseElf.h" +#include "Mappable.h" +#include "Logging.h" + +using namespace Elf; +using namespace mozilla; + +/* TODO: Fill ElfLoader::Singleton.lastError on errors. */ + +/* Function used to report library mappings from the custom linker to Gecko + * crash reporter */ +#ifdef ANDROID +extern "C" { + void report_mapping(char *name, void *base, uint32_t len, uint32_t offset); + void delete_mapping(const char *name); +} +#else +#define report_mapping(...) +#define delete_mapping(...) +#endif + +const Ehdr *Ehdr::validate(const void *buf) +{ + if (!buf || buf == MAP_FAILED) + return nullptr; + + const Ehdr *ehdr = reinterpret_cast<const Ehdr *>(buf); + + /* Only support ELF executables or libraries for the host system */ + if (memcmp(ELFMAG, &ehdr->e_ident, SELFMAG) || + ehdr->e_ident[EI_CLASS] != ELFCLASS || + ehdr->e_ident[EI_DATA] != ELFDATA || + ehdr->e_ident[EI_VERSION] != 1 || + (ehdr->e_ident[EI_OSABI] != ELFOSABI && ehdr->e_ident[EI_OSABI] != ELFOSABI_NONE) || +#ifdef EI_ABIVERSION + ehdr->e_ident[EI_ABIVERSION] != ELFABIVERSION || +#endif + (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) || + ehdr->e_machine != ELFMACHINE || + ehdr->e_version != 1 || + ehdr->e_phentsize != sizeof(Phdr)) + return nullptr; + + return ehdr; +} + +namespace { + +void debug_phdr(const char *type, const Phdr *phdr) +{ + DEBUG_LOG("%s @0x%08" PRIxAddr " (" + "filesz: 0x%08" PRIxAddr ", " + "memsz: 0x%08" PRIxAddr ", " + "offset: 0x%08" PRIxAddr ", " + "flags: %c%c%c)", + type, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz, + phdr->p_offset, phdr->p_flags & PF_R ? 'r' : '-', + phdr->p_flags & PF_W ? 'w' : '-', phdr->p_flags & PF_X ? 'x' : '-'); +} + +static int p_flags_to_mprot(Word flags) +{ + return ((flags & PF_X) ? PROT_EXEC : 0) | + ((flags & PF_W) ? PROT_WRITE : 0) | + ((flags & PF_R) ? PROT_READ : 0); +} + +} /* anonymous namespace */ + +/** + * RAII wrapper for a mapping of the first page off a Mappable object. + * This calls Mappable::munmap instead of system munmap. + */ +class Mappable1stPagePtr: public GenericMappedPtr<Mappable1stPagePtr> { +public: + Mappable1stPagePtr(Mappable *mappable) + : GenericMappedPtr<Mappable1stPagePtr>( + mappable->mmap(nullptr, PageSize(), PROT_READ, MAP_PRIVATE, 0)) + , mappable(mappable) + { + /* Ensure the content of this page */ + mappable->ensure(*this); + } + +private: + friend class GenericMappedPtr<Mappable1stPagePtr>; + void munmap(void *buf, size_t length) { + mappable->munmap(buf, length); + } + + RefPtr<Mappable> mappable; +}; + + +already_AddRefed<LibHandle> +CustomElf::Load(Mappable *mappable, const char *path, int flags) +{ + DEBUG_LOG("CustomElf::Load(\"%s\", 0x%x) = ...", path, flags); + if (!mappable) + return nullptr; + /* Keeping a RefPtr of the CustomElf is going to free the appropriate + * resources when returning nullptr */ + RefPtr<CustomElf> elf = new CustomElf(mappable, path); + /* Map the first page of the Elf object to access Elf and program headers */ + Mappable1stPagePtr ehdr_raw(mappable); + if (ehdr_raw == MAP_FAILED) + return nullptr; + + const Ehdr *ehdr = Ehdr::validate(ehdr_raw); + if (!ehdr) + return nullptr; + + /* Scan Elf Program Headers and gather some information about them */ + std::vector<const Phdr *> pt_loads; + Addr min_vaddr = (Addr) -1; // We want to find the lowest and biggest + Addr max_vaddr = 0; // virtual address used by this Elf. + const Phdr *dyn = nullptr; + + const Phdr *first_phdr = reinterpret_cast<const Phdr *>( + reinterpret_cast<const char *>(ehdr) + ehdr->e_phoff); + const Phdr *end_phdr = &first_phdr[ehdr->e_phnum]; +#ifdef __ARM_EABI__ + const Phdr *arm_exidx_phdr = nullptr; +#endif + + for (const Phdr *phdr = first_phdr; phdr < end_phdr; phdr++) { + switch (phdr->p_type) { + case PT_LOAD: + debug_phdr("PT_LOAD", phdr); + pt_loads.push_back(phdr); + if (phdr->p_vaddr < min_vaddr) + min_vaddr = phdr->p_vaddr; + if (max_vaddr < phdr->p_vaddr + phdr->p_memsz) + max_vaddr = phdr->p_vaddr + phdr->p_memsz; + break; + case PT_DYNAMIC: + debug_phdr("PT_DYNAMIC", phdr); + if (!dyn) { + dyn = phdr; + } else { + ERROR("%s: Multiple PT_DYNAMIC segments detected", elf->GetPath()); + return nullptr; + } + break; + case PT_TLS: + debug_phdr("PT_TLS", phdr); + if (phdr->p_memsz) { + ERROR("%s: TLS is not supported", elf->GetPath()); + return nullptr; + } + break; + case PT_GNU_STACK: + debug_phdr("PT_GNU_STACK", phdr); +// Skip on Android until bug 706116 is fixed +#ifndef ANDROID + if (phdr->p_flags & PF_X) { + ERROR("%s: Executable stack is not supported", elf->GetPath()); + return nullptr; + } +#endif + break; +#ifdef __ARM_EABI__ + case PT_ARM_EXIDX: + /* We cannot initialize arm_exidx here + because we don't have a base yet */ + arm_exidx_phdr = phdr; + break; +#endif + default: + DEBUG_LOG("%s: Program header type #%d not handled", + elf->GetPath(), phdr->p_type); + } + } + + if (min_vaddr != 0) { + ERROR("%s: Unsupported minimal virtual address: 0x%08" PRIxAddr, + elf->GetPath(), min_vaddr); + return nullptr; + } + if (!dyn) { + ERROR("%s: No PT_DYNAMIC segment found", elf->GetPath()); + return nullptr; + } + + /* Reserve enough memory to map the complete virtual address space for this + * library. + * As we are using the base address from here to mmap something else with + * MAP_FIXED | MAP_SHARED, we need to make sure these mmaps will work. For + * instance, on armv6, MAP_SHARED mappings require a 16k alignment, but mmap + * MAP_PRIVATE only returns a 4k aligned address. So we first get a base + * address with MAP_SHARED, which guarantees the kernel returns an address + * that we'll be able to use with MAP_FIXED, and then remap MAP_PRIVATE at + * the same address, because of some bad side effects of keeping it as + * MAP_SHARED. */ + elf->base.Assign(MemoryRange::mmap(nullptr, max_vaddr, PROT_NONE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if ((elf->base == MAP_FAILED) || + (mmap(elf->base, max_vaddr, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != elf->base)) { + ERROR("%s: Failed to mmap", elf->GetPath()); + return nullptr; + } + + /* Load and initialize library */ + for (std::vector<const Phdr *>::iterator it = pt_loads.begin(); + it < pt_loads.end(); ++it) + if (!elf->LoadSegment(*it)) + return nullptr; + + /* We're not going to mmap anymore */ + mappable->finalize(); + + report_mapping(const_cast<char *>(elf->GetName()), elf->base, + (max_vaddr + PAGE_SIZE - 1) & PAGE_MASK, 0); + + elf->l_addr = elf->base; + elf->l_name = elf->GetPath(); + elf->l_ld = elf->GetPtr<Dyn>(dyn->p_vaddr); + ElfLoader::Singleton.Register(elf); + + if (!elf->InitDyn(dyn)) + return nullptr; + + if (elf->has_text_relocs) { + for (std::vector<const Phdr *>::iterator it = pt_loads.begin(); + it < pt_loads.end(); ++it) + mprotect(PageAlignedPtr(elf->GetPtr((*it)->p_vaddr)), + PageAlignedEndPtr((*it)->p_memsz), + p_flags_to_mprot((*it)->p_flags) | PROT_WRITE); + } + + if (!elf->Relocate() || !elf->RelocateJumps()) + return nullptr; + + if (elf->has_text_relocs) { + for (std::vector<const Phdr *>::iterator it = pt_loads.begin(); + it < pt_loads.end(); ++it) + mprotect(PageAlignedPtr(elf->GetPtr((*it)->p_vaddr)), + PageAlignedEndPtr((*it)->p_memsz), + p_flags_to_mprot((*it)->p_flags)); + } + + if (!elf->CallInit()) + return nullptr; + +#ifdef __ARM_EABI__ + if (arm_exidx_phdr) + elf->arm_exidx.InitSize(elf->GetPtr(arm_exidx_phdr->p_vaddr), + arm_exidx_phdr->p_memsz); +#endif + + if (MOZ_UNLIKELY(Logging::isVerbose())) { + elf->stats("oneLibLoaded"); + } + DEBUG_LOG("CustomElf::Load(\"%s\", 0x%x) = %p", path, flags, + static_cast<void *>(elf)); + return elf.forget(); +} + +CustomElf::~CustomElf() +{ + DEBUG_LOG("CustomElf::~CustomElf(%p [\"%s\"])", + reinterpret_cast<void *>(this), GetPath()); + CallFini(); + /* Normally, __cxa_finalize is called by the .fini function. However, + * Android NDK before r6b doesn't do that. Our wrapped cxa_finalize only + * calls destructors once, so call it in all cases. */ + ElfLoader::__wrap_cxa_finalize(this); + ElfLoader::Singleton.Forget(this); + delete_mapping(GetName()); +} + +void * +CustomElf::GetSymbolPtrInDeps(const char *symbol) const +{ + /* Resolve dlopen and related functions to point to ours */ + if (symbol[0] == 'd' && symbol[1] == 'l') { + if (strcmp(symbol + 2, "open") == 0) + return FunctionPtr(__wrap_dlopen); + if (strcmp(symbol + 2, "error") == 0) + return FunctionPtr(__wrap_dlerror); + if (strcmp(symbol + 2, "close") == 0) + return FunctionPtr(__wrap_dlclose); + if (strcmp(symbol + 2, "sym") == 0) + return FunctionPtr(__wrap_dlsym); + if (strcmp(symbol + 2, "addr") == 0) + return FunctionPtr(__wrap_dladdr); + if (strcmp(symbol + 2, "_iterate_phdr") == 0) + return FunctionPtr(__wrap_dl_iterate_phdr); + } else if (symbol[0] == '_' && symbol[1] == '_') { + /* Resolve a few C++ ABI specific functions to point to ours */ +#ifdef __ARM_EABI__ + if (strcmp(symbol + 2, "aeabi_atexit") == 0) + return FunctionPtr(&ElfLoader::__wrap_aeabi_atexit); +#else + if (strcmp(symbol + 2, "cxa_atexit") == 0) + return FunctionPtr(&ElfLoader::__wrap_cxa_atexit); +#endif + if (strcmp(symbol + 2, "cxa_finalize") == 0) + return FunctionPtr(&ElfLoader::__wrap_cxa_finalize); + if (strcmp(symbol + 2, "dso_handle") == 0) + return const_cast<CustomElf *>(this); + if (strcmp(symbol + 2, "moz_linker_stats") == 0) + return FunctionPtr(&ElfLoader::stats); +#ifdef __ARM_EABI__ + if (strcmp(symbol + 2, "gnu_Unwind_Find_exidx") == 0) + return FunctionPtr(__wrap___gnu_Unwind_Find_exidx); +#endif + } else if (symbol[0] == 's' && symbol[1] == 'i') { + if (strcmp(symbol + 2, "gnal") == 0) + return FunctionPtr(signal); + if (strcmp(symbol + 2, "gaction") == 0) + return FunctionPtr(sigaction); + } + + void *sym; + + unsigned long hash = Hash(symbol); + + /* self_elf should never be NULL, but better safe than sorry. */ + if (ElfLoader::Singleton.self_elf) { + /* We consider the library containing this code a permanent LD_PRELOAD, + * so, check if the symbol exists here first. */ + sym = static_cast<BaseElf *>( + ElfLoader::Singleton.self_elf.get())->GetSymbolPtr(symbol, hash); + if (sym) + return sym; + } + + /* Then search the symbol in our dependencies. Since we already searched in + * libraries the system linker loaded, skip those (on glibc systems). We + * also assume the symbol is to be found in one of the dependent libraries + * directly, not in their own dependent libraries. Building libraries with + * --no-allow-shlib-undefined ensures such indirect symbol dependency don't + * happen. */ + for (std::vector<RefPtr<LibHandle> >::const_iterator it = dependencies.begin(); + it < dependencies.end(); ++it) { + /* Skip if it's the library containing this code, since we've already + * looked at it above. */ + if (*it == ElfLoader::Singleton.self_elf) + continue; + if (BaseElf *be = (*it)->AsBaseElf()) { + sym = be->GetSymbolPtr(symbol, hash); + } else { + sym = (*it)->GetSymbolPtr(symbol); + } + if (sym) + return sym; + } + return nullptr; +} + +void +CustomElf::stats(const char *when) const +{ + mappable->stats(when, GetPath()); +} + +bool +CustomElf::LoadSegment(const Phdr *pt_load) const +{ + if (pt_load->p_type != PT_LOAD) { + DEBUG_LOG("%s: Elf::LoadSegment only takes PT_LOAD program headers", GetPath()); + return false;; + } + + int prot = p_flags_to_mprot(pt_load->p_flags); + + /* Mmap at page boundary */ + Addr align = PageSize(); + Addr align_offset; + void *mapped, *where; + do { + align_offset = pt_load->p_vaddr - AlignedPtr(pt_load->p_vaddr, align); + where = GetPtr(pt_load->p_vaddr - align_offset); + DEBUG_LOG("%s: Loading segment @%p %c%c%c", GetPath(), where, + prot & PROT_READ ? 'r' : '-', + prot & PROT_WRITE ? 'w' : '-', + prot & PROT_EXEC ? 'x' : '-'); + mapped = mappable->mmap(where, pt_load->p_filesz + align_offset, + prot, MAP_PRIVATE | MAP_FIXED, + pt_load->p_offset - align_offset); + if ((mapped != MAP_FAILED) || (pt_load->p_vaddr == 0) || + (pt_load->p_align == align)) + break; + /* The virtual address space for the library is properly aligned at + * 16k on ARMv6 (see CustomElf::Load), and so is the first segment + * (p_vaddr == 0). But subsequent segments may not be 16k aligned + * and fail to mmap. In such case, try to mmap again at the p_align + * boundary instead of page boundary. */ + DEBUG_LOG("%s: Failed to mmap, retrying", GetPath()); + align = pt_load->p_align; + } while (1); + + if (mapped != where) { + if (mapped == MAP_FAILED) { + ERROR("%s: Failed to mmap", GetPath()); + } else { + ERROR("%s: Didn't map at the expected location (wanted: %p, got: %p)", + GetPath(), where, mapped); + } + return false; + } + + /* Ensure the availability of all pages within the mapping if on-demand + * decompression is disabled (MOZ_LINKER_ONDEMAND=0 or signal handler not + * registered). */ + const char *ondemand = getenv("MOZ_LINKER_ONDEMAND"); + if (!ElfLoader::Singleton.hasRegisteredHandler() || + (ondemand && !strncmp(ondemand, "0", 2 /* Including '\0' */))) { + for (Addr off = 0; off < pt_load->p_filesz + align_offset; + off += PageSize()) { + mappable->ensure(reinterpret_cast<char *>(mapped) + off); + } + } + /* When p_memsz is greater than p_filesz, we need to have nulled out memory + * after p_filesz and before p_memsz. + * Above the end of the last page, and up to p_memsz, we already have nulled + * out memory because we mapped anonymous memory on the whole library virtual + * address space. We just need to adjust this anonymous memory protection + * flags. */ + if (pt_load->p_memsz > pt_load->p_filesz) { + Addr file_end = pt_load->p_vaddr + pt_load->p_filesz; + Addr mem_end = pt_load->p_vaddr + pt_load->p_memsz; + Addr next_page = PageAlignedEndPtr(file_end); + if (next_page > file_end) { + /* The library is not registered at this point, so we can't rely on + * on-demand decompression to handle missing pages here. */ + void *ptr = GetPtr(file_end); + mappable->ensure(ptr); + memset(ptr, 0, next_page - file_end); + } + if (mem_end > next_page) { + if (mprotect(GetPtr(next_page), mem_end - next_page, prot) < 0) { + ERROR("%s: Failed to mprotect", GetPath()); + return false; + } + } + } + return true; +} + +namespace { + +void debug_dyn(const char *type, const Dyn *dyn) +{ + DEBUG_LOG("%s 0x%08" PRIxAddr, type, dyn->d_un.d_val); +} + +} /* anonymous namespace */ + +bool +CustomElf::InitDyn(const Phdr *pt_dyn) +{ + /* Scan PT_DYNAMIC segment and gather some information */ + const Dyn *first_dyn = GetPtr<Dyn>(pt_dyn->p_vaddr); + const Dyn *end_dyn = GetPtr<Dyn>(pt_dyn->p_vaddr + pt_dyn->p_filesz); + std::vector<Word> dt_needed; + size_t symnum = 0; + for (const Dyn *dyn = first_dyn; dyn < end_dyn && dyn->d_tag; dyn++) { + switch (dyn->d_tag) { + case DT_NEEDED: + debug_dyn("DT_NEEDED", dyn); + dt_needed.push_back(dyn->d_un.d_val); + break; + case DT_HASH: + { + debug_dyn("DT_HASH", dyn); + const Word *hash_table_header = GetPtr<Word>(dyn->d_un.d_ptr); + symnum = hash_table_header[1]; + buckets.Init(&hash_table_header[2], hash_table_header[0]); + chains.Init(&*buckets.end()); + } + break; + case DT_STRTAB: + debug_dyn("DT_STRTAB", dyn); + strtab.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case DT_SYMTAB: + debug_dyn("DT_SYMTAB", dyn); + symtab.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case DT_SYMENT: + debug_dyn("DT_SYMENT", dyn); + if (dyn->d_un.d_val != sizeof(Sym)) { + ERROR("%s: Unsupported DT_SYMENT", GetPath()); + return false; + } + break; + case DT_TEXTREL: + if (strcmp("libflashplayer.so", GetName()) == 0) { + has_text_relocs = true; + } else { + ERROR("%s: Text relocations are not supported", GetPath()); + return false; + } + break; + case DT_STRSZ: /* Ignored */ + debug_dyn("DT_STRSZ", dyn); + break; + case UNSUPPORTED_RELOC(): + case UNSUPPORTED_RELOC(SZ): + case UNSUPPORTED_RELOC(ENT): + ERROR("%s: Unsupported relocations", GetPath()); + return false; + case RELOC(): + debug_dyn(STR_RELOC(), dyn); + relocations.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case RELOC(SZ): + debug_dyn(STR_RELOC(SZ), dyn); + relocations.InitSize(dyn->d_un.d_val); + break; + case RELOC(ENT): + debug_dyn(STR_RELOC(ENT), dyn); + if (dyn->d_un.d_val != sizeof(Reloc)) { + ERROR("%s: Unsupported DT_RELENT", GetPath()); + return false; + } + break; + case DT_JMPREL: + debug_dyn("DT_JMPREL", dyn); + jumprels.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case DT_PLTRELSZ: + debug_dyn("DT_PLTRELSZ", dyn); + jumprels.InitSize(dyn->d_un.d_val); + break; + case DT_PLTGOT: + debug_dyn("DT_PLTGOT", dyn); + break; + case DT_INIT: + debug_dyn("DT_INIT", dyn); + init = dyn->d_un.d_ptr; + break; + case DT_INIT_ARRAY: + debug_dyn("DT_INIT_ARRAY", dyn); + init_array.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case DT_INIT_ARRAYSZ: + debug_dyn("DT_INIT_ARRAYSZ", dyn); + init_array.InitSize(dyn->d_un.d_val); + break; + case DT_FINI: + debug_dyn("DT_FINI", dyn); + fini = dyn->d_un.d_ptr; + break; + case DT_FINI_ARRAY: + debug_dyn("DT_FINI_ARRAY", dyn); + fini_array.Init(GetPtr(dyn->d_un.d_ptr)); + break; + case DT_FINI_ARRAYSZ: + debug_dyn("DT_FINI_ARRAYSZ", dyn); + fini_array.InitSize(dyn->d_un.d_val); + break; + case DT_PLTREL: + if (dyn->d_un.d_val != RELOC()) { + ERROR("%s: Error: DT_PLTREL is not " STR_RELOC(), GetPath()); + return false; + } + break; + case DT_FLAGS: + { + Addr flags = dyn->d_un.d_val; + /* Treat as a DT_TEXTREL tag */ + if (flags & DF_TEXTREL) { + if (strcmp("libflashplayer.so", GetName()) == 0) { + has_text_relocs = true; + } else { + ERROR("%s: Text relocations are not supported", GetPath()); + return false; + } + } + /* we can treat this like having a DT_SYMBOLIC tag */ + flags &= ~DF_SYMBOLIC; + if (flags) + WARN("%s: unhandled flags #%" PRIxAddr" not handled", + GetPath(), flags); + } + break; + case DT_SONAME: /* Should match GetName(), but doesn't matter */ + case DT_SYMBOLIC: /* Indicates internal symbols should be looked up in + * the library itself first instead of the executable, + * which is actually what this linker does by default */ + case RELOC(COUNT): /* Indicates how many relocations are relative, which + * is usually used to skip relocations on prelinked + * libraries. They are not supported anyways. */ + case UNSUPPORTED_RELOC(COUNT): /* This should error out, but it doesn't + * really matter. */ + case DT_FLAGS_1: /* Additional linker-internal flags that we don't care about. See + * DF_1_* values in src/include/elf/common.h in binutils. */ + case DT_VERSYM: /* DT_VER* entries are used for symbol versioning, which */ + case DT_VERDEF: /* this linker doesn't support yet. */ + case DT_VERDEFNUM: + case DT_VERNEED: + case DT_VERNEEDNUM: + /* Ignored */ + break; + default: + WARN("%s: dynamic header type #%" PRIxAddr" not handled", + GetPath(), dyn->d_tag); + } + } + + if (!buckets || !symnum) { + ERROR("%s: Missing or broken DT_HASH", GetPath()); + return false; + } + if (!strtab) { + ERROR("%s: Missing DT_STRTAB", GetPath()); + return false; + } + if (!symtab) { + ERROR("%s: Missing DT_SYMTAB", GetPath()); + return false; + } + + /* Load dependent libraries */ + for (size_t i = 0; i < dt_needed.size(); i++) { + const char *name = strtab.GetStringAt(dt_needed[i]); + RefPtr<LibHandle> handle = + ElfLoader::Singleton.Load(name, RTLD_GLOBAL | RTLD_LAZY, this); + if (!handle) + return false; + dependencies.push_back(handle); + } + + return true; +} + +bool +CustomElf::Relocate() +{ + DEBUG_LOG("Relocate %s @%p", GetPath(), static_cast<void *>(base)); + uint32_t symtab_index = (uint32_t) -1; + void *symptr = nullptr; + for (Array<Reloc>::iterator rel = relocations.begin(); + rel < relocations.end(); ++rel) { + /* Location of the relocation */ + void *ptr = GetPtr(rel->r_offset); + + /* R_*_RELATIVE relocations apply directly at the given location */ + if (ELF_R_TYPE(rel->r_info) == R_RELATIVE) { + *(void **) ptr = GetPtr(rel->GetAddend(base)); + continue; + } + /* Other relocation types need a symbol resolution */ + /* Avoid symbol resolution when it's the same symbol as last iteration */ + if (symtab_index != ELF_R_SYM(rel->r_info)) { + symtab_index = ELF_R_SYM(rel->r_info); + const Sym sym = symtab[symtab_index]; + if (sym.st_shndx != SHN_UNDEF) { + symptr = GetPtr(sym.st_value); + } else { + /* TODO: handle symbol resolving to nullptr vs. being undefined. */ + symptr = GetSymbolPtrInDeps(strtab.GetStringAt(sym.st_name)); + } + } + + if (symptr == nullptr) + WARN("%s: Relocation to NULL @0x%08" PRIxAddr, + GetPath(), rel->r_offset); + + /* Apply relocation */ + switch (ELF_R_TYPE(rel->r_info)) { + case R_GLOB_DAT: + /* R_*_GLOB_DAT relocations simply use the symbol value */ + *(void **) ptr = symptr; + break; + case R_ABS: + /* R_*_ABS* relocations add the relocation added to the symbol value */ + *(const char **) ptr = (const char *)symptr + rel->GetAddend(base); + break; + default: + ERROR("%s: Unsupported relocation type: 0x%" PRIxAddr, + GetPath(), ELF_R_TYPE(rel->r_info)); + return false; + } + } + return true; +} + +bool +CustomElf::RelocateJumps() +{ + /* TODO: Dynamic symbol resolution */ + for (Array<Reloc>::iterator rel = jumprels.begin(); + rel < jumprels.end(); ++rel) { + /* Location of the relocation */ + void *ptr = GetPtr(rel->r_offset); + + /* Only R_*_JMP_SLOT relocations are expected */ + if (ELF_R_TYPE(rel->r_info) != R_JMP_SLOT) { + ERROR("%s: Jump relocation type mismatch", GetPath()); + return false; + } + + /* TODO: Avoid code duplication with the relocations above */ + const Sym sym = symtab[ELF_R_SYM(rel->r_info)]; + void *symptr; + if (sym.st_shndx != SHN_UNDEF) + symptr = GetPtr(sym.st_value); + else + symptr = GetSymbolPtrInDeps(strtab.GetStringAt(sym.st_name)); + + if (symptr == nullptr) { + if (ELF_ST_BIND(sym.st_info) == STB_WEAK) { + WARN("%s: Relocation to NULL @0x%08" PRIxAddr " for symbol \"%s\"", + GetPath(), + rel->r_offset, strtab.GetStringAt(sym.st_name)); + } else { + ERROR("%s: Relocation to NULL @0x%08" PRIxAddr " for symbol \"%s\"", + GetPath(), + rel->r_offset, strtab.GetStringAt(sym.st_name)); + return false; + } + } + /* Apply relocation */ + *(void **) ptr = symptr; + } + return true; +} + +bool +CustomElf::CallInit() +{ + if (init) + CallFunction(init); + + for (Array<void *>::iterator it = init_array.begin(); + it < init_array.end(); ++it) { + /* Android x86 NDK wrongly puts 0xffffffff in INIT_ARRAY */ + if (*it && *it != reinterpret_cast<void *>(-1)) + CallFunction(*it); + } + initialized = true; + return true; +} + +void +CustomElf::CallFini() +{ + if (!initialized) + return; + for (Array<void *>::reverse_iterator it = fini_array.rbegin(); + it < fini_array.rend(); ++it) { + /* Android x86 NDK wrongly puts 0xffffffff in FINI_ARRAY */ + if (*it && *it != reinterpret_cast<void *>(-1)) + CallFunction(*it); + } + if (fini) + CallFunction(fini); +} + +Mappable * +CustomElf::GetMappable() const +{ + if (!mappable) + return nullptr; + if (mappable->GetKind() == Mappable::MAPPABLE_EXTRACT_FILE) + return mappable; + return ElfLoader::GetMappableFromPath(GetPath()); +} diff --git a/mozglue/linker/CustomElf.h b/mozglue/linker/CustomElf.h new file mode 100644 index 0000000000..5ecc7da085 --- /dev/null +++ b/mozglue/linker/CustomElf.h @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CustomElf_h +#define CustomElf_h + +#include "ElfLoader.h" +#include "BaseElf.h" +#include "Logging.h" +#include "Elfxx.h" + +/** + * Library Handle class for ELF libraries we don't let the system linker + * handle. + */ +class CustomElf: public BaseElf, private ElfLoader::link_map +{ + friend class ElfLoader; + friend class SEGVHandler; +public: + /** + * Returns a new CustomElf using the given file descriptor to map ELF + * content. The file descriptor ownership is stolen, and it will be closed + * in CustomElf's destructor if an instance is created, or by the Load + * method otherwise. The path corresponds to the file descriptor, and flags + * are the same kind of flags that would be given to dlopen(), though + * currently, none are supported and the behaviour is more or less that of + * RTLD_GLOBAL | RTLD_BIND_NOW. + */ + static already_AddRefed<LibHandle> Load(Mappable *mappable, + const char *path, int flags); + + /** + * Inherited from LibHandle/BaseElf + */ + virtual ~CustomElf(); + +protected: + virtual Mappable *GetMappable() const; + +public: + /** + * Shows some stats about the Mappable instance. The when argument is to be + * used by the caller to give an identifier of the when the stats call is + * made. + */ + virtual void stats(const char *when) const; + + /** + * Returns the instance, casted as BaseElf. (short of a better way to do + * this without RTTI) + */ + virtual BaseElf *AsBaseElf() { return this; } + +private: + /** + * Scan dependent libraries to find the address corresponding to the + * given symbol name. This is used to find symbols that are undefined + * in the Elf object. + */ + void *GetSymbolPtrInDeps(const char *symbol) const; + + /** + * Private constructor + */ + CustomElf(Mappable *mappable, const char *path) + : BaseElf(path, mappable) + , link_map() + , init(0) + , fini(0) + , initialized(false) + , has_text_relocs(false) + { } + + /** + * Loads an Elf segment defined by the given PT_LOAD header. + * Returns whether this succeeded or failed. + */ + bool LoadSegment(const Elf::Phdr *pt_load) const; + + /** + * Initializes the library according to information found in the given + * PT_DYNAMIC header. + * Returns whether this succeeded or failed. + */ + bool InitDyn(const Elf::Phdr *pt_dyn); + + /** + * Apply .rel.dyn/.rela.dyn relocations. + * Returns whether this succeeded or failed. + */ + bool Relocate(); + + /** + * Apply .rel.plt/.rela.plt relocations. + * Returns whether this succeeded or failed. + */ + bool RelocateJumps(); + + /** + * Call initialization functions (.init/.init_array) + * Returns true; + */ + bool CallInit(); + + /** + * Call destructor functions (.fini_array/.fini) + * Returns whether this succeeded or failed. + */ + void CallFini(); + + /** + * Call a function given a pointer to its location. + */ + void CallFunction(void *ptr) const + { + /* C++ doesn't allow direct conversion between pointer-to-object + * and pointer-to-function. */ + union { + void *ptr; + void (*func)(void); + } f; + f.ptr = ptr; + DEBUG_LOG("%s: Calling function @%p", GetPath(), ptr); + f.func(); + } + + /** + * Call a function given a an address relative to the library base + */ + void CallFunction(Elf::Addr addr) const + { + return CallFunction(GetPtr(addr)); + } + + /* List of dependent libraries */ + std::vector<RefPtr<LibHandle> > dependencies; + + /* List of .rel.dyn/.rela.dyn relocations */ + Array<Elf::Reloc> relocations; + + /* List of .rel.plt/.rela.plt relocation */ + Array<Elf::Reloc> jumprels; + + /* Relative address of the initialization and destruction functions + * (.init/.fini) */ + Elf::Addr init, fini; + + /* List of initialization and destruction functions + * (.init_array/.fini_array) */ + Array<void *> init_array, fini_array; + + bool initialized; + + bool has_text_relocs; +}; + +#endif /* CustomElf_h */ diff --git a/mozglue/linker/ElfLoader.cpp b/mozglue/linker/ElfLoader.cpp new file mode 100644 index 0000000000..76225d1e77 --- /dev/null +++ b/mozglue/linker/ElfLoader.cpp @@ -0,0 +1,1310 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <string> +#include <cstring> +#include <cstdlib> +#include <cstdio> +#include <dlfcn.h> +#include <unistd.h> +#include <errno.h> +#include <algorithm> +#include <fcntl.h> +#include "ElfLoader.h" +#include "BaseElf.h" +#include "CustomElf.h" +#include "Mappable.h" +#include "Logging.h" +#include <inttypes.h> + +#if defined(ANDROID) +#include <sys/syscall.h> + +#include <android/api-level.h> +#if __ANDROID_API__ < 8 +/* Android API < 8 doesn't provide sigaltstack */ + +extern "C" { + +inline int sigaltstack(const stack_t *ss, stack_t *oss) { + return syscall(__NR_sigaltstack, ss, oss); +} + +} /* extern "C" */ +#endif /* __ANDROID_API__ */ +#endif /* ANDROID */ + +#ifdef __ARM_EABI__ +extern "C" MOZ_EXPORT const void * +__gnu_Unwind_Find_exidx(void *pc, int *pcount) __attribute__((weak)); +#endif + +/* Pointer to the PT_DYNAMIC section of the executable or library + * containing this code. */ +extern "C" Elf::Dyn _DYNAMIC[]; + +using namespace mozilla; + +/** + * dlfcn.h replacements functions + */ + +void * +__wrap_dlopen(const char *path, int flags) +{ + RefPtr<LibHandle> handle = ElfLoader::Singleton.Load(path, flags); + if (handle) + handle->AddDirectRef(); + return handle; +} + +const char * +__wrap_dlerror(void) +{ + const char *error = ElfLoader::Singleton.lastError; + ElfLoader::Singleton.lastError = nullptr; + return error; +} + +void * +__wrap_dlsym(void *handle, const char *symbol) +{ + if (!handle) { + ElfLoader::Singleton.lastError = "dlsym(NULL, sym) unsupported"; + return nullptr; + } + if (handle != RTLD_DEFAULT && handle != RTLD_NEXT) { + LibHandle *h = reinterpret_cast<LibHandle *>(handle); + return h->GetSymbolPtr(symbol); + } + return dlsym(handle, symbol); +} + +int +__wrap_dlclose(void *handle) +{ + if (!handle) { + ElfLoader::Singleton.lastError = "No handle given to dlclose()"; + return -1; + } + reinterpret_cast<LibHandle *>(handle)->ReleaseDirectRef(); + return 0; +} + +int +__wrap_dladdr(void *addr, Dl_info *info) +{ + RefPtr<LibHandle> handle = ElfLoader::Singleton.GetHandleByPtr(addr); + if (!handle) { + return dladdr(addr, info); + } + info->dli_fname = handle->GetPath(); + info->dli_fbase = handle->GetBase(); + return 1; +} + +int +__wrap_dl_iterate_phdr(dl_phdr_cb callback, void *data) +{ + if (!ElfLoader::Singleton.dbg) + return -1; + + int pipefd[2]; + bool valid_pipe = (pipe(pipefd) == 0); + AutoCloseFD read_fd(pipefd[0]); + AutoCloseFD write_fd(pipefd[1]); + + for (ElfLoader::DebuggerHelper::iterator it = ElfLoader::Singleton.dbg.begin(); + it < ElfLoader::Singleton.dbg.end(); ++it) { + dl_phdr_info info; + info.dlpi_addr = reinterpret_cast<Elf::Addr>(it->l_addr); + info.dlpi_name = it->l_name; + info.dlpi_phdr = nullptr; + info.dlpi_phnum = 0; + + // Assuming l_addr points to Elf headers (in most cases, this is true), + // get the Phdr location from there. + // Unfortunately, when l_addr doesn't point to Elf headers, it may point + // to unmapped memory, or worse, unreadable memory. The only way to detect + // the latter without causing a SIGSEGV is to use the pointer in a system + // call that will try to read from there, and return an EFAULT error if + // it can't. One such system call is write(). It used to be possible to + // use a file descriptor on /dev/null for these kind of things, but recent + // Linux kernels never return an EFAULT error when using /dev/null. + // So instead, we use a self pipe. We do however need to read() from the + // read end of the pipe as well so as to not fill up the pipe buffer and + // block on subsequent writes. + // In the unlikely event reads from or write to the pipe fail for some + // other reason than EFAULT, we don't try any further and just skip setting + // the Phdr location for all subsequent libraries, rather than trying to + // start over with a new pipe. + int can_read = true; + if (valid_pipe) { + int ret; + char raw_ehdr[sizeof(Elf::Ehdr)]; + static_assert(sizeof(raw_ehdr) < PIPE_BUF, "PIPE_BUF is too small"); + do { + // writes are atomic when smaller than PIPE_BUF, per POSIX.1-2008. + ret = write(write_fd, it->l_addr, sizeof(raw_ehdr)); + } while (ret == -1 && errno == EINTR); + if (ret != sizeof(raw_ehdr)) { + if (ret == -1 && errno == EFAULT) { + can_read = false; + } else { + valid_pipe = false; + } + } else { + size_t nbytes = 0; + do { + // Per POSIX.1-2008, interrupted reads can return a length smaller + // than the given one instead of failing with errno EINTR. + ret = read(read_fd, raw_ehdr + nbytes, sizeof(raw_ehdr) - nbytes); + if (ret > 0) + nbytes += ret; + } while ((nbytes != sizeof(raw_ehdr) && ret > 0) || + (ret == -1 && errno == EINTR)); + if (nbytes != sizeof(raw_ehdr)) { + valid_pipe = false; + } + } + } + + if (valid_pipe && can_read) { + const Elf::Ehdr *ehdr = Elf::Ehdr::validate(it->l_addr); + if (ehdr) { + info.dlpi_phdr = reinterpret_cast<const Elf::Phdr *>( + reinterpret_cast<const char *>(ehdr) + ehdr->e_phoff); + info.dlpi_phnum = ehdr->e_phnum; + } + } + + int ret = callback(&info, sizeof(dl_phdr_info), data); + if (ret) + return ret; + } + return 0; +} + +#ifdef __ARM_EABI__ +const void * +__wrap___gnu_Unwind_Find_exidx(void *pc, int *pcount) +{ + RefPtr<LibHandle> handle = ElfLoader::Singleton.GetHandleByPtr(pc); + if (handle) + return handle->FindExidx(pcount); + if (__gnu_Unwind_Find_exidx) + return __gnu_Unwind_Find_exidx(pc, pcount); + *pcount = 0; + return nullptr; +} +#endif + +/** + * faulty.lib public API + */ + +MFBT_API size_t +__dl_get_mappable_length(void *handle) { + if (!handle) + return 0; + return reinterpret_cast<LibHandle *>(handle)->GetMappableLength(); +} + +MFBT_API void * +__dl_mmap(void *handle, void *addr, size_t length, off_t offset) +{ + if (!handle) + return nullptr; + return reinterpret_cast<LibHandle *>(handle)->MappableMMap(addr, length, + offset); +} + +MFBT_API void +__dl_munmap(void *handle, void *addr, size_t length) +{ + if (!handle) + return; + return reinterpret_cast<LibHandle *>(handle)->MappableMUnmap(addr, length); +} + +MFBT_API bool +IsSignalHandlingBroken() +{ + return ElfLoader::Singleton.isSignalHandlingBroken(); +} + +namespace { + +/** + * Returns the part after the last '/' for the given path + */ +const char * +LeafName(const char *path) +{ + const char *lastSlash = strrchr(path, '/'); + if (lastSlash) + return lastSlash + 1; + return path; +} + +} /* Anonymous namespace */ + +/** + * LibHandle + */ +LibHandle::~LibHandle() +{ + free(path); +} + +const char * +LibHandle::GetName() const +{ + return path ? LeafName(path) : nullptr; +} + +size_t +LibHandle::GetMappableLength() const +{ + if (!mappable) + mappable = GetMappable(); + if (!mappable) + return 0; + return mappable->GetLength(); +} + +void * +LibHandle::MappableMMap(void *addr, size_t length, off_t offset) const +{ + if (!mappable) + mappable = GetMappable(); + if (!mappable) + return MAP_FAILED; + void* mapped = mappable->mmap(addr, length, PROT_READ, MAP_PRIVATE, offset); + if (mapped != MAP_FAILED) { + /* Ensure the availability of all pages within the mapping */ + for (size_t off = 0; off < length; off += PageSize()) { + mappable->ensure(reinterpret_cast<char *>(mapped) + off); + } + } + return mapped; +} + +void +LibHandle::MappableMUnmap(void *addr, size_t length) const +{ + if (mappable) + mappable->munmap(addr, length); +} + +/** + * SystemElf + */ +already_AddRefed<LibHandle> +SystemElf::Load(const char *path, int flags) +{ + /* The Android linker returns a handle when the file name matches an + * already loaded library, even when the full path doesn't exist */ + if (path && path[0] == '/' && (access(path, F_OK) == -1)){ + DEBUG_LOG("dlopen(\"%s\", 0x%x) = %p", path, flags, (void *)nullptr); + return nullptr; + } + + void *handle = dlopen(path, flags); + DEBUG_LOG("dlopen(\"%s\", 0x%x) = %p", path, flags, handle); + ElfLoader::Singleton.lastError = dlerror(); + if (handle) { + SystemElf *elf = new SystemElf(path, handle); + ElfLoader::Singleton.Register(elf); + RefPtr<LibHandle> lib(elf); + return lib.forget(); + } + return nullptr; +} + +SystemElf::~SystemElf() +{ + if (!dlhandle) + return; + DEBUG_LOG("dlclose(%p [\"%s\"])", dlhandle, GetPath()); + dlclose(dlhandle); + ElfLoader::Singleton.lastError = dlerror(); + ElfLoader::Singleton.Forget(this); +} + +void * +SystemElf::GetSymbolPtr(const char *symbol) const +{ + void *sym = dlsym(dlhandle, symbol); + DEBUG_LOG("dlsym(%p [\"%s\"], \"%s\") = %p", dlhandle, GetPath(), symbol, sym); + ElfLoader::Singleton.lastError = dlerror(); + return sym; +} + +Mappable * +SystemElf::GetMappable() const +{ + const char *path = GetPath(); + if (!path) + return nullptr; +#ifdef ANDROID + /* On Android, if we don't have the full path, try in /system/lib */ + const char *name = LeafName(path); + std::string systemPath; + if (name == path) { + systemPath = "/system/lib/"; + systemPath += path; + path = systemPath.c_str(); + } +#endif + + return MappableFile::Create(path); +} + +#ifdef __ARM_EABI__ +const void * +SystemElf::FindExidx(int *pcount) const +{ + /* TODO: properly implement when ElfLoader::GetHandleByPtr + does return SystemElf handles */ + *pcount = 0; + return nullptr; +} +#endif + +/** + * ElfLoader + */ + +/* Unique ElfLoader instance */ +ElfLoader ElfLoader::Singleton; + +already_AddRefed<LibHandle> +ElfLoader::Load(const char *path, int flags, LibHandle *parent) +{ + /* Ensure logging is initialized or refresh if environment changed. */ + Logging::Init(); + + /* Ensure self_elf initialization. */ + if (!self_elf) + Init(); + + RefPtr<LibHandle> handle; + + /* Handle dlopen(nullptr) directly. */ + if (!path) { + handle = SystemElf::Load(nullptr, flags); + return handle.forget(); + } + + /* TODO: Handle relative paths correctly */ + const char *name = LeafName(path); + + /* Search the list of handles we already have for a match. When the given + * path is not absolute, compare file names, otherwise compare full paths. */ + if (name == path) { + for (LibHandleList::iterator it = handles.begin(); it < handles.end(); ++it) + if ((*it)->GetName() && (strcmp((*it)->GetName(), name) == 0)) { + handle = *it; + return handle.forget(); + } + } else { + for (LibHandleList::iterator it = handles.begin(); it < handles.end(); ++it) + if ((*it)->GetPath() && (strcmp((*it)->GetPath(), path) == 0)) { + handle = *it; + return handle.forget(); + } + } + + char *abs_path = nullptr; + const char *requested_path = path; + + /* When the path is not absolute and the library is being loaded for + * another, first try to load the library from the directory containing + * that parent library. */ + if ((name == path) && parent) { + const char *parentPath = parent->GetPath(); + abs_path = new char[strlen(parentPath) + strlen(path)]; + strcpy(abs_path, parentPath); + char *slash = strrchr(abs_path, '/'); + strcpy(slash + 1, path); + path = abs_path; + } + + Mappable *mappable = GetMappableFromPath(path); + + /* Try loading with the custom linker if we have a Mappable */ + if (mappable) + handle = CustomElf::Load(mappable, path, flags); + + /* Try loading with the system linker if everything above failed */ + if (!handle) + handle = SystemElf::Load(path, flags); + + /* If we didn't have an absolute path and haven't been able to load + * a library yet, try in the system search path */ + if (!handle && abs_path) + handle = SystemElf::Load(name, flags); + + delete [] abs_path; + DEBUG_LOG("ElfLoader::Load(\"%s\", 0x%x, %p [\"%s\"]) = %p", requested_path, flags, + reinterpret_cast<void *>(parent), parent ? parent->GetPath() : "", + static_cast<void *>(handle)); + + return handle.forget(); +} + +already_AddRefed<LibHandle> +ElfLoader::GetHandleByPtr(void *addr) +{ + /* Scan the list of handles we already have for a match */ + for (LibHandleList::iterator it = handles.begin(); it < handles.end(); ++it) { + if ((*it)->Contains(addr)) { + RefPtr<LibHandle> lib = *it; + return lib.forget(); + } + } + return nullptr; +} + +Mappable * +ElfLoader::GetMappableFromPath(const char *path) +{ + const char *name = LeafName(path); + Mappable *mappable = nullptr; + RefPtr<Zip> zip; + const char *subpath; + if ((subpath = strchr(path, '!'))) { + char *zip_path = strndup(path, subpath - path); + while (*(++subpath) == '/') { } + zip = ZipCollection::GetZip(zip_path); + free(zip_path); + Zip::Stream s; + if (zip && zip->GetStream(subpath, &s)) { + /* When the MOZ_LINKER_EXTRACT environment variable is set to "1", + * compressed libraries are going to be (temporarily) extracted as + * files, in the directory pointed by the MOZ_LINKER_CACHE + * environment variable. */ + const char *extract = getenv("MOZ_LINKER_EXTRACT"); + if (extract && !strncmp(extract, "1", 2 /* Including '\0' */)) + mappable = MappableExtractFile::Create(name, zip, &s); + if (!mappable) { + if (s.GetType() == Zip::Stream::DEFLATE) { + mappable = MappableDeflate::Create(name, zip, &s); + } else if (s.GetType() == Zip::Stream::STORE) { + mappable = MappableSeekableZStream::Create(name, zip, &s); + } + } + } + } + /* If we couldn't load above, try with a MappableFile */ + if (!mappable && !zip) + mappable = MappableFile::Create(path); + + return mappable; +} + +void +ElfLoader::Register(LibHandle *handle) +{ + handles.push_back(handle); +} + +void +ElfLoader::Register(CustomElf *handle) +{ + Register(static_cast<LibHandle *>(handle)); + if (dbg) { + dbg.Add(handle); + } +} + +void +ElfLoader::Forget(LibHandle *handle) +{ + /* Ensure logging is initialized or refresh if environment changed. */ + Logging::Init(); + + LibHandleList::iterator it = std::find(handles.begin(), handles.end(), handle); + if (it != handles.end()) { + DEBUG_LOG("ElfLoader::Forget(%p [\"%s\"])", reinterpret_cast<void *>(handle), + handle->GetPath()); + handles.erase(it); + } else { + DEBUG_LOG("ElfLoader::Forget(%p [\"%s\"]): Handle not found", + reinterpret_cast<void *>(handle), handle->GetPath()); + } +} + +void +ElfLoader::Forget(CustomElf *handle) +{ + Forget(static_cast<LibHandle *>(handle)); + if (dbg) { + dbg.Remove(handle); + } +} + +void +ElfLoader::Init() +{ + Dl_info info; + /* On Android < 4.1 can't reenter dl* functions. So when the library + * containing this code is dlopen()ed, it can't call dladdr from a + * static initializer. */ + if (dladdr(_DYNAMIC, &info) != 0) { + self_elf = LoadedElf::Create(info.dli_fname, info.dli_fbase); + } +#if defined(ANDROID) + if (dladdr(FunctionPtr(syscall), &info) != 0) { + libc = LoadedElf::Create(info.dli_fname, info.dli_fbase); + } +#endif +} + +ElfLoader::~ElfLoader() +{ + LibHandleList list; + + if (!Singleton.IsShutdownExpected()) { + MOZ_CRASH("Unexpected shutdown"); + } + + /* Release self_elf and libc */ + self_elf = nullptr; +#if defined(ANDROID) + libc = nullptr; +#endif + + /* Build up a list of all library handles with direct (external) references. + * We actually skip system library handles because we want to keep at least + * some of these open. Most notably, Mozilla codebase keeps a few libgnome + * libraries deliberately open because of the mess that libORBit destruction + * is. dlclose()ing these libraries actually leads to problems. */ + for (LibHandleList::reverse_iterator it = handles.rbegin(); + it < handles.rend(); ++it) { + if ((*it)->DirectRefCount()) { + if (SystemElf *se = (*it)->AsSystemElf()) { + se->Forget(); + } else { + list.push_back(*it); + } + } + } + /* Force release all external references to the handles collected above */ + for (LibHandleList::iterator it = list.begin(); it < list.end(); ++it) { + while ((*it)->ReleaseDirectRef()) { } + } + /* Remove the remaining system handles. */ + if (handles.size()) { + list = handles; + for (LibHandleList::reverse_iterator it = list.rbegin(); + it < list.rend(); ++it) { + if ((*it)->AsSystemElf()) { + DEBUG_LOG("ElfLoader::~ElfLoader(): Remaining handle for \"%s\" " + "[%d direct refs, %d refs total]", (*it)->GetPath(), + (*it)->DirectRefCount(), (*it)->refCount()); + } else { + DEBUG_LOG("ElfLoader::~ElfLoader(): Unexpected remaining handle for \"%s\" " + "[%d direct refs, %d refs total]", (*it)->GetPath(), + (*it)->DirectRefCount(), (*it)->refCount()); + /* Not removing, since it could have references to other libraries, + * destroying them as a side effect, and possibly leaving dangling + * pointers in the handle list we're scanning */ + } + } + } +} + +void +ElfLoader::stats(const char *when) +{ + if (MOZ_LIKELY(!Logging::isVerbose())) + return; + + for (LibHandleList::iterator it = Singleton.handles.begin(); + it < Singleton.handles.end(); ++it) + (*it)->stats(when); +} + +#ifdef __ARM_EABI__ +int +ElfLoader::__wrap_aeabi_atexit(void *that, ElfLoader::Destructor destructor, + void *dso_handle) +{ + Singleton.destructors.push_back( + DestructorCaller(destructor, that, dso_handle)); + return 0; +} +#else +int +ElfLoader::__wrap_cxa_atexit(ElfLoader::Destructor destructor, void *that, + void *dso_handle) +{ + Singleton.destructors.push_back( + DestructorCaller(destructor, that, dso_handle)); + return 0; +} +#endif + +void +ElfLoader::__wrap_cxa_finalize(void *dso_handle) +{ + /* Call all destructors for the given DSO handle in reverse order they were + * registered. */ + std::vector<DestructorCaller>::reverse_iterator it; + for (it = Singleton.destructors.rbegin(); + it < Singleton.destructors.rend(); ++it) { + if (it->IsForHandle(dso_handle)) { + it->Call(); + } + } +} + +void +ElfLoader::DestructorCaller::Call() +{ + if (destructor) { + DEBUG_LOG("ElfLoader::DestructorCaller::Call(%p, %p, %p)", + FunctionPtr(destructor), object, dso_handle); + destructor(object); + destructor = nullptr; + } +} + +ElfLoader::DebuggerHelper::DebuggerHelper(): dbg(nullptr), firstAdded(nullptr) +{ + /* Find ELF auxiliary vectors. + * + * The kernel stores the following data on the stack when starting a + * program: + * argc + * argv[0] (pointer into argv strings defined below) + * argv[1] (likewise) + * ... + * argv[argc - 1] (likewise) + * nullptr + * envp[0] (pointer into environment strings defined below) + * envp[1] (likewise) + * ... + * envp[n] (likewise) + * nullptr + * ... (more NULLs on some platforms such as Android 4.3) + * auxv[0] (first ELF auxiliary vector) + * auxv[1] (second ELF auxiliary vector) + * ... + * auxv[p] (last ELF auxiliary vector) + * (AT_NULL, nullptr) + * padding + * argv strings, separated with '\0' + * environment strings, separated with '\0' + * nullptr + * + * What we are after are the auxv values defined by the following struct. + */ + struct AuxVector { + Elf::Addr type; + Elf::Addr value; + }; + + /* Pointer to the environment variables list */ + extern char **environ; + + /* The environment may have changed since the program started, in which + * case the environ variables list isn't the list the kernel put on stack + * anymore. But in this new list, variables that didn't change still point + * to the strings the kernel put on stack. It is quite unlikely that two + * modified environment variables point to two consecutive strings in memory, + * so we assume that if two consecutive environment variables point to two + * consecutive strings, we found strings the kernel put on stack. */ + char **env; + for (env = environ; *env; env++) + if (*env + strlen(*env) + 1 == env[1]) + break; + if (!*env) + return; + + /* Next, we scan the stack backwards to find a pointer to one of those + * strings we found above, which will give us the location of the original + * envp list. As we are looking for pointers, we need to look at 32-bits or + * 64-bits aligned values, depening on the architecture. */ + char **scan = reinterpret_cast<char **>( + reinterpret_cast<uintptr_t>(*env) & ~(sizeof(void *) - 1)); + while (*env != *scan) + scan--; + + /* Finally, scan forward to find the last environment variable pointer and + * thus the first auxiliary vector. */ + while (*scan++); + + /* Some platforms have more NULLs here, so skip them if we encounter them */ + while (!*scan) + scan++; + + AuxVector *auxv = reinterpret_cast<AuxVector *>(scan); + + /* The two values of interest in the auxiliary vectors are AT_PHDR and + * AT_PHNUM, which gives us the the location and size of the ELF program + * headers. */ + Array<Elf::Phdr> phdrs; + char *base = nullptr; + while (auxv->type) { + if (auxv->type == AT_PHDR) { + phdrs.Init(reinterpret_cast<Elf::Phdr*>(auxv->value)); + /* Assume the base address is the first byte of the same page */ + base = reinterpret_cast<char *>(PageAlignedPtr(auxv->value)); + } + if (auxv->type == AT_PHNUM) + phdrs.Init(auxv->value); + auxv++; + } + + if (!phdrs) { + DEBUG_LOG("Couldn't find program headers"); + return; + } + + /* In some cases, the address for the program headers we get from the + * auxiliary vectors is not mapped, because of the PT_LOAD segments + * definitions in the program executable. Trying to map anonymous memory + * with a hint giving the base address will return a different address + * if something is mapped there, and the base address otherwise. */ + MappedPtr mem(MemoryRange::mmap(base, PageSize(), PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (mem == base) { + /* If program headers aren't mapped, try to map them */ + int fd = open("/proc/self/exe", O_RDONLY); + if (fd == -1) { + DEBUG_LOG("Failed to open /proc/self/exe"); + return; + } + mem.Assign(MemoryRange::mmap(base, PageSize(), PROT_READ, MAP_PRIVATE, + fd, 0)); + /* If we don't manage to map at the right address, just give up. */ + if (mem != base) { + DEBUG_LOG("Couldn't read program headers"); + return; + } + } + /* Sanity check: the first bytes at the base address should be an ELF + * header. */ + if (!Elf::Ehdr::validate(base)) { + DEBUG_LOG("Couldn't find program base"); + return; + } + + /* Search for the program PT_DYNAMIC segment */ + Array<Elf::Dyn> dyns; + for (Array<Elf::Phdr>::iterator phdr = phdrs.begin(); phdr < phdrs.end(); + ++phdr) { + /* While the program headers are expected within the first mapped page of + * the program executable, the executable PT_LOADs may actually make them + * loaded at an address that is not the wanted base address of the + * library. We thus need to adjust the base address, compensating for the + * virtual address of the PT_LOAD segment corresponding to offset 0. */ + if (phdr->p_type == PT_LOAD && phdr->p_offset == 0) + base -= phdr->p_vaddr; + if (phdr->p_type == PT_DYNAMIC) + dyns.Init(base + phdr->p_vaddr, phdr->p_filesz); + } + if (!dyns) { + DEBUG_LOG("Failed to find PT_DYNAMIC section in program"); + return; + } + + /* Search for the DT_DEBUG information */ + for (Array<Elf::Dyn>::iterator dyn = dyns.begin(); dyn < dyns.end(); ++dyn) { + if (dyn->d_tag == DT_DEBUG) { + dbg = reinterpret_cast<r_debug *>(dyn->d_un.d_ptr); + break; + } + } + DEBUG_LOG("DT_DEBUG points at %p", static_cast<void *>(dbg)); +} + +/** + * Helper class to ensure the given pointer is writable within the scope of + * an instance. Permissions to the memory page where the pointer lies are + * restored to their original value when the instance is destroyed. + */ +class EnsureWritable +{ +public: + template <typename T> + EnsureWritable(T *ptr, size_t length_ = sizeof(T)) + { + MOZ_ASSERT(length_ < PageSize()); + prot = -1; + page = MAP_FAILED; + + char *firstPage = PageAlignedPtr(reinterpret_cast<char *>(ptr)); + char *lastPageEnd = PageAlignedEndPtr(reinterpret_cast<char *>(ptr) + length_); + length = lastPageEnd - firstPage; + uintptr_t start = reinterpret_cast<uintptr_t>(firstPage); + uintptr_t end; + + prot = getProt(start, &end); + if (prot == -1 || (start + length) > end) + MOZ_CRASH(); + + if (prot & PROT_WRITE) + return; + + page = firstPage; + mprotect(page, length, prot | PROT_WRITE); + } + + ~EnsureWritable() + { + if (page != MAP_FAILED) { + mprotect(page, length, prot); +} + } + +private: + int getProt(uintptr_t addr, uintptr_t *end) + { + /* The interesting part of the /proc/self/maps format looks like: + * startAddr-endAddr rwxp */ + int result = 0; + AutoCloseFILE f(fopen("/proc/self/maps", "r")); + while (f) { + unsigned long long startAddr, endAddr; + char perms[5]; + if (fscanf(f, "%llx-%llx %4s %*1024[^\n] ", &startAddr, &endAddr, perms) != 3) + return -1; + if (addr < startAddr || addr >= endAddr) + continue; + if (perms[0] == 'r') + result |= PROT_READ; + else if (perms[0] != '-') + return -1; + if (perms[1] == 'w') + result |= PROT_WRITE; + else if (perms[1] != '-') + return -1; + if (perms[2] == 'x') + result |= PROT_EXEC; + else if (perms[2] != '-') + return -1; + *end = endAddr; + return result; + } + return -1; + } + + int prot; + void *page; + size_t length; +}; + +/** + * The system linker maintains a doubly linked list of library it loads + * for use by the debugger. Unfortunately, it also uses the list pointers + * in a lot of operations and adding our data in the list is likely to + * trigger crashes when the linker tries to use data we don't provide or + * that fall off the amount data we allocated. Fortunately, the linker only + * traverses the list forward and accesses the head of the list from a + * private pointer instead of using the value in the r_debug structure. + * This means we can safely add members at the beginning of the list. + * Unfortunately, gdb checks the coherency of l_prev values, so we have + * to adjust the l_prev value for the first element the system linker + * knows about. Fortunately, it doesn't use l_prev, and the first element + * is not ever going to be released before our elements, since it is the + * program executable, so the system linker should not be changing + * r_debug::r_map. + */ +void +ElfLoader::DebuggerHelper::Add(ElfLoader::link_map *map) +{ + if (!dbg->r_brk) + return; + dbg->r_state = r_debug::RT_ADD; + dbg->r_brk(); + map->l_prev = nullptr; + map->l_next = dbg->r_map; + if (!firstAdded) { + firstAdded = map; + /* When adding a library for the first time, r_map points to data + * handled by the system linker, and that data may be read-only */ + EnsureWritable w(&dbg->r_map->l_prev); + dbg->r_map->l_prev = map; + } else + dbg->r_map->l_prev = map; + dbg->r_map = map; + dbg->r_state = r_debug::RT_CONSISTENT; + dbg->r_brk(); +} + +void +ElfLoader::DebuggerHelper::Remove(ElfLoader::link_map *map) +{ + if (!dbg->r_brk) + return; + dbg->r_state = r_debug::RT_DELETE; + dbg->r_brk(); + if (dbg->r_map == map) + dbg->r_map = map->l_next; + else if (map->l_prev) { + map->l_prev->l_next = map->l_next; + } + if (map == firstAdded) { + firstAdded = map->l_prev; + /* When removing the first added library, its l_next is going to be + * data handled by the system linker, and that data may be read-only */ + EnsureWritable w(&map->l_next->l_prev); + map->l_next->l_prev = map->l_prev; + } else if (map->l_next) { + map->l_next->l_prev = map->l_prev; + } + dbg->r_state = r_debug::RT_CONSISTENT; + dbg->r_brk(); +} + +#if defined(ANDROID) +/* As some system libraries may be calling signal() or sigaction() to + * set a SIGSEGV handler, effectively breaking MappableSeekableZStream, + * or worse, restore our SIGSEGV handler with wrong flags (which using + * signal() will do), we want to hook into the system's sigaction() to + * replace it with our own wrapper instead, so that our handler is never + * replaced. We used to only do that with libraries this linker loads, + * but it turns out at least one system library does call signal() and + * breaks us (libsc-a3xx.so on the Samsung Galaxy S4). + * As libc's signal (bsd_signal/sysv_signal, really) calls sigaction + * under the hood, instead of calling the signal system call directly, + * we only need to hook sigaction. This is true for both bionic and + * glibc. + */ + +/* libc's sigaction */ +extern "C" int +sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact); + +/* Simple reimplementation of sigaction. This is roughly equivalent + * to the assembly that comes in bionic, but not quite equivalent to + * glibc's implementation, so we only use this on Android. */ +int +sys_sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact) +{ + return syscall(__NR_sigaction, signum, act, oldact); +} + +/* Replace the first instructions of the given function with a jump + * to the given new function. */ +template <typename T> +static bool +Divert(T func, T new_func) +{ + void *ptr = FunctionPtr(func); + uintptr_t addr = reinterpret_cast<uintptr_t>(ptr); + +#if defined(__i386__) + // A 32-bit jump is a 5 bytes instruction. + EnsureWritable w(ptr, 5); + *reinterpret_cast<unsigned char *>(addr) = 0xe9; // jmp + *reinterpret_cast<intptr_t *>(addr + 1) = + reinterpret_cast<uintptr_t>(new_func) - addr - 5; // target displacement + return true; +#elif defined(__arm__) + const unsigned char trampoline[] = { + // .thumb + 0x46, 0x04, // nop + 0x78, 0x47, // bx pc + 0x46, 0x04, // nop + // .arm + 0x04, 0xf0, 0x1f, 0xe5, // ldr pc, [pc, #-4] + // .word <new_func> + }; + const unsigned char *start; + if (addr & 0x01) { + /* Function is thumb, the actual address of the code is without the + * least significant bit. */ + addr--; + /* The arm part of the trampoline needs to be 32-bit aligned */ + if (addr & 0x02) + start = trampoline; + else + start = trampoline + 2; + } else { + /* Function is arm, we only need the arm part of the trampoline */ + start = trampoline + 6; + } + + size_t len = sizeof(trampoline) - (start - trampoline); + EnsureWritable w(reinterpret_cast<void *>(addr), len + sizeof(void *)); + memcpy(reinterpret_cast<void *>(addr), start, len); + *reinterpret_cast<void **>(addr + len) = FunctionPtr(new_func); + cacheflush(addr, addr + len + sizeof(void *), 0); + return true; +#else + return false; +#endif +} +#else +#define sys_sigaction sigaction +template <typename T> +static bool +Divert(T func, T new_func) +{ + return false; +} +#endif + +namespace { + +/* Clock that only accounts for time spent in the current process. */ +static uint64_t ProcessTimeStamp_Now() +{ + struct timespec ts; + int rv = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts); + + if (rv != 0) { + return 0; + } + + uint64_t baseNs = (uint64_t)ts.tv_sec * 1000000000; + return baseNs + (uint64_t)ts.tv_nsec; +} + +} + +/* Data structure used to pass data to the temporary signal handler, + * as well as triggering a test crash. */ +struct TmpData { + volatile int crash_int; + volatile uint64_t crash_timestamp; +}; + +SEGVHandler::SEGVHandler() +: initialized(false), registeredHandler(false), signalHandlingBroken(true) +, signalHandlingSlow(true) +{ + /* Ensure logging is initialized before the DEBUG_LOG in the test_handler. + * As this constructor runs before the ElfLoader constructor (by effect + * of ElfLoader inheriting from this class), this also initializes on behalf + * of ElfLoader and DebuggerHelper. */ + Logging::Init(); + + /* Initialize oldStack.ss_flags to an invalid value when used to set + * an alternative stack, meaning we haven't got information about the + * original alternative stack and thus don't mean to restore it in + * the destructor. */ + oldStack.ss_flags = SS_ONSTACK; + + /* Get the current segfault signal handler. */ + struct sigaction old_action; + sys_sigaction(SIGSEGV, nullptr, &old_action); + + /* Some devices don't provide useful information to their SIGSEGV handlers, + * making it impossible for on-demand decompression to work. To check if + * we're on such a device, setup a temporary handler and deliberately + * trigger a segfault. The handler will set signalHandlingBroken if the + * provided information is bogus. + * Some other devices have a kernel option enabled that makes SIGSEGV handler + * have an overhead so high that it affects how on-demand decompression + * performs. The handler will also set signalHandlingSlow if the triggered + * SIGSEGV took too much time. */ + struct sigaction action; + action.sa_sigaction = &SEGVHandler::test_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_SIGINFO | SA_NODEFER; + action.sa_restorer = nullptr; + stackPtr.Assign(MemoryRange::mmap(nullptr, PageSize(), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (stackPtr.get() == MAP_FAILED) + return; + if (sys_sigaction(SIGSEGV, &action, nullptr)) + return; + + TmpData *data = reinterpret_cast<TmpData*>(stackPtr.get()); + data->crash_timestamp = ProcessTimeStamp_Now(); + mprotect(stackPtr, stackPtr.GetLength(), PROT_NONE); + data->crash_int = 123; + /* Restore the original segfault signal handler. */ + sys_sigaction(SIGSEGV, &old_action, nullptr); + stackPtr.Assign(MAP_FAILED, 0); +} + +void +SEGVHandler::FinishInitialization() +{ + /* Ideally, we'd need some locking here, but in practice, we're not + * going to race with another thread. */ + initialized = true; + + if (signalHandlingBroken || signalHandlingSlow) + return; + + typedef int (*sigaction_func)(int, const struct sigaction *, + struct sigaction *); + + sigaction_func libc_sigaction; + +#if defined(ANDROID) + /* Android > 4.4 comes with a sigaction wrapper in a LD_PRELOADed library + * (libsigchain) for ART. That wrapper kind of does the same trick as we + * do, so we need extra care in handling it. + * - Divert the libc's sigaction, assuming the LD_PRELOADed library uses + * it under the hood (which is more or less true according to the source + * of that library, since it's doing a lookup in RTLD_NEXT) + * - With the LD_PRELOADed library in place, all calls to sigaction from + * from system libraries will go to the LD_PRELOADed library. + * - The LD_PRELOADed library calls to sigaction go to our __wrap_sigaction. + * - The calls to sigaction from libraries faulty.lib loads are sent to + * the LD_PRELOADed library. + * In practice, for signal handling, this means: + * - The signal handler registered to the kernel is ours. + * - Our handler redispatches to the LD_PRELOADed library's if there's a + * segfault we don't handle. + * - The LD_PRELOADed library redispatches according to whatever system + * library or faulty.lib-loaded library set with sigaction. + * + * When there is no sigaction wrapper in place: + * - Divert the libc's sigaction. + * - Calls to sigaction from system library and faulty.lib-loaded libraries + * all go to the libc's sigaction, which end up in our __wrap_sigaction. + * - The signal handler registered to the kernel is ours. + * - Our handler redispatches according to whatever system library or + * faulty.lib-loaded library set with sigaction. + */ + void *libc = dlopen("libc.so", RTLD_GLOBAL | RTLD_LAZY); + if (libc) { + /* + * Lollipop bionic only has a small trampoline in sigaction, with the real + * work happening in __sigaction. Divert there instead of sigaction if it exists. + * Bug 1154803 + */ + libc_sigaction = reinterpret_cast<sigaction_func>(dlsym(libc, "__sigaction")); + + if (!libc_sigaction) { + libc_sigaction = + reinterpret_cast<sigaction_func>(dlsym(libc, "sigaction")); + } + } else +#endif + { + libc_sigaction = sigaction; + } + + if (!Divert(libc_sigaction, __wrap_sigaction)) + return; + + /* Setup an alternative stack if the already existing one is not big + * enough, or if there is none. */ + if (sigaltstack(nullptr, &oldStack) == 0) { + if (oldStack.ss_flags == SS_ONSTACK) + oldStack.ss_flags = 0; + if (!oldStack.ss_sp || oldStack.ss_size < stackSize) { + stackPtr.Assign(MemoryRange::mmap(nullptr, stackSize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (stackPtr.get() == MAP_FAILED) + return; + stack_t stack; + stack.ss_sp = stackPtr; + stack.ss_size = stackSize; + stack.ss_flags = 0; + if (sigaltstack(&stack, nullptr) != 0) + return; + } + } + /* Register our own handler, and store the already registered one in + * SEGVHandler's struct sigaction member */ + action.sa_sigaction = &SEGVHandler::handler; + action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; + registeredHandler = !sys_sigaction(SIGSEGV, &action, &this->action); +} + +SEGVHandler::~SEGVHandler() +{ + /* Restore alternative stack for signals */ + if (oldStack.ss_flags != SS_ONSTACK) + sigaltstack(&oldStack, nullptr); + /* Restore original signal handler */ + if (registeredHandler) + sys_sigaction(SIGSEGV, &this->action, nullptr); +} + +/* Test handler for a deliberately triggered SIGSEGV that determines whether + * useful information is provided to signal handlers, particularly whether + * si_addr is filled in properly, and whether the segfault handler is called + * quickly enough. */ +void SEGVHandler::test_handler(int signum, siginfo_t *info, void *context) +{ + SEGVHandler &that = ElfLoader::Singleton; + if (signum == SIGSEGV && info && + info->si_addr == that.stackPtr.get()) + that.signalHandlingBroken = false; + mprotect(that.stackPtr, that.stackPtr.GetLength(), PROT_READ | PROT_WRITE); + TmpData *data = reinterpret_cast<TmpData*>(that.stackPtr.get()); + uint64_t latency = ProcessTimeStamp_Now() - data->crash_timestamp; + DEBUG_LOG("SEGVHandler latency: %" PRIu64, latency); + /* See bug 886736 for timings on different devices, 150 µs is reasonably above + * the latency on "working" devices and seems to be short enough to not incur + * a huge overhead to on-demand decompression. */ + if (latency <= 150000) + that.signalHandlingSlow = false; +} + +/* TODO: "properly" handle signal masks and flags */ +void SEGVHandler::handler(int signum, siginfo_t *info, void *context) +{ + //ASSERT(signum == SIGSEGV); + DEBUG_LOG("Caught segmentation fault @%p", info->si_addr); + + /* Check whether we segfaulted in the address space of a CustomElf. We're + * only expecting that to happen as an access error. */ + if (info->si_code == SEGV_ACCERR) { + RefPtr<LibHandle> handle = + ElfLoader::Singleton.GetHandleByPtr(info->si_addr); + BaseElf *elf; + if (handle && (elf = handle->AsBaseElf())) { + DEBUG_LOG("Within the address space of %s", handle->GetPath()); + if (elf->mappable && elf->mappable->ensure(info->si_addr)) { + return; + } + } + } + + /* Redispatch to the registered handler */ + SEGVHandler &that = ElfLoader::Singleton; + if (that.action.sa_flags & SA_SIGINFO) { + DEBUG_LOG("Redispatching to registered handler @%p", + FunctionPtr(that.action.sa_sigaction)); + that.action.sa_sigaction(signum, info, context); + } else if (that.action.sa_handler == SIG_DFL) { + DEBUG_LOG("Redispatching to default handler"); + /* Reset the handler to the default one, and trigger it. */ + sys_sigaction(signum, &that.action, nullptr); + raise(signum); + } else if (that.action.sa_handler != SIG_IGN) { + DEBUG_LOG("Redispatching to registered handler @%p", + FunctionPtr(that.action.sa_handler)); + that.action.sa_handler(signum); + } else { + DEBUG_LOG("Ignoring"); + } +} + +int +SEGVHandler::__wrap_sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact) +{ + SEGVHandler &that = ElfLoader::Singleton; + + /* Use system sigaction() function for all but SIGSEGV signals. */ + if (!that.registeredHandler || (signum != SIGSEGV)) + return sys_sigaction(signum, act, oldact); + + if (oldact) + *oldact = that.action; + if (act) + that.action = *act; + return 0; +} + +Logging Logging::Singleton; diff --git a/mozglue/linker/ElfLoader.h b/mozglue/linker/ElfLoader.h new file mode 100644 index 0000000000..0d26a011ee --- /dev/null +++ b/mozglue/linker/ElfLoader.h @@ -0,0 +1,674 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ElfLoader_h +#define ElfLoader_h + +#include <vector> +#include <dlfcn.h> +#include <signal.h> +#include "mozilla/RefCounted.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "Zip.h" +#include "Elfxx.h" +#include "Mappable.h" + +/** + * dlfcn.h replacement functions + */ +extern "C" { + void *__wrap_dlopen(const char *path, int flags); + const char *__wrap_dlerror(void); + void *__wrap_dlsym(void *handle, const char *symbol); + int __wrap_dlclose(void *handle); + +#ifndef HAVE_DLADDR + typedef struct { + const char *dli_fname; + void *dli_fbase; + const char *dli_sname; + void *dli_saddr; + } Dl_info; +#endif + int __wrap_dladdr(void *addr, Dl_info *info); + + struct dl_phdr_info { + Elf::Addr dlpi_addr; + const char *dlpi_name; + const Elf::Phdr *dlpi_phdr; + Elf::Half dlpi_phnum; + }; + + typedef int (*dl_phdr_cb)(struct dl_phdr_info *, size_t, void *); + int __wrap_dl_iterate_phdr(dl_phdr_cb callback, void *data); + +#ifdef __ARM_EABI__ + const void *__wrap___gnu_Unwind_Find_exidx(void *pc, int *pcount); +#endif + +/** + * faulty.lib public API + */ +MFBT_API size_t +__dl_get_mappable_length(void *handle); + +MFBT_API void * +__dl_mmap(void *handle, void *addr, size_t length, off_t offset); + +MFBT_API void +__dl_munmap(void *handle, void *addr, size_t length); + +MFBT_API bool +IsSignalHandlingBroken(); + +} + +/* Forward declarations for use in LibHandle */ +class BaseElf; +class CustomElf; +class SystemElf; + +/** + * Specialize RefCounted template for LibHandle. We may get references to + * LibHandles during the execution of their destructor, so we need + * RefCounted<LibHandle>::Release to support some reentrancy. See further + * below. + */ +class LibHandle; + +namespace mozilla { +namespace detail { + +template <> inline void RefCounted<LibHandle, AtomicRefCount>::Release() const; + +template <> inline RefCounted<LibHandle, AtomicRefCount>::~RefCounted() +{ + MOZ_ASSERT(mRefCnt == 0x7fffdead); +} + +} /* namespace detail */ +} /* namespace mozilla */ + +/** + * Abstract class for loaded libraries. Libraries may be loaded through the + * system linker or this linker, both cases will be derived from this class. + */ +class LibHandle: public mozilla::external::AtomicRefCounted<LibHandle> +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(LibHandle) + /** + * Constructor. Takes the path of the loaded library and will store a copy + * of the leaf name. + */ + LibHandle(const char *path) + : directRefCnt(0), path(path ? strdup(path) : nullptr), mappable(nullptr) { } + + /** + * Destructor. + */ + virtual ~LibHandle(); + + /** + * Returns the pointer to the address to which the given symbol resolves + * inside the library. It is not supposed to resolve the symbol in other + * libraries, although in practice, it will for system libraries. + */ + virtual void *GetSymbolPtr(const char *symbol) const = 0; + + /** + * Returns whether the given address is part of the virtual address space + * covered by the loaded library. + */ + virtual bool Contains(void *addr) const = 0; + + /** + * Returns the base address of the loaded library. + */ + virtual void *GetBase() const = 0; + + /** + * Returns the file name of the library without the containing directory. + */ + const char *GetName() const; + + /** + * Returns the full path of the library, when available. Otherwise, returns + * the file name. + */ + const char *GetPath() const + { + return path; + } + + /** + * Library handles can be referenced from other library handles or + * externally (when dlopen()ing using this linker). We need to be + * able to distinguish between the two kind of referencing for better + * bookkeeping. + */ + void AddDirectRef() + { + ++directRefCnt; + mozilla::external::AtomicRefCounted<LibHandle>::AddRef(); + } + + /** + * Releases a direct reference, and returns whether there are any direct + * references left. + */ + bool ReleaseDirectRef() + { + bool ret = false; + if (directRefCnt) { + MOZ_ASSERT(directRefCnt <= + mozilla::external::AtomicRefCounted<LibHandle>::refCount()); + if (--directRefCnt) + ret = true; + mozilla::external::AtomicRefCounted<LibHandle>::Release(); + } + return ret; + } + + /** + * Returns the number of direct references + */ + MozRefCountType DirectRefCount() + { + return directRefCnt; + } + + /** + * Returns the complete size of the file or stream behind the library + * handle. + */ + size_t GetMappableLength() const; + + /** + * Returns a memory mapping of the file or stream behind the library + * handle. + */ + void *MappableMMap(void *addr, size_t length, off_t offset) const; + + /** + * Unmaps a memory mapping of the file or stream behind the library + * handle. + */ + void MappableMUnmap(void *addr, size_t length) const; + +#ifdef __ARM_EABI__ + /** + * Find the address and entry count of the ARM.exidx section + * associated with the library + */ + virtual const void *FindExidx(int *pcount) const = 0; +#endif + + /** + * Shows some stats about the Mappable instance. The when argument is to be + * used by the caller to give an identifier of the when the stats call is + * made. + */ + virtual void stats(const char *when) const { }; + +protected: + /** + * Returns a mappable object for use by MappableMMap and related functions. + */ + virtual Mappable *GetMappable() const = 0; + + /** + * Returns the instance, casted as the wanted type. Returns nullptr if + * that's not the actual type. (short of a better way to do this without + * RTTI) + */ + friend class ElfLoader; + friend class CustomElf; + friend class SEGVHandler; + virtual BaseElf *AsBaseElf() { return nullptr; } + virtual SystemElf *AsSystemElf() { return nullptr; } + +private: + MozRefCountType directRefCnt; + char *path; + + /* Mappable object keeping the result of GetMappable() */ + mutable RefPtr<Mappable> mappable; +}; + +/** + * Specialized RefCounted<LibHandle>::Release. Under normal operation, when + * mRefCnt reaches 0, the LibHandle is deleted. Its mRefCnt is however + * increased to 1 on normal builds, and 0x7fffdead on debug builds so that the + * LibHandle can still be referenced while the destructor is executing. The + * mRefCnt is allowed to grow > 0x7fffdead, but not to decrease under that + * value, which would mean too many Releases from within the destructor. + */ +namespace mozilla { +namespace detail { + +template <> inline void RefCounted<LibHandle, AtomicRefCount>::Release() const { +#ifdef DEBUG + if (mRefCnt > 0x7fff0000) + MOZ_ASSERT(mRefCnt > 0x7fffdead); +#endif + MOZ_ASSERT(mRefCnt > 0); + if (mRefCnt > 0) { + if (0 == --mRefCnt) { +#ifdef DEBUG + mRefCnt = 0x7fffdead; +#else + mRefCnt = 1; +#endif + delete static_cast<const LibHandle*>(this); + } + } +} + +} /* namespace detail */ +} /* namespace mozilla */ + +/** + * Class handling libraries loaded by the system linker + */ +class SystemElf: public LibHandle +{ +public: + /** + * Returns a new SystemElf for the given path. The given flags are passed + * to dlopen(). + */ + static already_AddRefed<LibHandle> Load(const char *path, int flags); + + /** + * Inherited from LibHandle + */ + virtual ~SystemElf(); + virtual void *GetSymbolPtr(const char *symbol) const; + virtual bool Contains(void *addr) const { return false; /* UNIMPLEMENTED */ } + virtual void *GetBase() const { return nullptr; /* UNIMPLEMENTED */ } + +#ifdef __ARM_EABI__ + virtual const void *FindExidx(int *pcount) const; +#endif + +protected: + virtual Mappable *GetMappable() const; + + /** + * Returns the instance, casted as SystemElf. (short of a better way to do + * this without RTTI) + */ + friend class ElfLoader; + virtual SystemElf *AsSystemElf() { return this; } + + /** + * Remove the reference to the system linker handle. This avoids dlclose() + * being called when the instance is destroyed. + */ + void Forget() + { + dlhandle = nullptr; + } + +private: + /** + * Private constructor + */ + SystemElf(const char *path, void *handle) + : LibHandle(path), dlhandle(handle) { } + + /* Handle as returned by system dlopen() */ + void *dlhandle; +}; + +/** + * The ElfLoader registers its own SIGSEGV handler to handle segmentation + * faults within the address space of the loaded libraries. It however + * allows a handler to be set for faults in other places, and redispatches + * to the handler set through signal() or sigaction(). + */ +class SEGVHandler +{ +public: + bool hasRegisteredHandler() { + if (! initialized) + FinishInitialization(); + return registeredHandler; + } + + bool isSignalHandlingBroken() { + return signalHandlingBroken; + } + + static int __wrap_sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact); + +protected: + SEGVHandler(); + ~SEGVHandler(); + +private: + + /** + * The constructor doesn't do all initialization, and the tail is done + * at a later time. + */ + void FinishInitialization(); + + /** + * SIGSEGV handler registered with __wrap_signal or __wrap_sigaction. + */ + struct sigaction action; + + /** + * ElfLoader SIGSEGV handler. + */ + static void handler(int signum, siginfo_t *info, void *context); + + /** + * Temporary test handler. + */ + static void test_handler(int signum, siginfo_t *info, void *context); + + /** + * Size of the alternative stack. The printf family requires more than 8KB + * of stack, and our signal handler may print a few things. + */ + static const size_t stackSize = 12 * 1024; + + /** + * Alternative stack information used before initialization. + */ + stack_t oldStack; + + /** + * Pointer to an alternative stack for signals. Only set if oldStack is + * not set or not big enough. + */ + MappedPtr stackPtr; + + bool initialized; + bool registeredHandler; + bool signalHandlingBroken; + bool signalHandlingSlow; +}; + +/** + * Elf Loader class in charge of loading and bookkeeping libraries. + */ +class ElfLoader: public SEGVHandler +{ +public: + /** + * The Elf Loader instance + */ + static ElfLoader Singleton; + + /** + * Loads the given library with the given flags. Equivalent to dlopen() + * The extra "parent" argument optionally gives the handle of the library + * requesting the given library to be loaded. The loader may look in the + * directory containing that parent library for the library to load. + */ + already_AddRefed<LibHandle> Load(const char *path, int flags, + LibHandle *parent = nullptr); + + /** + * Returns the handle of the library containing the given address in + * its virtual address space, i.e. the library handle for which + * LibHandle::Contains returns true. Its purpose is to allow to + * implement dladdr(). + */ + already_AddRefed<LibHandle> GetHandleByPtr(void *addr); + + /** + * Returns a Mappable object for the path. Paths in the form + * /foo/bar/baz/archive!/directory/lib.so + * try to load the directory/lib.so in /foo/bar/baz/archive, provided + * that file is a Zip archive. + */ + static Mappable *GetMappableFromPath(const char *path); + + void ExpectShutdown(bool val) { expect_shutdown = val; } + bool IsShutdownExpected() { return expect_shutdown; } + +private: + bool expect_shutdown; + +protected: + /** + * Registers the given handle. This method is meant to be called by + * LibHandle subclass creators. + */ + void Register(LibHandle *handle); + void Register(CustomElf *handle); + + /** + * Forget about the given handle. This method is meant to be called by + * LibHandle subclass destructors. + */ + void Forget(LibHandle *handle); + void Forget(CustomElf *handle); + + /* Last error. Used for dlerror() */ + friend class SystemElf; + friend const char *__wrap_dlerror(void); + friend void *__wrap_dlsym(void *handle, const char *symbol); + friend int __wrap_dlclose(void *handle); + const char *lastError; + +private: + ElfLoader() : expect_shutdown(true) {} + ~ElfLoader(); + + /* Initialization code that can't run during static initialization. */ + void Init(); + + /* System loader handle for the library/program containing our code. This + * is used to resolve wrapped functions. */ + RefPtr<LibHandle> self_elf; + +#if defined(ANDROID) + /* System loader handle for the libc. This is used to resolve weak symbols + * that some libcs contain that the Android linker won't dlsym(). Normally, + * we wouldn't treat non-Android differently, but glibc uses versioned + * symbols which this linker doesn't support. */ + RefPtr<LibHandle> libc; +#endif + + /* Bookkeeping */ + typedef std::vector<LibHandle *> LibHandleList; + LibHandleList handles; + +protected: + friend class CustomElf; + friend class LoadedElf; + /** + * Show some stats about Mappables in CustomElfs. The when argument is to + * be used by the caller to give an identifier of the when the stats call + * is made. + */ + static void stats(const char *when); + + /* Definition of static destructors as to be used for C++ ABI compatibility */ + typedef void (*Destructor)(void *object); + + /** + * C++ ABI makes static initializers register destructors through a specific + * atexit interface. On glibc/linux systems, the dso_handle is a pointer + * within a given library. On bionic/android systems, it is an undefined + * symbol. Making sense of the value is not really important, and all that + * is really important is that it is different for each loaded library, so + * that they can be discriminated when shutting down. For convenience, on + * systems where the dso handle is a symbol, that symbol is resolved to + * point at corresponding CustomElf. + * + * Destructors are registered with __*_atexit with an associated object to + * be passed as argument when it is called. + * + * When __cxa_finalize is called, destructors registered for the given + * DSO handle are called in the reverse order they were registered. + */ +#ifdef __ARM_EABI__ + static int __wrap_aeabi_atexit(void *that, Destructor destructor, + void *dso_handle); +#else + static int __wrap_cxa_atexit(Destructor destructor, void *that, + void *dso_handle); +#endif + + static void __wrap_cxa_finalize(void *dso_handle); + + /** + * Registered destructor. Keeps track of the destructor function pointer, + * associated object to call it with, and DSO handle. + */ + class DestructorCaller { + public: + DestructorCaller(Destructor destructor, void *object, void *dso_handle) + : destructor(destructor), object(object), dso_handle(dso_handle) { } + + /** + * Call the destructor function with the associated object. + * Call only once, see CustomElf::~CustomElf. + */ + void Call(); + + /** + * Returns whether the destructor is associated to the given DSO handle + */ + bool IsForHandle(void *handle) const + { + return handle == dso_handle; + } + + private: + Destructor destructor; + void *object; + void *dso_handle; + }; + +private: + /* Keep track of all registered destructors */ + std::vector<DestructorCaller> destructors; + + /* Forward declaration, see further below */ + class DebuggerHelper; +public: + /* Loaded object descriptor for the debugger interface below*/ + struct link_map { + /* Base address of the loaded object. */ + const void *l_addr; + /* File name */ + const char *l_name; + /* Address of the PT_DYNAMIC segment. */ + const void *l_ld; + + private: + friend class ElfLoader::DebuggerHelper; + /* Double linked list of loaded objects. */ + link_map *l_next, *l_prev; + }; + +private: + /* Data structure used by the linker to give details about shared objects it + * loaded to debuggers. This is normally defined in link.h, but Android + * headers lack this file. */ + struct r_debug { + /* Version number of the protocol. */ + int r_version; + + /* Head of the linked list of loaded objects. */ + link_map *r_map; + + /* Function to be called when updates to the linked list of loaded objects + * are going to occur. The function is to be called before and after + * changes. */ + void (*r_brk)(void); + + /* Indicates to the debugger what state the linked list of loaded objects + * is in when the function above is called. */ + enum { + RT_CONSISTENT, /* Changes are complete */ + RT_ADD, /* Beginning to add a new object */ + RT_DELETE /* Beginning to remove an object */ + } r_state; + }; + + /* Memory representation of ELF Auxiliary Vectors */ + struct AuxVector { + Elf::Addr type; + Elf::Addr value; + }; + + /* Helper class used to integrate libraries loaded by this linker in + * r_debug */ + class DebuggerHelper + { + public: + DebuggerHelper(); + + void Init(AuxVector *auvx); + + operator bool() + { + return dbg; + } + + /* Make the debugger aware of a new loaded object */ + void Add(link_map *map); + + /* Make the debugger aware of the unloading of an object */ + void Remove(link_map *map); + + /* Iterates over all link_maps */ + class iterator + { + public: + const link_map *operator ->() const + { + return item; + } + + const link_map &operator ++() + { + item = item->l_next; + return *item; + } + + bool operator<(const iterator &other) const + { + if (other.item == nullptr) + return item ? true : false; + MOZ_CRASH("DebuggerHelper::iterator::operator< called with something else than DebuggerHelper::end()"); + } + protected: + friend class DebuggerHelper; + iterator(const link_map *item): item(item) { } + + private: + const link_map *item; + }; + + iterator begin() const + { + return iterator(dbg ? dbg->r_map : nullptr); + } + + iterator end() const + { + return iterator(nullptr); + } + + private: + r_debug *dbg; + link_map *firstAdded; + }; + friend int __wrap_dl_iterate_phdr(dl_phdr_cb callback, void *data); + DebuggerHelper dbg; +}; + +#endif /* ElfLoader_h */ diff --git a/mozglue/linker/Elfxx.h b/mozglue/linker/Elfxx.h new file mode 100644 index 0000000000..b21a893365 --- /dev/null +++ b/mozglue/linker/Elfxx.h @@ -0,0 +1,241 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Elfxx_h +#define Elfxx_h + +/** + * Android system headers have two different elf.h file. The one under linux/ + * is the most complete on older android API versions. + */ +#if defined(ANDROID) && __ANDROID_API__ < 21 +#include <linux/elf.h> +#else +#include <elf.h> +#endif +#include <endian.h> + +#if defined(__ARM_EABI__) && !defined(PT_ARM_EXIDX) +#define PT_ARM_EXIDX 0x70000001 +#endif + +/** + * Generic ELF macros for the target system + */ +#ifdef __LP64__ +#define Elf_(type) Elf64_ ## type +#define ELFCLASS ELFCLASS64 +#define ELF_R_TYPE ELF64_R_TYPE +#define ELF_R_SYM ELF64_R_SYM +#ifndef ELF_ST_BIND +#define ELF_ST_BIND ELF64_ST_BIND +#endif +#else +#define Elf_(type) Elf32_ ## type +#define ELFCLASS ELFCLASS32 +#define ELF_R_TYPE ELF32_R_TYPE +#define ELF_R_SYM ELF32_R_SYM +#ifndef ELF_ST_BIND +#define ELF_ST_BIND ELF32_ST_BIND +#endif +#endif + +#ifndef __BYTE_ORDER +#error Cannot find endianness +#endif + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define ELFDATA ELFDATA2LSB +#elif __BYTE_ORDER == __BIG_ENDIAN +#define ELFDATA ELFDATA2MSB +#endif + +#ifdef __linux__ +#define ELFOSABI ELFOSABI_LINUX +#ifdef EI_ABIVERSION +#define ELFABIVERSION 0 +#endif +#else +#error Unknown ELF OSABI +#endif + +#if defined(__i386__) +#define ELFMACHINE EM_386 + +// Doing this way probably doesn't scale to other architectures +#define R_ABS R_386_32 +#define R_GLOB_DAT R_386_GLOB_DAT +#define R_JMP_SLOT R_386_JMP_SLOT +#define R_RELATIVE R_386_RELATIVE +#define RELOC(n) DT_REL ## n +#define UNSUPPORTED_RELOC(n) DT_RELA ## n +#define STR_RELOC(n) "DT_REL" # n +#define Reloc Rel + +#elif defined(__x86_64__) +#define ELFMACHINE EM_X86_64 + +#define R_ABS R_X86_64_64 +#define R_GLOB_DAT R_X86_64_GLOB_DAT +#define R_JMP_SLOT R_X86_64_JUMP_SLOT +#define R_RELATIVE R_X86_64_RELATIVE +#define RELOC(n) DT_RELA ## n +#define UNSUPPORTED_RELOC(n) DT_REL ## n +#define STR_RELOC(n) "DT_RELA" # n +#define Reloc Rela + +#elif defined(__arm__) +#define ELFMACHINE EM_ARM + +#ifndef R_ARM_ABS32 +#define R_ARM_ABS32 2 +#endif +#ifndef R_ARM_GLOB_DAT +#define R_ARM_GLOB_DAT 21 +#endif +#ifndef R_ARM_JUMP_SLOT +#define R_ARM_JUMP_SLOT 22 +#endif +#ifndef R_ARM_RELATIVE +#define R_ARM_RELATIVE 23 +#endif + +#define R_ABS R_ARM_ABS32 +#define R_GLOB_DAT R_ARM_GLOB_DAT +#define R_JMP_SLOT R_ARM_JUMP_SLOT +#define R_RELATIVE R_ARM_RELATIVE +#define RELOC(n) DT_REL ## n +#define UNSUPPORTED_RELOC(n) DT_RELA ## n +#define STR_RELOC(n) "DT_REL" # n +#define Reloc Rel + +#else +#error Unknown ELF machine type +#endif + +/** + * Android system headers don't have all definitions + */ +#ifndef STN_UNDEF +#define STN_UNDEF 0 +#endif +#ifndef DT_INIT_ARRAY +#define DT_INIT_ARRAY 25 +#endif +#ifndef DT_FINI_ARRAY +#define DT_FINI_ARRAY 26 +#endif +#ifndef DT_INIT_ARRAYSZ +#define DT_INIT_ARRAYSZ 27 +#endif +#ifndef DT_FINI_ARRAYSZ +#define DT_FINI_ARRAYSZ 28 +#endif +#ifndef DT_RELACOUNT +#define DT_RELACOUNT 0x6ffffff9 +#endif +#ifndef DT_RELCOUNT +#define DT_RELCOUNT 0x6ffffffa +#endif +#ifndef DT_VERSYM +#define DT_VERSYM 0x6ffffff0 +#endif +#ifndef DT_VERDEF +#define DT_VERDEF 0x6ffffffc +#endif +#ifndef DT_VERDEFNUM +#define DT_VERDEFNUM 0x6ffffffd +#endif +#ifndef DT_VERNEED +#define DT_VERNEED 0x6ffffffe +#endif +#ifndef DT_VERNEEDNUM +#define DT_VERNEEDNUM 0x6fffffff +#endif +#ifndef DT_FLAGS_1 +#define DT_FLAGS_1 0x6ffffffb +#endif +#ifndef DT_FLAGS +#define DT_FLAGS 30 +#endif +#ifndef DF_SYMBOLIC +#define DF_SYMBOLIC 0x00000002 +#endif +#ifndef DF_TEXTREL +#define DF_TEXTREL 0x00000004 +#endif + +namespace Elf { + +/** + * Define a few basic Elf Types + */ +typedef Elf_(Phdr) Phdr; +typedef Elf_(Dyn) Dyn; +typedef Elf_(Sym) Sym; +typedef Elf_(Addr) Addr; +typedef Elf_(Word) Word; +typedef Elf_(Half) Half; + +/** + * Helper class around the standard Elf header struct + */ +struct Ehdr: public Elf_(Ehdr) +{ + /** + * Equivalent to reinterpret_cast<const Ehdr *>(buf), but additionally + * checking that this is indeed an Elf header and that the Elf type + * corresponds to that of the system + */ + static const Ehdr *validate(const void *buf); +}; + +/** + * Elf String table + */ +class Strtab: public UnsizedArray<const char> +{ +public: + /** + * Returns the string at the given index in the table + */ + const char *GetStringAt(off_t index) const + { + return &UnsizedArray<const char>::operator[](index); + } +}; + +/** + * Helper class around Elf relocation. + */ +struct Rel: public Elf_(Rel) +{ + /** + * Returns the addend for the relocation, which is the value stored + * at r_offset. + */ + Addr GetAddend(void *base) const + { + return *(reinterpret_cast<const Addr *>( + reinterpret_cast<const char *>(base) + r_offset)); + } +}; + +/** + * Helper class around Elf relocation with addend. + */ +struct Rela: public Elf_(Rela) +{ + /** + * Returns the addend for the relocation. + */ + Addr GetAddend(void *base) const + { + return r_addend; + } +}; + +} /* namespace Elf */ + +#endif /* Elfxx_h */ diff --git a/mozglue/linker/Logging.h b/mozglue/linker/Logging.h new file mode 100644 index 0000000000..046d918f4a --- /dev/null +++ b/mozglue/linker/Logging.h @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Logging_h +#define Logging_h + +#include "mozilla/Likely.h" + +#ifdef ANDROID +#include <android/log.h> +#define LOG(...) __android_log_print(ANDROID_LOG_INFO, "GeckoLinker", __VA_ARGS__) +#define WARN(...) __android_log_print(ANDROID_LOG_WARN, "GeckoLinker", __VA_ARGS__) +#define ERROR(...) __android_log_print(ANDROID_LOG_ERROR, "GeckoLinker", __VA_ARGS__) +#else +#include <cstdio> + +/* Expand to 1 or m depending on whether there is one argument or more + * given. */ +#define MOZ_ONE_OR_MORE_ARGS_IMPL2(_1, _2, _3, _4, _5, _6, _7, _8, _9, N, ...) \ + N +#define MOZ_ONE_OR_MORE_ARGS_IMPL(args) MOZ_ONE_OR_MORE_ARGS_IMPL2 args +#define MOZ_ONE_OR_MORE_ARGS(...) \ + MOZ_ONE_OR_MORE_ARGS_IMPL((__VA_ARGS__, m, m, m, m, m, m, m, m, 1, 0)) + +#define MOZ_MACRO_GLUE(a, b) a b +#define MOZ_CONCAT2(a, b) a ## b +#define MOZ_CONCAT1(a, b) MOZ_CONCAT2(a, b) +#define MOZ_CONCAT(a, b) MOZ_CONCAT1(a, b) + +/* Some magic to choose between LOG1 and LOGm depending on the number of + * arguments */ +#define MOZ_CHOOSE_LOG(...) \ + MOZ_MACRO_GLUE(MOZ_CONCAT(LOG, MOZ_ONE_OR_MORE_ARGS(__VA_ARGS__)), \ + (__VA_ARGS__)) + +#define LOG1(format) fprintf(stderr, format "\n") +#define LOGm(format, ...) fprintf(stderr, format "\n", __VA_ARGS__) +#define LOG(...) MOZ_CHOOSE_LOG(__VA_ARGS__) +#define WARN(...) MOZ_CHOOSE_LOG("Warning: " __VA_ARGS__) +#define ERROR(...) MOZ_CHOOSE_LOG("Error: " __VA_ARGS__) + +#endif + +class Logging +{ +public: + static bool isVerbose() + { + return Singleton.verbose; + } + +private: + bool verbose; + +public: + static void Init() + { + const char *env = getenv("MOZ_DEBUG_LINKER"); + if (env && *env == '1') + Singleton.verbose = true; + } + +private: + static Logging Singleton; +}; + +#define DEBUG_LOG(...) \ + do { \ + if (MOZ_UNLIKELY(Logging::isVerbose())) { \ + LOG(__VA_ARGS__); \ + } \ + } while(0) + +#if defined(__LP64__) +# define PRIxAddr "lx" +# define PRIxSize "lx" +# define PRIdSize "ld" +# define PRIuSize "lu" +#else +# define PRIxAddr "x" +# define PRIxSize "x" +# define PRIdSize "d" +# define PRIuSize "u" +#endif + +#endif /* Logging_h */ diff --git a/mozglue/linker/Mappable.cpp b/mozglue/linker/Mappable.cpp new file mode 100644 index 0000000000..47b883d2d5 --- /dev/null +++ b/mozglue/linker/Mappable.cpp @@ -0,0 +1,681 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <fcntl.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <cstring> +#include <cstdlib> +#include <cstdio> +#include <string> + +#include "Mappable.h" + +#include "mozilla/UniquePtr.h" + +#ifdef ANDROID +#include <linux/ashmem.h> +#endif +#include <sys/stat.h> +#include <errno.h> +#include "ElfLoader.h" +#include "SeekableZStream.h" +#include "XZStream.h" +#include "Logging.h" + +using mozilla::MakeUnique; +using mozilla::UniquePtr; + +class CacheValidator +{ +public: + CacheValidator(const char* aCachedLibPath, Zip* aZip, Zip::Stream* aStream) + : mCachedLibPath(aCachedLibPath) + { + static const char kChecksumSuffix[] = ".crc"; + + mCachedChecksumPath = + MakeUnique<char[]>(strlen(aCachedLibPath) + sizeof(kChecksumSuffix)); + sprintf(mCachedChecksumPath.get(), "%s%s", aCachedLibPath, kChecksumSuffix); + DEBUG_LOG("mCachedChecksumPath: %s", mCachedChecksumPath.get()); + + mChecksum = aStream->GetCRC32(); + DEBUG_LOG("mChecksum: %x", mChecksum); + } + + // Returns whether the cache is valid and up-to-date. + bool IsValid() const + { + // Validate based on checksum. + RefPtr<Mappable> checksumMap = MappableFile::Create(mCachedChecksumPath.get()); + if (!checksumMap) { + // Force caching if checksum is missing in cache. + return false; + } + + DEBUG_LOG("Comparing %x with %s", mChecksum, mCachedChecksumPath.get()); + MappedPtr checksumBuf = checksumMap->mmap(nullptr, checksumMap->GetLength(), + PROT_READ, MAP_PRIVATE, 0); + if (checksumBuf == MAP_FAILED) { + WARN("Couldn't map %s to validate checksum", mCachedChecksumPath.get()); + return false; + } + if (memcmp(checksumBuf, &mChecksum, sizeof(mChecksum))) { + return false; + } + return !access(mCachedLibPath.c_str(), R_OK); + } + + // Caches the APK-provided checksum used in future cache validations. + void CacheChecksum() const + { + AutoCloseFD fd(open(mCachedChecksumPath.get(), + O_TRUNC | O_RDWR | O_CREAT | O_NOATIME, + S_IRUSR | S_IWUSR)); + if (fd == -1) { + WARN("Couldn't open %s to update checksum", mCachedChecksumPath.get()); + return; + } + + DEBUG_LOG("Updating checksum %s", mCachedChecksumPath.get()); + + const size_t size = sizeof(mChecksum); + size_t written = 0; + while (written < size) { + ssize_t ret = write(fd, + reinterpret_cast<const uint8_t*>(&mChecksum) + written, + size - written); + if (ret >= 0) { + written += ret; + } else if (errno != EINTR) { + WARN("Writing checksum %s failed with errno %d", + mCachedChecksumPath.get(), errno); + break; + } + } + } + +private: + const std::string mCachedLibPath; + UniquePtr<char[]> mCachedChecksumPath; + uint32_t mChecksum; +}; + +Mappable * +MappableFile::Create(const char *path) +{ + int fd = open(path, O_RDONLY); + if (fd != -1) + return new MappableFile(fd); + return nullptr; +} + +MemoryRange +MappableFile::mmap(const void *addr, size_t length, int prot, int flags, + off_t offset) +{ + MOZ_ASSERT(fd != -1); + MOZ_ASSERT(!(flags & MAP_SHARED)); + flags |= MAP_PRIVATE; + + return MemoryRange::mmap(const_cast<void *>(addr), length, prot, flags, + fd, offset); +} + +void +MappableFile::finalize() +{ + /* Close file ; equivalent to close(fd.forget()) */ + fd = -1; +} + +size_t +MappableFile::GetLength() const +{ + struct stat st; + return fstat(fd, &st) ? 0 : st.st_size; +} + +Mappable * +MappableExtractFile::Create(const char *name, Zip *zip, Zip::Stream *stream) +{ + MOZ_ASSERT(zip && stream); + + const char *cachePath = getenv("MOZ_LINKER_CACHE"); + if (!cachePath || !*cachePath) { + WARN("MOZ_LINKER_EXTRACT is set, but not MOZ_LINKER_CACHE; " + "not extracting"); + return nullptr; + } + + // Ensure that the cache dir is private. + chmod(cachePath, 0770); + + UniquePtr<char[]> path = + MakeUnique<char[]>(strlen(cachePath) + strlen(name) + 2); + sprintf(path.get(), "%s/%s", cachePath, name); + + CacheValidator validator(path.get(), zip, stream); + if (validator.IsValid()) { + DEBUG_LOG("Reusing %s", static_cast<char *>(path.get())); + return MappableFile::Create(path.get()); + } + DEBUG_LOG("Extracting to %s", static_cast<char *>(path.get())); + AutoCloseFD fd; + fd = open(path.get(), O_TRUNC | O_RDWR | O_CREAT | O_NOATIME, + S_IRUSR | S_IWUSR); + if (fd == -1) { + ERROR("Couldn't open %s to decompress library", path.get()); + return nullptr; + } + AutoUnlinkFile file(path.release()); + if (stream->GetType() == Zip::Stream::DEFLATE) { + if (ftruncate(fd, stream->GetUncompressedSize()) == -1) { + ERROR("Couldn't ftruncate %s to decompress library", file.get()); + return nullptr; + } + /* Map the temporary file for use as inflate buffer */ + MappedPtr buffer(MemoryRange::mmap(nullptr, stream->GetUncompressedSize(), + PROT_WRITE, MAP_SHARED, fd, 0)); + if (buffer == MAP_FAILED) { + ERROR("Couldn't map %s to decompress library", file.get()); + return nullptr; + } + + zxx_stream zStream = stream->GetZStream(buffer); + + /* Decompress */ + if (inflateInit2(&zStream, -MAX_WBITS) != Z_OK) { + ERROR("inflateInit failed: %s", zStream.msg); + return nullptr; + } + if (inflate(&zStream, Z_FINISH) != Z_STREAM_END) { + ERROR("inflate failed: %s", zStream.msg); + return nullptr; + } + if (inflateEnd(&zStream) != Z_OK) { + ERROR("inflateEnd failed: %s", zStream.msg); + return nullptr; + } + if (zStream.total_out != stream->GetUncompressedSize()) { + ERROR("File not fully uncompressed! %ld / %d", zStream.total_out, + static_cast<unsigned int>(stream->GetUncompressedSize())); + return nullptr; + } + } else if (XZStream::IsXZ(stream->GetBuffer(), stream->GetSize())) { + XZStream xzStream(stream->GetBuffer(), stream->GetSize()); + + if (!xzStream.Init()) { + ERROR("Couldn't initialize XZ decoder"); + return nullptr; + } + DEBUG_LOG("XZStream created, compressed=%u, uncompressed=%u", + xzStream.Size(), xzStream.UncompressedSize()); + + if (ftruncate(fd, xzStream.UncompressedSize()) == -1) { + ERROR("Couldn't ftruncate %s to decompress library", file.get()); + return nullptr; + } + MappedPtr buffer(MemoryRange::mmap(nullptr, xzStream.UncompressedSize(), + PROT_WRITE, MAP_SHARED, fd, 0)); + if (buffer == MAP_FAILED) { + ERROR("Couldn't map %s to decompress library", file.get()); + return nullptr; + } + const size_t written = xzStream.Decode(buffer, buffer.GetLength()); + DEBUG_LOG("XZStream decoded %u", written); + if (written != buffer.GetLength()) { + ERROR("Error decoding XZ file %s", file.get()); + return nullptr; + } + } else if (stream->GetType() == Zip::Stream::STORE) { + SeekableZStream zStream; + if (!zStream.Init(stream->GetBuffer(), stream->GetSize())) { + ERROR("Couldn't initialize SeekableZStream for %s", name); + return nullptr; + } + if (ftruncate(fd, zStream.GetUncompressedSize()) == -1) { + ERROR("Couldn't ftruncate %s to decompress library", file.get()); + return nullptr; + } + MappedPtr buffer(MemoryRange::mmap(nullptr, zStream.GetUncompressedSize(), + PROT_WRITE, MAP_SHARED, fd, 0)); + if (buffer == MAP_FAILED) { + ERROR("Couldn't map %s to decompress library", file.get()); + return nullptr; + } + + if (!zStream.Decompress(buffer, 0, zStream.GetUncompressedSize())) { + ERROR("%s: failed to decompress", name); + return nullptr; + } + } else { + return nullptr; + } + + validator.CacheChecksum(); + return new MappableExtractFile(fd.forget(), file.release()); +} + +/** + * _MappableBuffer is a buffer which content can be mapped at different + * locations in the virtual address space. + * On Linux, uses a (deleted) temporary file on a tmpfs for sharable content. + * On Android, uses ashmem. + */ +class _MappableBuffer: public MappedPtr +{ +public: + /** + * Returns a _MappableBuffer instance with the given name and the given + * length. + */ + static _MappableBuffer *Create(const char *name, size_t length) + { + AutoCloseFD fd; +#ifdef ANDROID + /* On Android, initialize an ashmem region with the given length */ + fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600); + if (fd == -1) + return nullptr; + char str[ASHMEM_NAME_LEN]; + strlcpy(str, name, sizeof(str)); + ioctl(fd, ASHMEM_SET_NAME, str); + if (ioctl(fd, ASHMEM_SET_SIZE, length)) + return nullptr; + + /* The Gecko crash reporter is confused by adjacent memory mappings of + * the same file and chances are we're going to map from the same file + * descriptor right away. To avoid problems with the crash reporter, + * create an empty anonymous page before or after the ashmem mapping, + * depending on how mappings grow in the address space. + */ +#if defined(__arm__) + void *buf = ::mmap(nullptr, length + PAGE_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buf != MAP_FAILED) { + ::mmap(AlignedEndPtr(reinterpret_cast<char *>(buf) + length, PAGE_SIZE), + PAGE_SIZE, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + DEBUG_LOG("Decompression buffer of size 0x%x in ashmem \"%s\", mapped @%p", + length, str, buf); + return new _MappableBuffer(fd.forget(), buf, length); + } +#elif defined(__i386__) + size_t anon_mapping_length = length + PAGE_SIZE; + void *buf = ::mmap(nullptr, anon_mapping_length, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf != MAP_FAILED) { + char *first_page = reinterpret_cast<char *>(buf); + char *map_page = first_page + PAGE_SIZE; + + void *actual_buf = ::mmap(map_page, length, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_SHARED, fd, 0); + if (actual_buf == MAP_FAILED) { + ::munmap(buf, anon_mapping_length); + DEBUG_LOG("Fixed allocation of decompression buffer at %p failed", map_page); + return nullptr; + } + + DEBUG_LOG("Decompression buffer of size 0x%x in ashmem \"%s\", mapped @%p", + length, str, actual_buf); + return new _MappableBuffer(fd.forget(), actual_buf, length); + } +#else +#error need to add a case for your CPU +#endif +#else + /* On Linux, use /dev/shm as base directory for temporary files, assuming + * it's on tmpfs */ + /* TODO: check that /dev/shm is tmpfs */ + char path[256]; + sprintf(path, "/dev/shm/%s.XXXXXX", name); + fd = mkstemp(path); + if (fd == -1) + return nullptr; + unlink(path); + ftruncate(fd, length); + + void *buf = ::mmap(nullptr, length, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (buf != MAP_FAILED) { + DEBUG_LOG("Decompression buffer of size %ld in \"%s\", mapped @%p", + length, path, buf); + return new _MappableBuffer(fd.forget(), buf, length); + } +#endif + return nullptr; + } + + void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset) + { + MOZ_ASSERT(fd != -1); +#ifdef ANDROID + /* Mapping ashmem MAP_PRIVATE is like mapping anonymous memory, even when + * there is content in the ashmem */ + if (flags & MAP_PRIVATE) { + flags &= ~MAP_PRIVATE; + flags |= MAP_SHARED; + } +#endif + return ::mmap(const_cast<void *>(addr), length, prot, flags, fd, offset); + } + +#ifdef ANDROID + ~_MappableBuffer() { + /* Free the additional page we allocated. See _MappableBuffer::Create */ +#if defined(__arm__) + ::munmap(AlignedEndPtr(*this + GetLength(), PAGE_SIZE), PAGE_SIZE); +#elif defined(__i386__) + ::munmap(*this - PAGE_SIZE, GetLength() + PAGE_SIZE); +#else +#error need to add a case for your CPU +#endif + } +#endif + +private: + _MappableBuffer(int fd, void *buf, size_t length) + : MappedPtr(buf, length), fd(fd) { } + + /* File descriptor for the temporary file or ashmem */ + AutoCloseFD fd; +}; + + +Mappable * +MappableDeflate::Create(const char *name, Zip *zip, Zip::Stream *stream) +{ + MOZ_ASSERT(stream->GetType() == Zip::Stream::DEFLATE); + _MappableBuffer *buf = _MappableBuffer::Create(name, stream->GetUncompressedSize()); + if (buf) + return new MappableDeflate(buf, zip, stream); + return nullptr; +} + +MappableDeflate::MappableDeflate(_MappableBuffer *buf, Zip *zip, + Zip::Stream *stream) +: zip(zip), buffer(buf), zStream(stream->GetZStream(*buf)) { } + +MappableDeflate::~MappableDeflate() { } + +MemoryRange +MappableDeflate::mmap(const void *addr, size_t length, int prot, int flags, off_t offset) +{ + MOZ_ASSERT(buffer); + MOZ_ASSERT(!(flags & MAP_SHARED)); + flags |= MAP_PRIVATE; + + /* The deflate stream is uncompressed up to the required offset + length, if + * it hasn't previously been uncompressed */ + ssize_t missing = offset + length + zStream.avail_out - buffer->GetLength(); + if (missing > 0) { + uInt avail_out = zStream.avail_out; + zStream.avail_out = missing; + if ((*buffer == zStream.next_out) && + (inflateInit2(&zStream, -MAX_WBITS) != Z_OK)) { + ERROR("inflateInit failed: %s", zStream.msg); + return MemoryRange(MAP_FAILED, 0); + } + int ret = inflate(&zStream, Z_SYNC_FLUSH); + if (ret < 0) { + ERROR("inflate failed: %s", zStream.msg); + return MemoryRange(MAP_FAILED, 0); + } + if (ret == Z_NEED_DICT) { + ERROR("zstream requires a dictionary. %s", zStream.msg); + return MemoryRange(MAP_FAILED, 0); + } + zStream.avail_out = avail_out - missing + zStream.avail_out; + if (ret == Z_STREAM_END) { + if (inflateEnd(&zStream) != Z_OK) { + ERROR("inflateEnd failed: %s", zStream.msg); + return MemoryRange(MAP_FAILED, 0); + } + if (zStream.total_out != buffer->GetLength()) { + ERROR("File not fully uncompressed! %ld / %d", zStream.total_out, + static_cast<unsigned int>(buffer->GetLength())); + return MemoryRange(MAP_FAILED, 0); + } + } + } +#if defined(ANDROID) && defined(__arm__) + if (prot & PROT_EXEC) { + /* We just extracted data that may be executed in the future. + * We thus need to ensure Instruction and Data cache coherency. */ + DEBUG_LOG("cacheflush(%p, %p)", *buffer + offset, *buffer + (offset + length)); + cacheflush(reinterpret_cast<uintptr_t>(*buffer + offset), + reinterpret_cast<uintptr_t>(*buffer + (offset + length)), 0); + } +#endif + + return MemoryRange(buffer->mmap(addr, length, prot, flags, offset), length); +} + +void +MappableDeflate::finalize() +{ + /* Free zlib internal buffers */ + inflateEnd(&zStream); + /* Free decompression buffer */ + buffer = nullptr; + /* Remove reference to Zip archive */ + zip = nullptr; +} + +size_t +MappableDeflate::GetLength() const +{ + return buffer->GetLength(); +} + +Mappable * +MappableSeekableZStream::Create(const char *name, Zip *zip, + Zip::Stream *stream) +{ + MOZ_ASSERT(stream->GetType() == Zip::Stream::STORE); + UniquePtr<MappableSeekableZStream> mappable(new MappableSeekableZStream(zip)); + + pthread_mutexattr_t recursiveAttr; + pthread_mutexattr_init(&recursiveAttr); + pthread_mutexattr_settype(&recursiveAttr, PTHREAD_MUTEX_RECURSIVE); + + if (pthread_mutex_init(&mappable->mutex, &recursiveAttr)) + return nullptr; + + if (!mappable->zStream.Init(stream->GetBuffer(), stream->GetSize())) + return nullptr; + + mappable->buffer.reset(_MappableBuffer::Create(name, + mappable->zStream.GetUncompressedSize())); + if (!mappable->buffer) + return nullptr; + + mappable->chunkAvail = MakeUnique<unsigned char[]>(mappable->zStream.GetChunksNum()); + + return mappable.release(); +} + +MappableSeekableZStream::MappableSeekableZStream(Zip *zip) +: zip(zip), chunkAvailNum(0) { } + +MappableSeekableZStream::~MappableSeekableZStream() +{ + pthread_mutex_destroy(&mutex); +} + +MemoryRange +MappableSeekableZStream::mmap(const void *addr, size_t length, int prot, + int flags, off_t offset) +{ + /* Map with PROT_NONE so that accessing the mapping would segfault, and + * bring us to ensure() */ + void *res = buffer->mmap(addr, length, PROT_NONE, flags, offset); + if (res == MAP_FAILED) + return MemoryRange(MAP_FAILED, 0); + + /* Store the mapping, ordered by offset and length */ + std::vector<LazyMap>::reverse_iterator it; + for (it = lazyMaps.rbegin(); it < lazyMaps.rend(); ++it) { + if ((it->offset < offset) || + ((it->offset == offset) && (it->length < length))) + break; + } + LazyMap map = { res, length, prot, offset }; + lazyMaps.insert(it.base(), map); + return MemoryRange(res, length); +} + +void +MappableSeekableZStream::munmap(void *addr, size_t length) +{ + std::vector<LazyMap>::iterator it; + for (it = lazyMaps.begin(); it < lazyMaps.end(); ++it) + if ((it->addr = addr) && (it->length == length)) { + lazyMaps.erase(it); + ::munmap(addr, length); + return; + } + MOZ_CRASH("munmap called with unknown mapping"); +} + +void +MappableSeekableZStream::finalize() { } + +bool +MappableSeekableZStream::ensure(const void *addr) +{ + DEBUG_LOG("ensure @%p", addr); + const void *addrPage = PageAlignedPtr(addr); + /* Find the mapping corresponding to the given page */ + std::vector<LazyMap>::iterator map; + for (map = lazyMaps.begin(); map < lazyMaps.end(); ++map) { + if (map->Contains(addrPage)) + break; + } + if (map == lazyMaps.end()) + return false; + + /* Find corresponding chunk */ + off_t mapOffset = map->offsetOf(addrPage); + off_t chunk = mapOffset / zStream.GetChunkSize(); + + /* In the typical case, we just need to decompress the chunk entirely. But + * when the current mapping ends in the middle of the chunk, we want to + * stop at the end of the corresponding page. + * However, if another mapping needs the last part of the chunk, we still + * need to continue. As mappings are ordered by offset and length, we don't + * need to scan the entire list of mappings. + * It is safe to run through lazyMaps here because the linker is never + * going to call mmap (which adds lazyMaps) while this function is + * called. */ + size_t length = zStream.GetChunkSize(chunk); + off_t chunkStart = chunk * zStream.GetChunkSize(); + off_t chunkEnd = chunkStart + length; + std::vector<LazyMap>::iterator it; + for (it = map; it < lazyMaps.end(); ++it) { + if (chunkEnd <= it->endOffset()) + break; + } + if ((it == lazyMaps.end()) || (chunkEnd > it->endOffset())) { + /* The mapping "it" points at now is past the interesting one */ + --it; + length = it->endOffset() - chunkStart; + } + + length = PageAlignedSize(length); + + /* The following lock can be re-acquired by the thread holding it. + * If this happens, it means the following code is interrupted somehow by + * some signal, and ends up retriggering a chunk decompression for the + * same MappableSeekableZStream. + * If the chunk to decompress is different the second time, then everything + * is safe as the only common data touched below is chunkAvailNum, and it is + * atomically updated (leaving out any chance of an interruption while it is + * updated affecting the result). If the chunk to decompress is the same, the + * worst thing that can happen is chunkAvailNum being incremented one too + * many times, which doesn't affect functionality. The chances of it + * happening being pretty slim, and the effect being harmless, we can just + * ignore the issue. Other than that, we'd just be wasting time decompressing + * the same chunk twice. */ + AutoLock lock(&mutex); + + /* The very first page is mapped and accessed separately of the rest, and + * as such, only the first page of the first chunk is decompressed this way. + * When we fault in the remaining pages of that chunk, we want to decompress + * the complete chunk again. Short of doing that, we would end up with + * no data between PageSize() and chunkSize, which would effectively corrupt + * symbol resolution in the underlying library. */ + if (chunkAvail[chunk] < PageNumber(length)) { + if (!zStream.DecompressChunk(*buffer + chunkStart, chunk, length)) + return false; + +#if defined(ANDROID) && defined(__arm__) + if (map->prot & PROT_EXEC) { + /* We just extracted data that may be executed in the future. + * We thus need to ensure Instruction and Data cache coherency. */ + DEBUG_LOG("cacheflush(%p, %p)", *buffer + chunkStart, *buffer + (chunkStart + length)); + cacheflush(reinterpret_cast<uintptr_t>(*buffer + chunkStart), + reinterpret_cast<uintptr_t>(*buffer + (chunkStart + length)), 0); + } +#endif + /* Only count if we haven't already decompressed parts of the chunk */ + if (chunkAvail[chunk] == 0) + chunkAvailNum++; + + chunkAvail[chunk] = PageNumber(length); + } + + /* Flip the chunk mapping protection to the recorded flags. We could + * also flip the protection for other mappings of the same chunk, + * but it's easier to skip that and let further segfaults call + * ensure again. */ + const void *chunkAddr = reinterpret_cast<const void *> + (reinterpret_cast<uintptr_t>(addrPage) + - mapOffset % zStream.GetChunkSize()); + const void *chunkEndAddr = reinterpret_cast<const void *> + (reinterpret_cast<uintptr_t>(chunkAddr) + length); + + const void *start = std::max(map->addr, chunkAddr); + const void *end = std::min(map->end(), chunkEndAddr); + length = reinterpret_cast<uintptr_t>(end) + - reinterpret_cast<uintptr_t>(start); + + if (mprotect(const_cast<void *>(start), length, map->prot) == 0) { + DEBUG_LOG("mprotect @%p, 0x%" PRIxSize ", 0x%x", start, length, map->prot); + return true; + } + + ERROR("mprotect @%p, 0x%" PRIxSize ", 0x%x failed with errno %d", + start, length, map->prot, errno); + return false; +} + +void +MappableSeekableZStream::stats(const char *when, const char *name) const +{ + size_t nEntries = zStream.GetChunksNum(); + DEBUG_LOG("%s: %s; %" PRIdSize "/%" PRIdSize " chunks decompressed", + name, when, static_cast<size_t>(chunkAvailNum), nEntries); + + size_t len = 64; + UniquePtr<char[]> map = MakeUnique<char[]>(len + 3); + map[0] = '['; + + for (size_t i = 0, j = 1; i < nEntries; i++, j++) { + map[j] = chunkAvail[i] ? '*' : '_'; + if ((j == len) || (i == nEntries - 1)) { + map[j + 1] = ']'; + map[j + 2] = '\0'; + DEBUG_LOG("%s", static_cast<char *>(map.get())); + j = 0; + } + } +} + +size_t +MappableSeekableZStream::GetLength() const +{ + return buffer->GetLength(); +} diff --git a/mozglue/linker/Mappable.h b/mozglue/linker/Mappable.h new file mode 100644 index 0000000000..0224ae3b51 --- /dev/null +++ b/mozglue/linker/Mappable.h @@ -0,0 +1,267 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Mappable_h +#define Mappable_h + +#include "Zip.h" +#include "SeekableZStream.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "zlib.h" + +/** + * Abstract class to handle mmap()ing from various kind of entities, such as + * plain files or Zip entries. The virtual members are meant to act as the + * equivalent system functions, except mapped memory is always MAP_PRIVATE, + * even though a given implementation may use something different internally. + */ +class Mappable: public mozilla::RefCounted<Mappable> +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(Mappable) + virtual ~Mappable() { } + + virtual MemoryRange mmap(const void *addr, size_t length, int prot, int flags, + off_t offset) = 0; + + enum Kind { + MAPPABLE_FILE, + MAPPABLE_EXTRACT_FILE, + MAPPABLE_DEFLATE, + MAPPABLE_SEEKABLE_ZSTREAM + }; + + virtual Kind GetKind() const = 0; + +private: + virtual void munmap(void *addr, size_t length) { + ::munmap(addr, length); + } + /* Limit use of Mappable::munmap to classes that keep track of the address + * and size of the mapping. This allows to ignore ::munmap return value. */ + friend class Mappable1stPagePtr; + friend class LibHandle; + +public: + /** + * Ensures the availability of the memory pages for the page(s) containing + * the given address. Returns whether the pages were successfully made + * available. + */ + virtual bool ensure(const void *addr) { return true; } + + /** + * Indicate to a Mappable instance that no further mmap is going to happen. + */ + virtual void finalize() = 0; + + /** + * Shows some stats about the Mappable instance. + * Meant for MappableSeekableZStream only. + * As Mappables don't keep track of what they are instanciated for, the name + * argument is used to make the stats logging useful to the reader. The when + * argument is to be used by the caller to give an identifier of the when + * the stats call is made. + */ + virtual void stats(const char *when, const char *name) const { } + + /** + * Returns the maximum length that can be mapped from this Mappable for + * offset = 0. + */ + virtual size_t GetLength() const = 0; +}; + +/** + * Mappable implementation for plain files + */ +class MappableFile: public Mappable +{ +public: + ~MappableFile() { } + + /** + * Create a MappableFile instance for the given file path. + */ + static Mappable *Create(const char *path); + + /* Inherited from Mappable */ + virtual MemoryRange mmap(const void *addr, size_t length, int prot, int flags, off_t offset); + virtual void finalize(); + virtual size_t GetLength() const; + + virtual Kind GetKind() const { return MAPPABLE_FILE; }; +protected: + MappableFile(int fd): fd(fd) { } + +private: + /* File descriptor */ + AutoCloseFD fd; +}; + +/** + * Mappable implementation for deflated stream in a Zip archive + * Inflates the complete stream into a cache file. + */ +class MappableExtractFile: public MappableFile +{ +public: + ~MappableExtractFile() = default; + + /** + * Create a MappableExtractFile instance for the given Zip stream. The name + * argument is used to create the cache file in the cache directory. + */ + static Mappable *Create(const char *name, Zip *zip, Zip::Stream *stream); + + /* Override finalize from MappableFile */ + virtual void finalize() {} + + virtual Kind GetKind() const { return MAPPABLE_EXTRACT_FILE; }; +private: + /** + * AutoUnlinkFile keeps track of a file name and removes (unlinks) the file + * when the instance is destroyed. + */ + struct UnlinkFile + { + void operator()(char *value) { + unlink(value); + delete [] value; + } + }; + typedef mozilla::UniquePtr<char[], UnlinkFile> AutoUnlinkFile; + + MappableExtractFile(int fd, const char* path) + : MappableFile(fd), path(path) { } + + /* Extracted file path */ + mozilla::UniquePtr<const char[]> path; +}; + +class _MappableBuffer; + +/** + * Mappable implementation for deflated stream in a Zip archive. + * Inflates the mapped bits in a temporary buffer. + */ +class MappableDeflate: public Mappable +{ +public: + ~MappableDeflate(); + + /** + * Create a MappableDeflate instance for the given Zip stream. The name + * argument is used for an appropriately named temporary file, and the Zip + * instance is given for the MappableDeflate to keep a reference of it. + */ + static Mappable *Create(const char *name, Zip *zip, Zip::Stream *stream); + + /* Inherited from Mappable */ + virtual MemoryRange mmap(const void *addr, size_t length, int prot, int flags, off_t offset); + virtual void finalize(); + virtual size_t GetLength() const; + + virtual Kind GetKind() const { return MAPPABLE_DEFLATE; }; +private: + MappableDeflate(_MappableBuffer *buf, Zip *zip, Zip::Stream *stream); + + /* Zip reference */ + RefPtr<Zip> zip; + + /* Decompression buffer */ + mozilla::UniquePtr<_MappableBuffer> buffer; + + /* Zlib data */ + zxx_stream zStream; +}; + +/** + * Mappable implementation for seekable zStreams. + * Inflates the mapped bits in a temporary buffer, on demand. + */ +class MappableSeekableZStream: public Mappable +{ +public: + ~MappableSeekableZStream(); + + /** + * Create a MappableSeekableZStream instance for the given Zip stream. The + * name argument is used for an appropriately named temporary file, and the + * Zip instance is given for the MappableSeekableZStream to keep a reference + * of it. + */ + static Mappable *Create(const char *name, Zip *zip, + Zip::Stream *stream); + + /* Inherited from Mappable */ + virtual MemoryRange mmap(const void *addr, size_t length, int prot, int flags, off_t offset); + virtual void munmap(void *addr, size_t length); + virtual void finalize(); + virtual bool ensure(const void *addr); + virtual void stats(const char *when, const char *name) const; + virtual size_t GetLength() const; + + virtual Kind GetKind() const { return MAPPABLE_SEEKABLE_ZSTREAM; }; +private: + MappableSeekableZStream(Zip *zip); + + /* Zip reference */ + RefPtr<Zip> zip; + + /* Decompression buffer */ + mozilla::UniquePtr<_MappableBuffer> buffer; + + /* Seekable ZStream */ + SeekableZStream zStream; + + /* Keep track of mappings performed with MappableSeekableZStream::mmap so + * that they can be realized by MappableSeekableZStream::ensure. + * Values stored in the struct are those passed to mmap */ + struct LazyMap + { + const void *addr; + size_t length; + int prot; + off_t offset; + + /* Returns addr + length, as a pointer */ + const void *end() const { + return reinterpret_cast<const void *> + (reinterpret_cast<const unsigned char *>(addr) + length); + } + + /* Returns offset + length */ + off_t endOffset() const { + return offset + length; + } + + /* Returns the offset corresponding to the given address */ + off_t offsetOf(const void *ptr) const { + return reinterpret_cast<uintptr_t>(ptr) + - reinterpret_cast<uintptr_t>(addr) + offset; + } + + /* Returns whether the given address is in the LazyMap range */ + bool Contains(const void *ptr) const { + return (ptr >= addr) && (ptr < end()); + } + }; + + /* List of all mappings */ + std::vector<LazyMap> lazyMaps; + + /* Array keeping track of which chunks have already been decompressed. + * Each value is the number of pages decompressed for the given chunk. */ + mozilla::UniquePtr<unsigned char[]> chunkAvail; + + /* Number of chunks that have already been decompressed. */ + mozilla::Atomic<size_t> chunkAvailNum; + + /* Mutex protecting decompression */ + pthread_mutex_t mutex; +}; + +#endif /* Mappable_h */ diff --git a/mozglue/linker/SeekableZStream.cpp b/mozglue/linker/SeekableZStream.cpp new file mode 100644 index 0000000000..6dd0ef6d58 --- /dev/null +++ b/mozglue/linker/SeekableZStream.cpp @@ -0,0 +1,261 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> +#include "SeekableZStream.h" +#include "Logging.h" + +bool +SeekableZStream::Init(const void *buf, size_t length) +{ + const SeekableZStreamHeader *header = SeekableZStreamHeader::validate(buf); + if (!header) { + ERROR("Not a seekable zstream"); + return false; + } + + buffer = reinterpret_cast<const unsigned char *>(buf); + totalSize = header->totalSize; + chunkSize = header->chunkSize; + lastChunkSize = header->lastChunkSize; + windowBits = header->windowBits; + dictionary.Init(buffer + sizeof(SeekableZStreamHeader), header->dictSize); + offsetTable.Init(buffer + sizeof(SeekableZStreamHeader) + header->dictSize, + header->nChunks); + filter = GetFilter(header->filter); + + /* Sanity check */ + if ((chunkSize == 0) || + (!IsPageAlignedSize(chunkSize)) || + (chunkSize > 8 * PageSize()) || + (offsetTable.numElements() < 1) || + (lastChunkSize == 0) || + (lastChunkSize > chunkSize) || + (length < totalSize)) { + ERROR("Malformed or broken seekable zstream"); + return false; + } + + return true; +} + +bool +SeekableZStream::Decompress(void *where, size_t chunk, size_t length) +{ + while (length) { + size_t len = std::min(length, static_cast<size_t>(chunkSize)); + if (!DecompressChunk(where, chunk, len)) + return false; + where = reinterpret_cast<unsigned char *>(where) + len; + length -= len; + chunk++; + } + return true; +} + +bool +SeekableZStream::DecompressChunk(void *where, size_t chunk, size_t length) +{ + if (chunk >= offsetTable.numElements()) { + ERROR("DecompressChunk: chunk #%" PRIdSize " out of range [0-%" PRIdSize ")", + chunk, offsetTable.numElements()); + return false; + } + + bool isLastChunk = (chunk == offsetTable.numElements() - 1); + + size_t chunkLen = isLastChunk ? lastChunkSize : chunkSize; + + if (length == 0 || length > chunkLen) + length = chunkLen; + + DEBUG_LOG("DecompressChunk #%" PRIdSize " @%p (%" PRIdSize "/% " PRIdSize ")", + chunk, where, length, chunkLen); + zxx_stream zStream(&allocator); + zStream.avail_in = (isLastChunk ? totalSize : uint32_t(offsetTable[chunk + 1])) + - uint32_t(offsetTable[chunk]); + zStream.next_in = const_cast<Bytef *>(buffer + uint32_t(offsetTable[chunk])); + zStream.avail_out = length; + zStream.next_out = reinterpret_cast<Bytef *>(where); + + /* Decompress chunk */ + if (inflateInit2(&zStream, windowBits) != Z_OK) { + ERROR("inflateInit failed: %s", zStream.msg); + return false; + } + if (dictionary && inflateSetDictionary(&zStream, dictionary, + dictionary.numElements()) != Z_OK) { + ERROR("inflateSetDictionary failed: %s", zStream.msg); + return false; + } + if (inflate(&zStream, (length == chunkLen) ? Z_FINISH : Z_SYNC_FLUSH) + != (length == chunkLen) ? Z_STREAM_END : Z_OK) { + ERROR("inflate failed: %s", zStream.msg); + return false; + } + if (inflateEnd(&zStream) != Z_OK) { + ERROR("inflateEnd failed: %s", zStream.msg); + return false; + } + if (filter) + filter(chunk * chunkSize, UNFILTER, (unsigned char *)where, chunkLen); + + return true; +} + +/* Branch/Call/Jump conversion filter for Thumb, derived from xz-utils + * by Igor Pavlov and Lasse Collin, published in the public domain */ +static void +BCJ_Thumb_filter(off_t offset, SeekableZStream::FilterDirection dir, + unsigned char *buf, size_t size) +{ + size_t i; + for (i = 0; i + 4 <= size; i += 2) { + if ((buf[i + 1] & 0xf8) == 0xf0 && (buf[i + 3] & 0xf8) == 0xf8) { + uint32_t src = (buf[i] << 11) + | ((buf[i + 1] & 0x07) << 19) + | buf[i + 2] + | ((buf[i + 3] & 0x07) << 8); + src <<= 1; + uint32_t dest; + if (dir == SeekableZStream::FILTER) + dest = offset + (uint32_t)(i) + 4 + src; + else + dest = src - (offset + (uint32_t)(i) + 4); + + dest >>= 1; + buf[i] = dest >> 11; + buf[i + 1] = 0xf0 | ((dest >> 19) & 0x07); + buf[i + 2] = dest; + buf[i + 3] = 0xf8 | ((dest >> 8) & 0x07); + i += 2; + } + } +} + +/* Branch/Call/Jump conversion filter for ARM, derived from xz-utils + * by Igor Pavlov and Lasse Collin, published in the public domain */ +static void +BCJ_ARM_filter(off_t offset, SeekableZStream::FilterDirection dir, + unsigned char *buf, size_t size) +{ + size_t i; + for (i = 0; i + 4 <= size; i += 4) { + if (buf[i + 3] == 0xeb) { + uint32_t src = buf[i] + | (buf[i + 1] << 8) + | (buf[i + 2] << 16); + src <<= 2; + uint32_t dest; + if (dir == SeekableZStream::FILTER) + dest = offset + (uint32_t)(i) + 8 + src; + else + dest = src - (offset + (uint32_t)(i) + 8); + + dest >>= 2; + buf[i] = dest; + buf[i + 1] = dest >> 8; + buf[i + 2] = dest >> 16; + } + } +} + +/* Branch/Call/Jump conversion filter for x86, derived from xz-utils + * by Igor Pavlov and Lasse Collin, published in the public domain */ + +#define Test86MSByte(b) ((b) == 0 || (b) == 0xff) + +static void +BCJ_X86_filter(off_t offset, SeekableZStream::FilterDirection dir, + unsigned char *buf, size_t size) +{ + static const bool MASK_TO_ALLOWED_STATUS[8] = + { true, true, true, false, true, false, false, false }; + + static const uint32_t MASK_TO_BIT_NUMBER[8] = + { 0, 1, 2, 2, 3, 3, 3, 3 }; + + uint32_t prev_mask = 0; + uint32_t prev_pos = 0; + + for (size_t i = 0; i + 5 <= size;) { + uint8_t b = buf[i]; + if (b != 0xe8 && b != 0xe9) { + ++i; + continue; + } + + const uint32_t off = offset + (uint32_t)(i) - prev_pos; + prev_pos = offset + (uint32_t)(i); + + if (off > 5) { + prev_mask = 0; + } else { + for (uint32_t i = 0; i < off; ++i) { + prev_mask &= 0x77; + prev_mask <<= 1; + } + } + + b = buf[i + 4]; + + if (Test86MSByte(b) && MASK_TO_ALLOWED_STATUS[(prev_mask >> 1) & 0x7] + && (prev_mask >> 1) < 0x10) { + + uint32_t src = ((uint32_t)(b) << 24) + | ((uint32_t)(buf[i + 3]) << 16) + | ((uint32_t)(buf[i + 2]) << 8) + | (buf[i + 1]); + + uint32_t dest; + while (true) { + if (dir == SeekableZStream::FILTER) + dest = src + (offset + (uint32_t)(i) + 5); + else + dest = src - (offset + (uint32_t)(i) + 5); + + if (prev_mask == 0) + break; + + const uint32_t i = MASK_TO_BIT_NUMBER[prev_mask >> 1]; + + b = (uint8_t)(dest >> (24 - i * 8)); + + if (!Test86MSByte(b)) + break; + + src = dest ^ ((1 << (32 - i * 8)) - 1); + } + + buf[i + 4] = (uint8_t)(~(((dest >> 24) & 1) - 1)); + buf[i + 3] = (uint8_t)(dest >> 16); + buf[i + 2] = (uint8_t)(dest >> 8); + buf[i + 1] = (uint8_t)(dest); + i += 5; + prev_mask = 0; + + } else { + ++i; + prev_mask |= 1; + if (Test86MSByte(b)) + prev_mask |= 0x10; + } + } +} + +SeekableZStream::ZStreamFilter +SeekableZStream::GetFilter(SeekableZStream::FilterId id) +{ + switch (id) { + case BCJ_THUMB: + return BCJ_Thumb_filter; + case BCJ_ARM: + return BCJ_ARM_filter; + case BCJ_X86: + return BCJ_X86_filter; + default: + return nullptr; + } + return nullptr; +} diff --git a/mozglue/linker/SeekableZStream.h b/mozglue/linker/SeekableZStream.h new file mode 100644 index 0000000000..3505c681e8 --- /dev/null +++ b/mozglue/linker/SeekableZStream.h @@ -0,0 +1,154 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SeekableZStream_h +#define SeekableZStream_h + +#include "Zip.h" + +/** + * Seekable compressed stream are created by splitting the original + * decompressed data in small chunks and compress these chunks + * individually. + * + * The seekable compressed file format consists in a header defined below, + * followed by a table of 32-bits words containing the offsets for each + * individual compressed chunk, then followed by the compressed chunks. + */ + +#pragma pack(1) +struct SeekableZStreamHeader: public Zip::SignedEntity<SeekableZStreamHeader> +{ + SeekableZStreamHeader() + : Zip::SignedEntity<SeekableZStreamHeader>(magic) + , totalSize(0), chunkSize(0), dictSize(0), nChunks(0), lastChunkSize(0) + , windowBits(0), filter(0) { } + + /* Reuse Zip::SignedEntity to handle the magic number used in the Seekable + * ZStream file format. The magic number is "SeZz". */ + static const uint32_t magic = 0x7a5a6553; + + /* Total size of the stream, including the 4 magic bytes. */ + le_uint32 totalSize; + + /* Chunk size */ + le_uint16 chunkSize; + + /* Size of the dictionary */ + le_uint16 dictSize; + + /* Number of chunks */ + le_uint32 nChunks; + + /* Size of last chunk (> 0, <= Chunk size) */ + le_uint16 lastChunkSize; + + /* windowBits value used when deflating */ + signed char windowBits; + + /* Filter Id */ + unsigned char filter; +}; +#pragma pack() + +static_assert(sizeof(SeekableZStreamHeader) == 5 * 4, + "SeekableZStreamHeader should be 5 32-bits words"); + +/** + * Helper class used to decompress Seekable ZStreams. + */ +class SeekableZStream { +public: + /* Initialize from the given buffer. Returns whether initialization + * succeeded (true) or failed (false). */ + bool Init(const void *buf, size_t length); + + /* Decompresses starting from the given chunk. The decompressed data is + * stored at the given location. The given length, in bytes, indicates + * how much data to decompress. If length is 0, then exactly one chunk + * is decompressed. + * Returns whether decompression succeeded (true) or failed (false). */ + bool Decompress(void *where, size_t chunk, size_t length = 0); + + /* Decompresses the given chunk at the given address. If a length is given, + * only decompresses that amount of data instead of the entire chunk. + * Returns whether decompression succeeded (true) or failed (false). */ + bool DecompressChunk(void *where, size_t chunk, size_t length = 0); + + /* Returns the uncompressed size of the complete zstream */ + size_t GetUncompressedSize() const + { + return (offsetTable.numElements() - 1) * chunkSize + lastChunkSize; + } + + /* Returns the chunk size of the given chunk */ + size_t GetChunkSize(size_t chunk = 0) const { + return (chunk == offsetTable.numElements() - 1) ? lastChunkSize : chunkSize; + } + + /* Returns the number of chunks */ + size_t GetChunksNum() const { + return offsetTable.numElements(); + } + + /** + * Filters used to improve compression rate. + */ + enum FilterDirection { + FILTER, + UNFILTER + }; + typedef void (*ZStreamFilter)(off_t, FilterDirection, + unsigned char *, size_t); + + enum FilterId { + NONE, + BCJ_THUMB, + BCJ_ARM, + BCJ_X86, + FILTER_MAX + }; + static ZStreamFilter GetFilter(FilterId id); + + static ZStreamFilter GetFilter(uint16_t id) { + return GetFilter(static_cast<FilterId>(id)); + } + +private: + /* RAW Seekable SZtream buffer */ + const unsigned char *buffer; + + /* Total size of the stream, including the 4 magic bytes. */ + uint32_t totalSize; + + /* Chunk size */ + uint32_t chunkSize; + + /* Size of last chunk (> 0, <= Chunk size) */ + uint32_t lastChunkSize; + + /* windowBits value used when deflating */ + int windowBits; + + /* Offsets table */ + Array<le_uint32> offsetTable; + + /* Filter */ + ZStreamFilter filter; + + /* Deflate dictionary */ + Array<unsigned char> dictionary; + + /* Special allocator for inflate to use the same buffers for every chunk */ + zxx_stream::StaticAllocator allocator; +}; + +inline void +operator++(SeekableZStream::FilterId &other) +{ + const int orig = static_cast<int>(other); + other = static_cast<SeekableZStream::FilterId>(orig + 1); +} + +#endif /* SeekableZStream_h */ diff --git a/mozglue/linker/Utils.h b/mozglue/linker/Utils.h new file mode 100644 index 0000000000..c5314ef603 --- /dev/null +++ b/mozglue/linker/Utils.h @@ -0,0 +1,618 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Utils_h +#define Utils_h + +#include <pthread.h> +#include <stdint.h> +#include <stddef.h> +#include <sys/mman.h> +#include <unistd.h> +#include "mozilla/Assertions.h" +#include "mozilla/Scoped.h" + +/** + * On architectures that are little endian and that support unaligned reads, + * we can use direct type, but on others, we want to have a special class + * to handle conversion and alignment issues. + */ +#if !defined(DEBUG) && (defined(__i386__) || defined(__x86_64__)) +typedef uint16_t le_uint16; +typedef uint32_t le_uint32; +#else + +/** + * Template that allows to find an unsigned int type from a (computed) bit size + */ +template <int s> struct UInt { }; +template <> struct UInt<16> { typedef uint16_t Type; }; +template <> struct UInt<32> { typedef uint32_t Type; }; + +/** + * Template to access 2 n-bit sized words as a 2*n-bit sized word, doing + * conversion from little endian and avoiding alignment issues. + */ +template <typename T> +class le_to_cpu +{ +public: + typedef typename UInt<16 * sizeof(T)>::Type Type; + + operator Type() const + { + return (b << (sizeof(T) * 8)) | a; + } + + const le_to_cpu& operator =(const Type &v) + { + a = v & ((1 << (sizeof(T) * 8)) - 1); + b = v >> (sizeof(T) * 8); + return *this; + } + + le_to_cpu() { } + le_to_cpu(const Type &v) + { + operator =(v); + } + + const le_to_cpu& operator +=(const Type &v) + { + return operator =(operator Type() + v); + } + + const le_to_cpu& operator ++(int) + { + return operator =(operator Type() + 1); + } + +private: + T a, b; +}; + +/** + * Type definitions + */ +typedef le_to_cpu<unsigned char> le_uint16; +typedef le_to_cpu<le_uint16> le_uint32; +#endif + + +/** + * AutoCloseFD is a RAII wrapper for POSIX file descriptors + */ +struct AutoCloseFDTraits +{ + typedef int type; + static int empty() { return -1; } + static void release(int fd) { if (fd != -1) close(fd); } +}; +typedef mozilla::Scoped<AutoCloseFDTraits> AutoCloseFD; + +/** + * AutoCloseFILE is a RAII wrapper for POSIX streams + */ +struct AutoCloseFILETraits +{ + typedef FILE *type; + static FILE *empty() { return nullptr; } + static void release(FILE *f) { if (f) fclose(f); } +}; +typedef mozilla::Scoped<AutoCloseFILETraits> AutoCloseFILE; + +/** + * Page alignment helpers + */ +static inline size_t PageSize() +{ + return 4096; +} + +static inline uintptr_t AlignedPtr(uintptr_t ptr, size_t alignment) +{ + return ptr & ~(alignment - 1); +} + +template <typename T> +static inline T *AlignedPtr(T *ptr, size_t alignment) +{ + return reinterpret_cast<T *>( + AlignedPtr(reinterpret_cast<uintptr_t>(ptr), alignment)); +} + +template <typename T> +static inline T PageAlignedPtr(T ptr) +{ + return AlignedPtr(ptr, PageSize()); +} + +static inline uintptr_t AlignedEndPtr(uintptr_t ptr, size_t alignment) +{ + return AlignedPtr(ptr + alignment - 1, alignment); +} + +template <typename T> +static inline T *AlignedEndPtr(T *ptr, size_t alignment) +{ + return reinterpret_cast<T *>( + AlignedEndPtr(reinterpret_cast<uintptr_t>(ptr), alignment)); +} + +template <typename T> +static inline T PageAlignedEndPtr(T ptr) +{ + return AlignedEndPtr(ptr, PageSize()); +} + +static inline size_t AlignedSize(size_t size, size_t alignment) +{ + return (size + alignment - 1) & ~(alignment - 1); +} + +static inline size_t PageAlignedSize(size_t size) +{ + return AlignedSize(size, PageSize()); +} + +static inline bool IsAlignedPtr(uintptr_t ptr, size_t alignment) +{ + return ptr % alignment == 0; +} + +template <typename T> +static inline bool IsAlignedPtr(T *ptr, size_t alignment) +{ + return IsAlignedPtr(reinterpret_cast<uintptr_t>(ptr), alignment); +} + +template <typename T> +static inline bool IsPageAlignedPtr(T ptr) +{ + return IsAlignedPtr(ptr, PageSize()); +} + +static inline bool IsAlignedSize(size_t size, size_t alignment) +{ + return size % alignment == 0; +} + +static inline bool IsPageAlignedSize(size_t size) +{ + return IsAlignedSize(size, PageSize()); +} + +static inline size_t PageNumber(size_t size) +{ + return (size + PageSize() - 1) / PageSize(); +} + +/** + * MemoryRange stores a pointer, size pair. + */ +class MemoryRange +{ +public: + MemoryRange(void *buf, size_t length): buf(buf), length(length) { } + + void Assign(void *b, size_t len) { + buf = b; + length = len; + } + + void Assign(const MemoryRange& other) { + buf = other.buf; + length = other.length; + } + + void *get() const + { + return buf; + } + + operator void *() const + { + return buf; + } + + operator unsigned char *() const + { + return reinterpret_cast<unsigned char *>(buf); + } + + bool operator ==(void *ptr) const { + return buf == ptr; + } + + bool operator ==(unsigned char *ptr) const { + return buf == ptr; + } + + void *operator +(off_t offset) const + { + return reinterpret_cast<char *>(buf) + offset; + } + + /** + * Returns whether the given address is within the mapped range + */ + bool Contains(void *ptr) const + { + return (ptr >= buf) && (ptr < reinterpret_cast<char *>(buf) + length); + } + + /** + * Returns the length of the mapped range + */ + size_t GetLength() const + { + return length; + } + + static MemoryRange mmap(void *addr, size_t length, int prot, int flags, + int fd, off_t offset) { + return MemoryRange(::mmap(addr, length, prot, flags, fd, offset), length); + } + +private: + void *buf; + size_t length; +}; + +/** + * MappedPtr is a RAII wrapper for mmap()ed memory. It can be used as + * a simple void * or unsigned char *. + * + * It is defined as a derivative of a template that allows to use a + * different unmapping strategy. + */ +template <typename T> +class GenericMappedPtr: public MemoryRange +{ +public: + GenericMappedPtr(void *buf, size_t length): MemoryRange(buf, length) { } + GenericMappedPtr(const MemoryRange& other): MemoryRange(other) { } + GenericMappedPtr(): MemoryRange(MAP_FAILED, 0) { } + + void Assign(void *b, size_t len) { + if (get() != MAP_FAILED) + static_cast<T *>(this)->munmap(get(), GetLength()); + MemoryRange::Assign(b, len); + } + + void Assign(const MemoryRange& other) { + Assign(other.get(), other.GetLength()); + } + + ~GenericMappedPtr() + { + if (get() != MAP_FAILED) + static_cast<T *>(this)->munmap(get(), GetLength()); + } + + void release() + { + MemoryRange::Assign(MAP_FAILED, 0); + } +}; + +struct MappedPtr: public GenericMappedPtr<MappedPtr> +{ + MappedPtr(void *buf, size_t length) + : GenericMappedPtr<MappedPtr>(buf, length) { } + MappedPtr(const MemoryRange& other) + : GenericMappedPtr<MappedPtr>(other) { } + MappedPtr(): GenericMappedPtr<MappedPtr>() { } + +private: + friend class GenericMappedPtr<MappedPtr>; + void munmap(void *buf, size_t length) + { + ::munmap(buf, length); + } +}; + +/** + * UnsizedArray is a way to access raw arrays of data in memory. + * + * struct S { ... }; + * UnsizedArray<S> a(buf); + * UnsizedArray<S> b; b.Init(buf); + * + * This is roughly equivalent to + * const S *a = reinterpret_cast<const S *>(buf); + * const S *b = nullptr; b = reinterpret_cast<const S *>(buf); + * + * An UnsizedArray has no known length, and it's up to the caller to make + * sure the accessed memory is mapped and makes sense. + */ +template <typename T> +class UnsizedArray +{ +public: + typedef size_t idx_t; + + /** + * Constructors and Initializers + */ + UnsizedArray(): contents(nullptr) { } + UnsizedArray(const void *buf): contents(reinterpret_cast<const T *>(buf)) { } + + void Init(const void *buf) + { + MOZ_ASSERT(contents == nullptr); + contents = reinterpret_cast<const T *>(buf); + } + + /** + * Returns the nth element of the array + */ + const T &operator[](const idx_t index) const + { + MOZ_ASSERT(contents); + return contents[index]; + } + + operator const T *() const + { + return contents; + } + /** + * Returns whether the array points somewhere + */ + operator bool() const + { + return contents != nullptr; + } +private: + const T *contents; +}; + +/** + * Array, like UnsizedArray, is a way to access raw arrays of data in memory. + * Unlike UnsizedArray, it has a known length, and is enumerable with an + * iterator. + * + * struct S { ... }; + * Array<S> a(buf, len); + * UnsizedArray<S> b; b.Init(buf, len); + * + * In the above examples, len is the number of elements in the array. It is + * also possible to initialize an Array with the buffer size: + * + * Array<S> c; c.InitSize(buf, size); + * + * It is also possible to initialize an Array in two steps, only providing + * one data at a time: + * + * Array<S> d; + * d.Init(buf); + * d.Init(len); // or d.InitSize(size); + * + */ +template <typename T> +class Array: public UnsizedArray<T> +{ +public: + typedef typename UnsizedArray<T>::idx_t idx_t; + + /** + * Constructors and Initializers + */ + Array(): UnsizedArray<T>(), length(0) { } + Array(const void *buf, const idx_t length) + : UnsizedArray<T>(buf), length(length) { } + + void Init(const void *buf) + { + UnsizedArray<T>::Init(buf); + } + + void Init(const idx_t len) + { + MOZ_ASSERT(length == 0); + length = len; + } + + void InitSize(const idx_t size) + { + Init(size / sizeof(T)); + } + + void Init(const void *buf, const idx_t len) + { + UnsizedArray<T>::Init(buf); + Init(len); + } + + void InitSize(const void *buf, const idx_t size) + { + UnsizedArray<T>::Init(buf); + InitSize(size); + } + + /** + * Returns the nth element of the array + */ + const T &operator[](const idx_t index) const + { + MOZ_ASSERT(index < length); + MOZ_ASSERT(operator bool()); + return UnsizedArray<T>::operator[](index); + } + + /** + * Returns the number of elements in the array + */ + idx_t numElements() const + { + return length; + } + + /** + * Returns whether the array points somewhere and has at least one element. + */ + operator bool() const + { + return (length > 0) && UnsizedArray<T>::operator bool(); + } + + /** + * Iterator for an Array. Use is similar to that of STL const_iterators: + * + * struct S { ... }; + * Array<S> a(buf, len); + * for (Array<S>::iterator it = a.begin(); it < a.end(); ++it) { + * // Do something with *it. + * } + */ + class iterator + { + public: + iterator(): item(nullptr) { } + + const T &operator *() const + { + return *item; + } + + const T *operator ->() const + { + return item; + } + + iterator &operator ++() + { + ++item; + return *this; + } + + bool operator<(const iterator &other) const + { + return item < other.item; + } + protected: + friend class Array<T>; + iterator(const T &item): item(&item) { } + + private: + const T *item; + }; + + /** + * Returns an iterator pointing at the beginning of the Array + */ + iterator begin() const { + if (length) + return iterator(UnsizedArray<T>::operator[](0)); + return iterator(); + } + + /** + * Returns an iterator pointing past the end of the Array + */ + iterator end() const { + if (length) + return iterator(UnsizedArray<T>::operator[](length)); + return iterator(); + } + + /** + * Reverse iterator for an Array. Use is similar to that of STL + * const_reverse_iterators: + * + * struct S { ... }; + * Array<S> a(buf, len); + * for (Array<S>::reverse_iterator it = a.rbegin(); it < a.rend(); ++it) { + * // Do something with *it. + * } + */ + class reverse_iterator + { + public: + reverse_iterator(): item(nullptr) { } + + const T &operator *() const + { + const T *tmp = item; + return *--tmp; + } + + const T *operator ->() const + { + return &operator*(); + } + + reverse_iterator &operator ++() + { + --item; + return *this; + } + + bool operator<(const reverse_iterator &other) const + { + return item > other.item; + } + protected: + friend class Array<T>; + reverse_iterator(const T &item): item(&item) { } + + private: + const T *item; + }; + + /** + * Returns a reverse iterator pointing at the end of the Array + */ + reverse_iterator rbegin() const { + if (length) + return reverse_iterator(UnsizedArray<T>::operator[](length)); + return reverse_iterator(); + } + + /** + * Returns a reverse iterator pointing past the beginning of the Array + */ + reverse_iterator rend() const { + if (length) + return reverse_iterator(UnsizedArray<T>::operator[](0)); + return reverse_iterator(); + } +private: + idx_t length; +}; + +/** + * Transforms a pointer-to-function to a pointer-to-object pointing at the + * same address. + */ +template <typename T> +void *FunctionPtr(T func) +{ + union { + void *ptr; + T func; + } f; + f.func = func; + return f.ptr; +} + +class AutoLock { +public: + AutoLock(pthread_mutex_t *mutex): mutex(mutex) + { + if (pthread_mutex_lock(mutex)) + MOZ_CRASH("pthread_mutex_lock failed"); + } + ~AutoLock() + { + if (pthread_mutex_unlock(mutex)) + MOZ_CRASH("pthread_mutex_unlock failed"); + } +private: + pthread_mutex_t *mutex; +}; + +#endif /* Utils_h */ + diff --git a/mozglue/linker/XZStream.cpp b/mozglue/linker/XZStream.cpp new file mode 100644 index 0000000000..bd71655f52 --- /dev/null +++ b/mozglue/linker/XZStream.cpp @@ -0,0 +1,213 @@ +#include "XZStream.h" + +#include <algorithm> +#include "mozilla/Assertions.h" +#include "Logging.h" + +// LZMA dictionary size, should have a minimum size for the given compression +// rate, see XZ Utils docs for details. +static const uint32_t kDictSize = 1 << 24; + +static const size_t kFooterSize = 12; + +// Parses a variable-length integer (VLI), +// see http://tukaani.org/xz/xz-file-format.txt for details. +static size_t +ParseVarLenInt(const uint8_t* aBuf, size_t aBufSize, uint64_t* aValue) +{ + if (!aBufSize) { + return 0; + } + aBufSize = std::min(9u, aBufSize); + + *aValue = aBuf[0] & 0x7F; + size_t i = 0; + + while (aBuf[i++] & 0x80) { + if (i >= aBufSize || aBuf[i] == 0x0) { + return 0; + } + *aValue |= static_cast<uint64_t>(aBuf[i] & 0x7F) << (i * 7); + } + return i; +} + +/* static */ bool +XZStream::IsXZ(const void* aBuf, size_t aBufSize) +{ + static const uint8_t kXzMagic[] = {0xfd, '7', 'z', 'X', 'Z', 0x0}; + MOZ_ASSERT(aBuf); + return aBufSize > sizeof(kXzMagic) && + !memcmp(reinterpret_cast<const void*>(kXzMagic), aBuf, sizeof(kXzMagic)); +} + +XZStream::XZStream(const void* aInBuf, size_t aInSize) + : mInBuf(static_cast<const uint8_t*>(aInBuf)) + , mUncompSize(0) + , mDec(nullptr) +{ + mBuffers.in = mInBuf; + mBuffers.in_pos = 0; + mBuffers.in_size = aInSize; +} + +XZStream::~XZStream() +{ + xz_dec_end(mDec); +} + +bool +XZStream::Init() +{ +#ifdef XZ_USE_CRC64 + xz_crc64_init(); +#endif + xz_crc32_init(); + + mDec = xz_dec_init(XZ_DYNALLOC, kDictSize); + + if (!mDec) { + return false; + } + + mUncompSize = ParseUncompressedSize(); + + return true; +} + +size_t +XZStream::Decode(void* aOutBuf, size_t aOutSize) +{ + if (!mDec) { + return 0; + } + + mBuffers.out = static_cast<uint8_t*>(aOutBuf); + mBuffers.out_pos = 0; + mBuffers.out_size = aOutSize; + + while (mBuffers.in_pos < mBuffers.in_size && + mBuffers.out_pos < mBuffers.out_size) { + const xz_ret ret = xz_dec_run(mDec, &mBuffers); + + switch (ret) { + case XZ_STREAM_END: + // Stream ended, the next loop iteration should terminate. + MOZ_ASSERT(mBuffers.in_pos == mBuffers.in_size); + MOZ_FALLTHROUGH; +#ifdef XZ_DEC_ANY_CHECK + case XZ_UNSUPPORTED_CHECK: + // Ignore unsupported check. + MOZ_FALLTHROUGH; +#endif + case XZ_OK: + // Chunk decoded, proceed. + break; + + case XZ_MEM_ERROR: + ERROR("XZ decoding: memory allocation failed"); + return 0; + + case XZ_MEMLIMIT_ERROR: + ERROR("XZ decoding: memory usage limit reached"); + return 0; + + case XZ_FORMAT_ERROR: + ERROR("XZ decoding: invalid stream format"); + return 0; + + case XZ_OPTIONS_ERROR: + ERROR("XZ decoding: unsupported header options"); + return 0; + + case XZ_DATA_ERROR: + MOZ_FALLTHROUGH; + case XZ_BUF_ERROR: + ERROR("XZ decoding: corrupt input stream"); + return 0; + + default: + MOZ_ASSERT_UNREACHABLE("XZ decoding: unknown error condition"); + return 0; + } + } + return mBuffers.out_pos; +} + +size_t +XZStream::RemainingInput() const +{ + return mBuffers.in_size - mBuffers.in_pos; +} + +size_t +XZStream::Size() const +{ + return mBuffers.in_size; +} + +size_t +XZStream::UncompressedSize() const +{ + return mUncompSize; +} + +size_t +XZStream::ParseIndexSize() const +{ + static const uint8_t kFooterMagic[] = {'Y', 'Z'}; + + const uint8_t* footer = mInBuf + mBuffers.in_size - kFooterSize; + // The magic bytes are at the end of the footer. + if (memcmp(reinterpret_cast<const void*>(kFooterMagic), + footer + kFooterSize - sizeof(kFooterMagic), + sizeof(kFooterMagic))) { + // Not a valid footer at stream end. + return 0; + } + // Backward size is a 32 bit LE integer field positioned after the 32 bit CRC32 + // code. It encodes the index size as a multiple of 4 bytes with a minimum + // size of 4 bytes. + const uint32_t backwardSize = *(footer + 4); + return (backwardSize + 1) * 4; +} + +size_t +XZStream::ParseUncompressedSize() const +{ + static const uint8_t kIndexIndicator[] = {0x0}; + + const size_t indexSize = ParseIndexSize(); + if (!indexSize) { + return 0; + } + // The footer follows directly the index, so we can use it as a reference. + const uint8_t* end = mInBuf + mBuffers.in_size; + const uint8_t* index = end - kFooterSize - indexSize; + + // The index consists of a one byte indicator followed by a VLI field for the + // number of records (1 expected) followed by a list of records. One record + // contains a VLI field for unpadded size followed by a VLI field for + // uncompressed size. + if (memcmp(reinterpret_cast<const void*>(kIndexIndicator), + index, sizeof(kIndexIndicator))) { + // Not a valid index. + return 0; + } + + index += sizeof(kIndexIndicator); + uint64_t numRecords = 0; + index += ParseVarLenInt(index, end - index, &numRecords); + if (!numRecords) { + return 0; + } + uint64_t unpaddedSize = 0; + index += ParseVarLenInt(index, end - index, &unpaddedSize); + if (!unpaddedSize) { + return 0; + } + uint64_t uncompressedSize = 0; + index += ParseVarLenInt(index, end - index, &uncompressedSize); + + return uncompressedSize; +} diff --git a/mozglue/linker/XZStream.h b/mozglue/linker/XZStream.h new file mode 100644 index 0000000000..76a979b135 --- /dev/null +++ b/mozglue/linker/XZStream.h @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XZSTREAM_h +#define XZSTREAM_h + +#include <cstdlib> + +#define XZ_DEC_DYNALLOC +#include "xz.h" + +// Used to decode XZ stream buffers. +class XZStream +{ +public: + // Returns whether the provided buffer is likely a XZ stream. + static bool IsXZ(const void* aBuf, size_t aBufSize); + + // Creates a XZ stream object for the given input buffer. + XZStream(const void* aInBuf, size_t aInSize); + ~XZStream(); + + // Initializes the decoder and returns whether decoding may commence. + bool Init(); + // Decodes the next chunk of input into the given output buffer. + size_t Decode(void* aOutBuf, size_t aOutSize); + // Returns the number of yet undecoded bytes in the input buffer. + size_t RemainingInput() const; + // Returns the total number of bytes in the input buffer (compressed size). + size_t Size() const; + // Returns the expected final number of bytes in the output buffer. + // Note: will return 0 before successful Init(). + size_t UncompressedSize() const; + +private: + // Parses the stream footer and returns the size of the index in bytes. + size_t ParseIndexSize() const; + // Parses the stream index and returns the expected uncompressed size in bytes. + size_t ParseUncompressedSize() const; + + const uint8_t* mInBuf; + size_t mUncompSize; + xz_buf mBuffers; + xz_dec* mDec; +}; + +#endif // XZSTREAM_h diff --git a/mozglue/linker/Zip.cpp b/mozglue/linker/Zip.cpp new file mode 100644 index 0000000000..d141eaa458 --- /dev/null +++ b/mozglue/linker/Zip.cpp @@ -0,0 +1,229 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <cstdlib> +#include <algorithm> +#include "Logging.h" +#include "Zip.h" + +already_AddRefed<Zip> +Zip::Create(const char *filename) +{ + /* Open and map the file in memory */ + AutoCloseFD fd(open(filename, O_RDONLY)); + if (fd == -1) { + ERROR("Error opening %s: %s", filename, strerror(errno)); + return nullptr; + } + struct stat st; + if (fstat(fd, &st) == -1) { + ERROR("Error stating %s: %s", filename, strerror(errno)); + return nullptr; + } + size_t size = st.st_size; + if (size <= sizeof(CentralDirectoryEnd)) { + ERROR("Error reading %s: too short", filename); + return nullptr; + } + void *mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); + if (mapped == MAP_FAILED) { + ERROR("Error mmapping %s: %s", filename, strerror(errno)); + return nullptr; + } + DEBUG_LOG("Mapped %s @%p", filename, mapped); + + return Create(filename, mapped, size); +} + +already_AddRefed<Zip> +Zip::Create(const char *filename, void *mapped, size_t size) +{ + RefPtr<Zip> zip = new Zip(filename, mapped, size); + + // If neither the first Local File entry nor central directory entries + // have been found, the zip was invalid. + if (!zip->nextFile && !zip->entries) { + ERROR("%s - Invalid zip", filename); + return nullptr; + } + + ZipCollection::Singleton.Register(zip); + return zip.forget(); +} + +Zip::Zip(const char *filename, void *mapped, size_t size) +: name(filename ? strdup(filename) : nullptr) +, mapped(mapped) +, size(size) +, nextFile(LocalFile::validate(mapped)) // first Local File entry +, nextDir(nullptr) +, entries(nullptr) +{ + pthread_mutex_init(&mutex, nullptr); + // If the first local file entry couldn't be found (which can happen + // with optimized jars), check the first central directory entry. + if (!nextFile) + GetFirstEntry(); +} + +Zip::~Zip() +{ + ZipCollection::Forget(this); + if (name) { + munmap(mapped, size); + DEBUG_LOG("Unmapped %s @%p", name, mapped); + free(name); + } + pthread_mutex_destroy(&mutex); +} + +bool +Zip::GetStream(const char *path, Zip::Stream *out) const +{ + AutoLock lock(&mutex); + + DEBUG_LOG("%s - GetFile %s", name, path); + /* Fast path: if the Local File header on store matches, we can return the + * corresponding stream right away. + * However, the Local File header may not contain enough information, in + * which case the 3rd bit on the generalFlag is set. Unfortunately, this + * bit is also set in some archives even when we do have the data (most + * notably the android packages as built by the Mozilla build system). + * So instead of testing the generalFlag bit, only use the fast path when + * we haven't read the central directory entries yet, and when the + * compressed size as defined in the header is not filled (which is a + * normal condition for the bit to be set). */ + if (nextFile && nextFile->GetName().Equals(path) && + !entries && (nextFile->compressedSize != 0)) { + DEBUG_LOG("%s - %s was next file: fast path", name, path); + /* Fill Stream info from Local File header content */ + const char *data = reinterpret_cast<const char *>(nextFile->GetData()); + out->compressedBuf = data; + out->compressedSize = nextFile->compressedSize; + out->uncompressedSize = nextFile->uncompressedSize; + out->CRC32 = nextFile->CRC32; + out->type = static_cast<Stream::Type>(uint16_t(nextFile->compression)); + + /* Find the next Local File header. It is usually simply following the + * compressed stream, but in cases where the 3rd bit of the generalFlag + * is set, there is a Data Descriptor header before. */ + data += nextFile->compressedSize; + if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) { + data += sizeof(DataDescriptor); + } + nextFile = LocalFile::validate(data); + return true; + } + + /* If the directory entry we have in store doesn't match, scan the Central + * Directory for the entry corresponding to the given path */ + if (!nextDir || !nextDir->GetName().Equals(path)) { + const DirectoryEntry *entry = GetFirstEntry(); + DEBUG_LOG("%s - Scan directory entries in search for %s", name, path); + while (entry && !entry->GetName().Equals(path)) { + entry = entry->GetNext(); + } + nextDir = entry; + } + if (!nextDir) { + DEBUG_LOG("%s - Couldn't find %s", name, path); + return false; + } + + /* Find the Local File header corresponding to the Directory entry that + * was found. */ + nextFile = LocalFile::validate(static_cast<const char *>(mapped) + + nextDir->offset); + if (!nextFile) { + ERROR("%s - Couldn't find the Local File header for %s", name, path); + return false; + } + + /* Fill Stream info from Directory entry content */ + const char *data = reinterpret_cast<const char *>(nextFile->GetData()); + out->compressedBuf = data; + out->compressedSize = nextDir->compressedSize; + out->uncompressedSize = nextDir->uncompressedSize; + out->CRC32 = nextDir->CRC32; + out->type = static_cast<Stream::Type>(uint16_t(nextDir->compression)); + + /* Store the next directory entry */ + nextDir = nextDir->GetNext(); + nextFile = nullptr; + return true; +} + +const Zip::DirectoryEntry * +Zip::GetFirstEntry() const +{ + if (entries) + return entries; + + const CentralDirectoryEnd *end = nullptr; + const char *_end = static_cast<const char *>(mapped) + size + - sizeof(CentralDirectoryEnd); + + /* Scan for the Central Directory End */ + for (; _end > mapped && !end; _end--) + end = CentralDirectoryEnd::validate(_end); + if (!end) { + ERROR("%s - Couldn't find end of central directory record", name); + return nullptr; + } + + entries = DirectoryEntry::validate(static_cast<const char *>(mapped) + + end->offset); + if (!entries) { + ERROR("%s - Couldn't find central directory record", name); + } + return entries; +} + +ZipCollection ZipCollection::Singleton; + +static pthread_mutex_t sZipCollectionMutex = PTHREAD_MUTEX_INITIALIZER; + +already_AddRefed<Zip> +ZipCollection::GetZip(const char *path) +{ + { + AutoLock lock(&sZipCollectionMutex); + /* Search the list of Zips we already have for a match */ + for (std::vector<Zip *>::iterator it = Singleton.zips.begin(); + it < Singleton.zips.end(); ++it) { + if ((*it)->GetName() && (strcmp((*it)->GetName(), path) == 0)) { + RefPtr<Zip> zip = *it; + return zip.forget(); + } + } + } + return Zip::Create(path); +} + +void +ZipCollection::Register(Zip *zip) +{ + AutoLock lock(&sZipCollectionMutex); + DEBUG_LOG("ZipCollection::Register(\"%s\")", zip->GetName()); + Singleton.zips.push_back(zip); +} + +void +ZipCollection::Forget(Zip *zip) +{ + AutoLock lock(&sZipCollectionMutex); + DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName()); + std::vector<Zip *>::iterator it = std::find(Singleton.zips.begin(), + Singleton.zips.end(), zip); + if (*it == zip) { + Singleton.zips.erase(it); + } else { + DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName()); + } +} diff --git a/mozglue/linker/Zip.h b/mozglue/linker/Zip.h new file mode 100644 index 0000000000..29e42592ad --- /dev/null +++ b/mozglue/linker/Zip.h @@ -0,0 +1,500 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Zip_h +#define Zip_h + +#include <cstring> +#include <stdint.h> +#include <vector> +#include <zlib.h> +#include <pthread.h> +#include "Utils.h" +#include "mozilla/Assertions.h" +#include "mozilla/RefCounted.h" +#include "mozilla/RefPtr.h" + +/** + * Helper class wrapping z_stream to avoid malloc() calls during + * inflate. Do not use for deflate. + * inflateInit allocates two buffers: + * - one for its internal state, which is "approximately 10K bytes" according + * to inflate.h from zlib. + * - one for the compression window, which depends on the window size passed + * to inflateInit2, but is never greater than 32K (1 << MAX_WBITS). + * Those buffers are created at instantiation time instead of when calling + * inflateInit2. When inflateInit2 is called, it will call zxx_stream::Alloc + * to get both these buffers. zxx_stream::Alloc will choose one of the + * pre-allocated buffers depending on the requested size. + */ +class zxx_stream: public z_stream +{ +public: + /* Forward declaration */ + class StaticAllocator; + + explicit zxx_stream(StaticAllocator *allocator_=nullptr) + : allocator(allocator_) + { + memset(this, 0, sizeof(z_stream)); + zalloc = Alloc; + zfree = Free; + opaque = this; + } + +private: + static void *Alloc(void *data, uInt items, uInt size) + { + zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data); + if (zStream->allocator) { + return zStream->allocator->Alloc(items, size); + } + size_t buf_size = items * size; + return ::operator new(buf_size); + } + + static void Free(void *data, void *ptr) + { + zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data); + if (zStream->allocator) { + zStream->allocator->Free(ptr); + } else { + ::operator delete(ptr); + } + } + + /** + * Helper class for each buffer in StaticAllocator. + */ + template <size_t Size> + class ZStreamBuf + { + public: + ZStreamBuf() : inUse(false) { } + + bool get(char*& out) + { + if (!inUse) { + inUse = true; + out = buf; + return true; + } else { + return false; + } + } + + void Release() + { + memset(buf, 0, Size); + inUse = false; + } + + bool Equals(const void *other) { return other == buf; } + + static const size_t size = Size; + + private: + char buf[Size]; + bool inUse; + }; + +public: + /** + * Special allocator that uses static buffers to allocate from. + */ + class StaticAllocator + { + public: + void *Alloc(uInt items, uInt size) + { + if (items == 1 && size <= stateBuf1.size) { + char* res = nullptr; + if (stateBuf1.get(res) || stateBuf2.get(res)) { + return res; + } + MOZ_CRASH("ZStreamBuf already in use"); + } else if (items * size == windowBuf1.size) { + char* res = nullptr; + if (windowBuf1.get(res) || windowBuf2.get(res)) { + return res; + } + MOZ_CRASH("ZStreamBuf already in use"); + } else { + MOZ_CRASH("No ZStreamBuf for allocation"); + } + } + + void Free(void *ptr) + { + if (stateBuf1.Equals(ptr)) { + stateBuf1.Release(); + } else if (stateBuf2.Equals(ptr)) { + stateBuf2.Release(); + }else if (windowBuf1.Equals(ptr)) { + windowBuf1.Release(); + } else if (windowBuf2.Equals(ptr)) { + windowBuf2.Release(); + } else { + MOZ_CRASH("Pointer doesn't match a ZStreamBuf"); + } + } + + // 0x3000 is an arbitrary size above 10K. + ZStreamBuf<0x3000> stateBuf1, stateBuf2; + ZStreamBuf<1 << MAX_WBITS> windowBuf1, windowBuf2; + }; + +private: + StaticAllocator *allocator; +}; + +/** + * Forward declaration + */ +class ZipCollection; + +/** + * Class to handle access to Zip archive streams. The Zip archive is mapped + * in memory, and streams are direct references to that mapped memory. + * Zip files are assumed to be correctly formed. No boundary checks are + * performed, which means hand-crafted malicious Zip archives can make the + * code fail in bad ways. However, since the only intended use is to load + * libraries from Zip archives, there is no interest in making this code + * safe, since the libraries could contain malicious code anyways. + */ +class Zip: public mozilla::external::AtomicRefCounted<Zip> +{ +public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(Zip) + /** + * Create a Zip instance for the given file name. Returns nullptr in case + * of failure. + */ + static already_AddRefed<Zip> Create(const char *filename); + + /** + * Create a Zip instance using the given buffer. + */ + static already_AddRefed<Zip> Create(void *buffer, size_t size) { + return Create(nullptr, buffer, size); + } + +private: + static already_AddRefed<Zip> Create(const char *filename, + void *buffer, size_t size); + + /** + * Private constructor + */ + Zip(const char *filename, void *buffer, size_t size); + +public: + /** + * Destructor + */ + ~Zip(); + + /** + * Class used to access Zip archive item streams + */ + class Stream + { + public: + /** + * Stream types + */ + enum Type { + STORE = 0, + DEFLATE = 8 + }; + + /** + * Constructor + */ + Stream(): compressedBuf(nullptr), compressedSize(0), uncompressedSize(0) + , CRC32(0) + , type(STORE) { } + + /** + * Getters + */ + const void *GetBuffer() { return compressedBuf; } + size_t GetSize() { return compressedSize; } + size_t GetUncompressedSize() { return uncompressedSize; } + size_t GetCRC32() { return CRC32; } + Type GetType() { return type; } + + /** + * Returns a zxx_stream for use with inflate functions using the given + * buffer as inflate output. The caller is expected to allocate enough + * memory for the Stream uncompressed size. + */ + zxx_stream GetZStream(void *buf) + { + zxx_stream zStream; + zStream.avail_in = compressedSize; + zStream.next_in = reinterpret_cast<Bytef *>( + const_cast<void *>(compressedBuf)); + zStream.avail_out = uncompressedSize; + zStream.next_out = static_cast<Bytef *>(buf); + return zStream; + } + + protected: + friend class Zip; + const void *compressedBuf; + size_t compressedSize; + size_t uncompressedSize; + size_t CRC32; + Type type; + }; + + /** + * Returns a stream from the Zip archive. + */ + bool GetStream(const char *path, Stream *out) const; + + /** + * Returns the file name of the archive + */ + const char *GetName() const + { + return name; + } + +private: + /* File name of the archive */ + char *name; + /* Address where the Zip archive is mapped */ + void *mapped; + /* Size of the archive */ + size_t size; + + /** + * Strings (file names, comments, etc.) in the Zip headers are NOT zero + * terminated. This class is a helper around them. + */ + class StringBuf + { + public: + /** + * Constructor + */ + StringBuf(const char *buf, size_t length): buf(buf), length(length) { } + + /** + * Returns whether the string has the same content as the given zero + * terminated string. + */ + bool Equals(const char *str) const + { + return (strncmp(str, buf, length) == 0 && str[length] == '\0'); + } + + private: + const char *buf; + size_t length; + }; + +/* All the following types need to be packed */ +#pragma pack(1) +public: + /** + * A Zip archive is an aggregate of entities which all start with a + * signature giving their type. This template is to be used as a base + * class for these entities. + */ + template <typename T> + class SignedEntity + { + public: + /** + * Equivalent to reinterpret_cast<const T *>(buf), with an additional + * check of the signature. + */ + static const T *validate(const void *buf) + { + const T *ret = static_cast<const T *>(buf); + if (ret->signature == T::magic) + return ret; + return nullptr; + } + + SignedEntity(uint32_t magic): signature(magic) { } + private: + le_uint32 signature; + }; + +private: + /** + * Header used to describe a Local File entry. The header is followed by + * the file name and an extra field, then by the data stream. + */ + struct LocalFile: public SignedEntity<LocalFile> + { + /* Signature for a Local File header */ + static const uint32_t magic = 0x04034b50; + + /** + * Returns the file name + */ + StringBuf GetName() const + { + return StringBuf(reinterpret_cast<const char *>(this) + sizeof(*this), + filenameSize); + } + + /** + * Returns a pointer to the data associated with this header + */ + const void *GetData() const + { + return reinterpret_cast<const char *>(this) + sizeof(*this) + + filenameSize + extraFieldSize; + } + + le_uint16 minVersion; + le_uint16 generalFlag; + le_uint16 compression; + le_uint16 lastModifiedTime; + le_uint16 lastModifiedDate; + le_uint32 CRC32; + le_uint32 compressedSize; + le_uint32 uncompressedSize; + le_uint16 filenameSize; + le_uint16 extraFieldSize; + }; + + /** + * In some cases, when a zip archive is created, compressed size and CRC + * are not known when writing the Local File header. In these cases, the + * 3rd bit of the general flag in the Local File header is set, and there + * is an additional header following the compressed data. + */ + struct DataDescriptor: public SignedEntity<DataDescriptor> + { + /* Signature for a Data Descriptor header */ + static const uint32_t magic = 0x08074b50; + + le_uint32 CRC32; + le_uint32 compressedSize; + le_uint32 uncompressedSize; + }; + + /** + * Header used to describe a Central Directory Entry. The header is + * followed by the file name, an extra field, and a comment. + */ + struct DirectoryEntry: public SignedEntity<DirectoryEntry> + { + /* Signature for a Central Directory Entry header */ + static const uint32_t magic = 0x02014b50; + + /** + * Returns the file name + */ + StringBuf GetName() const + { + return StringBuf(reinterpret_cast<const char *>(this) + sizeof(*this), + filenameSize); + } + + /** + * Returns the Central Directory Entry following this one. + */ + const DirectoryEntry *GetNext() const + { + return validate(reinterpret_cast<const char *>(this) + sizeof(*this) + + filenameSize + extraFieldSize + fileCommentSize); + } + + le_uint16 creatorVersion; + le_uint16 minVersion; + le_uint16 generalFlag; + le_uint16 compression; + le_uint16 lastModifiedTime; + le_uint16 lastModifiedDate; + le_uint32 CRC32; + le_uint32 compressedSize; + le_uint32 uncompressedSize; + le_uint16 filenameSize; + le_uint16 extraFieldSize; + le_uint16 fileCommentSize; + le_uint16 diskNum; + le_uint16 internalAttributes; + le_uint32 externalAttributes; + le_uint32 offset; + }; + + /** + * Header used to describe the End of Central Directory Record. + */ + struct CentralDirectoryEnd: public SignedEntity<CentralDirectoryEnd> + { + /* Signature for the End of Central Directory Record */ + static const uint32_t magic = 0x06054b50; + + le_uint16 diskNum; + le_uint16 startDisk; + le_uint16 recordsOnDisk; + le_uint16 records; + le_uint32 size; + le_uint32 offset; + le_uint16 commentSize; + }; +#pragma pack() + + /** + * Returns the first Directory entry + */ + const DirectoryEntry *GetFirstEntry() const; + + /* Pointer to the Local File Entry following the last one GetStream() used. + * This is used by GetStream to avoid scanning the Directory Entries when the + * requested entry is that one. */ + mutable const LocalFile *nextFile; + + /* Likewise for the next Directory entry */ + mutable const DirectoryEntry *nextDir; + + /* Pointer to the Directory entries */ + mutable const DirectoryEntry *entries; + + mutable pthread_mutex_t mutex; +}; + +/** + * Class for bookkeeping Zip instances + */ +class ZipCollection +{ +public: + static ZipCollection Singleton; + + /** + * Get a Zip instance for the given path. If there is an existing one + * already, return that one, otherwise create a new one. + */ + static already_AddRefed<Zip> GetZip(const char *path); + +protected: + friend class Zip; + /** + * Register the given Zip instance. This method is meant to be called + * by Zip::Create. + */ + static void Register(Zip *zip); + + /** + * Forget about the given Zip instance. This method is meant to be called + * by the Zip destructor. + */ + static void Forget(Zip *zip); + +private: + /* Zip instances bookkept in this collection */ + std::vector<Zip *> zips; +}; + +#endif /* Zip_h */ diff --git a/mozglue/linker/dladdr.h b/mozglue/linker/dladdr.h new file mode 100644 index 0000000000..9a209547a6 --- /dev/null +++ b/mozglue/linker/dladdr.h @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <dlfcn.h> + +#ifndef HAVE_DLADDR +typedef struct { + const char *dli_fname; + void *dli_fbase; + const char *dli_sname; + void *dli_saddr; +} Dl_info; +extern int dladdr(void *addr, Dl_info *info) __attribute__((weak)); +#define HAVE_DLADDR 1 +#endif diff --git a/mozglue/linker/moz.build b/mozglue/linker/moz.build new file mode 100644 index 0000000000..49f4d62f0c --- /dev/null +++ b/mozglue/linker/moz.build @@ -0,0 +1,54 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'BaseElf.cpp', + 'CustomElf.cpp', + 'ElfLoader.cpp', + 'Mappable.cpp', + 'SeekableZStream.cpp', + 'XZStream.cpp', + 'Zip.cpp', +] + +Library('linker') + +HOST_SOURCES += [ + 'SeekableZStream.cpp', + 'szip.cpp', +] + +HostProgram('szip') + +FINAL_LIBRARY = 'mozglue' + +DEFINES['IMPL_MFBT'] = True + +DISABLE_STL_WRAPPING = True + +TEST_DIRS += ['tests'] + +HOST_OS_LIBS += [ + 'z', +] + +if CONFIG['TARGET_CPU'] == 'arm': + if CONFIG['MOZ_THUMB2']: + HOST_DEFINES['TARGET_THUMB'] = True + else: + HOST_DEFINES['TARGET_ARM'] = True + +if CONFIG['CPU_ARCH'] == 'x86': + HOST_DEFINES['TARGET_X86'] = True + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +DEFINES['XZ_USE_CRC64'] = 1 + +USE_LIBS += [ + 'xz-embedded', +] diff --git a/mozglue/linker/szip.cpp b/mozglue/linker/szip.cpp new file mode 100644 index 0000000000..bfc882fbea --- /dev/null +++ b/mozglue/linker/szip.cpp @@ -0,0 +1,593 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> +#include <map> +#include <sys/stat.h> +#include <string> +#include <sstream> +#include <cstring> +#include <cstdlib> +#include <zlib.h> +#include <fcntl.h> +#include <errno.h> +#include "mozilla/Assertions.h" +#include "mozilla/Scoped.h" +#include "mozilla/UniquePtr.h" +#include "SeekableZStream.h" +#include "Utils.h" +#include "Logging.h" + +Logging Logging::Singleton; + +const char *filterName[] = { + "none", + "thumb", + "arm", + "x86", + "auto" +}; + +/* Maximum supported size for chunkSize */ +static const size_t maxChunkSize = + 1 << (8 * std::min(sizeof(((SeekableZStreamHeader *)nullptr)->chunkSize), + sizeof(((SeekableZStreamHeader *)nullptr)->lastChunkSize)) - 1); + +class Buffer: public MappedPtr +{ +public: + virtual ~Buffer() { } + + virtual bool Resize(size_t size) + { + MemoryRange buf = mmap(nullptr, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (buf == MAP_FAILED) + return false; + if (*this != MAP_FAILED) + memcpy(buf, *this, std::min(size, GetLength())); + Assign(buf); + return true; + } + + bool Fill(Buffer &other) + { + size_t size = other.GetLength(); + if (!size || !Resize(size)) + return false; + memcpy(static_cast<void *>(*this), static_cast<void *>(other), size); + return true; + } +}; + +class FileBuffer: public Buffer +{ +public: + bool Init(const char *name, bool writable_ = false) + { + fd = open(name, writable_ ? O_RDWR | O_CREAT | O_TRUNC : O_RDONLY, 0666); + if (fd == -1) + return false; + writable = writable_; + return true; + } + + virtual bool Resize(size_t size) + { + if (writable) { + if (ftruncate(fd, size) == -1) + return false; + } + Assign(MemoryRange::mmap(nullptr, size, + PROT_READ | (writable ? PROT_WRITE : 0), + writable ? MAP_SHARED : MAP_PRIVATE, fd, 0)); + return this != MAP_FAILED; + } + + int getFd() + { + return fd; + } + +private: + AutoCloseFD fd; + bool writable; +}; + +class FilteredBuffer: public Buffer +{ +public: + void Filter(Buffer &other, SeekableZStream::FilterId filter, size_t chunkSize) + { + SeekableZStream::ZStreamFilter filterCB = + SeekableZStream::GetFilter(filter); + MOZ_ASSERT(filterCB); + Fill(other); + size_t size = other.GetLength(); + Bytef *data = reinterpret_cast<Bytef *>(static_cast<void *>(*this)); + size_t avail = 0; + /* Filter needs to be applied in chunks. */ + while (size) { + avail = std::min(size, chunkSize); + filterCB(data - static_cast<unsigned char *>(static_cast<void *>(*this)), + SeekableZStream::FILTER, data, avail); + size -= avail; + data += avail; + } + } +}; + +template <typename T> +class Dictionary: public Buffer +{ + typedef T piece; + typedef std::pair<piece, int> stat_pair; + + static bool stat_cmp(stat_pair a, stat_pair b) + { + return a.second < b.second; + } + +public: + Dictionary(Buffer &inBuf, size_t size) + { + if (!size || !Resize(size)) + return; + DEBUG_LOG("Creating dictionary"); + piece *origBufPieces = reinterpret_cast<piece *>( + static_cast<void *>(inBuf)); + std::map<piece, int> stats; + for (unsigned int i = 0; i < inBuf.GetLength() / sizeof(piece); i++) { + stats[origBufPieces[i]]++; + } + std::vector<stat_pair> statsVec(stats.begin(), stats.end()); + std::sort(statsVec.begin(), statsVec.end(), stat_cmp); + + piece *dictPieces = reinterpret_cast<piece *>( + static_cast<void *>(*this)); + typename std::vector<stat_pair>::reverse_iterator it = statsVec.rbegin(); + for (int i = size / sizeof(piece); i > 0 && it < statsVec.rend(); + i--, ++it) { + dictPieces[i - 1] = it->first; + } + } +}; + +class SzipAction +{ +public: + virtual int run(const char *name, Buffer &origBuf, + const char *outName, Buffer &outBuf) = 0; + + virtual ~SzipAction() {} +}; + +class SzipDecompress: public SzipAction +{ +public: + int run(const char *name, Buffer &origBuf, + const char *outName, Buffer &outBuf); +}; + + +class SzipCompress: public SzipAction +{ +public: + int run(const char *name, Buffer &origBuf, + const char *outName, Buffer &outBuf); + + SzipCompress(size_t aChunkSize, SeekableZStream::FilterId aFilter, + size_t aDictSize) + : chunkSize(aChunkSize ? aChunkSize : 16384) + , filter(aFilter) + , dictSize(aDictSize) + {} + + const static signed char winSizeLog = 15; + const static size_t winSize = 1 << winSizeLog; + + const static SeekableZStream::FilterId DEFAULT_FILTER = +#if defined(TARGET_THUMB) + SeekableZStream::BCJ_THUMB; +#elif defined(TARGET_ARM) + SeekableZStream::BCJ_ARM; +#elif defined(TARGET_X86) + SeekableZStream::BCJ_X86; +#else + SeekableZStream::NONE; +#endif + +private: + + int do_compress(Buffer &origBuf, Buffer &outBuf, const unsigned char *aDict, + size_t aDictSize, SeekableZStream::FilterId aFilter); + + size_t chunkSize; + SeekableZStream::FilterId filter; + size_t dictSize; +}; + +/* Decompress a seekable compressed stream */ +int SzipDecompress::run(const char *name, Buffer &origBuf, + const char *outName, Buffer &outBuf) +{ + size_t origSize = origBuf.GetLength(); + if (origSize < sizeof(SeekableZStreamHeader)) { + ERROR("%s is not compressed", name); + return 0; + } + + SeekableZStream zstream; + if (!zstream.Init(origBuf, origSize)) + return 0; + + size_t size = zstream.GetUncompressedSize(); + + /* Give enough room for the uncompressed data */ + if (!outBuf.Resize(size)) { + ERROR("Error resizing %s: %s", outName, strerror(errno)); + return 1; + } + + if (!zstream.Decompress(outBuf, 0, size)) + return 1; + + return 0; +} + +/* Generate a seekable compressed stream. */ +int SzipCompress::run(const char *name, Buffer &origBuf, + const char *outName, Buffer &outBuf) +{ + size_t origSize = origBuf.GetLength(); + if (origSize == 0) { + ERROR("Won't compress %s: it's empty", name); + return 1; + } + if (SeekableZStreamHeader::validate(origBuf)) { + WARN("Skipping %s: it's already a szip", name); + return 0; + } + bool compressed = false; + LOG("Size = %" PRIuSize, origSize); + + /* Allocate a buffer the size of the uncompressed data: we don't want + * a compressed file larger than that anyways. */ + if (!outBuf.Resize(origSize)) { + ERROR("Couldn't allocate output buffer: %s", strerror(errno)); + return 1; + } + + /* Find the most appropriate filter */ + SeekableZStream::FilterId firstFilter, lastFilter; + bool scanFilters; + if (filter == SeekableZStream::FILTER_MAX) { + firstFilter = SeekableZStream::NONE; + lastFilter = SeekableZStream::FILTER_MAX; + scanFilters = true; + } else { + firstFilter = lastFilter = filter; + ++lastFilter; + scanFilters = false; + } + + mozilla::UniquePtr<Buffer> filteredBuf; + Buffer *origData; + for (SeekableZStream::FilterId f = firstFilter; f < lastFilter; ++f) { + mozilla::UniquePtr<FilteredBuffer> filteredTmp; + Buffer tmpBuf; + if (f != SeekableZStream::NONE) { + DEBUG_LOG("Applying filter \"%s\"", filterName[f]); + filteredTmp = mozilla::MakeUnique<FilteredBuffer>(); + filteredTmp->Filter(origBuf, f, chunkSize); + origData = filteredTmp.get(); + } else { + origData = &origBuf; + } + if (dictSize && !scanFilters) { + filteredBuf = mozilla::Move(filteredTmp); + break; + } + DEBUG_LOG("Compressing with no dictionary"); + if (do_compress(*origData, tmpBuf, nullptr, 0, f) == 0) { + if (tmpBuf.GetLength() < outBuf.GetLength()) { + outBuf.Fill(tmpBuf); + compressed = true; + filter = f; + filteredBuf = mozilla::Move(filteredTmp); + continue; + } + } + } + + origData = filteredBuf ? filteredBuf.get() : &origBuf; + + if (dictSize) { + Dictionary<uint64_t> dict(*origData, dictSize ? SzipCompress::winSize : 0); + + /* Find the most appropriate dictionary size */ + size_t firstDictSize, lastDictSize; + if (dictSize == (size_t) -1) { + /* If we scanned for filters, we effectively already tried dictSize=0 */ + firstDictSize = scanFilters ? 4096 : 0; + lastDictSize = SzipCompress::winSize; + } else { + firstDictSize = lastDictSize = dictSize; + } + + Buffer tmpBuf; + for (size_t d = firstDictSize; d <= lastDictSize; d += 4096) { + DEBUG_LOG("Compressing with dictionary of size %" PRIuSize, d); + if (do_compress(*origData, tmpBuf, static_cast<unsigned char *>(dict) + + SzipCompress::winSize - d, d, filter)) + continue; + if (!compressed || tmpBuf.GetLength() < outBuf.GetLength()) { + outBuf.Fill(tmpBuf); + compressed = true; + dictSize = d; + } + } + } + + if (!compressed) { + outBuf.Fill(origBuf); + LOG("Not compressed"); + return 0; + } + + if (dictSize == (size_t) -1) + dictSize = 0; + + DEBUG_LOG("Used filter \"%s\" and dictionary size of %" PRIuSize, + filterName[filter], dictSize); + LOG("Compressed size is %" PRIuSize, outBuf.GetLength()); + + /* Sanity check */ + Buffer tmpBuf; + SzipDecompress decompress; + if (decompress.run("buffer", outBuf, "buffer", tmpBuf)) + return 1; + + size_t size = tmpBuf.GetLength(); + if (size != origSize) { + ERROR("Compression error: %" PRIuSize " != %" PRIuSize, size, origSize); + return 1; + } + if (memcmp(static_cast<void *>(origBuf), static_cast<void *>(tmpBuf), size)) { + ERROR("Compression error: content mismatch"); + return 1; + } + return 0; +} + +int SzipCompress::do_compress(Buffer &origBuf, Buffer &outBuf, + const unsigned char *aDict, size_t aDictSize, + SeekableZStream::FilterId aFilter) +{ + size_t origSize = origBuf.GetLength(); + MOZ_ASSERT(origSize != 0); + + /* Expected total number of chunks */ + size_t nChunks = ((origSize + chunkSize - 1) / chunkSize); + + /* The first chunk is going to be stored after the header, the dictionary + * and the offset table */ + size_t offset = sizeof(SeekableZStreamHeader) + aDictSize + + nChunks * sizeof(uint32_t); + + if (offset >= origSize) + return 1; + + /* Allocate a buffer the size of the uncompressed data: we don't want + * a compressed file larger than that anyways. */ + if (!outBuf.Resize(origSize)) { + ERROR("Couldn't allocate output buffer: %s", strerror(errno)); + return 1; + } + + SeekableZStreamHeader *header = new (outBuf) SeekableZStreamHeader; + unsigned char *dictionary = static_cast<unsigned char *>( + outBuf + sizeof(SeekableZStreamHeader)); + le_uint32 *entry = + reinterpret_cast<le_uint32 *>(dictionary + aDictSize); + + /* Initialize header */ + header->chunkSize = chunkSize; + header->dictSize = aDictSize; + header->totalSize = offset; + header->windowBits = -SzipCompress::winSizeLog; // Raw stream, + // window size of 32k. + header->filter = aFilter; + if (aDictSize) + memcpy(dictionary, aDict, aDictSize); + + /* Initialize zlib structure */ + z_stream zStream; + memset(&zStream, 0, sizeof(zStream)); + zStream.avail_out = origSize - offset; + zStream.next_out = static_cast<Bytef*>(outBuf) + offset; + + size_t avail = 0; + size_t size = origSize; + unsigned char *data = reinterpret_cast<unsigned char *>( + static_cast<void *>(origBuf)); + while (size) { + avail = std::min(size, chunkSize); + + /* Compress chunk */ + int ret = deflateInit2(&zStream, 9, Z_DEFLATED, header->windowBits, + MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (aDictSize) + deflateSetDictionary(&zStream, dictionary, aDictSize); + MOZ_ASSERT(ret == Z_OK); + zStream.avail_in = avail; + zStream.next_in = data; + ret = deflate(&zStream, Z_FINISH); + /* Under normal conditions, deflate returns Z_STREAM_END. If there is not + * enough room to compress, deflate returns Z_OK and avail_out is 0. We + * still want to deflateEnd in that case, so fall through. It will bail + * on the avail_out test that follows. */ + MOZ_ASSERT(ret == Z_STREAM_END || ret == Z_OK); + ret = deflateEnd(&zStream); + MOZ_ASSERT(ret == Z_OK); + if (zStream.avail_out <= 0) + return 1; + + size_t len = origSize - offset - zStream.avail_out; + + /* Adjust headers */ + header->totalSize += len; + *entry++ = offset; + header->nChunks++; + + /* Prepare for next iteration */ + size -= avail; + data += avail; + offset += len; + } + header->lastChunkSize = avail; + MOZ_ASSERT(header->totalSize == offset); + MOZ_ASSERT(header->nChunks == nChunks); + + if (!outBuf.Resize(offset)) { + ERROR("Error truncating output: %s", strerror(errno)); + return 1; + } + + return 0; + +} + +bool GetSize(const char *str, size_t *out) +{ + char *end; + MOZ_ASSERT(out); + errno = 0; + *out = strtol(str, &end, 10); + return (!errno && !*end); +} + +int main(int argc, char* argv[]) +{ + mozilla::UniquePtr<SzipAction> action; + char **firstArg; + bool compress = true; + size_t chunkSize = 0; + SeekableZStream::FilterId filter = SzipCompress::DEFAULT_FILTER; + size_t dictSize = (size_t) 0; + + Logging::Init(); + + for (firstArg = &argv[1]; argc > 2; argc--, firstArg++) { + if (!firstArg[0] || firstArg[0][0] != '-') + break; + if (strcmp(firstArg[0], "-d") == 0) { + compress = false; + } else if (strcmp(firstArg[0], "-c") == 0) { + firstArg++; + argc--; + if (!firstArg[0]) + break; + if (!GetSize(firstArg[0], &chunkSize) || !chunkSize || + (chunkSize % 4096) || (chunkSize > maxChunkSize)) { + ERROR("Invalid chunk size"); + return 1; + } + } else if (strcmp(firstArg[0], "-f") == 0) { + firstArg++; + argc--; + if (!firstArg[0]) + break; + bool matched = false; + for (unsigned int i = 0; i < sizeof(filterName) / sizeof(char *); ++i) { + if (strcmp(firstArg[0], filterName[i]) == 0) { + filter = static_cast<SeekableZStream::FilterId>(i); + matched = true; + break; + } + } + if (!matched) { + ERROR("Invalid filter"); + return 1; + } + } else if (strcmp(firstArg[0], "-D") == 0) { + firstArg++; + argc--; + if (!firstArg[0]) + break; + if (strcmp(firstArg[0], "auto") == 0) { + dictSize = -1; + } else if (!GetSize(firstArg[0], &dictSize) || (dictSize >= 1 << 16)) { + ERROR("Invalid dictionary size"); + return 1; + } + } + } + + if (argc != 2 || !firstArg[0]) { + LOG("usage: %s [-d] [-c CHUNKSIZE] [-f FILTER] [-D DICTSIZE] file", + argv[0]); + return 1; + } + + if (compress) { + action.reset(new SzipCompress(chunkSize, filter, dictSize)); + } else { + if (chunkSize) { + ERROR("-c is incompatible with -d"); + return 1; + } + if (dictSize) { + ERROR("-D is incompatible with -d"); + return 1; + } + action.reset(new SzipDecompress()); + } + + std::stringstream tmpOutStream; + tmpOutStream << firstArg[0] << ".sz." << getpid(); + std::string tmpOut(tmpOutStream.str()); + int ret; + struct stat st; + { + FileBuffer origBuf; + if (!origBuf.Init(firstArg[0])) { + ERROR("Couldn't open %s: %s", firstArg[0], strerror(errno)); + return 1; + } + + ret = fstat(origBuf.getFd(), &st); + if (ret == -1) { + ERROR("Couldn't stat %s: %s", firstArg[0], strerror(errno)); + return 1; + } + + size_t origSize = st.st_size; + + /* Mmap the original file */ + if (!origBuf.Resize(origSize)) { + ERROR("Couldn't mmap %s: %s", firstArg[0], strerror(errno)); + return 1; + } + + /* Create the compressed file */ + FileBuffer outBuf; + if (!outBuf.Init(tmpOut.c_str(), true)) { + ERROR("Couldn't open %s: %s", tmpOut.c_str(), strerror(errno)); + return 1; + } + + ret = action->run(firstArg[0], origBuf, tmpOut.c_str(), outBuf); + if ((ret == 0) && (fstat(outBuf.getFd(), &st) == -1)) { + st.st_size = 0; + } + } + + if ((ret == 0) && st.st_size) { + rename(tmpOut.c_str(), firstArg[0]); + } else { + unlink(tmpOut.c_str()); + } + return ret; +} diff --git a/mozglue/linker/tests/TestZip.cpp b/mozglue/linker/tests/TestZip.cpp new file mode 100644 index 0000000000..25873f5e5f --- /dev/null +++ b/mozglue/linker/tests/TestZip.cpp @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <cstdio> +#include <unistd.h> +#include "Zip.h" +#include "mozilla/RefPtr.h" + +extern "C" void report_mapping() { } +extern "C" void delete_mapping() { } + +/** + * test.zip is a basic test zip file with a central directory. It contains + * four entries, in the following order: + * "foo", "bar", "baz", "qux". + * The entries are going to be read out of order. + */ +const char *test_entries[] = { + "baz", "foo", "bar", "qux" +}; + +/** + * no_central_dir.zip is a hand crafted test zip with no central directory + * entries. The Zip reader is expected to be able to traverse these entries + * if requested in order, without reading a central directory + * - First entry is a file "a", STOREd. + * - Second entry is a file "b", STOREd, using a data descriptor. CRC is + * unknown, but compressed and uncompressed sizes are known in the local + * file header. + * - Third entry is a file "c", DEFLATEd, using a data descriptor. CRC, + * compressed and uncompressed sizes are known in the local file header. + * This is the kind of entry that can be found in a zip that went through + * zipalign if it had a data descriptor originally. + * - Fourth entry is a file "d", STOREd. + */ +const char *no_central_dir_entries[] = { + "a", "b", "c", "d" +}; + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "TEST-FAIL | TestZip | Expecting the directory containing test Zips\n"); + return 1; + } + chdir(argv[1]); + Zip::Stream s; + RefPtr<Zip> z = ZipCollection::GetZip("test.zip"); + for (size_t i = 0; i < sizeof(test_entries) / sizeof(*test_entries); i++) { + if (!z->GetStream(test_entries[i], &s)) { + fprintf(stderr, "TEST-UNEXPECTED-FAIL | TestZip | test.zip: Couldn't get entry \"%s\"\n", test_entries[i]); + return 1; + } + } + fprintf(stderr, "TEST-PASS | TestZip | test.zip could be accessed fully\n"); + + z = ZipCollection::GetZip("no_central_dir.zip"); + for (size_t i = 0; i < sizeof(no_central_dir_entries) + / sizeof(*no_central_dir_entries); i++) { + if (!z->GetStream(no_central_dir_entries[i], &s)) { + fprintf(stderr, "TEST-UNEXPECTED-FAIL | TestZip | no_central_dir.zip: Couldn't get entry \"%s\"\n", no_central_dir_entries[i]); + return 1; + } + } + fprintf(stderr, "TEST-PASS | TestZip | no_central_dir.zip could be accessed in order\n"); + + return 0; +} diff --git a/mozglue/linker/tests/moz.build b/mozglue/linker/tests/moz.build new file mode 100644 index 0000000000..3d4c1c6162 --- /dev/null +++ b/mozglue/linker/tests/moz.build @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIST_INSTALL = False + +SimplePrograms([ + 'TestZip', +]) +LOCAL_INCLUDES += ['..'] +USE_LIBS += [ + 'linker', + 'mfbt', +] +OS_LIBS += CONFIG['MOZ_ZLIB_LIBS'] +DISABLE_STL_WRAPPING = True + +PYTHON_UNIT_TESTS += ['run_test_zip.py'] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/mozglue/linker/tests/no_central_dir.zip b/mozglue/linker/tests/no_central_dir.zip Binary files differnew file mode 100644 index 0000000000..df882220d1 --- /dev/null +++ b/mozglue/linker/tests/no_central_dir.zip diff --git a/mozglue/linker/tests/run_test_zip.py b/mozglue/linker/tests/run_test_zip.py new file mode 100644 index 0000000000..e606e16d49 --- /dev/null +++ b/mozglue/linker/tests/run_test_zip.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import buildconfig +import mozpack.path as mozpath +import mozunit +import subprocess +import unittest + +class TestZip(unittest.TestCase): + def test_zip(self): + srcdir = mozpath.dirname(__file__) + relsrcdir = mozpath.relpath(srcdir, buildconfig.topsrcdir) + test_bin = mozpath.join(buildconfig.topobjdir, relsrcdir, + 'TestZip' + buildconfig.substs['BIN_SUFFIX']) + self.assertEqual(0, subprocess.call([test_bin, srcdir])) + +if __name__ == '__main__': + mozunit.main() diff --git a/mozglue/linker/tests/test.zip b/mozglue/linker/tests/test.zip Binary files differnew file mode 100644 index 0000000000..657835b0ca --- /dev/null +++ b/mozglue/linker/tests/test.zip diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp new file mode 100644 index 0000000000..bb23b922a8 --- /dev/null +++ b/mozglue/misc/StackWalk.cpp @@ -0,0 +1,1204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* API for getting a stack trace of the C/C++ stack on the current thread */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/StackWalk.h" + +#include <string.h> + +using namespace mozilla; + +// The presence of this address is the stack must stop the stack walk. If +// there is no such address, the structure will be {nullptr, true}. +struct CriticalAddress +{ + void* mAddr; + bool mInit; +}; +static CriticalAddress gCriticalAddress; + +// for _Unwind_Backtrace from libcxxrt or libunwind +// cxxabi.h from libcxxrt implicitly includes unwind.h first +#if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE +#endif + +#if defined(HAVE_DLOPEN) || defined(XP_DARWIN) +#include <dlfcn.h> +#endif + +#if (defined(XP_DARWIN) && \ + (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) +#define MOZ_STACKWALK_SUPPORTS_MACOSX 1 +#else +#define MOZ_STACKWALK_SUPPORTS_MACOSX 0 +#endif + +#if (defined(linux) && \ + ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ + defined(HAVE__UNWIND_BACKTRACE))) +#define MOZ_STACKWALK_SUPPORTS_LINUX 1 +#else +#define MOZ_STACKWALK_SUPPORTS_LINUX 0 +#endif + +#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) +#define HAVE___LIBC_STACK_END 1 +#else +#define HAVE___LIBC_STACK_END 0 +#endif + +#if HAVE___LIBC_STACK_END +extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so +#endif + +#ifdef ANDROID +#include <algorithm> +#include <unistd.h> +#include <pthread.h> +#endif + +#if MOZ_STACKWALK_SUPPORTS_MACOSX +#include <pthread.h> +#include <sys/errno.h> +#ifdef MOZ_WIDGET_COCOA +#include <CoreServices/CoreServices.h> +#endif + +typedef void +malloc_logger_t(uint32_t aType, + uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, + uintptr_t aResult, uint32_t aNumHotFramesToSkip); +extern malloc_logger_t* malloc_logger; + +static void +stack_callback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure) +{ + const char* name = static_cast<char*>(aClosure); + Dl_info info; + + // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The + // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The + // correct one is the first that we find on our way up, so the + // following check for gCriticalAddress.mAddr is critical. + if (gCriticalAddress.mAddr || dladdr(aPc, &info) == 0 || + !info.dli_sname || strcmp(info.dli_sname, name) != 0) { + return; + } + gCriticalAddress.mAddr = aPc; +} + +static void +my_malloc_logger(uint32_t aType, + uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3, + uintptr_t aResult, uint32_t aNumHotFramesToSkip) +{ + static bool once = false; + if (once) { + return; + } + once = true; + + // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The + // stack shows up as having two pthread_cond_wait$UNIX2003 frames. + const char* name = "new_sem_from_pool"; + MozStackWalk(stack_callback, /* skipFrames */ 0, /* maxFrames */ 0, + const_cast<char*>(name), 0, nullptr); +} + +// This is called from NS_LogInit() and from the stack walking functions, but +// only the first call has any effect. We need to call this function from both +// places because it must run before any mutexes are created, and also before +// any objects whose refcounts we're logging are created. Running this +// function during NS_LogInit() ensures that we meet the first criterion, and +// running this function during the stack walking functions ensures we meet the +// second criterion. +MFBT_API void +StackWalkInitCriticalAddress() +{ + if (gCriticalAddress.mInit) { + return; + } + gCriticalAddress.mInit = true; + // We must not do work when 'new_sem_from_pool' calls realloc, since + // it holds a non-reentrant spin-lock and we will quickly deadlock. + // new_sem_from_pool is not directly accessible using dlsym, so + // we force a situation where new_sem_from_pool is on the stack and + // use dladdr to check the addresses. + + // malloc_logger can be set by external tools like 'Instruments' or 'leaks' + malloc_logger_t* old_malloc_logger = malloc_logger; + malloc_logger = my_malloc_logger; + + pthread_cond_t cond; + int r = pthread_cond_init(&cond, 0); + MOZ_ASSERT(r == 0); + pthread_mutex_t mutex; + r = pthread_mutex_init(&mutex, 0); + MOZ_ASSERT(r == 0); + r = pthread_mutex_lock(&mutex); + MOZ_ASSERT(r == 0); + struct timespec abstime = { 0, 1 }; + r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime); + + // restore the previous malloc logger + malloc_logger = old_malloc_logger; + + MOZ_ASSERT(r == ETIMEDOUT); + r = pthread_mutex_unlock(&mutex); + MOZ_ASSERT(r == 0); + r = pthread_mutex_destroy(&mutex); + MOZ_ASSERT(r == 0); + r = pthread_cond_destroy(&cond); + MOZ_ASSERT(r == 0); +} + +static bool +IsCriticalAddress(void* aPC) +{ + return gCriticalAddress.mAddr == aPC; +} +#else +static bool +IsCriticalAddress(void* aPC) +{ + return false; +} +// We still initialize gCriticalAddress.mInit so that this code behaves +// the same on all platforms. Otherwise a failure to init would be visible +// only on OS X. +MFBT_API void +StackWalkInitCriticalAddress() +{ + gCriticalAddress.mInit = true; +} +#endif + +#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code + +#include <windows.h> +#include <process.h> +#include <stdio.h> +#include <malloc.h> +#include "mozilla/ArrayUtils.h" +#include "mozilla/StackWalk_windows.h" + +#include <imagehlp.h> +// We need a way to know if we are building for WXP (or later), as if we are, we +// need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. +// A value of 9 indicates we want to use the new APIs. +#if API_VERSION_NUMBER < 9 +#error Too old imagehlp.h +#endif + +struct WalkStackData +{ + // Are we walking the stack of the calling thread? Note that we need to avoid + // calling fprintf and friends if this is false, in order to avoid deadlocks. + bool walkCallingThread; + uint32_t skipFrames; + HANDLE thread; + HANDLE process; + HANDLE eventStart; + HANDLE eventEnd; + void** pcs; + uint32_t pc_size; + uint32_t pc_count; + uint32_t pc_max; + void** sps; + uint32_t sp_size; + uint32_t sp_count; + void* platformData; +}; + +DWORD gStackWalkThread; +CRITICAL_SECTION gDbgHelpCS; + +// Routine to print an error message to standard error. +static void +PrintError(const char* aPrefix) +{ + LPSTR lpMsgBuf; + DWORD lastErr = GetLastError(); + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + lastErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPSTR)&lpMsgBuf, + 0, + nullptr + ); + fprintf(stderr, "### ERROR: %s: %s", + aPrefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); + fflush(stderr); + LocalFree(lpMsgBuf); +} + +static unsigned int WINAPI WalkStackThread(void* aData); + +static bool +EnsureWalkThreadReady() +{ + static bool walkThreadReady = false; + static HANDLE stackWalkThread = nullptr; + static HANDLE readyEvent = nullptr; + + if (walkThreadReady) { + return walkThreadReady; + } + + if (!stackWalkThread) { + readyEvent = ::CreateEvent(nullptr, FALSE /* auto-reset*/, + FALSE /* initially non-signaled */, + nullptr); + if (!readyEvent) { + PrintError("CreateEvent"); + return false; + } + + unsigned int threadID; + stackWalkThread = (HANDLE)_beginthreadex(nullptr, 0, WalkStackThread, + (void*)readyEvent, 0, &threadID); + if (!stackWalkThread) { + PrintError("CreateThread"); + ::CloseHandle(readyEvent); + readyEvent = nullptr; + return false; + } + gStackWalkThread = threadID; + ::CloseHandle(stackWalkThread); + } + + MOZ_ASSERT((stackWalkThread && readyEvent) || + (!stackWalkThread && !readyEvent)); + + // The thread was created. Try to wait an arbitrary amount of time (1 second + // should be enough) for its event loop to start before posting events to it. + DWORD waitRet = ::WaitForSingleObject(readyEvent, 1000); + if (waitRet == WAIT_TIMEOUT) { + // We get a timeout if we're called during static initialization because + // the thread will only start executing after we return so it couldn't + // have signalled the event. If that is the case, give up for now and + // try again next time we're called. + return false; + } + ::CloseHandle(readyEvent); + stackWalkThread = nullptr; + readyEvent = nullptr; + + + ::InitializeCriticalSection(&gDbgHelpCS); + + return walkThreadReady = true; +} + +static void +WalkStackMain64(struct WalkStackData* aData) +{ + // Get a context for the specified thread. + CONTEXT context; + if (!aData->platformData) { + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + if (!GetThreadContext(aData->thread, &context)) { + if (aData->walkCallingThread) { + PrintError("GetThreadContext"); + } + return; + } + } else { + context = *static_cast<CONTEXT*>(aData->platformData); + } + +#if defined(_M_IX86) || defined(_M_IA64) + // Setup initial stack frame to walk from. + STACKFRAME64 frame64; + memset(&frame64, 0, sizeof(frame64)); +#ifdef _M_IX86 + frame64.AddrPC.Offset = context.Eip; + frame64.AddrStack.Offset = context.Esp; + frame64.AddrFrame.Offset = context.Ebp; +#elif defined _M_IA64 + frame64.AddrPC.Offset = context.StIIP; + frame64.AddrStack.Offset = context.SP; + frame64.AddrFrame.Offset = context.RsBSP; +#endif + frame64.AddrPC.Mode = AddrModeFlat; + frame64.AddrStack.Mode = AddrModeFlat; + frame64.AddrFrame.Mode = AddrModeFlat; + frame64.AddrReturn.Mode = AddrModeFlat; +#endif + + // Skip our own stack walking frames. + int skip = (aData->walkCallingThread ? 3 : 0) + aData->skipFrames; + + // Now walk the stack. + while (true) { + DWORD64 addr; + DWORD64 spaddr; + +#if defined(_M_IX86) || defined(_M_IA64) + // 32-bit frame unwinding. + // Debug routines are not threadsafe, so grab the lock. + EnterCriticalSection(&gDbgHelpCS); + BOOL ok = StackWalk64( +#if defined _M_IA64 + IMAGE_FILE_MACHINE_IA64, +#elif defined _M_IX86 + IMAGE_FILE_MACHINE_I386, +#endif + aData->process, + aData->thread, + &frame64, + &context, + nullptr, + SymFunctionTableAccess64, // function table access routine + SymGetModuleBase64, // module base routine + 0 + ); + LeaveCriticalSection(&gDbgHelpCS); + + if (ok) { + addr = frame64.AddrPC.Offset; + spaddr = frame64.AddrStack.Offset; + } else { + addr = 0; + spaddr = 0; + if (aData->walkCallingThread) { + PrintError("WalkStack64"); + } + } + + if (!ok) { + break; + } + +#elif defined(_M_AMD64) + // 64-bit frame unwinding. + // Try to look up unwind metadata for the current function. + ULONG64 imageBase; + PRUNTIME_FUNCTION runtimeFunction = + RtlLookupFunctionEntry(context.Rip, &imageBase, NULL); + + if (!runtimeFunction) { + // Alas, this is probably a JIT frame, for which we don't generate unwind + // info and so we have to give up. + break; + } + + PVOID dummyHandlerData; + ULONG64 dummyEstablisherFrame; + RtlVirtualUnwind(UNW_FLAG_NHANDLER, + imageBase, + context.Rip, + runtimeFunction, + &context, + &dummyHandlerData, + &dummyEstablisherFrame, + nullptr); + + addr = context.Rip; + spaddr = context.Rsp; + +#else +#error "unknown platform" +#endif + + if (addr == 0) { + break; + } + + if (skip-- > 0) { + continue; + } + + if (aData->pc_count < aData->pc_size) { + aData->pcs[aData->pc_count] = (void*)addr; + } + ++aData->pc_count; + + if (aData->sp_count < aData->sp_size) { + aData->sps[aData->sp_count] = (void*)spaddr; + } + ++aData->sp_count; + + if (aData->pc_max != 0 && aData->pc_count == aData->pc_max) { + break; + } + +#if defined(_M_IX86) || defined(_M_IA64) + if (frame64.AddrReturn.Offset == 0) { + break; + } +#endif + } +} + +// The JIT needs to allocate executable memory. Because of the inanity of +// the win64 APIs, this requires locks that stalk walkers also need. Provide +// another lock to allow synchronization around these resources. +#ifdef _M_AMD64 + +struct CriticalSectionAutoInitializer { + CRITICAL_SECTION lock; + + CriticalSectionAutoInitializer() { + InitializeCriticalSection(&lock); + } +}; + +static CriticalSectionAutoInitializer gWorkaroundLock; + +#endif // _M_AMD64 + +MFBT_API void +AcquireStackWalkWorkaroundLock() +{ +#ifdef _M_AMD64 + EnterCriticalSection(&gWorkaroundLock.lock); +#endif +} + +MFBT_API bool +TryAcquireStackWalkWorkaroundLock() +{ +#ifdef _M_AMD64 + return TryEnterCriticalSection(&gWorkaroundLock.lock); +#else + return true; +#endif +} + +MFBT_API void +ReleaseStackWalkWorkaroundLock() +{ +#ifdef _M_AMD64 + LeaveCriticalSection(&gWorkaroundLock.lock); +#endif +} + +static unsigned int WINAPI +WalkStackThread(void* aData) +{ + BOOL msgRet; + MSG msg; + + // Call PeekMessage to force creation of a message queue so that + // other threads can safely post events to us. + ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + // and tell the thread that created us that we're ready. + HANDLE readyEvent = (HANDLE)aData; + ::SetEvent(readyEvent); + + while ((msgRet = ::GetMessage(&msg, (HWND)-1, 0, 0)) != 0) { + if (msgRet == -1) { + PrintError("GetMessage"); + } else { + DWORD ret; + + struct WalkStackData* data = (WalkStackData*)msg.lParam; + if (!data) { + continue; + } + + // Don't suspend the calling thread until it's waiting for + // us; otherwise the number of frames on the stack could vary. + ret = ::WaitForSingleObject(data->eventStart, INFINITE); + if (ret != WAIT_OBJECT_0) { + PrintError("WaitForSingleObject"); + } + + // Suspend the calling thread, dump his stack, and then resume him. + // He's currently waiting for us to finish so now should be a good time. + ret = ::SuspendThread(data->thread); + if (ret == -1) { + PrintError("ThreadSuspend"); + } else { + WalkStackMain64(data); + + ret = ::ResumeThread(data->thread); + if (ret == -1) { + PrintError("ThreadResume"); + } + } + + ::SetEvent(data->eventEnd); + } + } + + return 0; +} + +/** + * Walk the stack, translating PC's found into strings and recording the + * chain in aBuffer. For this to work properly, the DLLs must be rebased + * so that the address in the file agrees with the address in memory. + * Otherwise StackWalk will return FALSE when it hits a frame in a DLL + * whose in memory address doesn't match its in-file address. + */ + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + StackWalkInitCriticalAddress(); + static HANDLE myProcess = nullptr; + HANDLE myThread; + DWORD walkerReturn; + struct WalkStackData data; + + if (!EnsureWalkThreadReady()) { + return false; + } + + HANDLE currentThread = ::GetCurrentThread(); + HANDLE targetThread = + aThread ? reinterpret_cast<HANDLE>(aThread) : currentThread; + data.walkCallingThread = (targetThread == currentThread); + + // Have to duplicate handle to get a real handle. + if (!myProcess) { + if (!::DuplicateHandle(::GetCurrentProcess(), + ::GetCurrentProcess(), + ::GetCurrentProcess(), + &myProcess, + PROCESS_ALL_ACCESS, FALSE, 0)) { + if (data.walkCallingThread) { + PrintError("DuplicateHandle (process)"); + } + return false; + } + } + if (!::DuplicateHandle(::GetCurrentProcess(), + targetThread, + ::GetCurrentProcess(), + &myThread, + THREAD_ALL_ACCESS, FALSE, 0)) { + if (data.walkCallingThread) { + PrintError("DuplicateHandle (thread)"); + } + return false; + } + + data.skipFrames = aSkipFrames; + data.thread = myThread; + data.process = myProcess; + void* local_pcs[1024]; + data.pcs = local_pcs; + data.pc_count = 0; + data.pc_size = ArrayLength(local_pcs); + data.pc_max = aMaxFrames; + void* local_sps[1024]; + data.sps = local_sps; + data.sp_count = 0; + data.sp_size = ArrayLength(local_sps); + data.platformData = aPlatformData; + + if (aThread) { + // If we're walking the stack of another thread, we don't need to + // use a separate walker thread. + WalkStackMain64(&data); + + if (data.pc_count > data.pc_size) { + data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); + data.pc_size = data.pc_count; + data.pc_count = 0; + data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); + data.sp_size = data.sp_count; + data.sp_count = 0; + WalkStackMain64(&data); + } + } else { + data.eventStart = ::CreateEvent(nullptr, FALSE /* auto-reset*/, + FALSE /* initially non-signaled */, nullptr); + data.eventEnd = ::CreateEvent(nullptr, FALSE /* auto-reset*/, + FALSE /* initially non-signaled */, nullptr); + + ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); + + walkerReturn = ::SignalObjectAndWait(data.eventStart, + data.eventEnd, INFINITE, FALSE); + if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) { + PrintError("SignalObjectAndWait (1)"); + } + if (data.pc_count > data.pc_size) { + data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); + data.pc_size = data.pc_count; + data.pc_count = 0; + data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); + data.sp_size = data.sp_count; + data.sp_count = 0; + ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); + walkerReturn = ::SignalObjectAndWait(data.eventStart, + data.eventEnd, INFINITE, FALSE); + if (walkerReturn != WAIT_OBJECT_0 && data.walkCallingThread) { + PrintError("SignalObjectAndWait (2)"); + } + } + + ::CloseHandle(data.eventStart); + ::CloseHandle(data.eventEnd); + } + + ::CloseHandle(myThread); + + for (uint32_t i = 0; i < data.pc_count; ++i) { + (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure); + } + + return data.pc_count != 0; +} + + +static BOOL CALLBACK +callbackEspecial64( + PCSTR aModuleName, + DWORD64 aModuleBase, + ULONG aModuleSize, + PVOID aUserContext) +{ + BOOL retval = TRUE; + DWORD64 addr = *(DWORD64*)aUserContext; + + /* + * You'll want to control this if we are running on an + * architecture where the addresses go the other direction. + * Not sure this is even a realistic consideration. + */ + const BOOL addressIncreases = TRUE; + + /* + * If it falls in side the known range, load the symbols. + */ + if (addressIncreases + ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize)) + : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize)) + ) { + retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, + (PSTR)aModuleName, nullptr, + aModuleBase, aModuleSize); + if (!retval) { + PrintError("SymLoadModule64"); + } + } + + return retval; +} + +/* + * SymGetModuleInfoEspecial + * + * Attempt to determine the module information. + * Bug 112196 says this DLL may not have been loaded at the time + * SymInitialize was called, and thus the module information + * and symbol information is not available. + * This code rectifies that problem. + */ + +// New members were added to IMAGEHLP_MODULE64 (that show up in the +// Platform SDK that ships with VC8, but not the Platform SDK that ships +// with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to +// use them, and it's useful to be able to function correctly with the +// older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll +// version 5.1.) Since Platform SDK version need not correspond to +// compiler version, and the version number in debughlp.h was NOT bumped +// when these changes were made, ifdef based on a constant that was +// added between these versions. +#ifdef SSRVOPT_SETCONTEXT +#define NS_IMAGEHLP_MODULE64_SIZE (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / sizeof(DWORD64)) * sizeof(DWORD64)) +#else +#define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) +#endif + +BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, + PIMAGEHLP_MODULE64 aModuleInfo, + PIMAGEHLP_LINE64 aLineInfo) +{ + BOOL retval = FALSE; + + /* + * Init the vars if we have em. + */ + aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; + if (aLineInfo) { + aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + } + + /* + * Give it a go. + * It may already be loaded. + */ + retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); + if (retval == FALSE) { + /* + * Not loaded, here's the magic. + * Go through all the modules. + */ + // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the + // constness of the first parameter of + // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from + // non-const to const over time). See bug 391848 and bug + // 415426. + BOOL enumRes = EnumerateLoadedModules64( + aProcess, + (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, + (PVOID)&aAddr); + if (enumRes != FALSE) { + /* + * One final go. + * If it fails, then well, we have other problems. + */ + retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); + } + } + + /* + * If we got module info, we may attempt line info as well. + * We will not report failure if this does not work. + */ + if (retval != FALSE && aLineInfo) { + DWORD displacement = 0; + BOOL lineRes = FALSE; + lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo); + if (!lineRes) { + // Clear out aLineInfo to indicate that it's not valid + memset(aLineInfo, 0, sizeof(*aLineInfo)); + } + } + + return retval; +} + +static bool +EnsureSymInitialized() +{ + static bool gInitialized = false; + bool retStat; + + if (gInitialized) { + return gInitialized; + } + + if (!EnsureWalkThreadReady()) { + return false; + } + + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE); + if (!retStat) { + PrintError("SymInitialize"); + } + + gInitialized = retStat; + /* XXX At some point we need to arrange to call SymCleanup */ + + return retStat; +} + + +MFBT_API bool +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) +{ + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + + if (!EnsureSymInitialized()) { + return false; + } + + HANDLE myProcess = ::GetCurrentProcess(); + BOOL ok; + + // debug routines are not threadsafe, so grab the lock. + EnterCriticalSection(&gDbgHelpCS); + + // + // Attempt to load module info before we attempt to resolve the symbol. + // This just makes sure we get good info if available. + // + + DWORD64 addr = (DWORD64)aPC; + IMAGEHLP_MODULE64 modInfo; + IMAGEHLP_LINE64 lineInfo; + BOOL modInfoRes; + modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); + + if (modInfoRes) { + strncpy(aDetails->library, modInfo.LoadedImageName, + sizeof(aDetails->library)); + aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; + aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage; + + if (lineInfo.FileName) { + strncpy(aDetails->filename, lineInfo.FileName, + sizeof(aDetails->filename)); + aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0'; + aDetails->lineno = lineInfo.LineNumber; + } + } + + ULONG64 buffer[(sizeof(SYMBOL_INFO) + + MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement; + ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); + + if (ok) { + strncpy(aDetails->function, pSymbol->Name, + sizeof(aDetails->function)); + aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; + aDetails->foffset = static_cast<ptrdiff_t>(displacement); + } + + LeaveCriticalSection(&gDbgHelpCS); // release our lock + return true; +} + +// i386 or PPC Linux stackwalking code +#elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || MOZ_STACKWALK_SUPPORTS_MACOSX) + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +// On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed +// if __USE_GNU is defined. I suppose its some kind of standards +// adherence thing. +// +#if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) +#define __USE_GNU +#endif + +// This thing is exported by libstdc++ +// Yes, this is a gcc only hack +#if defined(MOZ_DEMANGLE_SYMBOLS) +#include <cxxabi.h> +#endif // MOZ_DEMANGLE_SYMBOLS + +void DemangleSymbol(const char* aSymbol, + char* aBuffer, + int aBufLen) +{ + aBuffer[0] = '\0'; + +#if defined(MOZ_DEMANGLE_SYMBOLS) + /* See demangle.h in the gcc source for the voodoo */ + char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0); + + if (demangled) { + strncpy(aBuffer, demangled, aBufLen); + aBuffer[aBufLen - 1] = '\0'; + free(demangled); + } +#endif // MOZ_DEMANGLE_SYMBOLS +} + +// {x86, ppc} x {Linux, Mac} stackwalking code. +#if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \ + (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX)) + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + MOZ_ASSERT(!aThread); + MOZ_ASSERT(!aPlatformData); + StackWalkInitCriticalAddress(); + + // Get the frame pointer + void** bp = (void**)__builtin_frame_address(0); + + void* stackEnd; +#if HAVE___LIBC_STACK_END + stackEnd = __libc_stack_end; +#elif defined(XP_DARWIN) + stackEnd = pthread_get_stackaddr_np(pthread_self()); +#elif defined(ANDROID) + pthread_attr_t sattr; + pthread_attr_init(&sattr); + pthread_getattr_np(pthread_self(), &sattr); + void* stackBase = stackEnd = nullptr; + size_t stackSize = 0; + if (gettid() != getpid()) { + // bionic's pthread_attr_getstack doesn't tell the truth for the main + // thread (see bug 846670). So don't use it for the main thread. + if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) { + stackEnd = static_cast<char*>(stackBase) + stackSize; + } else { + stackEnd = nullptr; + } + } + if (!stackEnd) { + // So consider the current frame pointer + an arbitrary size of 8MB + // (modulo overflow ; not really arbitrary as it's the default stack + // size for the main thread) if pthread_attr_getstack failed for + // some reason (or was skipped). + static const uintptr_t kMaxStackSize = 8 * 1024 * 1024; + uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize; + uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp)); + stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize); + } +#else +# error Unsupported configuration +#endif + return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, + aClosure, bp, stackEnd); +} + +#elif defined(HAVE__UNWIND_BACKTRACE) + +// libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 +#include <unwind.h> + +struct unwind_info +{ + MozWalkStackCallback callback; + int skip; + int maxFrames; + int numFrames; + bool isCriticalAbort; + void* closure; +}; + +static _Unwind_Reason_Code +unwind_callback(struct _Unwind_Context* context, void* closure) +{ + unwind_info* info = static_cast<unwind_info*>(closure); + void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context)); + // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. + if (IsCriticalAddress(pc)) { + info->isCriticalAbort = true; + // We just want to stop the walk, so any error code will do. Using + // _URC_NORMAL_STOP would probably be the most accurate, but it is not + // defined on Android for ARM. + return _URC_FOREIGN_EXCEPTION_CAUGHT; + } + if (--info->skip < 0) { + info->numFrames++; + (*info->callback)(info->numFrames, pc, nullptr, info->closure); + if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { + // Again, any error code that stops the walk will do. + return _URC_FOREIGN_EXCEPTION_CAUGHT; + } + } + return _URC_NO_REASON; +} + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + MOZ_ASSERT(!aThread); + MOZ_ASSERT(!aPlatformData); + StackWalkInitCriticalAddress(); + unwind_info info; + info.callback = aCallback; + info.skip = aSkipFrames + 1; + info.maxFrames = aMaxFrames; + info.numFrames = 0; + info.isCriticalAbort = false; + info.closure = aClosure; + + (void)_Unwind_Backtrace(unwind_callback, &info); + + // We ignore the return value from _Unwind_Backtrace and instead determine + // the outcome from |info|. There are two main reasons for this: + // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns + // _URC_FAILURE. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. + // - If aMaxFrames != 0, we want to stop early, and the only way to do that + // is to make unwind_callback return something other than _URC_NO_REASON, + // which causes _Unwind_Backtrace to return a non-success code. + if (info.isCriticalAbort) { + return false; + } + return info.numFrames != 0; +} + +#endif + +bool MFBT_API +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) +{ + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + + Dl_info info; + int ok = dladdr(aPC, &info); + if (!ok) { + return true; + } + + strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library)); + aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; + aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; + + const char* symbol = info.dli_sname; + if (!symbol || symbol[0] == '\0') { + return true; + } + + DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); + + if (aDetails->function[0] == '\0') { + // Just use the mangled symbol if demangling failed. + strncpy(aDetails->function, symbol, sizeof(aDetails->function)); + aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; + } + + aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; + return true; +} + +#else // unsupported platform. + +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData) +{ + MOZ_ASSERT(!aThread); + MOZ_ASSERT(!aPlatformData); + return false; +} + +MFBT_API bool +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) +{ + aDetails->library[0] = '\0'; + aDetails->loffset = 0; + aDetails->filename[0] = '\0'; + aDetails->lineno = 0; + aDetails->function[0] = '\0'; + aDetails->foffset = 0; + return false; +} + +#endif + +#if defined(XP_WIN) || defined (XP_MACOSX) || defined (XP_LINUX) +namespace mozilla { +bool +FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, void** bp, + void* aStackEnd) +{ + // Stack walking code courtesy Kipp's "leaky". + + int32_t skip = aSkipFrames; + uint32_t numFrames = 0; + while (bp) { + void** next = (void**)*bp; + // bp may not be a frame pointer on i386 if code was compiled with + // -fomit-frame-pointer, so do some sanity checks. + // (bp should be a frame pointer on ppc(64) but checking anyway may help + // a little if the stack has been corrupted.) + // We don't need to check against the begining of the stack because + // we can assume that bp > sp + if (next <= bp || + next > aStackEnd || + (uintptr_t(next) & 3)) { + break; + } +#if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) + // ppc mac or powerpc64 linux + void* pc = *(bp + 2); + bp += 3; +#else // i386 or powerpc32 linux + void* pc = *(bp + 1); + bp += 2; +#endif + if (IsCriticalAddress(pc)) { + return false; + } + if (--skip < 0) { + // Assume that the SP points to the BP of the function + // it called. We can't know the exact location of the SP + // but this should be sufficient for our use the SP + // to order elements on the stack. + numFrames++; + (*aCallback)(numFrames, pc, bp, aClosure); + if (aMaxFrames != 0 && numFrames == aMaxFrames) { + break; + } + } + bp = next; + } + return numFrames != 0; +} +} // namespace mozilla + +#else + +namespace mozilla { +MFBT_API bool +FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + void* aClosure, void** aBp) +{ + return false; +} +} + +#endif + +MFBT_API void +MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, void* aPC, + const MozCodeAddressDetails* aDetails) +{ + MozFormatCodeAddress(aBuffer, aBufferSize, + aFrameNumber, aPC, aDetails->function, + aDetails->library, aDetails->loffset, + aDetails->filename, aDetails->lineno); +} + +MFBT_API void +MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, + const void* aPC, const char* aFunction, + const char* aLibrary, ptrdiff_t aLOffset, + const char* aFileName, uint32_t aLineNo) +{ + const char* function = aFunction && aFunction[0] ? aFunction : "???"; + if (aFileName && aFileName[0]) { + // We have a filename and (presumably) a line number. Use them. + snprintf(aBuffer, aBufferSize, + "#%02u: %s (%s:%u)", + aFrameNumber, function, aFileName, aLineNo); + } else if (aLibrary && aLibrary[0]) { + // We have no filename, but we do have a library name. Use it and the + // library offset, and print them in a way that scripts like + // fix_{linux,macosx}_stacks.py can easily post-process. + snprintf(aBuffer, aBufferSize, + "#%02u: %s[%s +0x%" PRIxPTR "]", + aFrameNumber, function, aLibrary, static_cast<uintptr_t>(aLOffset)); + } else { + // We have nothing useful to go on. (The format string is split because + // '??)' is a trigraph and causes a warning, sigh.) + snprintf(aBuffer, aBufferSize, + "#%02u: ??? (???:???" ")", + aFrameNumber); + } +} diff --git a/mozglue/misc/StackWalk.h b/mozglue/misc/StackWalk.h new file mode 100644 index 0000000000..534c0bd825 --- /dev/null +++ b/mozglue/misc/StackWalk.h @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* API for getting a stack trace of the C/C++ stack on the current thread */ + +#ifndef mozilla_StackWalk_h +#define mozilla_StackWalk_h + +/* WARNING: This file is intended to be included from C or C++ files. */ + +#include "mozilla/Types.h" +#include <stdint.h> + +/** + * The callback for MozStackWalk. + * + * @param aFrameNumber The frame number (starts at 1, not 0). + * @param aPC The program counter value. + * @param aSP The best approximation possible of what the stack + * pointer will be pointing to when the execution returns + * to executing that at aPC. If no approximation can + * be made it will be nullptr. + * @param aClosure Extra data passed in via MozStackWalk(). + */ +typedef void +(*MozWalkStackCallback)(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure); + +/** + * Call aCallback for the C/C++ stack frames on the current thread, from + * the caller of MozStackWalk to main (or above). + * + * @param aCallback Callback function, called once per frame. + * @param aSkipFrames Number of initial frames to skip. 0 means that + * the first callback will be for the caller of + * MozStackWalk. + * @param aMaxFrames Maximum number of frames to trace. 0 means no limit. + * @param aClosure Caller-supplied data passed through to aCallback. + * @param aThread The thread for which the stack is to be retrieved. + * Passing null causes us to walk the stack of the + * current thread. On Windows, this is a thread HANDLE. + * It is currently not supported on any other platform. + * @param aPlatformData Platform specific data that can help in walking the + * stack, this should be nullptr unless you really know + * what you're doing! This needs to be a pointer to a + * CONTEXT on Windows and should not be passed on other + * platforms. + * + * May skip some stack frames due to compiler optimizations or code + * generation. + * + */ +MFBT_API bool +MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, uintptr_t aThread, + void* aPlatformData); + +typedef struct +{ + /* + * The name of the shared library or executable containing an + * address and the address's offset within that library, or empty + * string and zero if unknown. + */ + char library[256]; + ptrdiff_t loffset; + /* + * The name of the file name and line number of the code + * corresponding to the address, or empty string and zero if + * unknown. + */ + char filename[256]; + unsigned long lineno; + /* + * The name of the function containing an address and the address's + * offset within that function, or empty string and zero if unknown. + */ + char function[256]; + ptrdiff_t foffset; +} MozCodeAddressDetails; + +/** + * For a given pointer to code, fill in the pieces of information used + * when printing a stack trace. + * + * @param aPC The code address. + * @param aDetails A structure to be filled in with the result. + */ +MFBT_API bool +MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails); + +/** + * Format the information about a code address in a format suitable for + * stack traces on the current platform. When available, this string + * should contain the function name, source file, and line number. When + * these are not available, library and offset should be reported, if + * possible. + * + * Note that this output is parsed by several scripts including the fix*.py and + * make-tree.pl scripts in tools/rb/. It should only be change with care, and + * in conjunction with those scripts. + * + * @param aBuffer A string to be filled in with the description. + * The string will always be null-terminated. + * @param aBufferSize The size, in bytes, of aBuffer, including + * room for the terminating null. If the information + * to be printed would be larger than aBuffer, it + * will be truncated so that aBuffer[aBufferSize-1] + * is the terminating null. + * @param aFrameNumber The frame number. + * @param aPC The code address. + * @param aFunction The function name. Possibly null or the empty string. + * @param aLibrary The library name. Possibly null or the empty string. + * @param aLOffset The library offset. + * @param aFileName The filename. Possibly null or the empty string. + * @param aLineNo The line number. Possibly zero. + */ +MFBT_API void +MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, + const void* aPC, const char* aFunction, + const char* aLibrary, ptrdiff_t aLOffset, + const char* aFileName, uint32_t aLineNo); + +/** + * Format the information about a code address in the same fashion as + * MozFormatCodeAddress. + * + * @param aBuffer A string to be filled in with the description. + * The string will always be null-terminated. + * @param aBufferSize The size, in bytes, of aBuffer, including + * room for the terminating null. If the information + * to be printed would be larger than aBuffer, it + * will be truncated so that aBuffer[aBufferSize-1] + * is the terminating null. + * @param aFrameNumber The frame number. + * @param aPC The code address. + * @param aDetails The value filled in by MozDescribeCodeAddress(aPC). + */ +MFBT_API void +MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, + uint32_t aFrameNumber, void* aPC, + const MozCodeAddressDetails* aDetails); + +namespace mozilla { + +MFBT_API bool +FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, + uint32_t aMaxFrames, void* aClosure, void** aBp, + void* aStackEnd); + +} // namespace mozilla + +/** + * Initialize the critical sections for this platform so that we can + * abort stack walks when needed. + */ +MFBT_API void +StackWalkInitCriticalAddress(void); + +#endif diff --git a/mozglue/misc/StackWalk_windows.h b/mozglue/misc/StackWalk_windows.h new file mode 100644 index 0000000000..5ffdd20683 --- /dev/null +++ b/mozglue/misc/StackWalk_windows.h @@ -0,0 +1,21 @@ +#ifndef mozilla_StackWalk_windows_h +#define mozilla_StackWalk_windows_h + +#include "mozilla/Types.h" + +/** + * Allow stack walkers to work around the egregious win64 dynamic lookup table + * list API by locking around SuspendThread to avoid deadlock. + * + * See comment in StackWalk.cpp + */ +MFBT_API void +AcquireStackWalkWorkaroundLock(); + +MFBT_API bool +TryAcquireStackWalkWorkaroundLock(); + +MFBT_API void +ReleaseStackWalkWorkaroundLock(); + +#endif // mozilla_StackWalk_windows_h diff --git a/mozglue/misc/TimeStamp.cpp b/mozglue/misc/TimeStamp.cpp new file mode 100644 index 0000000000..932b75c995 --- /dev/null +++ b/mozglue/misc/TimeStamp.cpp @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Implementation of the OS-independent methods of the TimeStamp class + */ + +#include "mozilla/TimeStamp.h" +#include <stdio.h> +#include <string.h> + +namespace mozilla { + +/** + * Wrapper class used to initialize static data used by the TimeStamp class + */ +struct TimeStampInitialization +{ + /** + * First timestamp taken when the class static initializers are run. This + * timestamp is used to sanitize timestamps coming from different sources. + */ + TimeStamp mFirstTimeStamp; + + /** + * Timestamp representing the time when the process was created. This field + * is populated lazily the first time this information is required and is + * replaced every time the process is restarted. + */ + TimeStamp mProcessCreation; + + TimeStampInitialization() + { + TimeStamp::Startup(); + mFirstTimeStamp = TimeStamp::Now(); + }; + + ~TimeStampInitialization() + { + TimeStamp::Shutdown(); + }; +}; + +static TimeStampInitialization sInitOnce; + +MFBT_API TimeStamp +TimeStamp::ProcessCreation(bool& aIsInconsistent) +{ + aIsInconsistent = false; + + if (sInitOnce.mProcessCreation.IsNull()) { + char* mozAppRestart = getenv("MOZ_APP_RESTART"); + TimeStamp ts; + + /* When calling PR_SetEnv() with an empty value the existing variable may + * be unset or set to the empty string depending on the underlying platform + * thus we have to check if the variable is present and not empty. */ + if (mozAppRestart && (strcmp(mozAppRestart, "") != 0)) { + /* Firefox was restarted, use the first time-stamp we've taken as the new + * process startup time. */ + ts = sInitOnce.mFirstTimeStamp; + } else { + TimeStamp now = Now(); + uint64_t uptime = ComputeProcessUptime(); + + ts = now - TimeDuration::FromMicroseconds(uptime); + + if ((ts > sInitOnce.mFirstTimeStamp) || (uptime == 0)) { + /* If the process creation timestamp was inconsistent replace it with + * the first one instead and notify that a telemetry error was + * detected. */ + aIsInconsistent = true; + ts = sInitOnce.mFirstTimeStamp; + } + } + + sInitOnce.mProcessCreation = ts; + } + + return sInitOnce.mProcessCreation; +} + +void +TimeStamp::RecordProcessRestart() +{ + sInitOnce.mProcessCreation = TimeStamp(); +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h new file mode 100644 index 0000000000..a1a0eb3607 --- /dev/null +++ b/mozglue/misc/TimeStamp.h @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimeStamp_h +#define mozilla_TimeStamp_h + +#include <stdint.h> +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/Types.h" + +namespace IPC { +template<typename T> struct ParamTraits; +} // namespace IPC + +#ifdef XP_WIN +// defines TimeStampValue as a complex value keeping both +// GetTickCount and QueryPerformanceCounter values +#include "TimeStamp_windows.h" +#endif + +namespace mozilla { + +#ifndef XP_WIN +typedef uint64_t TimeStampValue; +#endif + +class TimeStamp; + +/** + * Platform-specific implementation details of BaseTimeDuration. + */ +class BaseTimeDurationPlatformUtils +{ +public: + static MFBT_API double ToSeconds(int64_t aTicks); + static MFBT_API double ToSecondsSigDigits(int64_t aTicks); + static MFBT_API int64_t TicksFromMilliseconds(double aMilliseconds); + static MFBT_API int64_t ResolutionInTicks(); +}; + +/** + * Instances of this class represent the length of an interval of time. + * Negative durations are allowed, meaning the end is before the start. + * + * Internally the duration is stored as a int64_t in units of + * PR_TicksPerSecond() when building with NSPR interval timers, or a + * system-dependent unit when building with system clocks. The + * system-dependent unit must be constant, otherwise the semantics of + * this class would be broken. + * + * The ValueCalculator template parameter determines how arithmetic + * operations are performed on the integer count of ticks (mValue). + */ +template <typename ValueCalculator> +class BaseTimeDuration +{ +public: + // The default duration is 0. + constexpr BaseTimeDuration() : mValue(0) {} + // Allow construction using '0' as the initial value, for readability, + // but no other numbers (so we don't have any implicit unit conversions). + struct _SomethingVeryRandomHere; + MOZ_IMPLICIT BaseTimeDuration(_SomethingVeryRandomHere* aZero) : mValue(0) + { + MOZ_ASSERT(!aZero, "Who's playing funny games here?"); + } + // Default copy-constructor and assignment are OK + + // Converting copy-constructor and assignment operator + template <typename E> + explicit BaseTimeDuration(const BaseTimeDuration<E>& aOther) + : mValue(aOther.mValue) + { } + + template <typename E> + BaseTimeDuration& operator=(const BaseTimeDuration<E>& aOther) + { + mValue = aOther.mValue; + return *this; + } + + double ToSeconds() const + { + if (mValue == INT64_MAX) { + return PositiveInfinity<double>(); + } + if (mValue == INT64_MIN) { + return NegativeInfinity<double>(); + } + return BaseTimeDurationPlatformUtils::ToSeconds(mValue); + } + // Return a duration value that includes digits of time we think to + // be significant. This method should be used when displaying a + // time to humans. + double ToSecondsSigDigits() const + { + if (mValue == INT64_MAX) { + return PositiveInfinity<double>(); + } + if (mValue == INT64_MIN) { + return NegativeInfinity<double>(); + } + return BaseTimeDurationPlatformUtils::ToSecondsSigDigits(mValue); + } + double ToMilliseconds() const { return ToSeconds() * 1000.0; } + double ToMicroseconds() const { return ToMilliseconds() * 1000.0; } + + // Using a double here is safe enough; with 53 bits we can represent + // durations up to over 280,000 years exactly. If the units of + // mValue do not allow us to represent durations of that length, + // long durations are clamped to the max/min representable value + // instead of overflowing. + static inline BaseTimeDuration FromSeconds(double aSeconds) + { + return FromMilliseconds(aSeconds * 1000.0); + } + static BaseTimeDuration FromMilliseconds(double aMilliseconds) + { + if (aMilliseconds == PositiveInfinity<double>()) { + return Forever(); + } + if (aMilliseconds == NegativeInfinity<double>()) { + return FromTicks(INT64_MIN); + } + return FromTicks( + BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aMilliseconds)); + } + static inline BaseTimeDuration FromMicroseconds(double aMicroseconds) + { + return FromMilliseconds(aMicroseconds / 1000.0); + } + + static BaseTimeDuration Forever() + { + return FromTicks(INT64_MAX); + } + + BaseTimeDuration operator+(const BaseTimeDuration& aOther) const + { + return FromTicks(ValueCalculator::Add(mValue, aOther.mValue)); + } + BaseTimeDuration operator-(const BaseTimeDuration& aOther) const + { + return FromTicks(ValueCalculator::Subtract(mValue, aOther.mValue)); + } + BaseTimeDuration& operator+=(const BaseTimeDuration& aOther) + { + mValue = ValueCalculator::Add(mValue, aOther.mValue); + return *this; + } + BaseTimeDuration& operator-=(const BaseTimeDuration& aOther) + { + mValue = ValueCalculator::Subtract(mValue, aOther.mValue); + return *this; + } + BaseTimeDuration operator-() const + { + // We don't just use FromTicks(ValueCalculator::Subtract(0, mValue)) + // since that won't give the correct result for -TimeDuration::Forever(). + int64_t ticks; + if (mValue == INT64_MAX) { + ticks = INT64_MIN; + } else if (mValue == INT64_MIN) { + ticks = INT64_MAX; + } else { + ticks = -mValue; + } + + return FromTicks(ticks); + } + +private: + // Block double multiplier (slower, imprecise if long duration) - Bug 853398. + // If required, use MultDouble explicitly and with care. + BaseTimeDuration operator*(const double aMultiplier) const = delete; + + // Block double divisor (for the same reason, and because dividing by + // fractional values would otherwise invoke the int64_t variant, and rounding + // the passed argument can then cause divide-by-zero) - Bug 1147491. + BaseTimeDuration operator/(const double aDivisor) const = delete; + +public: + BaseTimeDuration MultDouble(double aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const int32_t aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const uint32_t aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const int64_t aMultiplier) const + { + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator*(const uint64_t aMultiplier) const + { + if (aMultiplier > INT64_MAX) { + return Forever(); + } + return FromTicks(ValueCalculator::Multiply(mValue, aMultiplier)); + } + BaseTimeDuration operator/(const int64_t aDivisor) const + { + MOZ_ASSERT(aDivisor != 0, "Division by zero"); + return FromTicks(ValueCalculator::Divide(mValue, aDivisor)); + } + double operator/(const BaseTimeDuration& aOther) const + { +#ifndef MOZ_B2G + // Bug 1066388 - This fails on B2G ICS Emulator + MOZ_ASSERT(aOther.mValue != 0, "Division by zero"); +#endif + return ValueCalculator::DivideDouble(mValue, aOther.mValue); + } + BaseTimeDuration operator%(const BaseTimeDuration& aOther) const + { + MOZ_ASSERT(aOther.mValue != 0, "Division by zero"); + return FromTicks(ValueCalculator::Modulo(mValue, aOther.mValue)); + } + + template<typename E> + bool operator<(const BaseTimeDuration<E>& aOther) const + { + return mValue < aOther.mValue; + } + template<typename E> + bool operator<=(const BaseTimeDuration<E>& aOther) const + { + return mValue <= aOther.mValue; + } + template<typename E> + bool operator>=(const BaseTimeDuration<E>& aOther) const + { + return mValue >= aOther.mValue; + } + template<typename E> + bool operator>(const BaseTimeDuration<E>& aOther) const + { + return mValue > aOther.mValue; + } + template<typename E> + bool operator==(const BaseTimeDuration<E>& aOther) const + { + return mValue == aOther.mValue; + } + template<typename E> + bool operator!=(const BaseTimeDuration<E>& aOther) const + { + return mValue != aOther.mValue; + } + bool IsZero() const + { + return mValue == 0; + } + explicit operator bool() const + { + return mValue != 0; + } + + // Return a best guess at the system's current timing resolution, + // which might be variable. BaseTimeDurations below this order of + // magnitude are meaningless, and those at the same order of + // magnitude or just above are suspect. + static BaseTimeDuration Resolution() { + return FromTicks(BaseTimeDurationPlatformUtils::ResolutionInTicks()); + } + + // We could define additional operators here: + // -- convert to/from other time units + // -- scale duration by a float + // but let's do that on demand. + // Comparing durations for equality will only lead to bugs on + // platforms with high-resolution timers. + +private: + friend class TimeStamp; + friend struct IPC::ParamTraits<mozilla::BaseTimeDuration<ValueCalculator>>; + template <typename> + friend class BaseTimeDuration; + + static BaseTimeDuration FromTicks(int64_t aTicks) + { + BaseTimeDuration t; + t.mValue = aTicks; + return t; + } + + static BaseTimeDuration FromTicks(double aTicks) + { + // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX)) + // overflows and gives INT64_MIN. + if (aTicks >= double(INT64_MAX)) { + return FromTicks(INT64_MAX); + } + + // This MUST be a <= test. + if (aTicks <= double(INT64_MIN)) { + return FromTicks(INT64_MIN); + } + + return FromTicks(int64_t(aTicks)); + } + + // Duration, result is implementation-specific difference of two TimeStamps + int64_t mValue; +}; + +/** + * Perform arithmetic operations on the value of a BaseTimeDuration without + * doing strict checks on the range of values. + */ +class TimeDurationValueCalculator +{ +public: + static int64_t Add(int64_t aA, int64_t aB) { return aA + aB; } + static int64_t Subtract(int64_t aA, int64_t aB) { return aA - aB; } + + template <typename T> + static int64_t Multiply(int64_t aA, T aB) + { + static_assert(IsIntegral<T>::value, + "Using integer multiplication routine with non-integer type." + " Further specialization required"); + return aA * static_cast<int64_t>(aB); + } + + static int64_t Divide(int64_t aA, int64_t aB) { return aA / aB; } + static double DivideDouble(int64_t aA, int64_t aB) + { + return static_cast<double>(aA) / aB; + } + static int64_t Modulo(int64_t aA, int64_t aB) { return aA % aB; } +}; + +template <> +inline int64_t +TimeDurationValueCalculator::Multiply<double>(int64_t aA, double aB) +{ + return static_cast<int64_t>(aA * aB); +} + +/** + * Specialization of BaseTimeDuration that uses TimeDurationValueCalculator for + * arithmetic on the mValue member. + * + * Use this class for time durations that are *not* expected to hold values of + * Forever (or the negative equivalent) or when such time duration are *not* + * expected to be used in arithmetic operations. + */ +typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; + +/** + * Instances of this class represent moments in time, or a special + * "null" moment. We do not use the non-monotonic system clock or + * local time, since they can be reset, causing apparent backward + * travel in time, which can confuse algorithms. Instead we measure + * elapsed time according to the system. This time can never go + * backwards (i.e. it never wraps around, at least not in less than + * five million years of system elapsed time). It might not advance + * while the system is sleeping. If TimeStamp::SetNow() is not called + * at all for hours or days, we might not notice the passage of some + * of that time. + * + * We deliberately do not expose a way to convert TimeStamps to some + * particular unit. All you can do is compute a difference between two + * TimeStamps to get a TimeDuration. You can also add a TimeDuration + * to a TimeStamp to get a new TimeStamp. You can't do something + * meaningless like add two TimeStamps. + * + * Internally this is implemented as either a wrapper around + * - high-resolution, monotonic, system clocks if they exist on this + * platform + * - PRIntervalTime otherwise. We detect wraparounds of + * PRIntervalTime and work around them. + * + * This class is similar to C++11's time_point, however it is + * explicitly nullable and provides an IsNull() method. time_point + * is initialized to the clock's epoch and provides a + * time_since_epoch() method that functions similiarly. i.e. + * t.IsNull() is equivalent to t.time_since_epoch() == decltype(t)::duration::zero(); + */ +class TimeStamp +{ +public: + /** + * Initialize to the "null" moment + */ + constexpr TimeStamp() : mValue(0) {} + // Default copy-constructor and assignment are OK + + /** + * The system timestamps are the same as the TimeStamp + * retrieved by mozilla::TimeStamp. Since we need this for + * vsync timestamps, we enable the creation of mozilla::TimeStamps + * on platforms that support vsync aligned refresh drivers / compositors + * Verified true as of Jan 31, 2015: B2G and OS X + * False on Windows 7 + * UNTESTED ON OTHER PLATFORMS + */ +#if defined(MOZ_WIDGET_GONK) || defined(XP_DARWIN) + static TimeStamp FromSystemTime(int64_t aSystemTime) + { + static_assert(sizeof(aSystemTime) == sizeof(TimeStampValue), + "System timestamp should be same units as TimeStampValue"); + return TimeStamp(aSystemTime); + } +#endif + + /** + * Return true if this is the "null" moment + */ + bool IsNull() const { return mValue == 0; } + + /** + * Return true if this is not the "null" moment, may be used in tests, e.g.: + * |if (timestamp) { ... }| + */ + explicit operator bool() const + { + return mValue != 0; + } + + /** + * Return a timestamp reflecting the current elapsed system time. This + * is monotonically increasing (i.e., does not decrease) over the + * lifetime of this process' XPCOM session. + * + * Now() is trying to ensure the best possible precision on each platform, + * at least one millisecond. + * + * NowLoRes() has been introduced to workaround performance problems of + * QueryPerformanceCounter on the Windows platform. NowLoRes() is giving + * lower precision, usually 15.6 ms, but with very good performance benefit. + * Use it for measurements of longer times, like >200ms timeouts. + */ + static TimeStamp Now() { return Now(true); } + static TimeStamp NowLoRes() { return Now(false); } + + /** + * Return a timestamp representing the time when the current process was + * created which will be comparable with other timestamps taken with this + * class. If the actual process creation time is detected to be inconsistent + * the @a aIsInconsistent parameter will be set to true, the returned + * timestamp however will still be valid though inaccurate. + * + * @param aIsInconsistent Set to true if an inconsistency was detected in the + * process creation time + * @returns A timestamp representing the time when the process was created, + * this timestamp is always valid even when errors are reported + */ + static MFBT_API TimeStamp ProcessCreation(bool& aIsInconsistent); + + /** + * Records a process restart. After this call ProcessCreation() will return + * the time when the browser was restarted instead of the actual time when + * the process was created. + */ + static MFBT_API void RecordProcessRestart(); + + /** + * Compute the difference between two timestamps. Both must be non-null. + */ + TimeDuration operator-(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + static_assert(-INT64_MAX > INT64_MIN, "int64_t sanity check"); + int64_t ticks = int64_t(mValue - aOther.mValue); + // Check for overflow. + if (mValue > aOther.mValue) { + if (ticks < 0) { + ticks = INT64_MAX; + } + } else { + if (ticks > 0) { + ticks = INT64_MIN; + } + } + return TimeDuration::FromTicks(ticks); + } + + TimeStamp operator+(const TimeDuration& aOther) const + { + TimeStamp result = *this; + result += aOther; + return result; + } + TimeStamp operator-(const TimeDuration& aOther) const + { + TimeStamp result = *this; + result -= aOther; + return result; + } + TimeStamp& operator+=(const TimeDuration& aOther) + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + TimeStampValue value = mValue + aOther.mValue; + // Check for underflow. + // (We don't check for overflow because it's not obvious what the error + // behavior should be in that case.) + if (aOther.mValue < 0 && value > mValue) { + value = 0; + } + mValue = value; + return *this; + } + TimeStamp& operator-=(const TimeDuration& aOther) + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + TimeStampValue value = mValue - aOther.mValue; + // Check for underflow. + // (We don't check for overflow because it's not obvious what the error + // behavior should be in that case.) + if (aOther.mValue > 0 && value > mValue) { + value = 0; + } + mValue = value; + return *this; + } + + bool operator<(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue < aOther.mValue; + } + bool operator<=(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue <= aOther.mValue; + } + bool operator>=(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue >= aOther.mValue; + } + bool operator>(const TimeStamp& aOther) const + { + MOZ_ASSERT(!IsNull(), "Cannot compute with a null value"); + MOZ_ASSERT(!aOther.IsNull(), "Cannot compute with aOther null value"); + return mValue > aOther.mValue; + } + bool operator==(const TimeStamp& aOther) const + { + return IsNull() + ? aOther.IsNull() + : !aOther.IsNull() && mValue == aOther.mValue; + } + bool operator!=(const TimeStamp& aOther) const + { + return !(*this == aOther); + } + + // Comparing TimeStamps for equality should be discouraged. Adding + // two TimeStamps, or scaling TimeStamps, is nonsense and must never + // be allowed. + + static MFBT_API void Startup(); + static MFBT_API void Shutdown(); + +private: + friend struct IPC::ParamTraits<mozilla::TimeStamp>; + friend void StartupTimelineRecordExternal(int, uint64_t); + + MOZ_IMPLICIT TimeStamp(TimeStampValue aValue) : mValue(aValue) {} + + static MFBT_API TimeStamp Now(bool aHighResolution); + + /** + * Computes the uptime of the current process in microseconds. The result + * is platform-dependent and needs to be checked against existing timestamps + * for consistency. + * + * @returns The number of microseconds since the calling process was started + * or 0 if an error was encountered while computing the uptime + */ + static MFBT_API uint64_t ComputeProcessUptime(); + + /** + * When built with PRIntervalTime, a value of 0 means this instance + * is "null". Otherwise, the low 32 bits represent a PRIntervalTime, + * and the high 32 bits represent a counter of the number of + * rollovers of PRIntervalTime that we've seen. This counter starts + * at 1 to avoid a real time colliding with the "null" value. + * + * PR_INTERVAL_MAX is set at 100,000 ticks per second. So the minimum + * time to wrap around is about 2^64/100000 seconds, i.e. about + * 5,849,424 years. + * + * When using a system clock, a value is system dependent. + */ + TimeStampValue mValue; +}; + +} // namespace mozilla + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/TimeStamp_darwin.cpp b/mozglue/misc/TimeStamp_darwin.cpp new file mode 100644 index 0000000000..f30bc98460 --- /dev/null +++ b/mozglue/misc/TimeStamp_darwin.cpp @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Implement TimeStamp::Now() with mach_absolute_time +// +// The "tick" unit for mach_absolute_time is defined using mach_timebase_info() which +// gives a conversion ratio to nanoseconds. For more information see Apple's QA1398. +// +// This code is inspired by Chromium's time_mac.cc. The biggest +// differences are that we explicitly initialize using +// TimeStamp::Initialize() instead of lazily in Now() and that +// we store the time value in ticks and convert when needed instead +// of storing the time value in nanoseconds. + +#include <mach/mach_time.h> +#include <sys/time.h> +#include <sys/sysctl.h> +#include <time.h> +#include <unistd.h> + +#include "mozilla/TimeStamp.h" + +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; + +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kUsPerSec = 1000000; +static const double kNsPerMsd = 1000000.0; +static const double kNsPerSecd = 1000000000.0; + +static bool gInitialized = false; +static double sNsPerTick; + +static uint64_t +ClockTime() +{ + // mach_absolute_time is it when it comes to ticks on the Mac. Other calls + // with less precision (such as TickCount) just call through to + // mach_absolute_time. + // + // At the time of writing mach_absolute_time returns the number of nanoseconds + // since boot. This won't overflow 64bits for 500+ years so we aren't going + // to worry about that possiblity + return mach_absolute_time(); +} + +static uint64_t +ClockResolutionNs() +{ + uint64_t start = ClockTime(); + uint64_t end = ClockTime(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTime(); + end = ClockTime(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on NSPR's resolution assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + +namespace mozilla { + +double +BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + return (aTicks * sNsPerTick) / kNsPerSecd; +} + +double +BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return (valueSigDigs * sNsPerTick) / kNsPerSecd; +} + +int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + double result = (aMilliseconds * kNsPerMsd) / sNsPerTick; + if (result > INT64_MAX) { + return INT64_MAX; + } else if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +int64_t +BaseTimeDurationPlatformUtils::ResolutionInTicks() +{ + MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); + return static_cast<int64_t>(sResolution); +} + +void +TimeStamp::Startup() +{ + if (gInitialized) { + return; + } + + mach_timebase_info_data_t timebaseInfo; + // Apple's QA1398 suggests that the output from mach_timebase_info + // will not change while a program is running, so it should be safe + // to cache the result. + kern_return_t kr = mach_timebase_info(&timebaseInfo); + if (kr != KERN_SUCCESS) { + MOZ_RELEASE_ASSERT(false, "mach_timebase_info failed"); + } + + sNsPerTick = double(timebaseInfo.numer) / timebaseInfo.denom; + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; + !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10); + + gInitialized = true; + + return; +} + +void +TimeStamp::Shutdown() +{ +} + +TimeStamp +TimeStamp::Now(bool aHighResolution) +{ + return TimeStamp(ClockTime()); +} + +// Computes and returns the process uptime in microseconds. +// Returns 0 if an error was encountered. + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + struct timeval tv; + int rv = gettimeofday(&tv, nullptr); + + if (rv == -1) { + return 0; + } + + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), + }; + u_int mibLen = sizeof(mib) / sizeof(mib[0]); + + struct kinfo_proc proc; + size_t bufferSize = sizeof(proc); + rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); + + if (rv == -1) { + return 0; + } + + uint64_t startTime = + ((uint64_t)proc.kp_proc.p_un.__p_starttime.tv_sec * kUsPerSec) + + proc.kp_proc.p_un.__p_starttime.tv_usec; + uint64_t now = (tv.tv_sec * kUsPerSec) + tv.tv_usec; + + if (startTime > now) { + return 0; + } + + return now - startTime; +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_posix.cpp b/mozglue/misc/TimeStamp_posix.cpp new file mode 100644 index 0000000000..05deddea49 --- /dev/null +++ b/mozglue/misc/TimeStamp_posix.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Implement TimeStamp::Now() with POSIX clocks. +// +// The "tick" unit for POSIX clocks is simply a nanosecond, as this is +// the smallest unit of time representable by struct timespec. That +// doesn't mean that a nanosecond is the resolution of TimeDurations +// obtained with this API; see TimeDuration::Resolution; +// + +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> +#include <string.h> + +#if defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/param.h> +#include <sys/sysctl.h> +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include <sys/user.h> +#endif + +#if defined(__NetBSD__) +#undef KERN_PROC +#define KERN_PROC KERN_PROC2 +#define KINFO_PROC struct kinfo_proc2 +#else +#define KINFO_PROC struct kinfo_proc +#endif + +#if defined(__DragonFly__) +#define KP_START_SEC kp_start.tv_sec +#define KP_START_USEC kp_start.tv_usec +#elif defined(__FreeBSD__) +#define KP_START_SEC ki_start.tv_sec +#define KP_START_USEC ki_start.tv_usec +#else +#define KP_START_SEC p_ustart_sec +#define KP_START_USEC p_ustart_usec +#endif + +#include "mozilla/Sprintf.h" +#include "mozilla/TimeStamp.h" +#include <pthread.h> + +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; + +static const uint16_t kNsPerUs = 1000; +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kNsPerSec = 1000000000; +static const double kNsPerMsd = 1000000.0; +static const double kNsPerSecd = 1000000000.0; + +static uint64_t +TimespecToNs(const struct timespec& aTs) +{ + uint64_t baseNs = uint64_t(aTs.tv_sec) * kNsPerSec; + return baseNs + uint64_t(aTs.tv_nsec); +} + +static uint64_t +ClockTimeNs() +{ + struct timespec ts; + // this can't fail: we know &ts is valid, and TimeStamp::Startup() + // checks that CLOCK_MONOTONIC is supported (and aborts if not) + clock_gettime(CLOCK_MONOTONIC, &ts); + + // tv_sec is defined to be relative to an arbitrary point in time, + // but it would be madness for that point in time to be earlier than + // the Epoch. So we can safely assume that even if time_t is 32 + // bits, tv_sec won't overflow while the browser is open. Revisit + // this argument if we're still building with 32-bit time_t around + // the year 2037. + return TimespecToNs(ts); +} + +static uint64_t +ClockResolutionNs() +{ + // NB: why not rely on clock_getres()? Two reasons: (i) it might + // lie, and (ii) it might return an "ideal" resolution that while + // theoretically true, could never be measured in practice. Since + // clock_gettime() likely involves a system call on your platform, + // the "actual" timing resolution shouldn't be lower than syscall + // overhead. + + uint64_t start = ClockTimeNs(); + uint64_t end = ClockTimeNs(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTimeNs(); + end = ClockTimeNs(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on clock_getres() + struct timespec ts; + if (0 == clock_getres(CLOCK_MONOTONIC, &ts)) { + minres = TimespecToNs(ts); + } + } + + if (0 == minres) { + // clock_getres probably failed. fall back on NSPR's resolution + // assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + +namespace mozilla { + +double +BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) +{ + return double(aTicks) / kNsPerSecd; +} + +double +BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) +{ + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; +} + +int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) +{ + double result = aMilliseconds * kNsPerMsd; + if (result > INT64_MAX) { + return INT64_MAX; + } else if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +int64_t +BaseTimeDurationPlatformUtils::ResolutionInTicks() +{ + return static_cast<int64_t>(sResolution); +} + +static bool gInitialized = false; + +void +TimeStamp::Startup() +{ + if (gInitialized) { + return; + } + + struct timespec dummy; + if (clock_gettime(CLOCK_MONOTONIC, &dummy) != 0) { + MOZ_CRASH("CLOCK_MONOTONIC is absent!"); + } + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; + !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10); + + gInitialized = true; + + return; +} + +void +TimeStamp::Shutdown() +{ +} + +TimeStamp +TimeStamp::Now(bool aHighResolution) +{ + return TimeStamp(ClockTimeNs()); +} + +#if defined(XP_LINUX) || defined(ANDROID) + +// Calculates the amount of jiffies that have elapsed since boot and up to the +// starttime value of a specific process as found in its /proc/*/stat file. +// Returns 0 if an error occurred. + +static uint64_t +JiffiesSinceBoot(const char* aFile) +{ + char stat[512]; + + FILE* f = fopen(aFile, "r"); + if (!f) { + return 0; + } + + int n = fread(&stat, 1, sizeof(stat) - 1, f); + + fclose(f); + + if (n <= 0) { + return 0; + } + + stat[n] = 0; + + long long unsigned startTime = 0; // instead of uint64_t to keep GCC quiet + char* s = strrchr(stat, ')'); + + if (!s) { + return 0; + } + + int rv = sscanf(s + 2, + "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u " + "%*u %*u %*u %*d %*d %*d %*d %*d %*d %llu", + &startTime); + + if (rv != 1 || !startTime) { + return 0; + } + + return startTime; +} + +// Computes the interval that has elapsed between the thread creation and the +// process creation by comparing the starttime fields in the respective +// /proc/*/stat files. The resulting value will be a good approximation of the +// process uptime. This value will be stored at the address pointed by aTime; +// if an error occurred 0 will be stored instead. + +static void* +ComputeProcessUptimeThread(void* aTime) +{ + uint64_t* uptime = static_cast<uint64_t*>(aTime); + long hz = sysconf(_SC_CLK_TCK); + + *uptime = 0; + + if (!hz) { + return nullptr; + } + + char threadStat[40]; + SprintfLiteral(threadStat, "/proc/self/task/%d/stat", (pid_t)syscall(__NR_gettid)); + + uint64_t threadJiffies = JiffiesSinceBoot(threadStat); + uint64_t selfJiffies = JiffiesSinceBoot("/proc/self/stat"); + + if (!threadJiffies || !selfJiffies) { + return nullptr; + } + + *uptime = ((threadJiffies - selfJiffies) * kNsPerSec) / hz; + return nullptr; +} + +// Computes and returns the process uptime in us on Linux & its derivatives. +// Returns 0 if an error was encountered. + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + uint64_t uptime = 0; + pthread_t uptime_pthread; + + if (pthread_create(&uptime_pthread, nullptr, ComputeProcessUptimeThread, &uptime)) { + MOZ_CRASH("Failed to create process uptime thread."); + return 0; + } + + pthread_join(uptime_pthread, NULL); + + return uptime / kNsPerUs; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) \ + || defined(__NetBSD__) || defined(__OpenBSD__) + +// Computes and returns the process uptime in us on various BSD flavors. +// Returns 0 if an error was encountered. + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + struct timespec ts; + int rv = clock_gettime(CLOCK_REALTIME, &ts); + + if (rv == -1) { + return 0; + } + + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +#if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +#endif + }; + u_int mibLen = sizeof(mib) / sizeof(mib[0]); + + KINFO_PROC proc; + size_t bufferSize = sizeof(proc); + rv = sysctl(mib, mibLen, &proc, &bufferSize, nullptr, 0); + + if (rv == -1) { + return 0; + } + + uint64_t startTime = ((uint64_t)proc.KP_START_SEC * kNsPerSec) + + (proc.KP_START_USEC * kNsPerUs); + uint64_t now = ((uint64_t)ts.tv_sec * kNsPerSec) + ts.tv_nsec; + + if (startTime > now) { + return 0; + } + + return (now - startTime) / kNsPerUs; +} + +#else + +uint64_t +TimeStamp::ComputeProcessUptime() +{ + return 0; +} + +#endif + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp new file mode 100644 index 0000000000..cd519affda --- /dev/null +++ b/mozglue/misc/TimeStamp_windows.cpp @@ -0,0 +1,576 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with +// values of GetTickCount(). + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/TimeStamp.h" + +#include <stdio.h> +#include <intrin.h> +#include <windows.h> + +// To enable logging define to your favorite logging API +#define LOG(x) + +class AutoCriticalSection +{ +public: + AutoCriticalSection(LPCRITICAL_SECTION aSection) + : mSection(aSection) + { + ::EnterCriticalSection(mSection); + } + ~AutoCriticalSection() + { + ::LeaveCriticalSection(mSection); + } +private: + LPCRITICAL_SECTION mSection; +}; + +// Estimate of the smallest duration of time we can measure. +static volatile ULONGLONG sResolution; +static volatile ULONGLONG sResolutionSigDigs; +static const double kNsPerSecd = 1000000000.0; +static const LONGLONG kNsPerMillisec = 1000000; + +// ---------------------------------------------------------------------------- +// Global constants +// ---------------------------------------------------------------------------- + +// Tolerance to failures settings. +// +// What is the interval we want to have failure free. +// in [ms] +static const uint32_t kFailureFreeInterval = 5000; +// How many failures we are willing to tolerate in the interval. +static const uint32_t kMaxFailuresPerInterval = 4; +// What is the threshold to treat fluctuations as actual failures. +// in [ms] +static const uint32_t kFailureThreshold = 50; + +// If we are not able to get the value of GTC time increment, use this value +// which is the most usual increment. +static const DWORD kDefaultTimeIncrement = 156001; + +// ---------------------------------------------------------------------------- +// Global variables, not changing at runtime +// ---------------------------------------------------------------------------- + +/** + * The [mt] unit: + * + * Many values are kept in ticks of the Performance Coutner x 1000, + * further just referred as [mt], meaning milli-ticks. + * + * This is needed to preserve maximum precision of the performance frequency + * representation. GetTickCount values in milliseconds are multiplied with + * frequency per second. Therefor we need to multiply QPC value by 1000 to + * have the same units to allow simple arithmentic with both QPC and GTC. + */ + +#define ms2mt(x) ((x) * sFrequencyPerSec) +#define mt2ms(x) ((x) / sFrequencyPerSec) +#define mt2ms_f(x) (double(x) / sFrequencyPerSec) + +// Result of QueryPerformanceFrequency +static LONGLONG sFrequencyPerSec = 0; + +// How much we are tolerant to GTC occasional loose of resoltion. +// This number says how many multiples of the minimal GTC resolution +// detected on the system are acceptable. This number is empirical. +static const LONGLONG kGTCTickLeapTolerance = 4; + +// Base tolerance (more: "inability of detection" range) threshold is calculated +// dynamically, and kept in sGTCResulutionThreshold. +// +// Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - +// (QPC_now - QPC_epoch)) was in [-sGTCResulutionThreshold, sGTCResulutionThreshold] +// interval every time we'd compared two time stamps. +// If not, then we check the overflow behind this basic threshold +// is in kFailureThreshold. If not, we condider it as a QPC failure. If too many +// failures in short time are detected, QPC is considered faulty and disabled. +// +// Kept in [mt] +static LONGLONG sGTCResulutionThreshold; + +// If QPC is found faulty for two stamps in this interval, we engage +// the fault detection algorithm. For duration larger then this limit +// we bypass using durations calculated from QPC when jitter is detected, +// but don't touch the sUseQPC flag. +// +// Value is in [ms]. +static const uint32_t kHardFailureLimit = 2000; +// Conversion to [mt] +static LONGLONG sHardFailureLimit; + +// Conversion of kFailureFreeInterval and kFailureThreshold to [mt] +static LONGLONG sFailureFreeInterval; +static LONGLONG sFailureThreshold; + +// ---------------------------------------------------------------------------- +// Systemm status flags +// ---------------------------------------------------------------------------- + +// Flag for stable TSC that indicates platform where QPC is stable. +static bool sHasStableTSC = false; + +// ---------------------------------------------------------------------------- +// Global state variables, changing at runtime +// ---------------------------------------------------------------------------- + +// Initially true, set to false when QPC is found unstable and never +// returns back to true since that time. +static bool volatile sUseQPC = true; + +// ---------------------------------------------------------------------------- +// Global lock +// ---------------------------------------------------------------------------- + +// Thread spin count before entering the full wait state for sTimeStampLock. +// Inspired by Rob Arnold's work on PRMJ_Now(). +static const DWORD kLockSpinCount = 4096; + +// Common mutex (thanks the relative complexity of the logic, this is better +// then using CMPXCHG8B.) +// It is protecting the globals bellow. +static CRITICAL_SECTION sTimeStampLock; + +// ---------------------------------------------------------------------------- +// Global lock protected variables +// ---------------------------------------------------------------------------- + +// Timestamp in future until QPC must behave correctly. +// Set to now + kFailureFreeInterval on first QPC failure detection. +// Set to now + E * kFailureFreeInterval on following errors, +// where E is number of errors detected during last kFailureFreeInterval +// milliseconds, calculated simply as: +// E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1. +// When E > kMaxFailuresPerInterval -> disable QPC. +// +// Kept in [mt] +static ULONGLONG sFaultIntoleranceCheckpoint = 0; + +// Used only when GetTickCount64 is not available on the platform. +// Last result of GetTickCount call. +// +// Kept in [ms] +static DWORD sLastGTCResult = 0; + +// Higher part of the 64-bit value of MozGetTickCount64, +// incremented atomically. +static DWORD sLastGTCRollover = 0; + +namespace mozilla { + +typedef ULONGLONG (WINAPI* GetTickCount64_t)(); +static GetTickCount64_t sGetTickCount64 = nullptr; + +// Function protecting GetTickCount result from rolling over, +// result is in [ms] +static ULONGLONG WINAPI +MozGetTickCount64() +{ + DWORD GTC = ::GetTickCount(); + + // Cheaper then CMPXCHG8B + AutoCriticalSection lock(&sTimeStampLock); + + // Pull the rollover counter forward only if new value of GTC goes way + // down under the last saved result + if ((sLastGTCResult > GTC) && ((sLastGTCResult - GTC) > (1UL << 30))) { + ++sLastGTCRollover; + } + + sLastGTCResult = GTC; + return ULONGLONG(sLastGTCRollover) << 32 | sLastGTCResult; +} + +// Result is in [mt] +static inline ULONGLONG +PerformanceCounter() +{ + LARGE_INTEGER pc; + ::QueryPerformanceCounter(&pc); + return pc.QuadPart * 1000ULL; +} + +static void +InitThresholds() +{ + DWORD timeAdjustment = 0, timeIncrement = 0; + BOOL timeAdjustmentDisabled; + GetSystemTimeAdjustment(&timeAdjustment, + &timeIncrement, + &timeAdjustmentDisabled); + + LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement)); + + if (!timeIncrement) { + timeIncrement = kDefaultTimeIncrement; + } + + // Ceiling to a millisecond + // Example values: 156001, 210000 + DWORD timeIncrementCeil = timeIncrement; + // Don't want to round up if already rounded, values will be: 156000, 209999 + timeIncrementCeil -= 1; + // Convert to ms, values will be: 15, 20 + timeIncrementCeil /= 10000; + // Round up, values will be: 16, 21 + timeIncrementCeil += 1; + // Convert back to 100ns, values will be: 160000, 210000 + timeIncrementCeil *= 10000; + + // How many milli-ticks has the interval rounded up + LONGLONG ticksPerGetTickCountResolutionCeiling = + (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL; + + // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value. + sGTCResulutionThreshold = + LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling); + + sHardFailureLimit = ms2mt(kHardFailureLimit); + sFailureFreeInterval = ms2mt(kFailureFreeInterval); + sFailureThreshold = ms2mt(kFailureThreshold); +} + +static void +InitResolution() +{ + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + + ULONGLONG minres = ~0ULL; + int loops = 10; + do { + ULONGLONG start = PerformanceCounter(); + ULONGLONG end = PerformanceCounter(); + + ULONGLONG candidate = (end - start); + if (candidate < minres) { + minres = candidate; + } + } while (--loops && minres); + + if (0 == minres) { + minres = 1; + } + + // Converting minres that is in [mt] to nanosecods, multiplicating + // the argument to preserve resolution. + ULONGLONG result = mt2ms(minres * kNsPerMillisec); + if (0 == result) { + result = 1; + } + + sResolution = result; + + // find the number of significant digits in mResolution, for the + // sake of ToSecondsSigDigits() + ULONGLONG sigDigs; + for (sigDigs = 1; + !(sigDigs == result || 10 * sigDigs > result); + sigDigs *= 10); + + sResolutionSigDigs = sigDigs; +} + +// ---------------------------------------------------------------------------- +// TimeStampValue implementation +// ---------------------------------------------------------------------------- +MFBT_API +TimeStampValue::TimeStampValue(ULONGLONG aGTC, ULONGLONG aQPC, bool aHasQPC) + : mGTC(aGTC) + , mQPC(aQPC) + , mHasQPC(aHasQPC) + , mIsNull(false) +{ +} + +MFBT_API TimeStampValue& +TimeStampValue::operator+=(const int64_t aOther) +{ + mGTC += aOther; + mQPC += aOther; + return *this; +} + +MFBT_API TimeStampValue& +TimeStampValue::operator-=(const int64_t aOther) +{ + mGTC -= aOther; + mQPC -= aOther; + return *this; +} + +// If the duration is less then two seconds, perform check of QPC stability +// by comparing both GTC and QPC calculated durations of this and aOther. +MFBT_API uint64_t +TimeStampValue::CheckQPC(const TimeStampValue& aOther) const +{ + uint64_t deltaGTC = mGTC - aOther.mGTC; + + if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC + return deltaGTC; + } + + uint64_t deltaQPC = mQPC - aOther.mQPC; + + if (sHasStableTSC) { // For stable TSC there is no need to check + return deltaQPC; + } + + // Check QPC is sane before using it. + int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC)); + if (diff <= sGTCResulutionThreshold) { + return deltaQPC; + } + + // Treat absolutely for calibration purposes + int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); + int64_t overflow = diff - sGTCResulutionThreshold; + + LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms", + mt2ms(duration), mt2ms_f(overflow))); + + if (overflow <= sFailureThreshold) { // We are in the limit, let go. + return deltaQPC; + } + + // QPC deviates, don't use it, since now this method may only return deltaGTC. + + if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance algorithm. + return deltaGTC; + } + + LOG(("TimeStamp: QPC jittered over failure threshold")); + + if (duration < sHardFailureLimit) { + // Interval between the two time stamps is very short, consider + // QPC as unstable and record a failure. + uint64_t now = ms2mt(sGetTickCount64()); + + AutoCriticalSection lock(&sTimeStampLock); + + if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) { + // There's already been an error in the last fault intollerant interval. + // Time since now to the checkpoint actually holds information on how many + // failures there were in the failure free interval we have defined. + uint64_t failureCount = + (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) / + sFailureFreeInterval; + if (failureCount > kMaxFailuresPerInterval) { + sUseQPC = false; + LOG(("TimeStamp: QPC disabled")); + } else { + // Move the fault intolerance checkpoint more to the future, prolong it + // to reflect the number of detected failures. + ++failureCount; + sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval; + LOG(("TimeStamp: recording %dth QPC failure", failureCount)); + } + } else { + // Setup fault intolerance checkpoint in the future for first detected error. + sFaultIntoleranceCheckpoint = now + sFailureFreeInterval; + LOG(("TimeStamp: recording 1st QPC failure")); + } + } + + return deltaGTC; +} + +MFBT_API uint64_t +TimeStampValue::operator-(const TimeStampValue& aOther) const +{ + if (mIsNull && aOther.mIsNull) { + return uint64_t(0); + } + + return CheckQPC(aOther); +} + +// ---------------------------------------------------------------------------- +// TimeDuration and TimeStamp implementation +// ---------------------------------------------------------------------------- + +MFBT_API double +BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) +{ + // Converting before arithmetic avoids blocked store forward + return double(aTicks) / (double(sFrequencyPerSec) * 1000.0); +} + +MFBT_API double +BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) +{ + // don't report a value < mResolution ... + LONGLONG resolution = sResolution; + LONGLONG resolutionSigDigs = sResolutionSigDigs; + LONGLONG valueSigDigs = resolution * (aTicks / resolution); + // and chop off insignificant digits + valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; +} + +MFBT_API int64_t +BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) +{ + double result = ms2mt(aMilliseconds); + if (result > INT64_MAX) { + return INT64_MAX; + } else if (result < INT64_MIN) { + return INT64_MIN; + } + + return result; +} + +MFBT_API int64_t +BaseTimeDurationPlatformUtils::ResolutionInTicks() +{ + return static_cast<int64_t>(sResolution); +} + +static bool +HasStableTSC() +{ + union + { + int regs[4]; + struct + { + int nIds; + char cpuString[12]; + }; + } cpuInfo; + + __cpuid(cpuInfo.regs, 0); + // Only allow Intel CPUs for now + // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the + // string so that we can compare in one go. + if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", + sizeof(cpuInfo.cpuString))) { + return false; + } + + int regs[4]; + + // detect if the Advanced Power Management feature is supported + __cpuid(regs, 0x80000000); + if (regs[0] < 0x80000007) { + return false; + } + + __cpuid(regs, 0x80000007); + // if bit 8 is set than TSC will run at a constant rate + // in all ACPI P-state, C-states and T-states + return regs[3] & (1 << 8); +} + +static bool gInitialized = false; + +MFBT_API void +TimeStamp::Startup() +{ + if (gInitialized) { + return; + } + + gInitialized = true; + + // Decide which implementation to use for the high-performance timer. + + HMODULE kernelDLL = GetModuleHandleW(L"kernel32.dll"); + sGetTickCount64 = reinterpret_cast<GetTickCount64_t>( + GetProcAddress(kernelDLL, "GetTickCount64")); + if (!sGetTickCount64) { + // If the platform does not support the GetTickCount64 (Windows XP doesn't), + // then use our fallback implementation based on GetTickCount. + sGetTickCount64 = MozGetTickCount64; + } + + InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); + + sHasStableTSC = HasStableTSC(); + LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); + + LARGE_INTEGER freq; + sUseQPC = ::QueryPerformanceFrequency(&freq); + if (!sUseQPC) { + // No Performance Counter. Fall back to use GetTickCount. + InitResolution(); + + LOG(("TimeStamp: using GetTickCount")); + return; + } + + sFrequencyPerSec = freq.QuadPart; + LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); + + InitThresholds(); + InitResolution(); + + return; +} + +MFBT_API void +TimeStamp::Shutdown() +{ + DeleteCriticalSection(&sTimeStampLock); +} + +MFBT_API TimeStamp +TimeStamp::Now(bool aHighResolution) +{ + // sUseQPC is volatile + bool useQPC = (aHighResolution && sUseQPC); + + // Both values are in [mt] units. + ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0); + ULONGLONG GTC = ms2mt(sGetTickCount64()); + return TimeStamp(TimeStampValue(GTC, QPC, useQPC)); +} + +// Computes and returns the process uptime in microseconds. +// Returns 0 if an error was encountered. + +MFBT_API uint64_t +TimeStamp::ComputeProcessUptime() +{ + SYSTEMTIME nowSys; + GetSystemTime(&nowSys); + + FILETIME now; + bool success = SystemTimeToFileTime(&nowSys, &now); + + if (!success) { + return 0; + } + + FILETIME start, foo, bar, baz; + success = GetProcessTimes(GetCurrentProcess(), &start, &foo, &bar, &baz); + + if (!success) { + return 0; + } + + ULARGE_INTEGER startUsec = {{ + start.dwLowDateTime, + start.dwHighDateTime + }}; + ULARGE_INTEGER nowUsec = {{ + now.dwLowDateTime, + now.dwHighDateTime + }}; + + return (nowUsec.QuadPart - startUsec.QuadPart) / 10ULL; +} + +} // namespace mozilla diff --git a/mozglue/misc/TimeStamp_windows.h b/mozglue/misc/TimeStamp_windows.h new file mode 100644 index 0000000000..c87d34efcd --- /dev/null +++ b/mozglue/misc/TimeStamp_windows.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimeStamp_windows_h +#define mozilla_TimeStamp_windows_h + +#include "mozilla/Types.h" + +namespace mozilla { + +class TimeStamp; + +class TimeStampValue +{ + friend struct IPC::ParamTraits<mozilla::TimeStampValue>; + friend class TimeStamp; + friend void StartupTimelineRecordExternal(int, uint64_t); + + // Both QPC and GTC are kept in [mt] units. + uint64_t mGTC; + uint64_t mQPC; + bool mHasQPC; + bool mIsNull; + + MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC); + + MFBT_API uint64_t CheckQPC(const TimeStampValue& aOther) const; + + struct _SomethingVeryRandomHere; + constexpr TimeStampValue(_SomethingVeryRandomHere* aNullValue) + : mGTC(0) + , mQPC(0) + , mHasQPC(false) + , mIsNull(true) + { + } + +public: + MFBT_API uint64_t operator-(const TimeStampValue& aOther) const; + + TimeStampValue operator+(const int64_t aOther) const + { + return TimeStampValue(mGTC + aOther, mQPC + aOther, mHasQPC); + } + TimeStampValue operator-(const int64_t aOther) const + { + return TimeStampValue(mGTC - aOther, mQPC - aOther, mHasQPC); + } + MFBT_API TimeStampValue& operator+=(const int64_t aOther); + MFBT_API TimeStampValue& operator-=(const int64_t aOther); + + bool operator<(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) < 0; + } + bool operator>(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) > 0; + } + bool operator<=(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) <= 0; + } + bool operator>=(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) >= 0; + } + bool operator==(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) == 0; + } + bool operator!=(const TimeStampValue& aOther) const + { + return int64_t(*this - aOther) != 0; + } +}; + +} + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build new file mode 100644 index 0000000000..9c2ef399c9 --- /dev/null +++ b/mozglue/misc/moz.build @@ -0,0 +1,37 @@ +FINAL_LIBRARY = 'mozglue' + +EXPORTS.mozilla += [ + 'StackWalk.h', + 'TimeStamp.h', +] + +if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS.mozilla += [ + 'StackWalk_windows.h', + 'TimeStamp_windows.h', + ] + +SOURCES += [ + 'StackWalk.cpp', + 'TimeStamp.cpp', +] + +OS_LIBS += CONFIG['REALTIME_LIBS'] + +DEFINES['IMPL_MFBT'] = True + +if CONFIG['OS_ARCH'] == 'WINNT': + SOURCES += [ + 'TimeStamp_windows.cpp', + ] + OS_LIBS += ['dbghelp'] +elif CONFIG['HAVE_CLOCK_MONOTONIC']: + SOURCES += [ + 'TimeStamp_posix.cpp', + ] +elif CONFIG['OS_ARCH'] == 'Darwin': + SOURCES += [ + 'TimeStamp_darwin.cpp', + ] +elif CONFIG['COMPILE_ENVIRONMENT']: + error('No TimeStamp implementation on this platform. Build will not succeed') diff --git a/mozglue/moz.build b/mozglue/moz.build new file mode 100644 index 0000000000..a951d64279 --- /dev/null +++ b/mozglue/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +if CONFIG['MOZ_LINKER']: + DIRS += ['linker'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android': + DIRS += ['android'] + +DIRS += [ + 'build', + 'misc', +] + +if not CONFIG['JS_STANDALONE']: + TEST_DIRS += ['tests'] diff --git a/mozglue/tests/ShowSSEConfig.cpp b/mozglue/tests/ShowSSEConfig.cpp new file mode 100644 index 0000000000..be17ffb525 --- /dev/null +++ b/mozglue/tests/ShowSSEConfig.cpp @@ -0,0 +1,123 @@ +/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SSE.h" +#include <stdio.h> + +int main() +{ + printf("CPUID detection present: %s\n", +#ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION + "yes" +#else + "no" +#endif + ); + +#ifdef MOZILLA_COMPILE_WITH_MMX +#define COMPILE_MMX_STRING "Y" +#else +#define COMPILE_MMX_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_MMX +#define PRESUME_MMX_STRING "Y" +#else +#define PRESUME_MMX_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSE +#define COMPILE_SSE_STRING "Y" +#else +#define COMPILE_SSE_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSE +#define PRESUME_SSE_STRING "Y" +#else +#define PRESUME_SSE_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSE2 +#define COMPILE_SSE2_STRING "Y" +#else +#define COMPILE_SSE2_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSE2 +#define PRESUME_SSE2_STRING "Y" +#else +#define PRESUME_SSE2_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSE3 +#define COMPILE_SSE3_STRING "Y" +#else +#define COMPILE_SSE3_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSE3 +#define PRESUME_SSE3_STRING "Y" +#else +#define PRESUME_SSE3_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSSE3 +#define COMPILE_SSSE3_STRING "Y" +#else +#define COMPILE_SSSE3_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSSE3 +#define PRESUME_SSSE3_STRING "Y" +#else +#define PRESUME_SSSE3_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSE4A +#define COMPILE_SSE4A_STRING "Y" +#else +#define COMPILE_SSE4A_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSE4A +#define PRESUME_SSE4A_STRING "Y" +#else +#define PRESUME_SSE4A_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSE4_1 +#define COMPILE_SSE4_1_STRING "Y" +#else +#define COMPILE_SSE4_1_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSE4_1 +#define PRESUME_SSE4_1_STRING "Y" +#else +#define PRESUME_SSE4_1_STRING "-" +#endif + +#ifdef MOZILLA_COMPILE_WITH_SSE4_2 +#define COMPILE_SSE4_2_STRING "Y" +#else +#define COMPILE_SSE4_2_STRING "-" +#endif +#ifdef MOZILLA_PRESUME_SSE4_2 +#define PRESUME_SSE4_2_STRING "Y" +#else +#define PRESUME_SSE4_2_STRING "-" +#endif + + printf("Feature Presume Compile Support Use\n"); +#define SHOW_INFO(featurelc_, featureuc_) \ + printf( "%7s %1s %1s %1s\n", \ + #featurelc_, \ + PRESUME_##featureuc_##_STRING, \ + COMPILE_##featureuc_##_STRING, \ + (mozilla::supports_##featurelc_() ? "Y" : "-")); + SHOW_INFO(mmx, MMX) + SHOW_INFO(sse, SSE) + SHOW_INFO(sse2, SSE2) + SHOW_INFO(sse3, SSE3) + SHOW_INFO(ssse3, SSSE3) + SHOW_INFO(sse4a, SSE4A) + SHOW_INFO(sse4_1, SSE4_1) + SHOW_INFO(sse4_2, SSE4_2) + return 0; +} diff --git a/mozglue/tests/moz.build b/mozglue/tests/moz.build new file mode 100644 index 0000000000..0c8c9c588b --- /dev/null +++ b/mozglue/tests/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DISABLE_STL_WRAPPING = True + +GeckoCppUnitTests([ + 'ShowSSEConfig', +], linkage=None) |