Use un-patched RN for Android by installing hook into JSC

This works by installing some assembly into JSGlobalContextCreateInGroup() that will immediately jump out into our own wrapper function so we always can inject the Realm constructor into the context.
This commit is contained in:
Scott Kyle 2016-02-10 02:08:30 -08:00
parent 49408ad767
commit 415b45be51
9 changed files with 151 additions and 106 deletions

View File

@ -73,6 +73,6 @@ android {
dependencies {
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.facebook.react:react-native:0.18.0-patched'
compile 'com.facebook.react:react-native:0.18.0'
compile project(':realm')
}

View File

@ -285,5 +285,5 @@ afterEvaluate { project ->
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.nanohttpd:nanohttpd:2.2.0'
compile 'com.facebook.react:react-native:0.18.0-patched'
compile 'com.facebook.react:react-native:0.18.0'
}

View File

@ -29,5 +29,5 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'org.nanohttpd:nanohttpd:2.2.0'
compile 'com.facebook.react:react-native:0.18.0-patched'
compile 'com.facebook.react:react-native:0.18.0'
}

View File

@ -21,19 +21,23 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule {
private static final int DEFAULT_PORT = 8082;
private AndroidWebServer webServer;
private long rpcServerPtr;
private String filesDirPath;
private Handler handler = new Handler(Looper.getMainLooper());
static {
SoLoader.loadLibrary("realmreact");
}
public RealmReactAndroid(ReactApplicationContext reactContext) {
super(reactContext);
String fileDir;
try {
filesDirPath = getReactApplicationContext().getFilesDir().getCanonicalPath();
fileDir = reactContext.getFilesDir().getCanonicalPath();
} catch (IOException e) {
throw new IllegalStateException(e);
}
SoLoader.loadLibrary("realmreact");
setDefaultRealmFileDirectory(fileDir);
}
@Override
@ -43,31 +47,18 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule {
@Override
public Map<String, Object> getConstants() {
long contexts = injectRealmJsContext(filesDirPath);
if (contexts == -1) {
Log.e("RealmReactAndroid", "Error during initializing Realm context");
throw new IllegalStateException("Error during initializing Realm context");
}
Log.i("RealmReactAndroid", "Initialized " + contexts + " contexts");
if (contexts == 0) {// we're running in Chrome debug mode
startWebServer();
}
// FIXME: Only start web server when in Chrome debug mode!
startWebServer();
return Collections.EMPTY_MAP;
}
@Override
public void onCatalystInstanceDestroy() {
if (webServer != null) {
Log.i("RealmReactAndroid", "Stopping the debugging Webserver");
webServer.stop();
}
stopWebServer();
}
private void startWebServer() {
rpcServerPtr = setupChromeDebugModeRealmJsContext();
setupChromeDebugModeRealmJsContext();
webServer = new AndroidWebServer(DEFAULT_PORT);
try {
webServer.start();
@ -77,7 +68,13 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule {
}
}
// WebServer
private void stopWebServer() {
if (webServer != null) {
Log.i("RealmReactAndroid", "Stopping the webserver");
webServer.stop();
}
}
class AndroidWebServer extends NanoHTTPD {
public AndroidWebServer(int port) {
super(port);
@ -105,7 +102,7 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule {
handler.post(new Runnable() {
@Override
public void run() {
jsonResponse[0] = processChromeDebugCommand(rpcServerPtr, cmdUri, json);
jsonResponse[0] = processChromeDebugCommand(cmdUri, json);
latch.countDown();
}
});
@ -122,11 +119,11 @@ public class RealmReactAndroid extends ReactContextBaseJavaModule {
}
// fileDir: path of the internal storage of the application
private native long injectRealmJsContext(String fileDir);
private native void setDefaultRealmFileDirectory(String fileDir);
// responsible for creating the rpcServer that will accept the chrome Websocket command
private native long setupChromeDebugModeRealmJsContext();
// this receives one command from Chrome debug then return the processing we should post back
private native String processChromeDebugCommand(long rpcServerPointer, String cmd, String args);
private native String processChromeDebugCommand(String cmd, String args);
}

View File

@ -25,6 +25,7 @@ LOCAL_SRC_FILES := \
src/rpc.cpp \
src/android/platform.cpp \
src/android/io_realm_react_RealmReactAndroid.cpp \
src/android/jsc_override.cpp \
src/object-store/index_set.cpp \
src/object-store/list.cpp \
src/object-store/object_schema.cpp \

View File

@ -3,88 +3,42 @@
*/
#include <jni.h>
#include <dlfcn.h>
#include <iostream>
#include <sstream>
#include "io_realm_react_RealmReactAndroid.h"
#include "js_init.h"
#include "rpc.hpp"
#include "platform.hpp"
#include "shared_realm.hpp"
#include <unordered_map>
#include <android/log.h>
namespace facebook {
namespace react {
class JSCExecutor;
}
}
#include "io_realm_react_RealmReactAndroid.h"
#include "rpc.hpp"
#include "platform.hpp"
/*
* Class: io_realm_react_RealmReactAndroid
* Method: injectRealmJsContext
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_injectRealmJsContext
static realm_js::RPCServer *s_rpc_server;
JNIEXPORT void JNICALL Java_io_realm_react_RealmReactAndroid_setDefaultRealmFileDirectory
(JNIEnv *env, jclass, jstring fileDir)
{
__android_log_print(ANDROID_LOG_DEBUG, "JSRealm", "Java_io_realm_react_RealmReactAndroid_injectRealmJsContext");
void* handle = dlopen ("libreactnativejni.so", RTLD_LAZY);
if (!handle) {
__android_log_print(ANDROID_LOG_ERROR, "JSRealm", "Cannot open libreactnativejni.so");
return -1;
}
{
__android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "setDefaultRealmFileDirectory");
// Getting the internal storage path for the application
const char* strFileDir = env->GetStringUTFChars(fileDir , NULL);
// Setting the internal storage path for the application
const char* strFileDir = env->GetStringUTFChars(fileDir, NULL);
realm::set_default_realm_file_directory(strFileDir);
env->ReleaseStringUTFChars(fileDir , strFileDir);
// load the symbol
typedef std::unordered_map<JSContextRef, facebook::react::JSCExecutor*> (*get_jsc_context_t)();
__android_log_print(ANDROID_LOG_DEBUG, "JSRealm", "Absolute path: %s", realm::default_realm_file_directory().c_str());
}
get_jsc_context_t get_jsc_context = (get_jsc_context_t) dlsym(handle, "get_jsc_context");
if (get_jsc_context != NULL) {
// clearing previous instances
realm::Realm::s_global_cache.clear();
std::unordered_map<JSContextRef, facebook::react::JSCExecutor*> s_globalContextRefToJSCExecutor = get_jsc_context();
for (auto pair : s_globalContextRefToJSCExecutor) {
RJSInitializeInContext(pair.first);
}
return s_globalContextRefToJSCExecutor.size();
} else {
__android_log_print(ANDROID_LOG_ERROR, "JSRealm", "Cannot find symbol get_jsc_context");
return -1;
}
}
/*
* Class: io_realm_react_RealmReactAndroid
* Method: setupChromeDebugModeRealmJsContext
*/
static realm_js::RPCServer *s_rpc_server;
JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_setupChromeDebugModeRealmJsContext
JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_setupChromeDebugModeRealmJsContext
(JNIEnv *, jclass)
{
__android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "Java_com_reacttests_RealmReactAndroid_setupChromeDebugModeRealmJsContext");
{
__android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "setupChromeDebugModeRealmJsContext");
if (s_rpc_server) {
delete s_rpc_server;
delete s_rpc_server;
}
s_rpc_server = new realm_js::RPCServer();
return (jlong)s_rpc_server;
}
}
/*
* Class: io_realm_react_RealmReactAndroid
* Method: processChromeDebugCommand
*/
JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand
(JNIEnv *env, jclass, jlong rpc_server_ptr, jstring chrome_cmd, jstring chrome_args)
{
__android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand");
JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand
(JNIEnv *env, jclass, jstring chrome_cmd, jstring chrome_args)
{
__android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "processChromeDebugCommand");
const char* cmd = env->GetStringUTFChars(chrome_cmd, NULL);
const char* args = env->GetStringUTFChars(chrome_args, NULL);
realm_js::json json = realm_js::json::parse(args);
@ -92,4 +46,4 @@ JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_injectRealmJsConte
env->ReleaseStringUTFChars(chrome_cmd, cmd);
env->ReleaseStringUTFChars(chrome_args, args);
return env->NewStringUTF(response.dump().c_str());
}
}

View File

@ -7,12 +7,12 @@
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: io_realm_react_RealmReactAndroid
* Method: injectRealmJsContext
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_injectRealmJsContext
* Class: io_realm_react_RealmReactAndroid
* Method: setDefaultRealmFileDirectory
*/
JNIEXPORT void JNICALL Java_io_realm_react_RealmReactAndroid_setDefaultRealmFileDirectory
(JNIEnv *, jclass, jstring);
/*
@ -24,11 +24,10 @@ JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactAndroid_setupChromeDebugMo
/*
* Class: io_realm_react_RealmReactAndroid
* Method: processsetupChromeDebugCommand
* Method: processChromeDebugCommand
*/
JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactAndroid_processChromeDebugCommand
(JNIEnv *, jclass, jlong, jstring, jstring);
(JNIEnv *, jclass, jstring, jstring);
#ifdef __cplusplus
}

View File

@ -0,0 +1,94 @@
/* Copyright 2016 Realm Inc - All Rights Reserved
* Proprietary and Confidential
*/
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <mutex>
#include <JavaScriptCore/JSContextRef.h>
#include "js_init.h"
#include "shared_realm.hpp"
#if __arm__
#define HOOK_SIZE 8
#else
#define HOOK_SIZE 5
#endif
static void swap_function() __attribute__((constructor));
static JSGlobalContextRef create_context(JSContextGroupRef group, JSClassRef global_class)
{
static std::mutex s_mutex;
std::lock_guard<std::mutex> lock(s_mutex);
// Replace JSGlobalContextCreateInGroup with its original implementation and call it.
swap_function();
JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(group, global_class);
// Reinstall the hook.
swap_function();
// Clear cache from previous instances.
realm::Realm::s_global_cache.clear();
RJSInitializeInContext(ctx);
return ctx;
}
static void swap_function()
{
static int8_t s_orig_code[HOOK_SIZE];
static bool s_swapped = false;
int8_t *orig_func = (int8_t*)&JSGlobalContextCreateInGroup;
int8_t *new_func = (int8_t*)&create_context;
#if __arm__
bool orig_thumb = (uintptr_t)orig_func % 4 != 0;
if (orig_thumb) {
orig_func--;
}
#endif
size_t page_size = sysconf(_SC_PAGESIZE);
uintptr_t page_start = (uintptr_t)orig_func & ~(page_size - 1);
uintptr_t code_end = (uintptr_t)orig_func + HOOK_SIZE;
// Make this memory region writable.
mprotect((void*)page_start, code_end - page_start, PROT_READ | PROT_WRITE | PROT_EXEC);
if (s_swapped) {
// Copy original code back into place.
memcpy(orig_func, s_orig_code, HOOK_SIZE);
} else {
// Store the original code before replacing it.
memcpy(s_orig_code, orig_func, HOOK_SIZE);
#if __arm__
if (orig_thumb) {
// LDR PC, [PC, #0]; BX PC;
memcpy(orig_func, "\x00\x4f\x38\x47", 4);
memcpy(orig_func + 4, &new_func, 4);
} else {
// LDR PC, [PC, #0];
memcpy(orig_func, "\x00\xf0\x9f\xe5", 4);
memcpy(orig_func + 4, &new_func, 4);
}
#else
int32_t jmp_offset = (int64_t)new_func - (int64_t)orig_func - HOOK_SIZE;
// Change original function to jump to our new one.
*orig_func = 0xE9; // JMP
*(int32_t*)(orig_func + 1) = jmp_offset;
#endif
}
s_swapped = !s_swapped;
// Return this region to no longer being writable.
mprotect((void*)page_start, code_end - page_start, PROT_READ | PROT_EXEC);
}

View File

@ -74,7 +74,7 @@ android {
dependencies {
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.facebook.react:react-native:0.18.0-patched'
compile 'com.facebook.react:react-native:0.18.0'
compile project(':realm')
compile project(':react-native-fs')
}