fix: resolve SIGBUS crash in JNI bridge — use CallVoidMethod instead of function pointer casts
The root cause of the SIGBUS (alignment fault) crash was casting jmethodID values to function pointers and invoking them directly. jmethodID is an opaque JNI type that must only be used via CallVoidMethod/CallObjectMethod etc. Calling it as a function pointer dereferences garbage memory, causing SIGBUS at address 0xb1. Changes: - Replace function pointer typedefs (output_callback_t etc.) with a struct storing JNIEnv + callback global ref + jmethodIDs - All callbacks now go through CallVoidMethod(env, ref, mid, ...) - Remove getTestProgress() (never used, unused start_time workaround) - Fix Kotlin arg cast: List<String> -> toTypedArray() (ClassCastException) - Update UI default server to speedtest.uztelecom.uz
This commit is contained in:
@@ -14,8 +14,6 @@
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "iperf.h"
|
||||
#include "iperf_api.h"
|
||||
@@ -30,24 +28,56 @@
|
||||
static struct iperf_test *g_test = NULL;
|
||||
static volatile int g_stop = 0;
|
||||
|
||||
/* Callback interface in Java/Kotlin */
|
||||
typedef void (*output_callback_t)(JNIEnv *env, jobject callback, jstring line);
|
||||
typedef void (*error_callback_t)(JNIEnv *env, jobject callback, jstring msg);
|
||||
typedef void (*complete_callback_t)(JNIEnv *env, jobject callback);
|
||||
|
||||
/* Per-call state to pass between JNI and the reader thread */
|
||||
/* Per-call state to pass between JNI and the reader thread.
|
||||
* We store the callback object (global ref) and method IDs.
|
||||
* Callbacks are invoked via CallVoidMethod, NOT as function pointers. */
|
||||
struct iperf_run_state {
|
||||
JNIEnv *env;
|
||||
jobject callback;
|
||||
jobject callback_ref; /* Global ref to the callback object */
|
||||
jmethodID mid_output; /* Method ID for onOutput(String) */
|
||||
jmethodID mid_error; /* Method ID for onError(String) */
|
||||
jmethodID mid_complete; /* Method ID for onComplete() */
|
||||
FILE *outfile;
|
||||
int pipe_fd[2];
|
||||
JavaVM *jvm;
|
||||
pthread_t reader_thread;
|
||||
output_callback_t on_output;
|
||||
error_callback_t on_error;
|
||||
complete_callback_t on_complete;
|
||||
};
|
||||
|
||||
/* Helper: invoke the output callback via CallVoidMethod */
|
||||
static void signal_output(struct iperf_run_state *state, const char *msg) {
|
||||
if (!state || !msg || !msg[0]) return;
|
||||
JNIEnv *env = state->env;
|
||||
if (!env || !state->mid_output) return;
|
||||
|
||||
jstring jline = (*env)->NewStringUTF(env, msg);
|
||||
if (jline) {
|
||||
(*env)->CallVoidMethod(env, state->callback_ref, state->mid_output, jline);
|
||||
(*env)->DeleteLocalRef(env, jline);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: invoke the error callback via CallVoidMethod */
|
||||
static void signal_error(struct iperf_run_state *state, const char *msg) {
|
||||
if (!state || !msg) return;
|
||||
JNIEnv *env = state->env;
|
||||
if (!env || !state->mid_error) return;
|
||||
|
||||
jstring jmsg = (*env)->NewStringUTF(env, msg);
|
||||
if (jmsg) {
|
||||
(*env)->CallVoidMethod(env, state->callback_ref, state->mid_error, jmsg);
|
||||
(*env)->DeleteLocalRef(env, jmsg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: invoke the complete callback via CallVoidMethod */
|
||||
static void signal_complete(struct iperf_run_state *state) {
|
||||
if (!state || !state->mid_complete) return;
|
||||
JNIEnv *env = state->env;
|
||||
if (!env) return;
|
||||
|
||||
(*env)->CallVoidMethod(env, state->callback_ref, state->mid_complete);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Output reader thread — reads from iperf's output pipe and forwards */
|
||||
/* each line to the Java callback via JNI. */
|
||||
@@ -64,10 +94,6 @@ static void *reader_thread_func(void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
jclass callback_cls = (*env)->GetObjectClass(env, state->callback);
|
||||
jmethodID mid_output = (*env)->GetMethodID(
|
||||
env, callback_cls, "onOutput", "(Ljava/lang/String;)V");
|
||||
|
||||
char buf[1024];
|
||||
FILE *fp = fdopen(state->pipe_fd[0], "r");
|
||||
if (fp) {
|
||||
@@ -77,8 +103,8 @@ static void *reader_thread_func(void *arg) {
|
||||
continue;
|
||||
jstring line = (*env)->NewStringUTF(env, buf);
|
||||
if (line != NULL) {
|
||||
(*env)->CallVoidMethod(env, state->callback,
|
||||
mid_output, line);
|
||||
(*env)->CallVoidMethod(env, state->callback_ref,
|
||||
state->mid_output, line);
|
||||
(*env)->DeleteLocalRef(env, line);
|
||||
}
|
||||
}
|
||||
@@ -133,40 +159,6 @@ static void free_argv(char **argv, int argc) {
|
||||
free(argv);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helper: signal an error through the Java callback. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void signal_error(struct iperf_run_state *state, const char *msg) {
|
||||
if (!state || !msg) return;
|
||||
JNIEnv *env = state->env;
|
||||
if (!env) return;
|
||||
|
||||
if (state->on_error) {
|
||||
jstring jmsg = (*env)->NewStringUTF(env, msg);
|
||||
if (jmsg) {
|
||||
state->on_error(env, state->callback, jmsg);
|
||||
(*env)->DeleteLocalRef(env, jmsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helper: signal output through the Java callback. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static void signal_output(struct iperf_run_state *state, const char *msg) {
|
||||
if (!state || !msg || !msg[0]) return;
|
||||
JNIEnv *env = state->env;
|
||||
if (!env) return;
|
||||
|
||||
if (state->on_output) {
|
||||
jstring jline = (*env)->NewStringUTF(env, msg);
|
||||
if (jline) {
|
||||
state->on_output(env, state->callback, jline);
|
||||
(*env)->DeleteLocalRef(env, jline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* JNI: Start an iperf3 test with the given arguments. */
|
||||
/* Signature: void runIperfLive(String[] args, IperfCallback cb) */
|
||||
@@ -176,18 +168,16 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
|
||||
JNIEnv *env, jobject thiz,
|
||||
jobjectArray arguments, jobject callback) {
|
||||
|
||||
/* Resolve callback methods */
|
||||
jclass callback_cls = (*env)->GetObjectClass(env, callback);
|
||||
jmethodID mid_output = (*env)->GetMethodID(
|
||||
env, callback_cls, "onOutput", "(Ljava/lang/String;)V");
|
||||
jmethodID mid_error = (*env)->GetMethodID(
|
||||
env, callback_cls, "onError", "(Ljava/lang/String;)V");
|
||||
jmethodID mid_complete = (*env)->GetMethodID(
|
||||
env, callback_cls, "onComplete", "()V");
|
||||
/* Create the iperf test object */
|
||||
struct iperf_test *test = iperf_new_test();
|
||||
if (!test) {
|
||||
const char *err = "Failed to create iperf test";
|
||||
LOGE("%s", err);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get JavaVM for thread attachment */
|
||||
JavaVM *jvm;
|
||||
(*env)->GetJavaVM(env, &jvm);
|
||||
iperf_defaults(test);
|
||||
g_test = test;
|
||||
|
||||
/* Convert Java String[] -> char** argv */
|
||||
int argc = 0;
|
||||
@@ -195,6 +185,22 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
|
||||
if (!argv) {
|
||||
const char *err = "Failed to parse arguments";
|
||||
signal_error((struct iperf_run_state *) 0, err);
|
||||
iperf_free_test(test);
|
||||
g_test = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse arguments */
|
||||
if (iperf_parse_arguments(test, argc, argv) < 0) {
|
||||
char errbuf[256];
|
||||
snprintf(errbuf, sizeof(errbuf), "Argument parse error: %s",
|
||||
iperf_strerror(i_errno));
|
||||
free_argv(argv, argc);
|
||||
|
||||
signal_error((struct iperf_run_state *) 0, errbuf);
|
||||
|
||||
iperf_free_test(test);
|
||||
g_test = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -204,11 +210,11 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
|
||||
free_argv(argv, argc);
|
||||
const char *err = "Failed to create output pipe";
|
||||
LOGE("pipe() failed: %s", strerror(errno));
|
||||
if (mid_error) {
|
||||
jstring jmsg = (*env)->NewStringUTF(env, err);
|
||||
(*env)->CallVoidMethod(env, callback, mid_error, jmsg);
|
||||
(*env)->DeleteLocalRef(env, jmsg);
|
||||
}
|
||||
|
||||
signal_error((struct iperf_run_state *) 0, err);
|
||||
|
||||
iperf_free_test(test);
|
||||
g_test = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,63 +225,59 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
|
||||
free_argv(argv, argc);
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
iperf_free_test(test);
|
||||
g_test = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Resolve callback method IDs (do this in the calling thread) */
|
||||
jclass callback_cls = (*env)->GetObjectClass(env, callback);
|
||||
state->mid_output = (*env)->GetMethodID(
|
||||
env, callback_cls, "onOutput", "(Ljava/lang/String;)V");
|
||||
state->mid_error = (*env)->GetMethodID(
|
||||
env, callback_cls, "onError", "(Ljava/lang/String;)V");
|
||||
state->mid_complete = (*env)->GetMethodID(
|
||||
env, callback_cls, "onComplete", "()V");
|
||||
|
||||
/* Get JavaVM for thread attachment */
|
||||
JavaVM *jvm;
|
||||
(*env)->GetJavaVM(env, &jvm);
|
||||
|
||||
state->env = env;
|
||||
state->callback = (*env)->NewGlobalRef(env, callback);
|
||||
state->callback_ref = (*env)->NewGlobalRef(env, callback);
|
||||
state->outfile = fdopen(pipefd[1], "w");
|
||||
state->pipe_fd[0] = pipefd[0];
|
||||
state->jvm = jvm;
|
||||
state->on_output = mid_output
|
||||
? (output_callback_t) mid_output : NULL;
|
||||
state->on_error = mid_error
|
||||
? (error_callback_t) mid_error : NULL;
|
||||
state->on_complete = mid_complete
|
||||
? (complete_callback_t) mid_complete : NULL;
|
||||
|
||||
/* Create the iperf test object */
|
||||
struct iperf_test *test = iperf_new_test();
|
||||
if (!test) {
|
||||
free_argv(argv, argc);
|
||||
free(state);
|
||||
close(pipefd[0]);
|
||||
const char *err = "Failed to create iperf test";
|
||||
LOGE("iperf_new_test() returned NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
iperf_defaults(test);
|
||||
g_test = test;
|
||||
|
||||
/* Redirect iperf's output to our pipe */
|
||||
test->outfile = state->outfile;
|
||||
setvbuf(test->outfile, NULL, _IOLBF, 0); /* Line-buffered */
|
||||
|
||||
/* Parse arguments */
|
||||
if (iperf_parse_arguments(test, argc, argv) < 0) {
|
||||
char errbuf[256];
|
||||
snprintf(errbuf, sizeof(errbuf), "Argument parse error: %s",
|
||||
iperf_strerror(i_errno));
|
||||
/* Notify Java that the test is starting */
|
||||
signal_output(state, "[iPerf JNI] Starting iPerf3 client...\n");
|
||||
|
||||
/* Spawn reader thread to forward iperf output to Java */
|
||||
if (pthread_create(&state->reader_thread, NULL,
|
||||
reader_thread_func, state) != 0) {
|
||||
free_argv(argv, argc);
|
||||
|
||||
signal_error(state, errbuf);
|
||||
|
||||
fclose(test->outfile);
|
||||
close(pipefd[0]);
|
||||
close(pipefd[1]);
|
||||
const char *err = "Failed to start output reader thread";
|
||||
LOGE("%s", err);
|
||||
signal_error(state, err);
|
||||
iperf_free_test(test);
|
||||
g_test = NULL;
|
||||
|
||||
/* Cleanup */
|
||||
free(state);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Notify Java that the test is starting */
|
||||
signal_output(state, "🚀 Initiating iPerf3 client request...\n");
|
||||
|
||||
/* Run the test */
|
||||
g_stop = 0;
|
||||
int ret = iperf_run_client(test);
|
||||
|
||||
/* Wait for reader thread to finish */
|
||||
pthread_join(state->reader_thread, NULL);
|
||||
|
||||
/* Flush and close output */
|
||||
fflush(state->outfile);
|
||||
fclose(state->outfile);
|
||||
@@ -287,25 +289,23 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_runIperfLive(
|
||||
/* Free argv */
|
||||
free_argv(argv, argc);
|
||||
|
||||
/* Signal completion */
|
||||
/* Signal result to Java */
|
||||
if (g_stop) {
|
||||
signal_output(state, "[iPerf JNI] Test was stopped by user.\n");
|
||||
} else if (ret < 0) {
|
||||
char errbuf[256];
|
||||
snprintf(errbuf, sizeof(errbuf), "Test failed: %s",
|
||||
snprintf(errbuf, sizeof(errbuf), "[iPerf JNI] Test failed: %s\n",
|
||||
iperf_strerror(i_errno));
|
||||
signal_error(state, errbuf);
|
||||
} else {
|
||||
signal_output(state,
|
||||
"[iPerf JNI] Test completed successfully.\n");
|
||||
signal_output(state, "[iPerf JNI] Test completed successfully.\n");
|
||||
}
|
||||
|
||||
/* Signal completion to Java */
|
||||
if (state->on_complete) {
|
||||
state->on_complete(env, state->callback);
|
||||
}
|
||||
signal_complete(state);
|
||||
|
||||
/* Free the state struct */
|
||||
/* Free global ref and state struct */
|
||||
(*env)->DeleteGlobalRef(env, state->callback_ref);
|
||||
free(state);
|
||||
}
|
||||
|
||||
@@ -340,21 +340,6 @@ Java_com_novoyuuparosk_iperf3gui_IperfRunner_forceStopIperfTest(
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* JNI: Get elapsed milliseconds since test started. */
|
||||
/* Signature: int getTestProgress() */
|
||||
/* ------------------------------------------------------------------ */
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_novoyuuparosk_iperf3gui_IperfRunner_getTestProgress(JNIEnv *env,
|
||||
jobject thiz) {
|
||||
if (!g_test) return -1;
|
||||
|
||||
/* Get elapsed time in usecs and convert to int ms */
|
||||
/* Note: iperf3 3.19 doesn't expose start_time on iperf_test struct.
|
||||
* We track it locally via a static variable set at test start. */
|
||||
return (jint)(g_test->duration * 1000); /* fallback: return configured duration */
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* JNI: Get whether iperf is currently running. */
|
||||
/* Signature: boolean isTestRunning() */
|
||||
|
||||
@@ -27,7 +27,7 @@ object IperfRunner : FlutterPlugin {
|
||||
channel?.setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"runIperf" -> {
|
||||
val args = call.argument<Array<String>>("args")
|
||||
val args = call.argument<List<String>>("args")?.toTypedArray()
|
||||
if (args != null) {
|
||||
runIperfLive(args, object : IperfCallback {
|
||||
override fun onOutput(line: String) {
|
||||
|
||||
@@ -30,7 +30,7 @@ class _TestRunContent extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TestRunContentState extends State<_TestRunContent> {
|
||||
final _serverController = TextEditingController(text: 'iperf.es.net');
|
||||
final _serverController = TextEditingController(text: 'speedtest.uztelecom.uz');
|
||||
final _portController = TextEditingController(text: '5201');
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user