Stdio logging through C

This commit is contained in:
artdeell 2023-05-01 23:15:32 +03:00
parent 4115b997f8
commit fbe587bea5
7 changed files with 95 additions and 98 deletions

View File

@ -60,7 +60,13 @@ public class LoggerView extends ConstraintLayout {
mToggleButton.setOnCheckedChangeListener( mToggleButton.setOnCheckedChangeListener(
(compoundButton, isChecked) -> { (compoundButton, isChecked) -> {
mLogTextView.setVisibility(isChecked ? VISIBLE : GONE); mLogTextView.setVisibility(isChecked ? VISIBLE : GONE);
if(!isChecked) mLogTextView.setText(""); if(isChecked) {
Logger.setLogListener(mLogListener);
}else{
mLogTextView.setText("");
Logger.setLogListener(null); // Makes the JNI code be able to skip expensive logger callbacks
// NOTE: was tested by rapidly smashing the log on/off button, no sync issues found :)
}
}); });
mToggleButton.setChecked(false); mToggleButton.setChecked(false);
@ -80,7 +86,6 @@ public class LoggerView extends ConstraintLayout {
}); });
}; };
Logger.getInstance().setLogListener(mLogListener);
} }
} }

View File

@ -55,7 +55,7 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_java_gui_launcher); setContentView(R.layout.activity_java_gui_launcher);
Logger.getInstance().reset(); Logger.begin(new File(Tools.DIR_GAME_HOME, "latestlog.txt").getAbsolutePath());
mTouchCharInput = findViewById(R.id.awt_touch_char); mTouchCharInput = findViewById(R.id.awt_touch_char);
mTouchCharInput.setCharacterSender(new AwtCharSender()); mTouchCharInput.setCharacterSender(new AwtCharSender());

View File

@ -2,75 +2,24 @@ package net.kdt.pojavlaunch;
import androidx.annotation.Keep; import androidx.annotation.Keep;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
/** Singleton class made to log on one file /** Singleton class made to log on one file
* The singleton part can be removed but will require more implementation from the end-dev * The singleton part can be removed but will require more implementation from the end-dev
*/ */
@Keep @Keep
public class Logger { public class Logger {
private static volatile Logger sLoggerSingleton = null;
/* Instance variables */ private static final Logger dummyLogger = new Logger();
private final File mLogFile;
private PrintStream mLogStream;
private WeakReference<eventLogListener> mLogListenerWeakReference = null;
/* No public construction */
private Logger(){
mLogFile = new File(Tools.DIR_GAME_HOME, "latestlog.txt");
// Make a new instance of the log file
// Default PrintStream constructor will overwrite the file for us
try {
mLogStream = new PrintStream(mLogFile.getAbsolutePath());
}catch (IOException e){e.printStackTrace();}
}
public static Logger getInstance(){ public static Logger getInstance(){
if(sLoggerSingleton == null){ return dummyLogger;
synchronized(Logger.class){
if(sLoggerSingleton == null){
sLoggerSingleton = new Logger();
}
}
}
return sLoggerSingleton;
} }
/** Print the text to the log file if not censored */ /** Print the text to the log file if not censored */
public void appendToLog(String text){ public static native void appendToLog(String text);
if(shouldCensorLog(text)) return;
appendToLogUnchecked(text);
}
/** Print the text to the log file, no china censoring there */
public void appendToLogUnchecked(String text){
mLogStream.println(text);
notifyLogListener(text);
}
/** Reset the log file, effectively erasing any previous logs */ /** Reset the log file, effectively erasing any previous logs */
public void reset(){ public static native void begin(String logFilePath);
try{
//Refer to line 30
mLogStream = new PrintStream(mLogFile.getAbsolutePath());
}catch (IOException e){ e.printStackTrace();}
}
/**
* Perform various checks to see if the log is safe to print
* Subclasses may want to override this behavior
* @param text The text to check
* @return Whether the log should be censored
*/
private static boolean shouldCensorLog(String text){
return text.contains("Session ID is");
}
/** Small listener for anything listening to the log */ /** Small listener for anything listening to the log */
public interface eventLogListener { public interface eventLogListener {
@ -78,18 +27,5 @@ public class Logger {
} }
/** Link a log listener to the logger */ /** Link a log listener to the logger */
public void setLogListener(eventLogListener logListener){ public static native void setLogListener(eventLogListener logListener);
this.mLogListenerWeakReference = new WeakReference<>(logListener);
}
/** Notifies the event listener, if it exists */
private void notifyLogListener(String text){
if(mLogListenerWeakReference == null) return;
eventLogListener logListener = mLogListenerWeakReference.get();
if(logListener == null){
mLogListenerWeakReference = null;
return;
}
logListener.onEventLogged(text);
}
} }

View File

@ -131,7 +131,7 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
try { try {
Logger.getInstance().reset(); Logger.begin(new File(Tools.DIR_GAME_HOME, "latestlog.txt").getAbsolutePath());
// FIXME: is it safe for multi thread? // FIXME: is it safe for multi thread?
GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
touchCharInput.setCharacterSender(new LwjglCharSender()); touchCharInput.setCharacterSender(new LwjglCharSender());

View File

@ -35,7 +35,6 @@ public class MultiRTUtils {
private static final File RUNTIME_FOLDER = new File(Tools.MULTIRT_HOME); private static final File RUNTIME_FOLDER = new File(Tools.MULTIRT_HOME);
private static final String JAVA_VERSION_STR = "JAVA_VERSION=\""; private static final String JAVA_VERSION_STR = "JAVA_VERSION=\"";
private static final String OS_ARCH_STR = "OS_ARCH=\""; private static final String OS_ARCH_STR = "OS_ARCH=\"";
private static String sSelectedRuntimeName;
public static List<Runtime> getRuntimes() { public static List<Runtime> getRuntimes() {
if(!RUNTIME_FOLDER.exists() && !RUNTIME_FOLDER.mkdirs()) { if(!RUNTIME_FOLDER.exists() && !RUNTIME_FOLDER.mkdirs()) {

View File

@ -96,7 +96,6 @@ public class JREUtils {
public static void redirectAndPrintJRELog() { public static void redirectAndPrintJRELog() {
Log.v("jrelog","Log starts here"); Log.v("jrelog","Log starts here");
JREUtils.logToLogger(Logger.getInstance());
new Thread(new Runnable(){ new Thread(new Runnable(){
int failTime = 0; int failTime = 0;
ProcessBuilder logcatPb; ProcessBuilder logcatPb;
@ -545,7 +544,6 @@ public class JREUtils {
} }
} }
public static native int chdir(String path); public static native int chdir(String path);
public static native void logToLogger(final Logger logger);
public static native boolean dlopen(String libPath); public static native boolean dlopen(String libPath);
public static native void setLdLibraryPath(String ldLibraryPath); public static native void setLdLibraryPath(String ldLibraryPath);
public static native void setupBridgeWindow(Object surface); public static native void setupBridgeWindow(Object surface);

View File

@ -5,46 +5,74 @@
#include <pthread.h> #include <pthread.h>
#include <stdio.h> #include <stdio.h>
#include <xhook.h> #include <xhook.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
// //
// Created by maks on 17.02.21. // Created by maks on 17.02.21.
// //
static JavaVM *stdiois_jvm;
static volatile jmethodID log_cbMethod;
static volatile jobject log_cbObject;
static volatile jobject exitTrap_ctx; static volatile jobject exitTrap_ctx;
static volatile jclass exitTrap_exitClass; static volatile jclass exitTrap_exitClass;
static volatile jmethodID exitTrap_staticMethod; static volatile jmethodID exitTrap_staticMethod;
static JavaVM *exitTrap_jvm; static JavaVM *exitTrap_jvm;
static JavaVM *stdiois_jvm;
static int pfd[2]; static int pfd[2];
static pthread_t logger; static pthread_t logger;
static jmethodID logger_onEventLogged;
static volatile jobject logListener = NULL;
static int latestlog_fd = -1;
static bool recordBuffer(char* buf, ssize_t len) {
if(strstr(buf, "Session ID is")) return false;
if(latestlog_fd != -1) {
write(latestlog_fd, buf, len);
fdatasync(latestlog_fd);
}
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() { static void *logger_thread() {
JNIEnv *env; JNIEnv *env;
jstring str; jstring writeString;
(*stdiois_jvm)->AttachCurrentThread(stdiois_jvm, &env, NULL); (*stdiois_jvm)->AttachCurrentThread(stdiois_jvm, &env, NULL);
ssize_t rsize; ssize_t rsize;
char buf[2048]; char buf[2050];
while((rsize = read(pfd[0], buf, sizeof(buf)-1)) > 0) { while((rsize = read(pfd[0], buf, sizeof(buf)-1)) > 0) {
if(buf[rsize-1]=='\n') { if(buf[rsize-1]=='\n') {
rsize=rsize-1; rsize=rsize-1;
} }
buf[rsize]=0x00; buf[rsize]=0x00;
str = (*env)->NewStringUTF(env,buf); if(recordBuffer(buf, rsize) && logListener != NULL) {
(*env)->CallVoidMethod(env, log_cbObject, log_cbMethod, str); writeString = (*env)->NewStringUTF(env, buf);
(*env)->DeleteLocalRef(env,str); (*env)->CallVoidMethod(env, logListener, logger_onEventLogged, writeString);
(*env)->DeleteLocalRef(env, writeString);
}
} }
(*env)->DeleteGlobalRef(env, log_cbMethod);
(*env)->DeleteGlobalRef(env, log_cbObject);
(*stdiois_jvm)->DetachCurrentThread(stdiois_jvm); (*stdiois_jvm)->DetachCurrentThread(stdiois_jvm);
return NULL; return NULL;
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_net_kdt_pojavlaunch_utils_JREUtils_logToLogger(JNIEnv *env, __attribute((unused)) jclass clazz, jobject javaLogger) { Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass clazz, jstring logPath) {
// TODO: implement logToActivity() // TODO: implement logToActivity()
jclass loggableActivityClass = (*env)->FindClass(env,"net/kdt/pojavlaunch/Logger"); if(latestlog_fd != -1) {
log_cbMethod = (*env)->GetMethodID(env, loggableActivityClass, "appendToLog", "(Ljava/lang/String;)V"); int localfd = latestlog_fd;
(*env)->GetJavaVM(env,&stdiois_jvm); latestlog_fd = -1;
log_cbObject = (*env)->NewGlobalRef(env, javaLogger); close(localfd);
}
jclass ioeClass = (*env)->FindClass(env, "java/io/IOException");
setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered
setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered
@ -54,17 +82,26 @@ Java_net_kdt_pojavlaunch_utils_JREUtils_logToLogger(JNIEnv *env, __attribute((un
dup2(pfd[1], 1); dup2(pfd[1], 1);
dup2(pfd[1], 2); dup2(pfd[1], 2);
/* open latestlog.txt for writing */
const char* logFilePath = (*env)->GetStringUTFChars(env, logPath, NULL);
latestlog_fd = open(logFilePath, O_WRONLY | O_TRUNC | O_CREAT | O_NOATIME, 666);
if(latestlog_fd == -1) {
latestlog_fd = 0;
(*env)->ThrowNew(env, ioeClass, strerror(errno));
return;
}
(*env)->ReleaseStringUTFChars(env, logPath, logFilePath);
/* spawn the logging thread */ /* spawn the logging thread */
if(pthread_create(&logger, 0, logger_thread, 0) != 0) { int result = pthread_create(&logger, 0, logger_thread, 0);
jstring str = (*env)->NewStringUTF(env,"Failed to start logging!"); if(result != 0) {
(*env)->CallVoidMethod(env, log_cbObject, log_cbMethod, str); close(latestlog_fd);
(*env)->DeleteLocalRef(env,str); (*env)->ThrowNew(env, ioeClass, strerror(result));
(*env)->DeleteGlobalRef(env, log_cbMethod);
(*env)->DeleteGlobalRef(env, log_cbObject);
} }
pthread_detach(logger); pthread_detach(logger);
} }
void (*old_exit)(int code); void (*old_exit)(int code);
void custom_exit(int code) { void custom_exit(int code) {
if(code != 0) { if(code != 0) {
@ -87,3 +124,25 @@ JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupExitTrap(JNI
xhook_register(".*\\.so$", "exit", custom_exit, (void **) &old_exit); xhook_register(".*\\.so$", "exit", custom_exit, (void **) &old_exit);
xhook_refresh(1); xhook_refresh(1);
} }
JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_Logger_appendToLog(JNIEnv *env, __attribute((unused)) jclass clazz, jstring text) {
jsize appendStringLength = (*env)->GetStringUTFLength(env, text);
char newChars[appendStringLength+2];
(*env)->GetStringUTFRegion(env, text, 0, appendStringLength, newChars);
newChars[appendStringLength] = '\n';
newChars[appendStringLength+1] = 0;
if(recordBuffer(newChars, appendStringLength+1) && logListener != NULL) {
(*env)->CallVoidMethod(env, logListener, logger_onEventLogged, text);
}
}
JNIEXPORT void JNICALL
Java_net_kdt_pojavlaunch_Logger_setLogListener(JNIEnv *env, __attribute((unused)) jclass clazz, jobject log_listener) {
jobject logListenerLocal = logListener;
if(log_listener == NULL) {
logListener = NULL;
}else{
logListener = (*env)->NewGlobalRef(env, log_listener);
}
if(logListenerLocal != NULL) (*env)->DeleteGlobalRef(env, logListenerLocal);
}