linux定位so快捷方式_unity crash 的快速定位

因为最近做的都是和unity作为一个库嵌入原生app的项目,一直以来饱受unity crash的折磨。中间碰到过很多崩溃,都是之前做游戏没有碰到过的,大多都是兼容性问题,还有一些是和原生层通信的方式不对导致的。不管是那种情况,都很让人崩溃,也由此熟练了crash日志定位的操作,今天在这里分享一下。

首先untiy的日志分两种:

1.如果我们打包的时候勾选了 development build选项

28034e55806adff615a9774c3d26ef4f.png

那么崩溃的时候,我们基本上可以在日志里面直接看到unity的代码输出,这里不多做表述。

2.第二种情况就是我们最常见的情况了,因为我们上线的时候是不可能输出调试的包的,这时候崩溃我们看到的一堆地址,类似

3f115cee62c95ae931943ea061700fdd.png

第一反应就是 “什么鬼”。

OK,接下来我们就详细说下这种情况怎么处理

1.我们先说下如何导出这种日志,大多数时候崩溃是发生在测试的手机或者设备上面,我们不可能时刻观察他的日志输出,而且大多时候崩溃内容不会显示在控制台,通常会惯性几连发

测试同学:某某某,这个东西又崩溃了,赶紧过来看一下

你:你做了什么异常操作吗?

测试同学:没有

你:有错误输出吗?

测试同学:没有

你:。。。。。一脸懵逼 那看个毛,啥信息也没有

测试同学:可是崩溃了

你:。。。。。。无言以对

没错,此时的你束手无措,可是这就是你的锅,因为程序崩溃了,尤其是unity作为表现层,可能崩溃的是其他sdk的库或者是系统相关的,但是表现就是画面卡了,然后锅就是我们的,这可能就是作为前端开发的悲哀吧。好了,废话不多说,接下来我就来说下大家怎么甩锅吧

首先,使用adb命令把崩溃日志输出,命令如下:

adb shell dumpsys dropbox --print > log-crash.txt

当然你要配置adb为环境变量,这是基本操作,不再赘述

2.这时候你就拿到了崩溃文件,通常操作是直接拉到文件最下方,文件通常是这样

03f7d0843ed2ece53fcfaedc1cef766d.png

大家如果看到unity或者包含il2cpp,不好意思,是你的锅没跑了,如果是下图这样

3c0d4dfc162fc9f2a7a49521bbfeebd3.png

那么恭喜你,你可以甩锅了。。。

3.当我们清楚了是谁的锅之后,我们就可以根据文件内容定位问题了,那么首先请参考官方的两篇文章

Symbolicate Android crash​support.unity3d.com Unity - Manual: Crashes​docs.unity3d.com

还有一个补充的点就是,如果你打包的版本是il2cpp的,那么最新的unity(我用的是2018.4的版本)已经把符号表在打包工程的时候,自动打包成一个压缩包同时输出了,我们就不用费心的去找符号表了,如下图

2e5537880c051a3b3e67dae9e316630a.png

解压就可以看到我们的符号表

b6baef11308171934e737c94dd7f3cdf.png

通常到这一步,我们已经可以根据第一个链接的方法去根据符号表反射出unity的代码了,这时候就可以分析是什么原因了。

有好多人可能不知道 arm-linux-androideabi-addr2line 这个东西是从哪里来的

51ab01898c1c09c00fb2630556056f14.png

就是这个东西,他是ndk里面包含的东西,大家下载后ndk之后解压就能找到了,这里给出ndk的下载地址

https://developer.android.com/ndk/downloads?hl=en​developer.android.com

然后解压后找到路径 ,我的电脑上是在

28a0d0b559d73a955d85259966674fa6.png

这个路径在后面自动化输出的时候还会用到。我这里做一个最简单的演示

2a848cef1f5e766406745e1ef815929b.png

4.但是这时候有一个很恶心的问题,就是日志可能有几十行,如果我们要看完整的堆栈,我们就要一行一行输出,这当然不是一个程序员该做的事情,我们当然要自动化。我根据自己经常碰到的情况,写了个脚本,分别处理两种情况,一种是以libuntiy.so结尾的崩溃如图

b646a5dd00ba060be5a21e81fa43b9e2.png

还有一种就是libunity和il2cpp在中间的如图

401c78cd65049680230d09849e914373.png

OK,上代码

import sys
import os

def OutCrash(filename):
	
	#addr2line 路径 (要替换成自己电脑的路径)
	addr2linePath = r'''D:android-ndk-r13b-windows-x86_64android-ndk-r13btoolchainsaarch64-linux-android-4.9prebuiltwindows-x86_64binaarch64-linux-android-addr2line.exe -f -C -e '''
	#libil2cpp.so.debug 路径 (要替换成自己电脑的路径)
	il2cppdebugsoPath = r''' "E:workobexMainService-0.1-v1.symbols (2)armeabi-v7alibil2cpp.so.debug" '''
	#unity.so.debug 路径 (要替换成自己电脑的路径)
	unitydebugsoPath = r''' "C:Program FilesUnity201841EditorDataPlaybackEnginesAndroidPlayerVariationsmonoReleaseSymbolsarmeabi-v7alibunity.sym.so" '''
	
	f = open(filename,'r')
	logstr = f.readlines()
	il2cppflag = 'libil2cpp'
	unityflag = 'libunity'
	crashEndFlag = 'libunity.son'
	for log in logstr:
		OutCmd(log,addr2linePath,crashEndFlag,unitydebugsoPath)
		OutCmd(log,addr2linePath,il2cppflag,il2cppdebugsoPath)
		OutCmd(log,addr2linePath,unityflag,unitydebugsoPath)

def OutCmd(log,addr2linePath,debugFlagStr,debugsoPath):
	
	if log.endswith(debugFlagStr):
		#找以libunity.so结尾的崩溃日志
		startIndex = log.index(' pc ')
		endflag = log.index(r' /data/')
		addstr = log[startIndex+4:endflag]
		print(addstr)
		cmdstr = addr2linePath +debugsoPath+addstr
		os.system(cmdstr)
	else:
		#查找 il2cpp和libunity 崩溃日志
		unitystart = log.find(debugFlagStr)
		if unitystart >= 0:
			unitylen = log.index(debugFlagStr)
			unitylen = unitylen + len(debugFlagStr) +1
			endlen = log.find('(')
			if endlen >= 0:
				endIndex = log.index('(') 
				addstr = log[unitylen:endIndex]
				addstr = addstr.replace(' ','')
		
				cmdstr = addr2linePath + debugsoPath +addstr
				print(addstr)
				os.system(cmdstr)

OutCrash('test.txt')

运行结果如下:

08369a4fa9931edb78b12a5b4abd43f3.png

我们可以通过堆栈来分析我们的逻辑,避免和绕过崩溃了,当然不得不吐槽一下,unity的bug不是你找到问题就能解决问题的,o(╥﹏╥)o

代码是用python写的 ,改替换的变量已经在注释里了,我python不熟悉,希望大佬勿喷,天知道我为啥用python写,(捂脸。。),应该用c#写的。

更新补充:

鉴于python对大家不太友好,这里补充一个c#解析错误的版本,大家可以直接放入工程使用

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public enum PathType
{
    unitySoPath=1,
    il2cppSoPath,
    addr2Line
}

public class AnalysisCrashWindow :EditorWindow
{
    bool groupEnabled;
    //path
    string addr2linePath = string.Empty; //ndk解析工具路径
    string il2cppdebugsoPath = string.Empty; //android 符号表路径
    string unitydebugsoPath = string.Empty;  //unity  符合表路径
    string MyCashPath = string.Empty;
    //flag
    string il2cppflag = "libil2cpp";
    string unityflag = "libunity";
    string crashEndFlag = "libunity.so";

    string unityPath = @"DataPlaybackEnginesAndroidPlayerVariationsmonoReleaseSymbolsarmeabi-v7alibunity.sym.so";

    bool isAnalysis = false;//文件是否解析

    private void OnEnable()
    {
        GetPathByMemory();
    }

    [MenuItem("Window/AnalysCrashWindow")]
    static void Init()
    {
        AnalysisCrashWindow window = (AnalysisCrashWindow)EditorWindow.GetWindow(typeof(AnalysisCrashWindow)); 
        window.Show();  
    }

    void OnGUI()
    {
        groupEnabled = EditorGUILayout.BeginToggleGroup("基础设置", groupEnabled);
        addr2linePath =  EditorGUILayout.TextField("NDK工具路径(addr2linePath)", addr2linePath, GUILayout.Width(400));
        unitydebugsoPath = EditorGUILayout.TextField("unity符号表(unitydebugsoPath)", unitydebugsoPath, GUILayout.MaxWidth(400));
        il2cppdebugsoPath = EditorGUILayout.TextField("ill2cpp符合表(il2cppdebugsoPath)", il2cppdebugsoPath, GUILayout.MaxWidth(400));
        MyCashPath = EditorGUILayout.TextField("崩溃日志文件路径", MyCashPath, GUILayout.MaxWidth(400));

        EditorGUILayout.EndToggleGroup();
        GUILayout.Label("检索内容", EditorStyles.boldLabel);
        GetCrashByPath(MyCashPath);
    }

    /// <summary>
    /// 从内存中获取存储的路径
    /// </summary>
    void GetPathByMemory()
    {
        addr2linePath = EditorPrefs.GetString("addr2linePath");
        il2cppdebugsoPath = EditorPrefs.GetString("il2cppdebugsoPath");
        unitydebugsoPath = EditorPrefs.GetString("unitydebugsoPath");
        if (string.IsNullOrEmpty(unitydebugsoPath))
        {
            unitydebugsoPath = string.Concat(System.AppDomain.CurrentDomain.BaseDirectory, unityPath);
            JudgePath(PathType.unitySoPath,unitydebugsoPath);
        }
        MyCashPath = EditorPrefs.GetString("MyCashPath", MyCashPath);
    }

    /// <summary>
    /// 路径判断
    /// </summary>
    /// <param name="type">路径类型</param>
    /// <param name="path"></param>
    bool JudgePath(PathType type, string path)
    {
        if (string.IsNullOrEmpty(path))
        {
            return false;
        }
        bool temp = true;
        if ((int)type == 1)
        {
            if (!path.EndsWith("libunity.sym.so"))
            {
                path = string.Empty;
                Debug.LogError("自动添加unity符合表路径出错,请手动添加");
                temp = false;
            }
            else
            {  
                if (!File.Exists(path))
                {
                    temp = false;
                    Debug.LogErrorFormat("当前路径{0}unity符号表不存在", path);
                }
            }
        }
        else if ((int)type == 2)
        {
            if (!path.EndsWith("libil2cpp.so.debug"))
            {
                temp = false;
            }
            else
            {
                if (!File.Exists(path))
                {
                    temp = false;
                }
            }
        }
        else
        {
            if (!path.EndsWith("aarch64-linux-android-addr2line.exe"))
            {
                temp = false;
            }
            else
            {
                if (!File.Exists(path))
                {
                    temp = false;
                }
            }
        }
        return temp;
    }

    /// <summary>
    /// 创建Button
    /// </summary>
    /// <param name="name"></param>
    /// <param name="path"></param>
    void CreatorButton(string name,string path)
    {
        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:WindowsSystem32cmd.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();
        }
    }
}



效果如下:

62a27ff5ce87f751cacd2fdda1db9d99.png

3d5e49413eee1cbb29cfebbd68f7e8a3.png

点击解析

252760574169a1b2d8f8c99e735a0c42.png

好了,各位,如果你觉得这篇文章对你有帮助,请不要吝惜你的鼓励,给我一个赞同吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: xiugoudisco_unity是什么? xiugoudisco_unity是指修构创意设计公司开发的Unity引擎相关资源的下载链接。Unity引擎是一款适用于2D和3D游戏开发的跨平台游戏引擎,被广泛应用于游戏开发、虚拟现实、增强现实和建筑可视化等领域。修构创意设计公司是一家专注于游戏、VR/AR、电影等领域的创新型企业,旗下包括游戏和动画事业部、VR / AR事业部和数字营销事业部。 如何下载xiugoudisco_unity? 想要下载xiugoudisco_unity相关资源,可以访问修构创意设计公司的官方网站,进入其Unity引擎资源下载页面,按照指示进行下载。在下载前,需要先了解自己所需要的资源种类,如游戏模型、素材、插件、脚本等等,并在下载时选择合适的版本与类型。下载完成后,可能需要进行解压缩,将其应用到自己的Unity项目中。 xiugoudisco_unity的应用场景及优势有哪些? xiugoudisco_unity提供的资源种类繁多,涵盖了游戏开发、VR/AR应用、建筑可视化、电影特效等多个领域,并且这些资源质量高、性能稳定。其所提供的优质资源不仅能够加快开发效率,满足不同开发者的需求,还能够为初学者提供快捷的学习途径。xiugoudisco_unity所开发的游戏、VR / AR应用等项目也备受好评,展现了其在相关领域的丰富经验和领先水平。 ### 回答2: xiugoudisco_unity 是一种基于 Unity 引擎的修建舞厅游戏资源包,主要用于建模和演示使用。该资源包可在多个游戏引擎中使用,其提供了丰富的资源和模型,方便用户进行快速的舞厅建模。同时,xiugoudisco_unity 能够让用户自行定制、设计和编程游戏,让用户有更大的自由度和创造力。 若要下载 xiugoudisco_unity 资源包,用户需要先在网上搜索相关的下载链接,并根据需要进行下载。安装时,用户需要先确认其已经安装好相应的游戏引擎,确保能够顺利地运行游戏。 总之,xiugoudisco_unity 是一种非常实用的游戏资源包,可帮助用户快速地建模和演示舞厅场景。其易用性和灵活性令人叹为观止,为许多游戏设计师和爱好者所钟爱。 ### 回答3: xiugoudisco_unity是一个游戏开发工具集,可以帮助游戏开发者快速创建游戏,实现游戏开发的各种功能。该工具集基于Unity引擎,具有强大的功能和灵活的扩展性,适用于各种类型的游戏开发。想要下载这个工具集,可以在官网或各大游戏开发论坛上进行下载。在下载前,需要确保自己的电脑符合该工具集的系统配置要求,安装前也需要查看相关的安装指南,确保安装顺利进行。使用xiugoudisco_unity,可以更加快速、高效的进行游戏开发,并且可以通过丰富的插件和资源库,快速构建精美的游戏场景和角色模型。总的来说,xiugoudisco_unity是众多游戏开发者的首选工具集之一,是实现各种游戏开发创意的重要基石。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值