Android JNI 嵌入 Wasmtime:从 SIGILL 崩溃到稳定运行配置与日志重定向
第一部分:问题复盘与解决方案 (From Zero to Hero)
我们按时间线梳理了遇到的 3 个核心问题:
1. Libc SIGILL (Fatal signal 4) 崩溃
- 现象:App 刚运行到函数调用就闪退,报错
ILL_ILLOPC(非法指令)。 - 原因:
- 信号冲突:Wasmtime 默认使用系统信号 (SIGBUS/SIGSEGV) 来处理内存越界,这与 Android 的 ART 虚拟机信号处理冲突。
- JIT 激进优化:默认生成的 ARM64 机器码可能包含你手机 CPU 不支持的指令(如 SIMD 或 BTI)。
- 解决方案:
signals_based_traps = false:关键修复,强制使用显式的if判断做边界检查,不依赖系统信号。opt_level = NONE:关闭优化,生成最保守的代码。simd = false:关闭向量指令,保证兼容性。
2. Wasm Trap: null reference
- 现象:不闪退了,但报错
Trap,提示空指针。 - 原因:Kotlin 运行时未初始化。Wasm 模块只是加载了,但 Kotlin 的 GC 和内存分配器还没启动。CLI 会自动运行
_start,但嵌入式 API 需要手动调用。 - 解决方案:
- 在调用
add之前,手动查找并调用_initialize(Reactor模式) 或_start(Command模式)。
- 在调用
3. 看不到 println 日志
- 现象:运行成功返回
3,但 Logcat 空空如也。 - 原因:WASI 的标准输出 (stdout) 默认指向
/dev/null。 - 解决方案:
- 使用
wasi_config_set_stdout_custom注册回调函数,将 stdout 数据流“偷”出来,通过__android_log_print转发给 Logcat。
- 使用
第二部分:配置影响分析 (Trade-offs)
为了修复 SIGILL 和崩溃,我们使用了一些“保守”的配置。以下是这些修改带来的具体影响:
1. 关闭信号陷阱 (signals_based_traps = false)
- 影响:性能略微下降,但稳定性极大提升。
- 解释:
- 默认模式:Wasmtime 假定内存访问是安全的,不做检查。如果越界,依靠操作系统抛出信号 (Hardware Trap) 来捕获。这很快,但在 Android 这种对信号管控严格的环境极易崩溃。
- 当前模式:Wasmtime 会在每一条涉及内存读写的指令前,插入一段汇编代码(类似
if (addr > max) throw error)。这增加了 CPU 指令数量,但确保了绝对安全,不会导致 App 闪退,只会返回可捕获的 Trap 错误。
2. 关闭优化 (opt_level = NONE)
- 影响:JIT 编译速度变快,但 Wasm 执行速度变慢。
- 解释:编译器不再花时间做循环展开、寄存器分配优化等高级操作。对于
add这种简单函数没感觉,但如果是复杂的算法(如图像处理),性能可能会比开启优化慢 2-5 倍。 - 未来优化:等环境稳定后,可以尝试改回
WASMTIME_OPT_LEVEL_SPEED,只要保留signals_based_traps = false,或许能跑通的。
3. 关闭 SIMD (simd = false)
- 影响:无法利用硬件加速进行并行计算。
- 解释:如果你的 Wasm 模块(比如图像滤镜、加密库)大量使用了向量指令,关闭此选项会导致模块加载失败或回退到标量计算(变慢)。但对于业务逻辑(如 Kotlin UI、数据处理),几乎没有影响。
总结
当前的配置是 “兼容性优先 (Stability First)” 的方案。它牺牲了一点点运行时性能,换取了在各种 Android 机型(无论新旧 CPU、无论系统信号机制如何)上都能不闪退、稳定运行的保障。对于初期集成和调试来说,这是最完美的起步点。
第三部分:优化后的完整 C++ 代码
我对代码进行了重构,特点如下:
- 增加配置开关:JNI 函数增加
jboolean enableLogs参数,由 Kotlin 控制是否开启日志转发。 - 结构清晰:将配置、初始化、调用分块注释。
- 内存安全:确保所有 JNI 资源和 Wasm 对象都被正确释放。
1. 修改 Kotlin 定义 (MainActivity.kt)
// 增加一个 boolean 参数控制日志
private external fun runWasmAdd(wasmBytes: ByteArray, enableLogs: Boolean): Int
// 调用时
runWasmAdd(wasmBytes, true) // 开启日志
2. C++ 实现 (wasm_host.cpp)
#include <jni.h>
#include <android/log.h>
#include <vector>
#include <string>
// Wasmtime Headers
#include "wasm.h"
#include "wasi.h"
#include "wasmtime.h"
#define TAG "KotlinWasm"
// 简化的日志宏
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
// ============================================================================
// 工具函数:错误打印
// ============================================================================
static void log_and_exit_error(const char *message, wasmtime_error_t *error, wasm_trap_t *trap) {
wasm_byte_vec_t error_message;
if (error != nullptr) {
wasmtime_error_message(error, &error_message);
wasmtime_error_delete(error);
LOGE("ERROR: %s -> %.*s", message, (int)error_message.size, error_message.data);
} else if (trap != nullptr) {
wasm_trap_message(trap, &error_message);
wasm_trap_delete(trap);
LOGE("TRAP: %s -> %.*s", message, (int)error_message.size, error_message.data);
} else {
LOGE("UNKNOWN ERROR: %s", message);
}
if (error || trap) wasm_byte_vec_delete(&error_message);
}
// ============================================================================
// 回调函数:WASI stdout -> Android Logcat
// ============================================================================
ptrdiff_t android_log_callback(void* data, const unsigned char* buffer, size_t size) {
if (size == 0) return 0;
// 构造字符串并移除末尾换行符(Logcat 自带换行)
std::string msg((const char*)buffer, size);
while (!msg.empty() && (msg.back() == '\n' || msg.back() == '\r')) {
msg.pop_back();
}
if (!msg.empty()) {
__android_log_print(ANDROID_LOG_INFO, TAG, "[WasmOut] %s", msg.c_str());
}
return size;
}
// ============================================================================
// JNI 主入口
// ============================================================================
extern "C" JNIEXPORT jint JNICALL
Java_crow_wasmedge_wasmline_MainActivity_runWasmAdd(JNIEnv *env, jobject thiz, jbyteArray wasmBytes, jboolean enableLogs) {
// --- 1. 获取 Wasm 字节数据 ---
jsize len = env->GetArrayLength(wasmBytes);
jbyte *bytes = env->GetByteArrayElements(wasmBytes, nullptr);
if (!bytes) return -1;
// --- 2. 配置 Wasmtime (The Silver Bullet Config) ---
wasm_config_t *config = wasm_config_new();
// 开启 Kotlin 所需特性
wasmtime_config_wasm_gc_set(config, true);
wasmtime_config_wasm_function_references_set(config, true);
wasmtime_config_wasm_exceptions_set(config, true);
// 解决 Android SIGILL 崩溃的核心配置
wasmtime_config_cranelift_opt_level_set(config, WASMTIME_OPT_LEVEL_NONE); // 关闭优化
wasmtime_config_wasm_simd_set(config, false); // 关闭 SIMD
wasmtime_config_wasm_relaxed_simd_set(config, false); // 关闭 Relaxed SIMD
wasmtime_config_signals_based_traps_set(config, false); // 关闭信号 Trap,改用显式检查
// --- 3. 创建 Engine & Store ---
wasm_engine_t *engine = wasm_engine_new_with_config(config);
if (!engine) {
env->ReleaseByteArrayElements(wasmBytes, bytes, JNI_ABORT);
LOGE("Failed to create engine");
return -1;
}
wasmtime_store_t *store = wasmtime_store_new(engine, nullptr, nullptr);
wasmtime_context_t *context = wasmtime_store_context(store);
// --- 4. 配置 WASI (含日志重定向) ---
wasi_config_t *wasi_config = wasi_config_new();
wasi_config_inherit_argv(wasi_config);
wasi_config_inherit_env(wasi_config);
if (enableLogs) {
// 如果启用,挂载回调函数
wasi_config_set_stdout_custom(wasi_config, android_log_callback, nullptr, nullptr);
wasi_config_set_stderr_custom(wasi_config, android_log_callback, nullptr, nullptr);
}
wasmtime_error_t *error = wasmtime_context_set_wasi(context, wasi_config);
if (error) {
log_and_exit_error("WASI Config Error", error, nullptr);
return -1;
}
// --- 5. 编译与实例化 ---
wasmtime_linker_t *linker = wasmtime_linker_new(engine);
wasmtime_linker_define_wasi(linker);
wasmtime_module_t *module = nullptr;
error = wasmtime_module_new(engine, (uint8_t *)bytes, len, &module);
// 释放 Java 数组,节省内存
env->ReleaseByteArrayElements(wasmBytes, bytes, JNI_ABORT);
if (error) {
log_and_exit_error("Compile Error", error, nullptr);
return -1;
}
wasmtime_instance_t instance;
wasm_trap_t *trap = nullptr;
error = wasmtime_linker_instantiate(linker, context, module, &instance, &trap);
if (error || trap) {
log_and_exit_error("Instantiate Error", error, trap);
return -1;
}
// --- 6. 核心步骤:初始化 Kotlin 运行时 (_initialize / _start) ---
// 这是解决 null reference 的关键
wasmtime_extern_t init_extern;
wasmtime_func_t init_func;
bool needs_init = false;
if (wasmtime_instance_export_get(context, &instance, "_initialize", 11, &init_extern) &&
init_extern.kind == WASM_EXTERN_FUNC) {
init_func = init_extern.of.func;
needs_init = true;
} else if (wasmtime_instance_export_get(context, &instance, "_start", 6, &init_extern) &&
init_extern.kind == WASM_EXTERN_FUNC) {
init_func = init_extern.of.func;
needs_init = true;
}
if (needs_init) {
error = wasmtime_func_call(context, &init_func, nullptr, 0, nullptr, 0, &trap);
if (error || trap) {
// _start 可能会因为 exit(0) 而 trap,这里简单处理,如果是严重错误才退出
// 实际生产中可能需要检查 trap message 是否包含 "exit"
log_and_exit_error("Runtime Init Error (Ignorable if exit code 0)", error, trap);
}
}
// --- 7. 调用目标函数 (add) ---
int32_t result_val = -1;
wasmtime_extern_t run_extern;
if (wasmtime_instance_export_get(context, &instance, "add", 3, &run_extern) &&
run_extern.kind == WASM_EXTERN_FUNC) {
wasmtime_func_t func_obj = run_extern.of.func;
// 参数:1, 2
wasmtime_val_t args[2];
args[0].kind = WASMTIME_I32; args[0].of.i32 = 1;
args[1].kind = WASMTIME_I32; args[1].of.i32 = 2;
wasmtime_val_t results[1];
error = wasmtime_func_call(context, &func_obj, args, 2, results, 1, &trap);
if (error || trap) {
log_and_exit_error("Function Call Error", error, trap);
} else {
if (results[0].kind == WASMTIME_I32) {
result_val = results[0].of.i32;
if (enableLogs) LOGD("Success! Result: %d", result_val);
}
}
} else {
LOGE("Export 'add' not found");
}
// --- 8. 清理资源 ---
wasmtime_module_delete(module);
wasmtime_linker_delete(linker);
wasmtime_store_delete(store);
wasm_engine_delete(engine);
return result_val;
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 crowforkotlin!
评论


