unity崩溃查找
android崩溃日志收集
-
直接使用logcat日志定位
unity自己会捕获崩溃,然后在 logcat 输出崩溃堆栈,不管是 java、c#、c++ 崩溃都可以
因此只要获得logcat日志,就能获得崩溃堆栈 -
手动转储崩溃日志
主要考虑3个部分:- c# 异常
这一部分已经被unity处理,会通过 Debug.Log 打印,只需要通过 Application.logMessageReceived 捕获日志并写入文件即可
原理参考 捕获全局异常
代码参考 unity日志输出到文件 - java 异常
可以使用 Thread.setDefaultUncaughtExceptionHandler 捕获 java 异常并写入文件中
原理参考 用代码捕获android崩溃日志
代码参考 unity崩溃输出到文件 - c++ 异常
这一部分已经被unity处理,会转成 java 异常抛出,所以不需要额外处理
- c# 异常
ios崩溃日志收集
-
使用自带的崩溃日志
-
手动转储崩溃日志
- IOS只有C++和C#两层,对于C#来说,和Android是一模一样的,不用多说。
对于C++这层,Unity已经为ios提供了一个crash report模块,他需要我们将工程生成好后,将 Classes/Crashreport.h 里面的 ENABLE_CUSTOM_CRASH_REPORTER 设置为1,
这样在c++ crash后,unity会为我们生存crash文件,等下次启动后,可以通过crashreport这个模块访问这些dmp文件,这些dmp文件都是ios上的标准dmp文件,可以使用ios的开发工具symbolicatecrash来查看。
unity内置的crash report其实也是采用了第三方的库plcrashreport来实现的,这个库在ios上的应用很多。 - 另外对于ios,其实也提供了一个函数NSSetUncaughtExceptionHandler ,用来当那些未捕捉的异常发生时,进入这个处理,可以拦截一些东西,
但是一些比如内存访问错误直接就退出了,不会进到这里,另外进到这里之后程序还是会退出,只是让我们可以记录一下,不过有了unity自带的crashreport 这个也就没啥用了。 - 还有在unity对ios的c#运行中,在player setting里面有个对代码的运行优化,选择slow but safe 还是fast but no exception,如果选择前者,所有c#的执行错误都会像脚本一样被catch住,会报c#的exception,
如果后者就不会,就直接不catch了,会造成程序退出,但是运行效率是高的,我们通常选择slow but safe。这是mono架构的特性。
- IOS只有C++和C#两层,对于C#来说,和Android是一模一样的,不用多说。
调用堆栈地址还原成符号
-
符号表介绍
- 首先符号表根据使用的编译参数不同,分为2种,debug 和 public,都能把地址转成函数名,不同的是 debug 会多一些信息,要调试的话只能用 debug 符号表
debug 符号表后缀为 .dbg.so, public 符号表后缀为 .sym.so,一般 unity 引擎只有 public 符号表,你的游戏逻辑会同时生成 debug 和 public 符号表
在使用的时候要移除 .dbg 或 .sym 后缀,比如 libunity.sym.so 改成 libunity.so - 其次debug和release也会生成不同的符号表,所以一共有4种符号表,debug版本的debug、public符号表,release版本的debug、public符号表
- 首先符号表根据使用的编译参数不同,分为2种,debug 和 public,都能把地址转成函数名,不同的是 debug 会多一些信息,要调试的话只能用 debug 符号表
-
首先定位符号表位置
- Android
参考 Unity 用户手册 2020.3 (LTS)/平台开发/Android/构建 Android 应用程序/Android symbols- unity引擎只有public 符号表,位于
/PlaybackEngines/AndroidPlayer/Variations///Symbols//
开启 Strip Engine Code enabled 后,libunity 会生成一个裁剪后的符号表 /Temp/StagingArea/symbols// - 用户代码只有开启 IL2CPP ,才会有符号表
Il2cpp.so 是游戏逻辑编译出来的,其符号表在打包工程时生成- Build Settings 勾选 Create symbols.zip
- 打包后会在 apk 输出目录生成 {应用名称}-{内部版本号}-v{版本号}.symbols.zip,
里面就包含 Il2cpp.so 的 public 和 debug 符号表,以及 libunity 经过代码裁剪后的public符号表
- unity引擎只有public 符号表,位于
- Android
-
然后使用符号表把地址转成函数
假设堆栈是这样的I/DEBUG(242): backtrace: I/DEBUG(242): #00 pc 006d4960 /data/app-lib/com.u.demo-1/libunity.so I/DEBUG(242): #01 pc 006d4c0c /data/app-lib/com.u.demo-1/libunity.so则通过以下命令可以获得 006d4960 对应的函数名称
arm-linux-androideabi-addr2line -f -C -e {unity安装目录}/PlaybackEngines/AndroidPlayer/Variations/{mono或il2cpp}/Release/Symbols/armeabi-v7a/libunity.sym.so 006d4960
-f - Show function names
-C - Demangle function names
-e - Set the input file name
arm-linux-androideabi-addr2line 位于 {NDK安装目录}/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line
unity日志输出到文件
[DefaultExecutionOrder(-10000)]
public class LogToFile : MGSingleton<LogToFile>
{
StreamWriter m_logWritter;
// Start is called before the first frame update
protected override void OnSingletonInit()
{
string nowTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss").Replace(" ", "_").Replace("/", "_").Replace(":", "_");
string logDir = Path.Combine(Application.persistentDataPath, "Log");
string logFilePath = Path.Combine(logDir, nowTime + "_log.txt");
string exceptionFilePath = Path.Combine(logDir, nowTime + "_crash.txt");
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
Debug.Log($"保存日志到文件 {logFilePath}");
m_logWritter = File.CreateText(logFilePath);
// 使用 logMessageReceived ,回调保证在主线程调用
// 使用 logMessageReceivedThreaded,回调会在多线程中调用,必须自己处理多线程问题
Application.logMessageReceived += WriteLog;
#if UNITY_ANDROID && !UNITY_EDITOR
try
{
// 把崩溃日志写入文件
using (AndroidJavaClass exceptionUtil = new AndroidJavaClass("com.MG.AndroidUtil.ExceptionUtil"))
{
exceptionUtil.CallStatic("CatchExceptionToFile", exceptionFilePath);
}
Debug.Log($"设置安卓崩溃日志文件 {exceptionFilePath}");
}
catch(AndroidJavaException e)
{
Debug.LogError(e.ToString());
}
#endif
}
private void WriteLog(string condition, string stackTrace, LogType type)
{
m_logWritter.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] [{type}] {condition}");
m_logWritter.WriteLine(stackTrace);
}
protected override void OnSingletonDestroy()
{
Application.logMessageReceived -= WriteLog;
if ( m_logWritter != null )
{
m_logWritter.Close();
}
}
private void OnApplicationPause(bool pause)
{
if ( pause )
{
// 避免切到后台时,日志写一半
m_logWritter?.Flush();
}
}
}
android崩溃输出到文件
package com.MG.AndroidUtil;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class ExceptionUtil {
static String s_exceptionFile;
static Thread.UncaughtExceptionHandler s_prevHandler = null;
static Thread.UncaughtExceptionHandler s_myHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
// Log.d("ExceptionUtil", "catch expection");
try
{
PrintWriter pw = new PrintWriter(new FileWriter(s_exceptionFile));
throwable.printStackTrace(pw);
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
// 如果要进行一些耗时的异步操作,比如上传日志,则需要延迟进程关闭
// SystemClock.sleep(5000);
s_prevHandler.uncaughtException(thread, throwable);
}
};
public static void CatchExceptionToFile(String file)
{
Log.d("ExceptionUtil", "CatchExceptionToFile "+file);
s_exceptionFile = file;
UnityPlayer.currentActivity.runOnUiThread(()->{
if ( s_prevHandler != null )
return ;
s_prevHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(s_myHandler);
});
}
}
Unity崩溃日志收集与分析
本文详细介绍了如何收集和分析Unity游戏引擎在Android和iOS平台上的崩溃日志。包括使用logcat收集Android崩溃日志,设置Unity的日志输出到文件,以及如何将堆栈地址还原成可读的函数名称。在iOS上,Unity提供了内置的崩溃报告模块来处理C++和C#异常。同时,文章还讲解了如何处理Android端的C#、Java异常,并提供了相应的代码参考。
2254

被折叠的 条评论
为什么被折叠?



