2020-12-03 Unity Crash定位

2020-12-03  本文已影响0人  VECTOR_Y

转自:https://zhuanlan.zhihu.com/p/77984555

最近做的项目上线之后崩溃率比较高,测试中没发现问题

后台收到的崩溃日志大概这样,我是懵逼的,网上查询看到了上面知乎上大神的方式,可以通过符号表来编译这些错误信息,就可以定位到Crash的位置


image.png

大神列举的两个官方文章
https://support.unity3d.com/hc/en-us/articles/115000292166-Symbolicate-Android-crash
https://support.unity3d.com/hc/en-us/articles/115000292166-Symbolicate-Android-crash

下面是我的尝试
我们再打包的时候需要勾选下图Shmbols选项,这样打包的时候会同时生成符号表压缩包,我理解的打包中会做混淆,符号表相当于我们的密码本,我们用这个才能反射错误信息中的内容


image.png

下图为生成的压缩包


image.png

我们先找到arm-linux-androideabi-addr2line,找到自己的Ndk位置就好

image.png
或者通过下面的链接下载
https://developer.android.com/ndk/downloads?hl=en

我们首先尝试用cmd来尝试一下

image.png
命令行,需要根据自己的 路径修改
"C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\aarch64-linux-android-addr2line.exe" -f -C -e "D:\git\greedysnake\Snake3D\build\snake-0.1-v1.symbols\arm64-v8a\libil2cpp.sym.so" 0x4d3190
反射的结果
RuntimeInvoker_FalseIntPtr_t_RuntimeObject_IntPtr_t_IntPtr_t(void ()(), MethodInfo const, void, void*)

下面贴上大神的代码,做了一点点修改


        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.TextField("名称", name, GUILayout.MaxWidth(400));
        GUILayout.Space(10);
        if (GUILayout.Button("解析", GUILayout.Width(50)))
        {
            if (!JudgePath(PathType.addr2Line,addr2linePath))
            {
                Debug.LogError("Ndk解析路径出错");
                return;
            }
            if (!JudgePath(PathType.unitySoPath, unitydebugsoPath) && !JudgePath(PathType.il2cppSoPath, il2cppdebugsoPath))
            {
                Debug.LogError("unity与il2cppSoPanth符合表路径出错");
                return;
            }
            if (!JudgePath(PathType.il2cppSoPath, il2cppdebugsoPath))
            {
                Debug.LogError("il2cppSoPanth符合表路径出错");
            }
            OutCrash(name,path);
        } 
        EditorGUILayout.EndHorizontal();
    }

    /// <summary>
    /// 根据获取Crash文件的文件创建Button与显示框
    /// </summary>
    /// <param name="path"></param>
    void GetCrashByPath(string path)
    {
        if (Directory.Exists(path))
        {
            var dirctory = new DirectoryInfo(path);
            var files = dirctory.GetFiles("*", SearchOption.AllDirectories);
            foreach (var fi in files)
            {
                CreatorButton(fi.Name, path);
            }
        }
    }

    /// <summary>
    /// 打开Crash
    /// </summary>
    void OutCrash(string filename,string path)
    {
        isAnalysis = false;
        string filePath = string.Join("/",path,filename);
        using (StreamReader sr =new StreamReader(filePath))
        {
            while (!sr.EndOfStream)
            {
                OutCmd(sr.ReadLine());
            }
        }
        if (!isAnalysis)
        {
            Debug.LogError("无法解析当前cash文件,请检查文件是否为设备崩溃日志");
        }
    }

    /// <summary>
    /// 解析Crash
    /// </summary>
    void OutCmd(string log)
    {
        if (log==null)
        {
            return;
        }       
        if (log.EndsWith(crashEndFlag))//找以libunity.so结尾的崩溃日志
        {
            if (log.Contains("pc"))
            {
                int startIndex = log.IndexOf("pc") + 3;
                if (log.Contains("/data/"))
                {
                    int endIndex = log.IndexOf("/data/");
                    string addStr = log.Substring(startIndex, endIndex - startIndex - 1);
                    string tempUnitySoPath = string.Format("\"{0}\"", unitydebugsoPath);
                    ExecuteCmd(tempUnitySoPath, addStr);
                }     
            } 
        }
        else//找 il2cpp和libunity 崩溃日志
        {
            if (log.Contains(il2cppflag) && JudgePath(PathType.il2cppSoPath,il2cppdebugsoPath))
            {
                string tempill2cppSoPath = string.Format("\"{0}\"", il2cppdebugsoPath);
                FindMiddleCrash(log, il2cppflag, tempill2cppSoPath);
            } else if(log.Contains(unityflag))
            {
                string tempUnitySoPath = string.Format("\"{0}\"", unitydebugsoPath);
                FindMiddleCrash(log,unityflag, tempUnitySoPath);
            }
        }
    }

    /// <summary>
    /// 找 il2cpp和libunity 崩溃日志
    /// </summary>
    /// <param name="log"></param>
    /// <param name="debugFlag">标志元素</param>
    /// <param name="SoPath">符号表路径</param>
    void FindMiddleCrash(string log,string debugFlag,string SoPath)
    {
        if (!string.IsNullOrEmpty(SoPath))
        {
            int startIndex = log.IndexOf(debugFlag);
            startIndex = startIndex + debugFlag.Length + 1;
            if (log.Contains("("))
            {
                int endIndex = log.IndexOf("(");
                if (endIndex > 0)
                {
                    string addStr = log.Substring(startIndex, endIndex - startIndex);
                    ExecuteCmd(SoPath, addStr);
                }
            }
        }
        else
        {
            Debug.LogErrorFormat("{0}的符号表路径为空",debugFlag);
        }
        
    }

    
    /// <summary>
    /// 执行CMD命令
    /// </summary>
    /// <param name="SoPath">符号表路径</param>
    /// <param name="addStr">崩溃代码地址</param>
    void ExecuteCmd(string soPath, string addStr)
    {
        string cmdStr = string.Join(" ", addr2linePath, "-f", "-C", "-e", soPath, addStr);
        CmdHandler.RunCmd(cmdStr, (str) =>
        {
           Debug.Log(string.Format("解析后{0}", ResultStr(str, addStr)));
            isAnalysis = true;
        });

    }
    /// <summary>
    /// 对解析结果进行分析
    /// </summary>
    /// <param name="str"></param>
    /// <param name="addStr"></param>
    /// <returns></returns>
    string ResultStr(string str,string addStr)
    {
        string tempStr = string.Empty;
        if (!string.IsNullOrEmpty(str))
        {
            if (str.Contains("exit"))
            {
                int startIndex = str.IndexOf("exit");
                if (startIndex < str.Length)
                {
                    tempStr = str.Substring(startIndex);
                    if (tempStr.Contains(")"))
                    {
                        startIndex = tempStr.IndexOf("t") + 1;
                        int endIndex = tempStr.LastIndexOf(")");
                        tempStr = tempStr.Substring(startIndex, endIndex - startIndex + 1);
                        tempStr = string.Format("<color=red>[{0}]</color> :<color=yellow>{1}</color>", addStr, tempStr);
                    }
                    else
                    {
                        startIndex = tempStr.IndexOf("t") + 1;
                        tempStr = tempStr.Substring(startIndex);
                        tempStr = string.Format("<color=red>[{0}]</color> :<color=yellow>{1}</color>", addStr, tempStr);
                    }
                    
                }
            }
            else
            {
                Debug.LogErrorFormat("当前结果未执行cmd命令", str);
            }
        }
        else
        {
            Debug.LogErrorFormat("执行cmd:{0}命令,返回值为空", str);
        }
        return tempStr;     
    }

    private void OnDestroy()
    {
        EditorPrefs.SetString("addr2linePath", addr2linePath);
        EditorPrefs.SetString("il2cppdebugsoPath", il2cppdebugsoPath);
        EditorPrefs.SetString("unitydebugsoPath", unitydebugsoPath);
        EditorPrefs.SetString("MyCashPath", MyCashPath);
    }


}
using System;
using System.Collections.Generic;
using System.Diagnostics;
public class CmdHandler
{
    private static string CmdPath = "cmd.exe";
    //C:\Windows\System32\cmd.exe
    /// <summary>
    /// 执行cmd命令 返回cmd窗口显示的信息
    /// 多命令请使用批处理命令连接符:
    /// <![CDATA[
    /// &:同时执行两个命令
    /// |:将上一个命令的输出,作为下一个命令的输入
    /// &&:当&&前的命令成功时,才执行&&后的命令
    /// ||:当||前的命令失败时,才执行||后的命令]]>
    /// </summary>
    /// <param name="cmd">执行的命令</param>
    public static string RunCmd(string cmd,Action <string>act=null)
    {
        cmd = cmd.Trim().TrimEnd('&') + "&exit";//说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态
        using (Process p = new Process())
        {
            p.StartInfo.FileName = CmdPath;
            p.StartInfo.UseShellExecute = false;        //是否使用操作系统shell启动
            p.StartInfo.RedirectStandardInput = true;   //接受来自调用程序的输入信息
            p.StartInfo.RedirectStandardOutput = true;  //由调用程序获取输出信息
            p.StartInfo.RedirectStandardError = true;   //重定向标准错误输出
            p.StartInfo.CreateNoWindow = true;          //不显示程序窗口
            p.Start();//启动程序

            //向cmd窗口写入命令
            p.StandardInput.WriteLine(cmd);
            p.StandardInput.AutoFlush = true;

            //获取cmd窗口的输出信息
            string output = p.StandardOutput.ReadToEnd();
            p.WaitForExit();//等待程序执行完退出进程
            p.Close();
            if (act!=null)
            {
                act(output);
            }
            return output;
        }
    }

    /// <summary>
    /// 执行多个cmd命令
    /// </summary>
    /// <param name="cmdList"></param>
    /// <param name="act"></param>
    public static void RunCmd(List<string> cmd, Action<string> act = null)
    {
        //cmd = cmd.Trim().TrimEnd('&') + "&exit";//说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态
        using (Process p = new Process())
        {
            p.StartInfo.FileName = CmdPath;
            p.StartInfo.UseShellExecute = false;        //是否使用操作系统shell启动
            p.StartInfo.RedirectStandardInput = true;   //接受来自调用程序的输入信息
            p.StartInfo.RedirectStandardOutput = true;  //由调用程序获取输出信息
            p.StartInfo.RedirectStandardError = true;   //重定向标准错误输出
            p.StartInfo.CreateNoWindow = true;          //不显示程序窗口
            p.Start();//启动程序

            //向cmd窗口写入命令
            foreach (var cm in cmd)
            {
                p.StandardInput.WriteLine(cm);
                p.StandardInput.WriteLine("exit");
                p.StandardInput.AutoFlush = true;
                //获取cmd窗口的输出信息
                string output = p.StandardOutput.ReadToEnd();
                if (act != null)
                {
                    act(output);
                }
                p.Start();
            }

            p.WaitForExit();//等待程序执行完退出进程
            p.Close();
        }
    }
}

效果如下


image.png image.png image.png

确实定位到了代码中的问题,真的是非常感谢,这里抄一下以防以后忘掉

上一篇下一篇

猜你喜欢

热点阅读