import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

465
mozglue/android/APKOpen.cpp Normal file
View file

@ -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);
}

39
mozglue/android/APKOpen.h Normal file
View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);
}
}

58
mozglue/android/moz.build Normal file
View file

@ -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']

View file

@ -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());
}

View file

@ -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));
}

View file

@ -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_ */

View file

@ -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

View file

@ -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

23
mozglue/build/Makefile.in Normal file
View file

@ -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

206
mozglue/build/SSE.cpp Normal file
View file

@ -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

332
mozglue/build/SSE.h Normal file
View file

@ -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_) */

View file

@ -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;
}

View file

@ -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

View file

@ -0,0 +1,4 @@
{
local:
__aeabi*;
};

147
mozglue/build/arm.cpp Normal file
View file

@ -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

146
mozglue/build/arm.h Normal file
View file

@ -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_) */

61
mozglue/build/cpuacct.c Normal file
View file

@ -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;
}

41
mozglue/build/cpuacct.h Normal file
View file

@ -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 */

6
mozglue/build/dummy.cpp Normal file
View file

@ -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) {
}

47
mozglue/build/mips.cpp Normal file
View file

@ -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

29
mozglue/build/mips.h Normal file
View file

@ -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_) */

108
mozglue/build/moz.build Normal file
View file

@ -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

View file

@ -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

View file

@ -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

215
mozglue/linker/BaseElf.cpp Normal file
View file

@ -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;
}

141
mozglue/linker/BaseElf.h Normal file
View file

@ -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 */

View file

@ -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());
}

159
mozglue/linker/CustomElf.h Normal file
View file

@ -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 */

1310
mozglue/linker/ElfLoader.cpp Normal file

File diff suppressed because it is too large Load diff

674
mozglue/linker/ElfLoader.h Normal file
View file

@ -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 */

241
mozglue/linker/Elfxx.h Normal file
View file

@ -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 */

87
mozglue/linker/Logging.h Normal file
View file

@ -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 */

681
mozglue/linker/Mappable.cpp Normal file
View file

@ -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();
}

267
mozglue/linker/Mappable.h Normal file
View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */

618
mozglue/linker/Utils.h Normal file
View file

@ -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 */

213
mozglue/linker/XZStream.cpp Normal file
View file

@ -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;
}

48
mozglue/linker/XZStream.h Normal file
View file

@ -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

229
mozglue/linker/Zip.cpp Normal file
View file

@ -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());
}
}

500
mozglue/linker/Zip.h Normal file
View file

@ -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 */

16
mozglue/linker/dladdr.h Normal file
View file

@ -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

54
mozglue/linker/moz.build Normal file
View file

@ -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',
]

593
mozglue/linker/szip.cpp Normal file
View file

@ -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;
}

View file

@ -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;
}

View file

@ -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']

Binary file not shown.

View file

@ -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()

Binary file not shown.

1204
mozglue/misc/StackWalk.cpp Normal file

File diff suppressed because it is too large Load diff

163
mozglue/misc/StackWalk.h Normal file
View file

@ -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

View file

@ -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

View file

@ -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

609
mozglue/misc/TimeStamp.h Normal file
View file

@ -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 */

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 */

37
mozglue/misc/moz.build Normal file
View file

@ -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')

19
mozglue/moz.build Normal file
View file

@ -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']

View file

@ -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;
}

11
mozglue/tests/moz.build Normal file
View file

@ -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)