#include "Dalvik.h"
#include "JniInternal.h"
+#include <sys/mman.h>
#include <zlib.h>
/*
* Abort if we are configured to bail out on JNI warnings.
*/
static void abortMaybe() {
- JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
- if (vm->warnError) {
+ if (!gDvmJni.warnOnly) {
dvmDumpThread(dvmThreadSelf(), false);
dvmAbort();
}
const void* originalPtr;
/* find the GuardedCopy given the pointer into the "live" data */
- static inline GuardedCopy* fromData(const void* dataBuf) {
- u1* fullBuf = ((u1*) dataBuf) - kGuardLen / 2;
- return reinterpret_cast<GuardedCopy*>(fullBuf);
+ static inline const GuardedCopy* fromData(const void* dataBuf) {
+ return reinterpret_cast<const GuardedCopy*>(actualBuffer(dataBuf));
}
/*
* We use a 16-bit pattern to make a rogue memset less likely to elude us.
*/
static void* create(const void* buf, size_t len, bool modOkay) {
- size_t newLen = (len + kGuardLen + 1) & ~0x01;
- u1* newBuf = (u1*)malloc(newLen);
- if (newBuf == NULL) {
- LOGE("GuardedCopy::create failed on alloc of %d bytes", newLen);
- dvmAbort();
- }
+ size_t newLen = actualLength(len);
+ u1* newBuf = debugAlloc(newLen);
/* fill it in with a pattern */
u2* pat = (u2*) newBuf;
* Free up the guard buffer, scrub it, and return the original pointer.
*/
static void* destroy(void* dataBuf) {
- u1* fullBuf = ((u1*) dataBuf) - kGuardLen / 2;
const GuardedCopy* pExtra = GuardedCopy::fromData(dataBuf);
void* originalPtr = (void*) pExtra->originalPtr;
size_t len = pExtra->originalLen;
- memset(dataBuf, 0xdd, len);
- free(fullBuf);
+ debugFree(dataBuf, len);
return originalPtr;
}
*/
static bool check(const void* dataBuf, bool modOkay) {
static const u4 kMagicCmp = kGuardMagic;
- const u1* fullBuf = ((const u1*) dataBuf) - kGuardLen / 2;
+ const u1* fullBuf = actualBuffer(dataBuf);
const GuardedCopy* pExtra = GuardedCopy::fromData(dataBuf);
/*
return true;
}
+
+private:
+ static u1* debugAlloc(size_t len) {
+ if (gDvmJni.forceDataUnmap) {
+ void* result = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
+ if (result == MAP_FAILED) {
+ LOGE("GuardedCopy::create mmap(%d) failed: %s", len, strerror(errno));
+ dvmAbort();
+ }
+ return reinterpret_cast<u1*>(result);
+ } else {
+ u1* result = new u1[len];
+ if (result == NULL) {
+ LOGE("GuardedCopy::create malloc(%d) failed: %s", len, strerror(errno));
+ dvmAbort();
+ }
+ return result;
+ }
+ }
+
+ static void debugFree(void* dataBuf, size_t len) {
+ u1* fullBuf = actualBuffer(dataBuf);
+ size_t totalByteCount = actualLength(len);
+ if (gDvmJni.forceDataUnmap) {
+ // TODO: we could mprotect instead, and keep the allocation around for a while.
+ // This would be even more expensive, but it might catch more errors.
+ // if (mprotect(fullBuf, totalByteCount, PROT_NONE) != 0) {
+ // LOGW("mprotect(PROT_NONE) failed: %s\n", strerror(errno));
+ // }
+ if (munmap(fullBuf, totalByteCount) != 0) {
+ LOGW("munmap failed: %s\n", strerror(errno));
+ dvmAbort();
+ }
+ } else {
+ memset(dataBuf, 0xdd, len); // TODO: wipe the underlying buffer, not just the user one?
+ delete[] fullBuf;
+ }
+ }
+
+ static const u1* actualBuffer(const void* dataBuf) {
+ return reinterpret_cast<const u1*>(dataBuf) - kGuardLen / 2;
+ }
+
+ static u1* actualBuffer(void* dataBuf) {
+ return reinterpret_cast<u1*>(dataBuf) - kGuardLen / 2;
+ }
+
+ // Underlying length of a user allocation of 'length' bytes.
+ static size_t actualLength(size_t length) {
+ return (length + kGuardLen + 1) & ~0x01;
+ }
};
/*
ScopedCheck sc(env, kFlag_CritOkay, __FUNCTION__);
sc.checkString(string);
const jchar* result = baseEnv(env)->GetStringChars(env, string, isCopy);
- if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
+ if (gDvmJni.forceDataCopy && result != NULL) {
ScopedJniThreadState ts(env);
StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(env, string);
int byteCount = dvmStringLen(strObj) * 2;
ScopedCheck sc(env, kFlag_Default | kFlag_ExcepOkay, __FUNCTION__);
sc.checkString(string);
sc.checkNonNull(chars);
- if (((JNIEnvExt*)env)->forceDataCopy) {
+ if (gDvmJni.forceDataCopy) {
if (!GuardedCopy::check(chars, false)) {
LOGE("JNI: failed guarded copy check in ReleaseStringChars");
abortMaybe();
ScopedCheck sc(env, kFlag_CritOkay, __FUNCTION__);
sc.checkString(string);
const char* result = baseEnv(env)->GetStringUTFChars(env, string, isCopy);
- if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
+ if (gDvmJni.forceDataCopy && result != NULL) {
result = (const char*) GuardedCopy::create(result, strlen(result) + 1, false);
if (isCopy != NULL) {
*isCopy = JNI_TRUE;
ScopedCheck sc(env, kFlag_ExcepOkay, __FUNCTION__);
sc.checkString(string);
sc.checkNonNull(utf);
- if (((JNIEnvExt*)env)->forceDataCopy) {
+ if (gDvmJni.forceDataCopy) {
if (!GuardedCopy::check(utf, false)) {
LOGE("JNI: failed guarded copy check in ReleaseStringUTFChars");
abortMaybe();
ScopedCheck sc(env, kFlag_Default, __FUNCTION__); \
sc.checkArray(array); \
u4 noCopy = 0; \
- if (((JNIEnvExt*)env)->forceDataCopy && isCopy != NULL) { \
+ if (gDvmJni.forceDataCopy && isCopy != NULL) { \
/* capture this before the base call tramples on it */ \
noCopy = *(u4*) isCopy; \
} \
_ctype* result = baseEnv(env)->Get##_jname##ArrayElements(env, array, isCopy); \
- if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) { \
+ if (gDvmJni.forceDataCopy && result != NULL) { \
if (noCopy == kNoCopyMagic) { \
LOGV("FC: not copying %p %x", array, noCopy); \
} else { \
sc.checkArray(array); \
sc.checkNonNull(elems); \
sc.checkReleaseMode(mode); \
- if (((JNIEnvExt*)env)->forceDataCopy) { \
+ if (gDvmJni.forceDataCopy) { \
if ((uintptr_t)elems == kNoCopyMagic) { \
LOGV("FC: not freeing %p", array); \
elems = NULL; /* base JNI call doesn't currently need */ \
ScopedCheck sc(env, kFlag_CritGet, __FUNCTION__);
sc.checkArray(array);
void* result = baseEnv(env)->GetPrimitiveArrayCritical(env, array, isCopy);
- if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
+ if (gDvmJni.forceDataCopy && result != NULL) {
result = createGuardedPACopy(env, array, isCopy);
}
return result;
sc.checkArray(array);
sc.checkNonNull(carray);
sc.checkReleaseMode(mode);
- if (((JNIEnvExt*)env)->forceDataCopy) {
+ if (gDvmJni.forceDataCopy) {
carray = releaseGuardedPACopy(env, array, carray, mode);
}
baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode);
ScopedCheck sc(env, kFlag_CritGet, __FUNCTION__);
sc.checkString(string);
const jchar* result = baseEnv(env)->GetStringCritical(env, string, isCopy);
- if (((JNIEnvExt*)env)->forceDataCopy && result != NULL) {
+ if (gDvmJni.forceDataCopy && result != NULL) {
ScopedJniThreadState ts(env);
StringObject* strObj = (StringObject*) dvmDecodeIndirectRef(env, string);
int byteCount = dvmStringLen(strObj) * 2;
ScopedCheck sc(env, kFlag_CritRelease | kFlag_ExcepOkay, __FUNCTION__);
sc.checkString(string);
sc.checkNonNull(carray);
- if (((JNIEnvExt*)env)->forceDataCopy) {
+ if (gDvmJni.forceDataCopy) {
if (!GuardedCopy::check(carray, false)) {
LOGE("JNI: failed guarded copy check in ReleaseStringCritical");
abortMaybe();
#endif
+struct DvmJniGlobals {
+ bool useCheckJni;
+ bool warnOnly;
+ bool forceDataCopy;
+ bool forceDataUnmap;
+};
+
+extern struct DvmJniGlobals gDvmJni;
+
#ifdef __cplusplus
}
#endif
/* global state */
struct DvmGlobals gDvm;
+struct DvmJniGlobals gDvmJni;
/* JIT-specific global state */
#if defined(WITH_JIT)
dvmFprintf(stderr, " -Xnoquithandler\n");
dvmFprintf(stderr,
" -Xjnigreflimit:N (must be multiple of 100, >= 200)\n");
- dvmFprintf(stderr, " -Xjniopts:{warnonly,forcecopy}\n");
+ dvmFprintf(stderr, " -Xjniopts:{warnonly,forcecopy,forcecopy-unmap}\n");
dvmFprintf(stderr, " -Xjnitrace:substring (eg NativeClass or nativeMethod)\n");
dvmFprintf(stderr, " -Xdeadlockpredict:{off,warn,err,abort}\n");
dvmFprintf(stderr, " -Xstacktracefile:<filename>\n");
#include "Dalvik.h"
#include "JniInternal.h"
#include "ScopedPthreadMutexLock.h"
+#include "UniquePtr.h"
#include <stdlib.h>
#include <stdarg.h>
/* watch for "excessive" use; not generally appropriate */
if (count >= gDvm.jniGrefLimit) {
- JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
- if (vm->warnError) {
- dvmDumpIndirectRefTable(&gDvm.jniGlobalRefTable,
- "JNI global");
+ if (gDvmJni.warnOnly) {
+ LOGW("Excessive JNI global references (%d)", count);
+ } else {
+ dvmDumpIndirectRefTable(&gDvm.jniGlobalRefTable, "JNI global");
LOGE("Excessive JNI global references (%d)", count);
dvmAbort();
- } else {
- LOGW("Excessive JNI global references (%d)", count);
}
}
}
}
/*
- * Returns "true" if CheckJNI is enabled in the VM.
- */
-static bool dvmIsCheckJNIEnabled() {
- JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
- return vm->useChecked;
-}
-
-/*
* Returns the appropriate JNI bridge for 'method', also taking into account
* the -Xcheck:jni setting.
*/
}
}
- return dvmIsCheckJNIEnabled() ? checkFunc[kind] : stdFunc[kind];
+ return gDvmJni.useCheckJni ? checkFunc[kind] : stdFunc[kind];
}
/*
JNIEnvExt* newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
newEnv->funcTable = &gNativeInterface;
newEnv->vm = vm;
- newEnv->forceDataCopy = vm->forceDataCopy;
if (self != NULL) {
dvmSetJniEnvThreadId((JNIEnv*) newEnv, self);
assert(newEnv->envThreadId != 0);
newEnv->envThreadId = 0x77777775;
newEnv->self = (Thread*) 0x77777779;
}
- if (vm->useChecked) {
+ if (gDvmJni.useCheckJni) {
dvmUseCheckedJniEnv(newEnv);
}
JavaVMExt* extVm = extEnv->vm;
assert(extVm != NULL);
- if (!extVm->useChecked) {
+ if (!gDvmJni.useCheckJni) {
LOGD("Late-enabling CheckJNI");
dvmUseCheckedJniVm(extVm);
- extVm->useChecked = true;
dvmUseCheckedJniEnv(extEnv);
-
- /* currently no way to pick up jniopts features */
} else {
LOGD("Not late-enabling CheckJNI (already on)");
}
*/
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
- JNIEnvExt* pEnv = NULL;
- JavaVMExt* pVM = NULL;
- const char** argv;
- int argc = 0;
- int i, curOpt;
- int result = JNI_ERR;
- bool checkJni = false;
- bool warnError = true;
- bool forceDataCopy = false;
-
if (args->version < JNI_VERSION_1_2) {
return JNI_EVERSION;
}
/*
* Set up structures for JNIEnv and VM.
*/
- //pEnv = (JNIEnvExt*) malloc(sizeof(JNIEnvExt));
- pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
-
- //memset(pEnv, 0, sizeof(JNIEnvExt));
- //pEnv->funcTable = &gNativeInterface;
- //pEnv->vm = pVM;
+ JavaVMExt* pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
memset(pVM, 0, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
- pVM->envList = pEnv;
+ pVM->envList = NULL;
dvmInitMutex(&pVM->envListLock);
- argv = (const char**) malloc(sizeof(char*) * (args->nOptions));
- memset(argv, 0, sizeof(char*) * (args->nOptions));
-
- curOpt = 0;
+ UniquePtr<const char*[]> argv(new const char*[args->nOptions]);
+ memset(argv.get(), 0, sizeof(char*) * (args->nOptions));
/*
* Convert JNI args to argv.
* "extraInfo" field to pass function pointer "hooks" in. We also
* look for the -Xcheck:jni stuff here.
*/
- for (i = 0; i < args->nOptions; i++) {
+ int curOpt = 0;
+ bool sawJniOpts = false;
+ for (int i = 0; i < args->nOptions; i++) {
const char* optStr = args->options[i].optionString;
-
if (optStr == NULL) {
- fprintf(stderr, "ERROR: arg %d string was null", i);
- goto bail;
+ dvmFprintf(stderr, "ERROR: CreateJavaVM failed: argument %d was NULL\n", i);
+ return JNI_ERR;
} else if (strcmp(optStr, "vfprintf") == 0) {
gDvm.vfprintfHook =
(int (*)(FILE *, const char*, va_list))args->options[i].extraInfo;
} else if (strcmp(optStr, "sensitiveThread") == 0) {
gDvm.isSensitiveThreadHook = (bool (*)(void))args->options[i].extraInfo;
} else if (strcmp(optStr, "-Xcheck:jni") == 0) {
- checkJni = true;
+ gDvmJni.useCheckJni = true;
} else if (strncmp(optStr, "-Xjniopts:", 10) == 0) {
+ sawJniOpts = true;
const char* jniOpts = optStr + 9;
while (jniOpts != NULL) {
jniOpts++; /* skip past ':' or ',' */
if (strncmp(jniOpts, "warnonly", 8) == 0) {
- warnError = false;
+ gDvmJni.warnOnly = true;
+ } else if (strncmp(jniOpts, "forcecopy-unmap", 15) == 0) {
+ gDvmJni.forceDataUnmap = true;
} else if (strncmp(jniOpts, "forcecopy", 9) == 0) {
- forceDataCopy = true;
+ gDvmJni.forceDataCopy = true;
} else {
LOGW("unknown jni opt starting at '%s'", jniOpts);
}
argv[curOpt++] = optStr;
}
}
- argc = curOpt;
+ int argc = curOpt;
- if (checkJni) {
+ if (sawJniOpts && !gDvmJni.useCheckJni) {
+ dvmFprintf(stderr, "ERROR: -Xjniopts only makes sense with -Xcheck:jni\n");
+ return JNI_ERR;
+ }
+ if (gDvmJni.forceDataUnmap && gDvmJni.forceDataCopy) {
+ dvmFprintf(stderr, "ERROR: choose one of forcecopy or forcecopy-unmap\n");
+ return JNI_ERR;
+ }
+ if (gDvmJni.forceDataUnmap) {
+ gDvmJni.forceDataCopy = true; // It simplifies CheckJNI if we only have to check one thing.
+ }
+
+ if (gDvmJni.useCheckJni) {
dvmUseCheckedJniVm(pVM);
- pVM->useChecked = true;
}
- pVM->warnError = warnError;
- pVM->forceDataCopy = forceDataCopy;
/* set this up before initializing VM, so it can create some JNIEnvs */
gDvm.vmList = (JavaVM*) pVM;
* here because some of the class initialization we do when starting
* up the VM will call into native code.
*/
- pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
+ JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
- /* initialize VM */
+ /* Initialize VM. */
gDvm.initializing = true;
- if (dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) != 0) {
+ int rc = dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
+ gDvm.initializing = false;
+
+ if (rc != 0) {
free(pEnv);
free(pVM);
- goto bail;
+ LOGW("CreateJavaVM failed");
+ return JNI_ERR;
}
/*
dvmChangeStatus(NULL, THREAD_NATIVE);
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
- result = JNI_OK;
-
-bail:
- gDvm.initializing = false;
- if (result == JNI_OK) {
- LOGV("JNI_CreateJavaVM succeeded");
- } else {
- LOGW("JNI_CreateJavaVM failed");
- }
- free(argv);
- return result;
+ LOGV("CreateJavaVM succeeded");
+ return JNI_OK;
}
/* if nonzero, we are in a "critical" JNI call */
int critical;
- /* keep a copy of this here for speed */
- bool forceDataCopy;
-
struct JNIEnvExt* prev;
struct JNIEnvExt* next;
} JNIEnvExt;
const struct JNIInvokeInterface* baseFuncTable;
- /* if multiple VMs are desired, add doubly-linked list stuff here */
-
- /* per-VM feature flags */
- bool useChecked;
- bool warnError;
- bool forceDataCopy;
-
/* head of list of JNIEnvs associated with this VM */
JNIEnvExt* envList;
pthread_mutex_t envListLock;
* easy to handle, because the JDWP thread isn't started until we call
* dvmInitAfterZygote().
* checkjni
- * If set, make sure "check JNI" is eabled. This is a little weird,
- * because we already have the JNIEnv for the main thread set up. However,
- * since we only have one thread at this point, it's easy to patch up.
+ * If set, make sure "check JNI" is enabled.
* assert
* If set, make sure assertions are enabled. This gets fairly weird,
* because it affects the result of a method called by class initializers,