2020-12-03 Unity Crash定位

转自: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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容