zoukankan      html  css  js  c++  java
  • 脚本处理iOS的Crash日志

    背景

    当我们打包app时,可以选择生成对应的符号表,其保存 16 进制函数地址映射信息,通过给定的函数起始地址和偏移量,可以对应函数具体信息以供分析。

    所以我们拿到测试给的闪退日志(.crash)时,需要找到打包时对应生成的符号表(.dSYM)作为钥匙解析。具体分为下面几个步骤

    1. dwarfdump --uuid 命令获取 .dSYMuuid

    2. 打开 .crash 文件,在特定位置找到 uuid

    3. 根据 arm 版本比对两者是否一致

    4. Xcode 目录下寻找 symbolicatecrash 工具

      不同版本文件路径不同,具体版本请谷歌。Xcode9路径是/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/

    5. 设置终端环境变量

      export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

    6. 使用 symbolicatecrash 工具解析日志 symbolicatecrash .crash .dsym > a.out

    虽然过程不复杂,但是每次都需要手动执行一次检查与命令,过于繁琐,所以决定用脚本化提高效率。


    步骤实现

    输入Crash日志

    #要求输入crash文件路径
    inputFile 'Please Input Crash File' 'crash'
    crashPath=$filePath
    

    由于需要输入两种不同后缀的文件路径,且都需要检查,因此统一定义一个方法。

    #定义全局变量
    filePath=
    #输入文件路径
    inputFile() {
        readSuccess=false
        #首先清空变量值
        filePath=
        while [ $readSuccess = false ]; do 
            echo $1
            #读取到变量中
            read -a filePath
            if [[ ! -e $filePath || ${filePath##*.} != $2 ]]; then
                echo "Input file is not ."$2
            else
                readSuccess=true
            fi
        done
    }
    

    .dSYM 是文件夹路径,所以这里简单的判断了路径是否存在,如果不存在就继续让用户输入。

    Shell命令中判断分为[]与[[]],后者比前者更通用,可以使用 || 正则运算等。

    判断中,-f表示检查是否存在该文件,-d表示检查是否存在文件夹,-e表示检查是否存在该路径

    输入dSYM符号表

    dsymSuccess=false
    while [ $dsymSuccess = false ]; do
        #要求输入dSYM文件路径
        inputFile 'Please Input dSYM File' 'dSYM'
        dsymPath=$filePath
        #检查是否匹配
        checkUUID "$crashPath" "$dsymPath"
        match=$?
        if [ $match -eq 0 ]; then
            echo 'UUID not match!'
        else
            dsymSuccess=true
        fi
    done
    

    循环获取匹配 UUIDdSYM ,这里使用了另一种方法获取方法返回值,具体之后章节会总结。

    查找symbolicatecrash工具

    Xcode 文件夹指定路径下查找工具,加快效率,如果没找到就停止运行。

    # 查找symbolicatecrash解析工具,内置在Xcode的库文件中
    toolPath=`find /Applications/Xcode.app/Contents/SharedFrameworks -name symbolicatecrash | head -n 1`
    if [ ! -f $toolPath ]; then
        echo "Symbolicatecrash not exist!"
        exit 0
    fi
    

    执行解析命令

    #先设置环境变量
    export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
    #指定解析结果路径
    crashName=`basename $crashPath`
    afterPath="$(dirname "$crashPath")"/"${crashName%%.*}""_after.crash"
    #开始解析
    $toolPath "$crashPath" "$dsymPath" > "$afterPath" 2> /dev/null 
    

    这里我将错误信息导流到 /dev/null,保证解析文件没有杂乱信息。


    遇到的问题

    怎么获取函数返回值?

    之前没有处理过需要返回数值的方法,所以一开始有点懵,查询资料后最终采用了两种方式实现了效果,现在做一些总结。

    全局变量记录

    #定义全局变量
    filePath=
    inputFile() {
        #读取到变量中
        read -a filePath
    }
    inputFile
    crashPath=$filePath
    

    通过 inputFile 方法来了解一下,首先定义一个全局变量为 filePath,在方法中重新赋值,方法结束后读取全局变量中的数据。

    这种方法的好处是可以自定义返回参数类型和个数,缺点是容易和其他变量搞混。

    Return返回值

    类似与C语言中的用法,脚本也支持 retrun 0 返回结果并停止运行。

    checkUUID() {
        grep "$arm64id" "$1"
        if [ $? -ne 0 ]; then
            return 1;
        fi
        return 0;
    }
    checkUUID "$crashPath" "$dsymPath"
    match=$?
    

    获取结果的方式为 $?,其能够返回环境中最后一个指令结果,也就是之前执行的checkUUID的结果。

    优点是简洁明了,符合编码习惯,缺点是返回值只能是 0-255 的数字,不能返回其他类型的数据。

    获取打印值

    还有一种方法其实平时一直在使用,只不过并不了解其运行方式。

    crashName=`basename $crashPath`
    
    print() {
        echo "Hello World"
    }
    text=$(print)
    

    运行系统预设的方法或者自定义方法,将执行命令用 $() 的方式使用,就可以获取该命令中所有打印的信息,赋值到变量就可以拿到需要的返回值。

    优点是功能全效率高,使用字符串的方式可以传递定制化信息,缺点是不可预期返回结果,需要通过字符串查找等命令辅助。

    循环输入合法路径

    在我的设想中,需要用户输入匹配的 dSYM 文件路径,如果不匹配,则重新输入,直到合法。为了支持嵌套,需要定义局部变量控制循环,具体代码如下

    dsymSuccess=false
    while [ $dsymSuccess = false ]; do
        #要求输入dSYM文件路径
        inputFile 'Please Input dSYM File' 'dSYM'
        dsymPath=$filePath
        #检查是否匹配
        checkUUID "$crashPath" "$dsymPath"
        match=$?
        if [ $match -eq 0 ]; then
            echo 'UUID not match!'
        else
            dsymSuccess=true
        fi
    done
    

    处理字符串

    获取到 UUID 所有输出信息后,需要截取出对应平台的信息,处理还是不太熟悉,特地整理如下

    #原始信息
    UUID: 92E495AA-C2D4-3E9F-A759-A50AAEF446CD (armv7) /Volumes/.dSYM/Contents/Resources/DWARF/app
    UUID: 536527A8-0243-34DB-AE08-F1F64ACA4351 (arm64) /Volumes/.dSYM/Contents/Resources/DWARF/app
    
    #去除中间间隔-
    uuid=${uuid//-/}
    
    #从后往前找第一个匹配 (arm64的,并且都删除
    arm64id=${uuid% (arm64*}
    #处理后
    UUID: 92E495AAC2D43E9FA759A50AAEF446CD (armv7) /Volumes/.dSYM/Contents/Resources/DWARF/app
    UUID: 536527A8024334DBAE08F1F64ACA4351
    
    #从前往后找最后一个UUID: ,并删除
    arm64id=${arm64id##*UUID: }
    #处理后 
    536527A8024334DBAE08F1F64ACA4351
    

    总结

    看似简单的脚本,也花了一天时间编写,总体还是不太熟练,仍需努力联系。

    这次特地尝试了与上次不同的参数输入方法,使用提示输入的方式,果然遇到了新的问题。好在都查资料解决了,结果还算满意。

    脚本我提交到了Github,欢迎大家指教共同进步!给个关注最好啦~

  • 相关阅读:
    【python-leetcode295-双堆】数据流的中位数
    python数组二分查找算法bisect
    python堆队列算法heapq
    python中的容器序列类型collections
    step(iter)、epoch、batch size之间的关系
    【python-leetcode437-树的深度遍历】路径总和Ⅲ
    Java 代码实现POST/GET请求
    .NET 按格式导出txt
    java fastjson解析json字符串
    Java 对象转XML xStream 别名的使用 附下载方式
  • 原文地址:https://www.cnblogs.com/vanch/p/9755040.html
Copyright © 2011-2022 走看看