Flutter 引擎崩溃分析方法简介

Android 项目信息 Flutter 后上线,偶尔会遇到一些 Flutter 引擎的崩溃。简单记录下如何还原引擎崩溃日志,方便在源码中定位问题。

Flutter Engine Crash 堆栈还原

首先,在 bin/internal/engine.version 找到 flutter engine revision

1
b851c718295a896918dc93cb1ff14f2f895a1b90

第二步,下载带符号表的 libflutter.so 文件

1
https://console.cloud.google.com/storage/browser/flutter_infra/flutter/b851c718295a896918dc93cb1ff14f2f895a1b90

第三步,利用 ndk-stack 还原堆栈信息

1
/path/to/android-ndk-r16b/ndk-stack -sym /path/to/symbols/armeabi-v7a -dump /path/to/stacktrace.txt

如果是将 crash 上报到 bugly 平台,则不必使用 ndk-stack,直接在 bugly 上配置符号表即可。

配置成功后就能在 bugly 平台上看到 native crash 堆栈信息。

原始堆栈信息

还原后的堆栈信息

拿到还原后的堆栈信息,就可以对着源码分析问题了。

一个疑问

官方提到每个 libflutter.so 文件中有一个 build id,native crash 堆栈中见到的 BuildId 要跟 libflutter.so 的匹配,否则没法还原 native crash 堆栈信息。原文如下:

The build system sets a build id for each libflutter.so file. In the tombstones, you would see the ID like so:

1
#00 pc 000000000062d6e0  /data/app/com.app-tARy3eLH2Y-QN8J0d0WFog==/lib/arm64/libflutter.so!libflutter.so (offset 0x270000) (BuildId: 34ad5bdf0830d77a)

This equals to a build id of 34ad5bdf0830d77a. The libflutter.so debug files downloaded as shown above could be verified using the file command:

1
2
% file ~/Downloads/libflutter.so
/Users/user/Downloads/libflutter.so: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, BuildID[xxHash]=34ad5bdf0830d77a, with debug_info, not stripped

Ensure the build IDs match, else you will not be able to symbolicate.

不过我往 bugly 上传了一个非严格匹配的符号表(上报 crash 的 app 使用的是自己编译的 libflutter.so,符号表是官方提供的 libflutter.so),也能还原出来。让人很是疑惑。

观察了下,bugly 上看到的 native crash 日志中并没有找到类似 (BuildId: 34ad5bdf0830d77a) 这样的信息。推测由此导致匹配变得不严格,所以也能还原出来。

实例

某个 native crash 还原后得到如下堆栈信息。

首先看到的是 message_loop_impl.cc 中的 MessageLoopImpl::RunExpiredTasksNow。这个方法会立即执行任务队列中的已过期任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void MessageLoopImpl::FlushTasks(FlushType type) {
TRACE_EVENT0("fml", "MessageLoop::FlushTasks");
std::vector<fml::closure> invocations;

task_queue_->GetTasksToRunNow(queue_id_, type, invocations);

for (const auto& invocation : invocations) {
invocation();
std::vector<fml::closure> observers =
task_queue_->GetObserversToNotify(queue_id_);
for (const auto& observer : observers) {
observer();
}
}
}

void MessageLoopImpl::RunExpiredTasksNow() {
FlushTasks(FlushType::kAll);
}

接下来看到的是 platform_view_android_jni.cc 中的 FlutterViewUpdateSemantics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

void FlutterViewUpdateSemantics(JNIEnv* env,
jobject obj,
jobject buffer,
jobjectArray strings) {
// 调用 g_update_semantics_method (Java 层方法)
env->CallVoidMethod(obj, g_update_semantics_method, buffer, strings);
// 检查 Java 代码是否发生异常,有异常则写入 fml 日志中
FML_CHECK(CheckException(env));
}

bool RegisterApi(JNIEnv* env) {
// g_update_semantics_method 对应的是
// Java 层中的 FlutterJNI.updateSemantics() 方法
g_update_semantics_method =
env->GetMethodID(g_flutter_jni_class->obj(), "updateSemantics",
"(Ljava/nio/ByteBuffer;[Ljava/lang/String;)V");
...
return true;
}

// 检查 Java 代码是否发生异常
// 发生异常时返回 false,否则返回 true
bool CheckException(JNIEnv* env) {
if (env->ExceptionCheck() == JNI_FALSE)
return true;

jthrowable exception = env->ExceptionOccurred();
env->ExceptionClear();
FML_LOG(ERROR) << fml::jni::GetJavaExceptionInfo(env, exception);
env->DeleteLocalRef(exception);
return false;
}

最后看到的是 logging.cc 第92行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
LogMessage::~LogMessage() {
stream_ << std::endl;

#if defined(OS_ANDROID)
android_LogPriority priority =
(severity_ < 0) ? ANDROID_LOG_VERBOSE : ANDROID_LOG_UNKNOWN;
switch (severity_) {
case LOG_INFO:
priority = ANDROID_LOG_INFO;
break;
case LOG_WARNING:
priority = ANDROID_LOG_WARN;
break;
case LOG_ERROR:
priority = ANDROID_LOG_ERROR;
break;
case LOG_FATAL:
priority = ANDROID_LOG_FATAL;
break;
}
__android_log_write(priority, "flutter", stream_.str().c_str());
#elif defined(OS_IOS)
syslog(LOG_ALERT, "%s", stream_.str().c_str());
#else
std::cerr << stream_.str();
std::cerr.flush();
#endif

if (severity_ >= LOG_FATAL) {
// 最终的 crash 由这里产生
abort();
}
}

初看起来可能会有点不明白 crash 如何产生,梳理一下就渐渐明了:

  • 调用 RunExpiredTasksNow 立即执行队伍中的过期任务
  • 过期任务中的某一个任务调用了 FlutterViewUpdateSemantics() (C++层)
  • FlutterViewUpdateSemantics() 调用 FlutterJNI.updateSemantics() (Java层)
  • FlutterViewUpdateSemantics() 调用 CheckException() 检查 Java 代码执行后是否产生异常
  • FlutterViewUpdateSemantics() 调用 FML_CHECK() 记录错误日志 (如有 Java 异常)

所以我们可以把怀疑目标锁定到 FlutterJNI.updateSemantics() 方法。剩下的工作就是进入 Java 层分析该方法的出错原因了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Keep
public class FlutterJNI {
...

/**
* Invoked by native to send semantics tree updates from Flutter to Android.
*
* The {@code buffer} and {@code strings} form a communication protocol that is implemented here:
* https://github.com/flutter/engine/blob/master/shell/platform/android/platform_view_android.cc#L207
*/
@SuppressWarnings("unused")
@UiThread
private void updateSemantics(@NonNull ByteBuffer buffer, @NonNull String[] strings) {
ensureRunningOnMainThread();
if (accessibilityDelegate != null) {
accessibilityDelegate.updateSemantics(buffer, strings);
}
// TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
}

...
}

参考