iOS crash 解析定位,shell脚本查找crash

iOS开发中,对于线上版本或公测版本产生的crash,我们可以通过结合.app ,.dSYM 及 crash log 三个文件来进行解析定位。

最新更新:
最近对查找线上Crash做了整理,写成CrashScript.sh,详情见下面查找Crash脚本

获取iOS设备上的 crash log

  1. 将iOS设备连接到电脑上,打开 Xcode -> Organizer -> Devices,找到该台设备,在 Device logs 中找到 crash log(后缀为 .crash 的 log 文件),将其导出即可。

  2. 如果你的应用已经上架App Store,那么开发者可以通过iTunes Connect(Manage Your Applications - View Details - Crash Reports)获取用户的crash log。不过这并不是100%有效的,而且大多数开发者并不依赖于此,因为这需要用户设备同意上传相关信息,详情可参见iOS: Providing Apple with diagnostics and usage information摘要。

  3. 第三方crash收集系统,甚至还带了符号化crash日志的功能。比较常用的有CrashlyticsFlurry等。

确保.app .dSYM和crash log的uuid相同

以上三者的uuid必须都一样才能进行解析,查看三个文件的 uuid :

  1. 查看xx.app文件的uuid的方法,在终端输入:

    $ dwarfdump –uuid xxx.app/xxx (xxx工程名)

  2. 查看xx.app.dSYM文件的uuid的方法,在命令行输入:

    $ dwarfdump –uuid xxx.app.dSYM (xxx工程名)

  3. 查看 crash log 文件的 uuid的方法:
    在 crash log 文件中,找到 Binary Images: 项目名后面第一个尖括号中的一串码就是改 crash log 文件的 uuid。
    如下,70464c7fc4df37f38f81eeaf88a0713d就是uuid。

    1
    2
    Binary Images:
    0x10000c000 - 0x100cf7fff xxx (xxx工程名) arm64 <70464c7fc4df37f38f81eeaf88a0713d>

显示.dYSM包内容

进入/Contents/Resources/DWARF路径下,执行命令:

atos -arch armv7 -o XXX(项目名称) 0x17D580(16进制crash奔溃地址)

显示结果

1
-[PCBabyMyGroupReplyTVCell setCellInfo:] (in BabyBook2) (PCBabyMyGroupReplyTVCell.m:66)

可以看到是 PCBabyMyGroupReplyTVCell 类第66行出错。

如果定位不对,可能涉及到地址偏移的计算。首先查看起始地址,即使每次iOS app启动都会加载(main module)主模块在不同的内存地址,但是dSYM文件总是假设你的main module加载在地址 0x100000000(64位) ,或者 0x4000(32位)
查看偏移,显示.dYSM包内容,进入/Contents/Resources/DWARF路径下,执行命令:

1
otool -arch arm64 -l XXX(XXX为项目名) | grep -B 1 -A 10 "LC_SEGM" | grep -B 3 -A 8 "__TEXT"

显示结果

1
2
3
4
5
6
7
8
9
10
11
12
Load command 1
cmd LC_SEGMENT_64
cmdsize 1032
segname __TEXT
vmaddr 0x0000000100000000
vmsize 0x0000000000620000
fileoff 0
filesize 6422528
maxprot 0x00000005
initprot 0x00000005
nsects 12
flags 0x0

看到vmaddr显示为0x0000000100000000,所以偏移地址计算为:0x17D580+0x0000000100000000=0x10017D580。
再次执行终端命令:

atos -arch armv7 -o XXX(项目名称) 0x10017D580

附:32位及64位设备的执行命令

32位: xcrun atos -arch armv7 -o xxx(应用名称)  xxx(偏移地址)
64位: xcrun atos -arch arm64 -o xxx(应用名称)  xxx(偏移地址)

查找Crash脚本

注意!!!Shell脚本默认无法处理带空格的文件路径,请确保.xcarchive 文件以及CrashScript.sh脚本所在路径名不存在空格。(Xcode Archive生成的.xcarchive文件名默认带空格,请去除)

获取打包生成的 .xcarchive 文件,将其和CrashScript.sh脚本放到同一级目录下,运行脚本:

1
./CrashScript.sh [-u] [-t <Device type>] -a <Code address> 

参数说明:[]表示可选参数,<>表示必填参数

1
2
3
-u 				         是否查看UUID
-t <Device type> 发生crash的设备类型,有两种值:32和64,默认64
-a <Code address> 10进制的出错地址

脚本完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/bin/bash

#--------------------------------------------------------------------------------
# 脚本说明:
#
# 1、实现功能:
# 1)、查看 .xcarchive 文件的UUID
# 2)、查看10进制的出错地址对应的代码
#
# 2、使用方式:
# 1)、将CrashScript.sh脚本和 .xcarchive 文件放到同一级文件夹下
# 2)、运行脚本:
# ./CrashScript.sh [-u] [-t <Device type>] -a <Code address>
# 参数说明:
# -u 是否查看UUID
# -t <Device type> 发生crash的设备类型,有两种值:32和64,默认64
# -a <Code address> 10进制的出错地址
#--------------------------------------------------------------------------------

# 脚本文件所在根目录
Release_path=$(pwd)
xcarchive_path=""
cd ${Release_path}
Valid_dic=false
for i in `ls`;
do
#获取文件后缀名
extension=${i##*.}
if [[ ${extension} == "xcarchive" ]]; then
Valid_dic=true
xcarchive_path="${Release_path}/${i}"
fi
done

if [[ ${Valid_dic} == false ]]; then
echo -e "\033[31mCrashScript.sh脚本所在路径不存在.xcarchive文件,请检查!!\033[0m"
exit 2
fi

Check_UUID="NO"
Device_type="64"
Have_code_address="NO"
Code_address=""

# 参数处理
param_pattern=":ut:a:"
OPTIND=1
while getopts $param_pattern optname
do
case "$optname" in
"u")
Check_UUID="YES"
;;
"t")
tmp_optind=$OPTIND
tmp_optname=$optname
tmp_optarg=$OPTARG

OPTIND=$OPTIND-1
if getopts $param_pattern optname ;then
echo -e "\033[31m选项参数错误 $tmp_optname\033[0m"
exit 2
fi
OPTIND=$tmp_optind
Device_type=$tmp_optarg
if [[ ${Device_type} != "32" && ${Device_type} != "64" ]]; then
echo -e "\033[31m选项$tmp_optname 参数错误 $Device_type\033[0m"
exit 2
fi
;;
"a")
tmp_optind=$OPTIND
tmp_optname=$optname
tmp_optarg=$OPTARG
Have_code_address="YES"

OPTIND=$OPTIND-1
if getopts $param_pattern optname ;then
echo -e "\033[31m选项参数错误 $tmp_optname\033[0m"
exit 2
fi
OPTIND=$tmp_optind
Code_address=$tmp_optarg
;;
"?")
echo -e "\033[31m选项错误: $OPTARG\033[0m"
exit 2
;;
":")
echo -e "\033[31m选项 $OPTARG 必须带参数\033[0m"
exit 2
;;
*)
echo -e "\033[31m参数错误\033[0m"
exit 2
;;
esac
done

dSYMs_path="${xcarchive_path}/dSYMs"
dSYM_file=""
cd ${dSYMs_path}

# if [ ! -d "${dSYMs_path}" ];then
if [ $? != 0 ]; then
echo -e "\033[31m************* dSYMs路径不存在 **************\033[0m"
echo -e "\033[31mdSYMs_path = ${dSYMs_path}\033[0m"
exit 2
fi

for file in `ls`;
do
#获取文件后缀名
extension=${file##*.}
if [[ ${extension} == "dSYM" ]]; then
dSYM_file=${file}
fi
done

if [[ ${Check_UUID} == "YES" ]]; then
echo -e "\033[32m*************** 获取 UUID ***************\033[0m"
echo -e "\033[36mdSYM文件 : ${dSYM_file}\033[0m";
echo ''
# 获取UUID
dwarfdump --uuid ${dSYM_file}
echo ''

if [ $? != 0 ]; then
echo -e "\033[31m************* 获取UUID出错 **************\033[0m"
exit 2
fi
fi

if [[ ${Have_code_address} == "NO" ]]; then
echo -e "\033[31m请输入出错的10进制代码地址!!\033[0m"
exit 2
fi

DWARF_path="${dSYMs_path}/${dSYM_file}/Contents/Resources/DWARF"
echo -e "\033[32m*************** 获取 DWARF ***************\033[0m"
# echo -e "\033[36mDWARF_path : ${DWARF_path}\033[0m";

cd ${DWARF_path}
for file in `ls`;
do
echo -e "\033[36mDWARF文件 : ${file}\033[0m"

echo ''
echo -e "\033[32m*************** 获取出错代码 ***************\033[0m"

if [[ ${Device_type} == "32" ]]; then
# 搜索地址=偏移地址转换为16进制后加0x4000;
searchAddress="0x"$(echo "ibase=10;obase=16;${Code_address}+16384"|bc);
xcrun atos -arch armv7 -o ${file} ${searchAddress}
elif [[ ${Device_type} == "64" ]]; then
#搜索地址=偏移地址转换为16进制后,64位系统加100000000
searchAddress="0x"$(echo "ibase=10;obase=16;${Code_address}+4294967296"|bc);
xcrun atos -arch arm64 -o ${file} ${searchAddress}
fi

done