Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/vm signal fix #6147

Merged
merged 4 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;

Expand All @@ -21,21 +20,26 @@ public class ExitActivity extends AppCompatActivity {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int code = -1;
boolean isSignal = false;
Bundle extras = getIntent().getExtras();
if(extras != null) {
code = extras.getInt("code",-1);
isSignal = extras.getBoolean("isSignal", false);
}

int message = isSignal ? R.string.mcn_signal_title : R.string.mcn_exit_title;

new AlertDialog.Builder(this)
.setMessage(getString(R.string.mcn_exit_title,code))
.setMessage(getString(message,code))
.setPositiveButton(R.string.main_share_logs, (dialog, which) -> shareLog(this))
.setOnDismissListener(dialog -> ExitActivity.this.finish())
.show();
}

public static void showExitMessage(Context ctx, int code) {
public static void showExitMessage(Context ctx, int code, boolean isSignal) {
Intent i = new Intent(ctx,ExitActivity.class);
i.putExtra("code",code);
i.putExtra("isSignal", isSignal);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,5 @@ public static int getDetectedVersion() {
static {
System.loadLibrary("pojavexec");
System.loadLibrary("pojavexec_awt");
System.loadLibrary("istdio");
}
}
14 changes: 4 additions & 10 deletions app_pojavlauncher/src/main/jni/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ LOCAL_SRC_FILES := tinywrapper/main.c tinywrapper/string_utils.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)/tinywrapper
include $(BUILD_SHARED_LIBRARY)

$(call import-module,prefab/bytehook)
LOCAL_PATH := $(HERE_PATH)

include $(CLEAR_VARS)
# Link GLESv2 for test
LOCAL_LDLIBS := -ldl -llog -landroid
# -lGLESv2
LOCAL_MODULE := pojavexec
LOCAL_SHARED_LIBRARIES := bytehook
# LOCAL_CFLAGS += -DDEBUG
# -DGLES_TEST
LOCAL_SRC_FILES := \
Expand All @@ -40,6 +43,7 @@ LOCAL_SRC_FILES := \
input_bridge_v3.c \
jre_launcher.c \
utils.c \
stdio_is.c \
driver_helper/nsbypass.c

ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
Expand All @@ -56,16 +60,6 @@ LOCAL_LDFLAGS := -z global
include $(BUILD_SHARED_LIBRARY)
#endif

$(call import-module,prefab/bytehook)
LOCAL_PATH := $(HERE_PATH)

include $(CLEAR_VARS)
LOCAL_MODULE := istdio
LOCAL_SHARED_LIBRARIES := bytehook
LOCAL_SRC_FILES := \
stdio_is.c
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := pojavexec_awt
LOCAL_SRC_FILES := \
Expand Down
117 changes: 71 additions & 46 deletions app_pojavlauncher/src/main/jni/jre_launcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,6 @@ static const char** const_appclasspath = NULL;
static const jboolean const_javaw = JNI_FALSE;
static const jboolean const_cpwildcard = JNI_TRUE;
static const jint const_ergo_class = 0; // DEFAULT_POLICY
static struct sigaction old_sa[NSIG];

void (*__old_sa)(int signal, siginfo_t *info, void *reserved);
int (*JVM_handle_linux_signal)(int signo, siginfo_t* siginfo, void* ucontext, int abort_if_unrecognized);

void android_sigaction(int signal, siginfo_t *info, void *reserved) {
printf("process killed with signal %d code %p addr %p\n", signal,info->si_code,info->si_addr);
if (JVM_handle_linux_signal == NULL) { // should not happen, but still
__old_sa = old_sa[signal].sa_sigaction;
__old_sa(signal,info,reserved);
exit(1);
} else {
// Based on https://github.com/PojavLauncherTeam/openjdk-multiarch-jdk8u/blob/aarch64-shenandoah-jdk8u272-b10/hotspot/src/os/linux/vm/os_linux.cpp#L4688-4693
int orig_errno = errno; // Preserve errno value over signal handler.
JVM_handle_linux_signal(signal, info, reserved, true);
errno = orig_errno;
}
}
typedef jint JNI_CreateJavaVM_func(JavaVM **pvm, void **penv, void *args);

typedef jint JLI_Launch_func(int argc, char ** argv, /* main argc, argc */
int jargc, const char** jargv, /* java args */
Expand All @@ -84,9 +65,80 @@ typedef jint JLI_Launch_func(int argc, char ** argv, /* main argc, argc */
jint ergo /* ergonomics class policy */
);

struct {
sigset_t tracked_sigset;
int pipe[2];
} abort_waiter_data;

_Noreturn extern void nominal_exit(int code, bool is_signal);

_Noreturn static void* abort_waiter_thread(void* extraArg) {
// Don't allow this thread to receive signals this thread is tracking.
// We should only receive them externally.
pthread_sigmask(SIG_BLOCK, &abort_waiter_data.tracked_sigset, NULL);
int signal;
// Block for reading the signal ID until it arrives
read(abort_waiter_data.pipe[0], &signal, sizeof(int));
// Die
nominal_exit(signal, true);
}

_Noreturn static void abort_waiter_handler(int signal) {
// Write the final signal into the pipe and block forever.
write(abort_waiter_data.pipe[1], &signal, sizeof(int));
while(1) {}
}

static void abort_waiter_setup() {
// Only abort on SIGABRT as the JVM either emits SIGABRT or SIGKILL (which we can't catch)
// when a fatal crash occurs. Still, keep expandability if we would want to add more
// user-friendly fatal signals in the future.
const static int tracked_signals[] = {SIGABRT};
const static int ntracked = (sizeof(tracked_signals) / sizeof(tracked_signals[0]));
struct sigaction sigactions[ntracked];
sigemptyset(&abort_waiter_data.tracked_sigset);
for(size_t i = 0; i < ntracked; i++) {
sigaddset(&abort_waiter_data.tracked_sigset, tracked_signals[i]);
sigactions[i].sa_handler = abort_waiter_handler;
}
if(pipe(abort_waiter_data.pipe) != 0) {
printf("Failed to set up aborter pipe: %s\n", strerror(errno));
return;
}
pthread_t waiter_thread; int result;
if((result = pthread_create(&waiter_thread, NULL, abort_waiter_thread, NULL)) != 0) {
printf("Failed to start up waiter thread: %s", strerror(result));
for(int i = 0; i < 2; i++) close(abort_waiter_data.pipe[i]);
return;
}
// Only set the sigactions *after* we have already set up the pipe and the thread.
for(size_t i = 0; i < ntracked; i++) {
if(sigaction(tracked_signals[i], &sigactions[i], NULL) != 0) {
// Not returning here because we may have set some handlers successfully.
// Some handling is better than no handling.
printf("Failed to set signal hander for signal %i: %s", i, strerror(errno));
}
}
}

static jint launchJVM(int margc, char** margv) {
void* libjli = dlopen("libjli.so", RTLD_LAZY | RTLD_GLOBAL);

// Unset all signal handlers to create a good slate for JVM signal detection.
struct sigaction clean_sa;
memset(&clean_sa, 0, sizeof (struct sigaction));
for(int sigid = SIGHUP; sigid < NSIG; sigid++) {
// For some reason Android specifically checks if you set SIGSEGV to SIG_DFL.
// There's probably a good reason for that but the signal handler here is
// temporary and will be replaced by the Java VM's signal/crash handler.
// Work around the warning by using SIG_IGN for SIGSEGV
if(sigid == SIGSEGV) clean_sa.sa_handler = SIG_IGN;
else clean_sa.sa_handler = SIG_DFL;
sigaction(sigid, &clean_sa, NULL);
}
// Set up the thread that will abort the launcher with an user-facing dialog on a signal.
abort_waiter_setup();

// Boardwalk: silence
// LOGD("JLI lib = %x", (int)libjli);
if (NULL == libjli) {
Expand Down Expand Up @@ -130,35 +182,8 @@ static jint launchJVM(int margc, char** margv) {
* Signature: ([Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_oracle_dalvik_VMLauncher_launchJVM(JNIEnv *env, jclass clazz, jobjectArray argsArray) {
#ifdef TRY_SIG2JVM
void* libjvm = dlopen("libjvm.so", RTLD_LAZY | RTLD_GLOBAL);
if (NULL == libjvm) {
LOGE("JVM lib = NULL: %s", dlerror());
return -1;
}
JVM_handle_linux_signal = dlsym(libjvm, "JVM_handle_linux_signal");
#endif

jint res = 0;
// int i;
//Prepare the signal trapper
struct sigaction catcher;
memset(&catcher,0,sizeof(sigaction));
catcher.sa_sigaction = android_sigaction;
catcher.sa_flags = SA_SIGINFO|SA_RESTART;
// SA_RESETHAND;
#define CATCHSIG(X) sigaction(X, &catcher, &old_sa[X])
CATCHSIG(SIGILL);
//CATCHSIG(SIGABRT);
CATCHSIG(SIGBUS);
CATCHSIG(SIGFPE);
#ifdef TRY_SIG2JVM
CATCHSIG(SIGSEGV);
#endif
CATCHSIG(SIGSTKFLT);
CATCHSIG(SIGPIPE);
CATCHSIG(SIGXFSZ);
//Signal trapper ready

// Save dalvik JNIEnv pointer for JVM launch thread
pojav_environ->dalvikJNIEnvPtr_ANDROID = env;
Expand Down
30 changes: 13 additions & 17 deletions app_pojavlauncher/src/main/jni/stdio_is.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <errno.h>
#include <stdlib.h>
#include <bytehook.h>
#include <environ/environ.h>

//
// Created by maks on 17.02.21.
Expand All @@ -19,7 +20,6 @@ static volatile jclass exitTrap_exitClass;
static volatile jmethodID exitTrap_staticMethod;
static JavaVM *exitTrap_jvm;

static JavaVM *stdiois_jvm;
static int pfd[2];
static pthread_t logger;
static jmethodID logger_onEventLogged;
Expand All @@ -37,19 +37,11 @@ static bool recordBuffer(char* buf, ssize_t len) {
return true;
}

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, __attribute((unused)) void* reserved) {
stdiois_jvm = vm;
JNIEnv *env;
(*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4);
jclass eventLogListener = (*env)->FindClass(env, "net/kdt/pojavlaunch/Logger$eventLogListener");
logger_onEventLogged = (*env)->GetMethodID(env, eventLogListener, "onEventLogged", "(Ljava/lang/String;)V");
return JNI_VERSION_1_4;
}

static void *logger_thread() {
JNIEnv *env;
jstring writeString;
(*stdiois_jvm)->AttachCurrentThread(stdiois_jvm, &env, NULL);
JavaVM* dvm = pojav_environ->dalvikJavaVMPtr;
(*dvm)->AttachCurrentThread(dvm, &env, NULL);
ssize_t rsize;
char buf[2050];
while((rsize = read(pfd[0], buf, sizeof(buf)-1)) > 0) {
Expand All @@ -64,7 +56,7 @@ static void *logger_thread() {
(*env)->DeleteLocalRef(env, writeString);
}
}
(*stdiois_jvm)->DetachCurrentThread(stdiois_jvm);
(*dvm)->DetachCurrentThread(dvm);
return NULL;
}
JNIEXPORT void JNICALL
Expand All @@ -74,6 +66,10 @@ Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass
latestlog_fd = -1;
close(localfd);
}
if(logger_onEventLogged == NULL) {
jclass eventLogListener = (*env)->FindClass(env, "net/kdt/pojavlaunch/Logger$eventLogListener");
logger_onEventLogged = (*env)->GetMethodID(env, eventLogListener, "onEventLogged", "(Ljava/lang/String;)V");
}
jclass ioeClass = (*env)->FindClass(env, "java/io/IOException");


Expand Down Expand Up @@ -109,7 +105,7 @@ Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass

typedef void (*exit_func)(int);

_Noreturn static void nominal_exit(int code) {
_Noreturn void nominal_exit(int code, bool is_signal) {
JNIEnv *env;
jint errorCode = (*exitTrap_jvm)->GetEnv(exitTrap_jvm, (void**)&env, JNI_VERSION_1_6);
if(errorCode == JNI_EDETACHED) {
Expand All @@ -124,7 +120,7 @@ _Noreturn static void nominal_exit(int code) {
if(code != 0) {
// Exit code 0 is pretty established as "eh it's fine"
// so only open the GUI if the code is != 0
(*env)->CallStaticVoidMethod(env, exitTrap_exitClass, exitTrap_staticMethod, exitTrap_ctx, code);
(*env)->CallStaticVoidMethod(env, exitTrap_exitClass, exitTrap_staticMethod, exitTrap_ctx, code, is_signal);
}
// Delete the reference, not gonna need 'em later anyway
(*env)->DeleteGlobalRef(env, exitTrap_ctx);
Expand Down Expand Up @@ -155,7 +151,7 @@ static void custom_exit(int code) {
}
exit_tripped = true;
// Perform a nominal exit, as we expect.
nominal_exit(code);
nominal_exit(code, false);
BYTEHOOK_POP_STACK();
}

Expand All @@ -165,14 +161,14 @@ static void custom_atexit() {
return;
}
exit_tripped = true;
nominal_exit(0);
nominal_exit(0, false);
}

JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupExitTrap(JNIEnv *env, __attribute((unused)) jclass clazz, jobject context) {
exitTrap_ctx = (*env)->NewGlobalRef(env,context);
(*env)->GetJavaVM(env,&exitTrap_jvm);
exitTrap_exitClass = (*env)->NewGlobalRef(env,(*env)->FindClass(env,"net/kdt/pojavlaunch/ExitActivity"));
exitTrap_staticMethod = (*env)->GetStaticMethodID(env,exitTrap_exitClass,"showExitMessage","(Landroid/content/Context;I)V");
exitTrap_staticMethod = (*env)->GetStaticMethodID(env,exitTrap_exitClass,"showExitMessage","(Landroid/content/Context;IZ)V");

if(bytehook_init(BYTEHOOK_MODE_AUTOMATIC, false) == BYTEHOOK_STATUS_CODE_OK) {
bytehook_hook_all(NULL,
Expand Down
1 change: 1 addition & 0 deletions app_pojavlauncher/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@

<!-- MainActivity: strings -->
<string name="mcn_exit_title">Application/Game exited with code %d, check latestlog.txt for more details.</string>
<string name="mcn_signal_title">Application/Game aborted by fatal signal %d, check latestlog.txt for more details.</string>
<string name="mcn_exit_confirm">Are you sure want to force close?</string>
<string name="mcn_check_fail_vulkan_support">Zink (Vulkan) renderer is not supported on this device!</string>

Expand Down
Loading